Skip to content

feat: add GLM (Zhipu AI) provider support#1059

Open
Aneaire wants to merge 2 commits intopingdotgg:mainfrom
Aneaire:feat/glm-provider-support
Open

feat: add GLM (Zhipu AI) provider support#1059
Aneaire wants to merge 2 commits intopingdotgg:mainfrom
Aneaire:feat/glm-provider-support

Conversation

@Aneaire
Copy link

@Aneaire Aneaire commented Mar 14, 2026

Summary

  • Adds GLM (Zhipu AI) as a new provider alongside Codex, enabling users to use GLM models (glm-4.7, glm-4.7-flash, glm-5) via the OpenAI-compatible chat completions API
  • Full HTTP-based adapter with SSE streaming, tool use (read/write/edit files, run commands, list directories, search files), and an agent loop
  • GLM health check validates GLM_API_KEY / ZAI_API_KEY at startup with a status banner in the UI
  • Web UI updates: provider picker, model selector, settings page for custom GLM models

Files changed

Contracts (packages/contracts/):

  • ProviderKind extended from "codex" to ["codex", "glm"]
  • Added GLM models, aliases, and model options

Shared (packages/shared/):

  • Added inferProviderFromModel() utility and GLM model slug set

Server (apps/server/):

  • New GlmAdapter service + layer (HTTP adapter with SSE streaming and 6 tools)
  • Registered in ProviderAdapterRegistry and serverLayers
  • GLM health check in ProviderHealth
  • Updated ProviderSessionDirectory and ProviderCommandReactor to recognize "glm"

Web (apps/web/):

  • Settings page with GLM custom model management
  • Provider picker with GLM icon
  • Store and composer draft store updated for GLM provider

Setup

Set GLM_API_KEY (or ZAI_API_KEY) as an environment variable before starting the server:

export GLM_API_KEY=your-api-key

The API key can be obtained from z.ai.

Test plan

  • Verify Codex provider still works unchanged (no regressions)
  • Set GLM_API_KEY and confirm the GLM health check shows "ready" in the UI
  • Without GLM_API_KEY, confirm the health banner shows the missing key message
  • Select GLM provider and a GLM model, send a message, verify streaming response
  • Verify tool use works (file read/write, command execution)
  • Verify custom GLM models can be added/removed in Settings
  • Verify model picker shows GLM models when GLM provider is selected

🤖 Generated with Claude Code

Note

Add GLM (Zhipu AI) as a provider alongside Codex

  • Adds a full GLM provider adapter in GlmAdapter.ts that streams OpenAI-compatible chat completions from the GLM API, executes tool calls locally (filesystem + shell), and emits lifecycle/runtime events matching the Codex provider contract.
  • Registers GLM in the provider registry, health checks (keyed on GLM_API_KEY/ZAI_API_KEY), session directory, and orchestration layers so GLM sessions are treated as first-class alongside Codex.
  • Extends contracts and shared model utilities in model.ts with GLM model options, default models, slug aliases, and reasoning effort maps.
  • Adds GLM to the web UI: provider picker, model picker with GlmIcon, settings page for custom models, and health banner.
  • Risk: tool calls (write, edit, run_command) execute with local filesystem and shell access; approval-required mode gates these but must be explicitly enabled per session.

Macroscope summarized e6899af.

Add GLM as a second provider alongside Codex, enabling users to use
GLM models (glm-4.7, glm-4.7-flash, glm-5) via the OpenAI-compatible
chat completions API at open.bigmodel.cn.

Changes span contracts, shared utilities, server adapter/registry/health,
and the web UI (settings, model picker, provider selector, store).

Authentication is via GLM_API_KEY or ZAI_API_KEY environment variables.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link

coderabbitai bot commented Mar 14, 2026

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: bdb48ca5-85f0-48c5-8f9b-6c48ef3ffd97

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Tip

You can disable the changed files summary in the walkthrough.

Disable the reviews.changed_files_summary setting to disable the changed files summary in the walkthrough.

@github-actions github-actions bot added size:XXL 1,000+ changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list. labels Mar 14, 2026
Comment on lines +361 to +370
const getSession = (threadId: ThreadId): GlmSession => {
const session = sessions.get(threadId);
if (!session) {
throw new ProviderAdapterSessionNotFoundError({
provider: PROVIDER,
threadId,
});
}
return session;
};
Copy link
Contributor

Choose a reason for hiding this comment

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

🟡 Medium Layers/GlmAdapter.ts:361

getSession throws ProviderAdapterSessionNotFoundError synchronously inside Effect.sync() blocks, so the exception becomes a Cause.Die defect instead of a typed Cause.Fail. Methods like sendTurn, interruptTurn, respondToRequest, readThread, and rollbackThread therefore die unrecoverably rather than returning a typed ProviderAdapterError that callers can handle. Consider using Effect.fail(new ProviderAdapterSessionNotFoundError(...)) or Effect.try with catch to convert the throw into a typed failure.

-      const getSession = (threadId: ThreadId): GlmSession => {
-        const session = sessions.get(threadId);
-        if (!session) {
-          throw new ProviderAdapterSessionNotFoundError({
-            provider: PROVIDER,
-            threadId,
-          });
-        }
-        return session;
-      };
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file apps/server/src/provider/Layers/GlmAdapter.ts around lines 361-370:

`getSession` throws `ProviderAdapterSessionNotFoundError` synchronously inside `Effect.sync()` blocks, so the exception becomes a `Cause.Die` defect instead of a typed `Cause.Fail`. Methods like `sendTurn`, `interruptTurn`, `respondToRequest`, `readThread`, and `rollbackThread` therefore die unrecoverably rather than returning a typed `ProviderAdapterError` that callers can handle. Consider using `Effect.fail(new ProviderAdapterSessionNotFoundError(...))` or `Effect.try` with `catch` to convert the throw into a typed failure.

Evidence trail:
1. GlmAdapter.ts lines 361-370: `getSession` function throws `ProviderAdapterSessionNotFoundError` synchronously
2. GlmAdapter.ts line 740-741: `sendTurn` calls `getSession` inside `Effect.sync()`
3. GlmAdapter.ts line 788-789: `interruptTurn` calls `getSession` inside `Effect.sync()`
4. GlmAdapter.ts line 806-807: `respondToRequest` calls `getSession` inside `Effect.sync()`
5. GlmAdapter.ts line 858-859: `readThread` calls `getSession` inside `Effect.sync()`
6. GlmAdapter.ts line 867-868: `rollbackThread` calls `getSession` inside `Effect.sync()`
7. Effect-TS docs (https://effect-ts.github.io/effect/effect/Effect.ts.html): "The provided function (thunk) must not throw errors; if it does, the error will be treated as a 'defect'."

}>;
}

async function* parseSseStream(
Copy link
Contributor

Choose a reason for hiding this comment

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

🟢 Low Layers/GlmAdapter.ts:307

In parseSseStream, when reader.read() returns done: true, the function breaks immediately without processing any remaining data in buffer. If the server sends a final data: line without a trailing newline before closing the connection, that chunk is lost and never yielded.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file apps/server/src/provider/Layers/GlmAdapter.ts around line 307:

In `parseSseStream`, when `reader.read()` returns `done: true`, the function breaks immediately without processing any remaining data in `buffer`. If the server sends a final `data:` line without a trailing newline before closing the connection, that chunk is lost and never yielded.

Evidence trail:
apps/server/src/provider/Layers/GlmAdapter.ts lines 306-337 at REVIEWED_COMMIT - specifically line 319 (`if (done) break;`), lines 323-324 (`const lines = buffer.split("\n"); buffer = lines.pop() ?? "";`), and the absence of any buffer processing after the while loop or in the finally block (line 336-337)

getSession was throwing ProviderAdapterSessionNotFoundError inside
Effect.sync blocks, which converts to a Cause.Die defect. Changed to
return Effect.fail for a typed Cause.Fail that callers can handle.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@zortos293
Copy link
Contributor

Julius will add claude soon, and I don’t think we should integrate his change until he releases his own Claude adapter. so you can build ontop of it.
See also: #179

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

Labels

size:XXL 1,000+ changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants