Skip to content

Add GLM-4 Plus as a model option#60

Open
devin-ai-integration[bot] wants to merge 4 commits intomainfrom
devin/1772486666-add-glm
Open

Add GLM-4 Plus as a model option#60
devin-ai-integration[bot] wants to merge 4 commits intomainfrom
devin/1772486666-add-glm

Conversation

@devin-ai-integration
Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration bot commented Mar 2, 2026

Add GLM-4 Plus as a model option

Summary

Adds Zhipu AI's GLM-4 Plus as a new chat model across the full stack:

Server (server/):

  • New glm.ts handler that streams completions from https://open.bigmodel.cn/api/paas/v4/chat/completions (OpenAI-compatible SSE format)
  • Registered as POST /chat/glm in the chat router
  • GLM_API_KEY added to .env.example

App (app/):

  • New GlmIcon component (generic SVG icon — not the official Zhipu logo)
  • glm4Plus added to the MODELS constant
  • getChatType routing updated to map glm labels → /chat/glm endpoint
  • New generateGlmResponse handler in the chat screen (single-turn prompt, same streaming pattern as GPT handler)

Review & Testing Checklist for Human

  • Verify Zhipu API endpoint and model name: The handler assumes glm-4-plus as the model identifier and https://open.bigmodel.cn/api/paas/v4/chat/completions as the endpoint — confirm these match current Zhipu AI docs
  • Test with a real GLM_API_KEY: This was not tested against the live API; streaming response parsing could have subtle issues if the format deviates from standard OpenAI SSE
  • No multi-turn conversation context: generateGlmResponse sends only the current message as prompt — it does not accumulate conversation history like the GPT handler does. Decide if this is acceptable or if history should be passed
  • Icon is a placeholder: GlmIcon uses a generic person-circle SVG, not the official Zhipu/GLM branding. Replace with the real logo if desired

Suggested manual test plan:

  1. Set a valid GLM_API_KEY in server/.env
  2. Start the server and the app
  3. Select "GLM-4 Plus" from the model picker
  4. Send a chat message and verify streaming response renders correctly
  5. Verify switching between GLM and other models maintains separate chat histories

Notes


Open with Devin

Co-Authored-By: bot_apk <apk@cognition.ai>
@devin-ai-integration
Copy link
Copy Markdown
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

Co-Authored-By: bot_apk <apk@cognition.ai>
devin-ai-integration[bot]

This comment was marked as resolved.

Co-Authored-By: bot_apk <apk@cognition.ai>
devin-ai-integration[bot]

This comment was marked as resolved.

Co-Authored-By: bot_apk <apk@cognition.ai>
Copy link
Copy Markdown
Contributor Author

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 1 new potential issue.

View 7 additional findings in Devin Review.

Open in Devin Review

Comment on lines +67 to +80
for (const line of lines) {
const trimmed = line.trim()
if (!trimmed || !trimmed.startsWith('data: ')) continue
const data = trimmed.replace('data: ', '')
if (data === '[DONE]') continue

try {
const parsed = JSON.parse(data)
if (parsed.choices?.[0]?.delta?.content) {
res.write(`data: ${JSON.stringify(parsed.choices[0].delta)}\n\n`)
}
} catch {
brokenLine = line
}
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

🟡 SSE stream parsing silently drops messages when chunk boundary splits within the data: prefix

The SSE stream parser in glm.ts silently discards entire messages when a TCP/HTTP chunk boundary falls within the data: prefix of an SSE line.

Root Cause

The brokenLine recovery mechanism (lines 60-63, 78-80) only saves a line as brokenLine when JSON.parse fails in the catch block. However, if a chunk boundary splits within the data: prefix itself (e.g., chunk 1 ends with \ndat and chunk 2 starts with a: {"choices":[...]}), the partial line dat is trimmed and skipped at line 69 (continue) because it doesn't start with data: . Since continue bypasses the catch block, brokenLine is never set.

On the next chunk, the continuation a: {"choices":[...]} also doesn't start with data: and is similarly skipped. The entire SSE message is silently lost.

// Line 67-70: Partial lines that don't start with 'data: ' are skipped
// but NOT saved to brokenLine for reassembly
for (const line of lines) {
  const trimmed = line.trim()
  if (!trimmed || !trimmed.startsWith('data: ')) continue  // <-- lost here

Impact: Occasional dropped tokens in streamed responses. In practice this is rare since chunk boundaries typically align to newlines in SSE streams, but it can happen under network conditions that fragment TCP segments at unfortunate boundaries.

Prompt for agents
In server/src/chat/glm.ts, the brokenLine handling needs to account for incomplete lines that don't yet contain the full 'data: ' prefix. Instead of only setting brokenLine in the JSON.parse catch block, the last line of each chunk should always be checked for completeness. One approach: after the for loop (line 67-81), always check if the last line from the split didn't end with a newline (i.e., the original chunk didn't end with '\n'). If so, save it as brokenLine regardless of whether it starts with 'data: '. For example, after line 65 (`const lines = chunk.split('\n')`), check if the chunk ends with '\n' — if not, pop the last element from `lines` and save it as `brokenLine` before entering the for loop. This ensures partial lines are always preserved for reassembly with the next chunk.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Acknowledged — this is a valid edge case but extremely unlikely in practice since SSE servers typically flush on line boundaries. The existing GPT and Claude handlers in this repo use similar patterns. Happy to apply the fix if the maintainer wants it, but leaving as-is for now to stay consistent with the rest of the codebase.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants