Skip to content

SD-2091 - Document API SDK - Agentic collab example code#2490

Open
mattConnHarbour wants to merge 5 commits intomainfrom
SD-2091
Open

SD-2091 - Document API SDK - Agentic collab example code#2490
mattConnHarbour wants to merge 5 commits intomainfrom
SD-2091

Conversation

@mattConnHarbour
Copy link
Contributor

No description provided.

@linear
Copy link

linear bot commented Mar 20, 2026

@mattConnHarbour mattConnHarbour marked this pull request as draft March 20, 2026 04:50
Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2bde471577

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

RUN npm ci --omit=dev

# Copy server files only (not the Vue client source)
COPY server.ts start.ts agent.ts ./

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Copy the actual agent sources into the backend image

I checked this example's tree and package.json: the only agent implementation lives under agent/ (agent/index.ts plus agent/chat.ts), while this Dockerfile copies a non-existent top-level agent.ts. That makes docker build/gcloud builds submit fail for the Cloud Run backend from a clean checkout, so deploy-backend.sh cannot produce a runnable image.

Useful? React with 👍 / 👎.


// Wait a bit for server to start, then start agent
setTimeout(() => {
const agent = spawn('npx', ['tsx', join(__dirname, 'agent.ts')], {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Launch agent/index.ts from the combined startup script

Even if the image contents are fixed, this startup path still points tsx at agent.ts, but the example's agent entrypoint is agent/index.ts (the same path used by npm run dev:agent). Running start.ts will therefore spawn a child that immediately exits because the file does not exist, and the parent then exits in agent.on('exit'), leaving the deployment without the AI agent.

Useful? React with 👍 / 👎.

ws.onopen = () => {
console.log('[Client] Chat connected');
// Clear chat history on connect (fresh start each page load)
ws.send(JSON.stringify({ type: 'clear' }));

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Stop resetting the shared chat state on every connect

Connecting a browser now sends clear unconditionally. In this example, server.ts handles clear by wiping room.messages and broadcasting the event, and agent/chat.ts maps that broadcast to clearConversation(). That means opening a second tab or refreshing the page erases the chat transcript and the agent's context for everyone in the room, which breaks follow-up requests in collaborative sessions.

Useful? React with 👍 / 👎.

@mattConnHarbour mattConnHarbour force-pushed the SD-2091 branch 5 times, most recently from 851c177 to d93c944 Compare March 23, 2026 04:55
@mattConnHarbour mattConnHarbour marked this pull request as ready for review March 23, 2026 05:59
Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5b2a145de1

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +53 to +55
```bash
npm install
```

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Tell users to install the child package dependencies

Following the Quick Start from a clean checkout cannot work: this example is not an npm workspace, so npm install here only installs the root concurrently dependency. The actual runtime packages live in client/package.json, server/package.json, and agent/package.json, so npm run dev immediately fails with missing vite/tsx binaries unless readers also run npm run install:all.

Useful? React with 👍 / 👎.

"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "concurrently \"npm run dev:server\" \"npm run dev:client\" \"npm run dev:agent\"",

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Wait for the backend before spawning the agent

npm run dev launches the server and agent at the same time, but agent/agent.ts immediately connects to ws://localhost:3050 twice (client.open(...collaboration...) and new Chat(...).connect()) without any retry/backoff. On a normal cold start the server can lose that race, causing the agent process to die with ECONNREFUSED and leaving the sidebar permanently offline until the user restarts it manually.

Useful? React with 👍 / 👎.

Comment on lines +54 to +58
while (true) {
const response = await openai.chat.completions.create({
model: 'gpt-4o',
messages,
tools: tools as OpenAI.ChatCompletionTool[],

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Cap the tool-calling loop for each chat turn

processMessage() has an unbounded while (true) around model/tool execution. If the model keeps emitting tool calls—for example after a malformed argument or a prompt that makes it bounce between read/edit actions—the request never completes and keeps burning OpenAI tokens until the process is killed. The example README already shows a bounded loop, so this runtime path should have the same guard.

Useful? React with 👍 / 👎.

Comment on lines +26 to +30
this.ws.on('message', async (data) => {
const msg = JSON.parse(data.toString());
if (msg.type === 'message' && msg.message?.role === 'user') {
this.setStatus('thinking');
try {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Serialize chat turns before touching shared history

Chat.serve() starts a fresh async handler for every incoming user message, but agent.ts keeps one module-level conversationHistory for all turns. In a collaborative room, two users can send requests close together and both turns will read/append that shared array concurrently, so future context can end up out of order or attributed to the wrong request.

Useful? React with 👍 / 👎.

Copy link
Contributor

@andrii-harbour andrii-harbour left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there are things that are not up to date
and a few things that need more attention from the dev perspective. We should assume this code can be run immediately after providing api key.


while (true) {
const response = await openai.chat.completions.create({
model: 'gpt-4o',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please use the most recent model

Comment on lines +50 to +82
async function processMessage(userMessage: string): Promise<string> {
conversationHistory.push({ role: 'user', content: userMessage });
const messages: OpenAI.ChatCompletionMessageParam[] = [...conversationHistory];

while (true) {
const response = await openai.chat.completions.create({
model: 'gpt-4o',
messages,
tools: tools as OpenAI.ChatCompletionTool[],
});

const message = response.choices[0].message;
messages.push(message);

if (!message.tool_calls?.length) {
conversationHistory.push({ role: 'assistant', content: message.content || 'Done.' });
return message.content || 'Done.';
}

for (const call of message.tool_calls) {
const result = await dispatchSuperDocTool(
doc,
call.function.name,
JSON.parse(call.function.arguments),
);
messages.push({
role: 'tool',
tool_call_id: call.id,
content: JSON.stringify(result),
});
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

while (true) {

No iteration cap. If the LLM keeps generating tool calls (hallucinating or stuck in a loop), this runs forever. Add a max iterations guard (e.g., 10-20 rounds).

"dependencies": {
"@superdoc-dev/sdk": "^1.0.0",
"dotenv": "^16.4.7",
"openai": "^4.78.0",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is not the most recent version

url: sampleDocument,
isNewFile: true,
},
pagination: false,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as I remember we don't have this parameter anymore, we're using layoutEngineOptions instead

Comment on lines +70 to +76
const handleOnChange = async (params: CollaborationParams): Promise<void> => {
// Quiet - too noisy
};

const handleAutoSave = async (params: CollaborationParams): Promise<void> => {
// Quiet - too noisy
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we really need them then?


const { tools } = await chooseTools({
provider: 'openai',
mode: 'all', // Include mutation tools
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we don't have that

Comment on lines +137 to +138
value: '# New Heading\n\nSome paragraph text.',
type: 'markdown',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's use another example (non-markdown one)

Comment on lines +190 to +206
- **Wrangler CLI** (`npx wrangler`) authenticated with Cloudflare
- **OpenAI API key** in `.env`

### Step 1: Deploy Backend (Cloud Run)

The backend includes the collaboration server and AI agent in a single container.

```bash
# Make sure .env has your OpenAI key
npm run deploy:backend
```

This will:
1. Build a Docker container with the server and agent
2. Push it to Google Container Registry
3. Deploy to Cloud Run with WebSocket support
4. Output the service URL (e.g., `https://superdoc-agent-demo-xxxxx.run.app`)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we really need that for example code?

Comment on lines +218 to +228
### Step 3: Deploy Frontend (Cloudflare Pages)

```bash
npm run deploy:frontend
```

This will:
1. Build the Vue client with the backend URL baked in
2. Deploy to Cloudflare Pages (project: `document-api-agentic-demo`)

The frontend will be available at: `https://document-api-agentic-demo.pages.dev`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as above

**When writing copy or content:** see `brand.md` for the full brand identity — strategy, voice, and visual guidelines. Product name is always **SuperDoc** (capital S, capital D).
**When writing copy or content:** see `brand/brand-guidelines.md` for voice, tone, and the dual-register pattern (developer vs. leader). Product name is always **SuperDoc** (capital S, capital D).

## SD-2091: chooseTools() Investigation Findings (RESOLVED)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this document is not up-to-date

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants