SD-2091 - Document API SDK - Agentic collab example code#2490
SD-2091 - Document API SDK - Agentic collab example code#2490mattConnHarbour wants to merge 5 commits intomainfrom
Conversation
There was a problem hiding this comment.
💡 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 ./ |
There was a problem hiding this comment.
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')], { |
There was a problem hiding this comment.
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' })); |
There was a problem hiding this comment.
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 👍 / 👎.
851c177 to
d93c944
Compare
d93c944 to
5b2a145
Compare
There was a problem hiding this comment.
💡 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".
| ```bash | ||
| npm install | ||
| ``` |
There was a problem hiding this comment.
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\"", |
There was a problem hiding this comment.
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 👍 / 👎.
| while (true) { | ||
| const response = await openai.chat.completions.create({ | ||
| model: 'gpt-4o', | ||
| messages, | ||
| tools: tools as OpenAI.ChatCompletionTool[], |
There was a problem hiding this comment.
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 👍 / 👎.
| this.ws.on('message', async (data) => { | ||
| const msg = JSON.parse(data.toString()); | ||
| if (msg.type === 'message' && msg.message?.role === 'user') { | ||
| this.setStatus('thinking'); | ||
| try { |
There was a problem hiding this comment.
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 👍 / 👎.
andrii-harbour
left a comment
There was a problem hiding this comment.
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', |
There was a problem hiding this comment.
please use the most recent model
| 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), | ||
| }); | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
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", |
There was a problem hiding this comment.
this is not the most recent version
| url: sampleDocument, | ||
| isNewFile: true, | ||
| }, | ||
| pagination: false, |
There was a problem hiding this comment.
as I remember we don't have this parameter anymore, we're using layoutEngineOptions instead
| const handleOnChange = async (params: CollaborationParams): Promise<void> => { | ||
| // Quiet - too noisy | ||
| }; | ||
|
|
||
| const handleAutoSave = async (params: CollaborationParams): Promise<void> => { | ||
| // Quiet - too noisy | ||
| }; |
There was a problem hiding this comment.
do we really need them then?
|
|
||
| const { tools } = await chooseTools({ | ||
| provider: 'openai', | ||
| mode: 'all', // Include mutation tools |
| value: '# New Heading\n\nSome paragraph text.', | ||
| type: 'markdown', |
There was a problem hiding this comment.
let's use another example (non-markdown one)
| - **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`) |
There was a problem hiding this comment.
do we really need that for example code?
| ### 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` |
| **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) |
There was a problem hiding this comment.
this document is not up-to-date
No description provided.