---
title: GLM Provider
---

# GLM Provider

## What it is

Omniscio supports spawning Claude-Code-style sessions backed by Zhipu's **GLM** Anthropic-compatible API (hosted at `z.ai`) as an additional provider, alongside `claude` (the default), `codex`, `gemini`, `antigravity`, `deepseek`, and `kimi`.

### What the user sees

A GLM session looks identical in the sidebar and main pane to a Claude session — same status dots, same streaming bubbles, same Ctrl+Enter to send, same Plan / Auto-Accept / Bypass Permissions modes, same tool-call approval UI. The provider is invisible in normal chat. The only user-visible difference is the GLM icon (a non-Claude provider mark, no text label) in the session header.

Tool-call behavior is **identical to Claude** because the same `claude` CLI binary is doing the talking — only the model and the upstream endpoint differ. There is **no yolo / auto-approve mode** like Gemini or Anti-Gravity; the standard Claude approval flow applies.

### Branding: "Z.ai" (account UI) vs "GLM" (model picker)

The vendor brand is surfaced as **"Z.ai"** in the account/subscription UI, while the **model family stays "GLM"** in the session/model picker — the two name the same provider (Zhipu's GLM models on z.ai's Anthropic-compatible API), shown under the name that fits each surface:

- **Account/subscription surfaces say "Z.ai"** — the Settings → Accounts card, its allow-toggle, key fields, and the account-popover tab (below) all read "Z.ai".
- **The session + model picker say "GLM"** — the per-project Default-provider radios, the `ChangeProviderButton`, and the model list (`glm-5.2[1m]`) keep "GLM" (the model-family name, stable as z.ai ships new versions).
- **Internal ids are unchanged** — `provider: 'glm'`, `providerSlug: 'glm'`, `allowGlmSessionSpawn`, `glmApiKey`, the `ACCOUNT_GLM_*` IPC channels, and `registry.ts`'s model-picker `label: 'GLM'` all stay `glm`/"GLM"; only display strings in the account surfaces changed.

## Where to find it

### Managing accounts — Settings and the account popover

Z.ai (GLM) accounts are personal z.ai API keys, backed by the shared `glmAccounts` store slice + the `ACCOUNT_GLM_*` IPC. (Omniscio credits are no longer an entry in this list: they are a row of GLM's supply list — see below.) Manage them in two interchangeable places:

- **Settings → Accounts → "Other AI tools" → Z.ai** — the full manager ([GlmAccountsManager.tsx](../../src/renderer/src/features/settings/sections/accounts/GlmAccountsManager.tsx)): add a key (validated before store), multi-account list with 5h + weekly usage bars, switch / rename / remove.
- **Account popover → "Z.ai" tab** — a condensed mirror ([GlmAccountsPopoverTab.tsx](../../src/renderer/src/components/ui/account/GlmAccountsPopoverTab.tsx)) in the lower-left account popover, beside the **Claude** and **Codex** tabs (`AccountIndicator.tsx`). Add a key inline (same validated flow), see per-account usage, refresh, switch, and confirm-gated remove. Modeled on the Codex popover tab — but Z.ai is added by pasting an API key (no OAuth login), so it carries a compact key form rather than a one-click login button. Reuses the same accounts + IPC as Settings, so the two stay in sync (both refetch on `ACCOUNTS_CHANGED`).

### How to enable

> **Master toggle required first.** GLM is an alternative provider — by default Omniscio ships as a Claude-only product, and the entire GLM setup section below is hidden in Settings → Accounts. Flip **Settings → Accounts → Show alternative AI providers** to ON (off by default) and the GLM panel appears alongside Gemini, Codex, Anti-Gravity, DeepSeek, and Kimi. With the master off, the per-project "Default provider" radio collapses to a single Claude row and the session header's provider switcher (ChangeProviderButton) hides GLM — GLM effectively does not exist in the UI even if every gate below is configured. Your saved API key and "Allow GLM sessions" toggle are preserved across master-toggle flips.

You need two of these in place (note: no binary check — same `claude` binary as Claude). The opt-in **toggle leads and gates the key field** — the API key input appears only once GLM is turned on, so a key can't be entered into a disabled provider (when off, a hint keeps the `glm-api-key` Settings-search anchor landable and a saved key is preserved):

1. **Settings → Account → Allow GLM sessions in any project** — opt-in toggle, off by default; same security stance as the Codex/Gemini/DeepSeek/Kimi spawn guards. Turning it on reveals the key field below.
2. **Settings → Account → GLM → API key** — paste a z.ai API key from the [z.ai API console](https://z.ai) (encrypted at rest via Electron `safeStorage`).

Once both are green, you can launch a GLM session in three ways:

- **Per-launch override** — on a fresh (zero-message) session, the **ChangeProviderButton** in the new session's main panel (the launch-config pickers on a fresh session) lets you pick a one-off provider before sending the first message. Non-ready providers appear `aria-disabled` with an "Add key…" / "Enable in Settings…" suffix and deep-link to the right Settings panel.
- **Per-project default** — Edit Project dialog (three-dot menu → Edit) has a "Default provider" section listing every enabled provider. Pick GLM and the project's sidebar "+ New Session" button spawns GLM automatically.
- **Programmatically** — anything that creates a session with `provider: 'glm'` (recipes, agent-driven sessions, the CLI control HTTP API). Same readiness gates apply on the backend.

### Company GLM credits (run on Omniscio's key, draw down your credits)

Instead of pasting your own z.ai key, a signed-in user on a **Pro** plan can run GLM sessions on **Omniscio's company key** — usage draws down that user's **prepaid credit balance** (the same balance shown in Settings → **My API Keys**). It is the **Omniscio credits** row of GLM's supply list in **Settings → Accounts → Who pays & who serves**: add the row (a new user's GLM list starts with their own z.ai account only), and place it above or below your own account to choose which pays first. See [model-vendors.md](model-vendors.md).

Rules:

- **Signed-in and Pro only.** The row authenticates to Omniscio's gateway with your sign-in token and is checked against your plan; signed out or below Pro it is skipped ("Not signed in" / "Not available to you" on the card) and the next row serves — never a broken session.
- **The list's order decides.** Whichever of your own account and the credits row comes first in GLM's list pays first; the other is used when the first cannot serve.
- **Out of credits → the next row, or a clean stop.** When your balance reaches zero the gateway refuses; the session moves to the next row of GLM's list that can serve, and only when none is left does it stop with an "out of credits — top up" message, not a raw error.

How it works: the GLM session runs through the **JLS gateway** (the same Cloud Run service behind "My API Keys") at `<gateway>/v1/glm`, with your sign-in token as the bearer. The **company z.ai key lives only in the gateway** (Secret Manager) — it never reaches your machine. The gateway meters the real per-call cost and debits your prepaid credits. The lane's tier and funding are one `glm` row in `FUNDABLE_PROVIDERS` ([fundable-providers.ts](../../src/shared/providers/fundable-providers.ts)), kept equal to the gateway's own route by a parity guard.

**Operator note (going live):** the credits row is **inert** until the org adds the company GLM key to the gateway (`gateway-glm-key` secret + the `glm/*` block in `gateway/litellm/config.yaml`), deploys, and grants users credits (`gateway/scripts/grant-credits.mjs`). A deploy-time check (a real `/v1/glm` request must return a non-zero cost) confirms credits actually drain. Full invariants + steps: [company-glm-gateway-contract.md](../../.claude/memory/contracts/company-glm-gateway-contract.md). (Gemini can't use this path — its CLI can't be routed through the gateway and reports no cost to meter.)

## How it behaves

### Choosing the model

A fresh GLM session lets you pick the model in the launch-config pickers (next to the provider chooser). The list is registry-driven (`MODELS_BY_PROVIDER.glm` in [provider-models.ts](../../src/shared/providers/provider-models.ts)) and offers **GLM 5.3** (the current flagship), **GLM 5.3 Flash** (its cheap, fast tier — z.ai's $0.15/$0.50-per-M model, roughly 9× cheaper than the flagship), and **GLM 5.2** (the previous generation), each in two id forms: a `[1m]` form (e.g. `glm-5.3[1m]`, where the bracket suffix unlocks the full 1,000,000-token context z.ai documents for Claude Code) and a bare form (e.g. `glm-5.3`, the same model at a smaller default context window). **Use default** spawns **`glm-5.3[1m]`** rather than omitting the model flag — GLM _requires_ a real z.ai model id (the shared `claude` binary's built-in default is a Claude id that z.ai 404s as "model not found"), so the registry pins one via `getProviderDefaultModel`. GLM has no separate "reasoning effort" knob. The model list is z.ai's own — a GLM session never inherits your Claude default model (those are Claude ids its endpoint would reject). See [start-a-new-session.md](start-a-new-session.md).

### Per-project default

Each project remembers a default provider in the `projectDefaultProviders` setting (a `Record<projectId, ProviderId>`). Empty by default — every project falls back to Claude. Change it from the project's three-dot menu → Edit → Default provider radios; the change persists immediately.

When you set a project's default to GLM but GLM isn't ready (toggle off or key missing), an amber **"!" badge** appears on that project's sidebar row. Hover the badge for a tooltip explaining the gap; click it to deep-link straight to the right Settings panel. The badge re-checks on every render, so fixing the gap clears it without a refresh. Claude defaults never show the badge — Claude is always considered ready.

### Error states and fixes

Two gap codes — fewer than Gemini/Codex because there is no separate binary to check:

| Gap           | What it means                           | Click-to-fix lands you at…                     |
| ------------- | --------------------------------------- | ---------------------------------------------- |
| `toggle-off`  | "Allow GLM sessions" is OFF in Settings | Settings → Account → Allow GLM sessions toggle |
| `key-missing` | No row of GLM's supply list can serve (no z.ai account or key, and no other row ready) | Settings → Account → GLM API key field, or Who pays & who serves |

**Test your key.** The GLM API-key field has a **"Test key"** button that pings z.ai's real endpoint with your key and the default model, returning one plain-language verdict (works / invalid key / out of balance / model not available) — so a misconfigured key surfaces _before_ you spawn a session rather than as a confusing mid-turn failure.

**Retired / unavailable model.** If a session's selected model is no longer accepted by z.ai — either Omniscio has removed it from the picker or the vendor killed a still-listed id server-side — Omniscio surfaces a clear _"this GLM model is no longer available — open the model picker and choose another"_ and parks the session as `needs_you` / `recovery_failed`, instead of silently retrying the rejected request into the generic placeholder-stuck give-up loop (which never names the real model problem). Two guards back this: a **pre-spawn refusal** (the model isn't in the current picker list) and a **placeholder-stuck config-error guard** (an anthropic-compat session that produced no output and hit the rejected-request signature). See [provider-spawn-model-availability-contract.md](../../.claude/memory/contracts/provider-spawn-model-availability-contract.md).

**The real reason, not a blanket "model unavailable."** A z.ai rejection arrives the same way regardless of cause — a synthetic placeholder with zero output — so when the rejection is _not_ about the model (the account ran out of balance, the conversation outgrew the context window, the key became invalid, or you hit a rate limit), Omniscio now reads z.ai's actual error off that placeholder and surfaces the _specific_ cause + fix instead of the generic "model unavailable": **out of balance** → _"Your GLM account is out of balance — recharge it, then resend"_ (switching models can't fund an unfunded account, and a retry just re-hits the wall); **conversation too long** → start a fresh session or pick a larger-context model; **bad/expired key** → check the GLM key in Settings; **rate-limited** → wait a moment, then resend. The "model no longer available — choose another" wording above is now reserved for a genuinely unknown/retired model. This is message-only — the session still parks as `needs_you` / `recovery_failed` exactly as before, except that running out of balance first tries the backup key (if you set one) and then the next row of GLM's supply list that can serve, and parks only when neither can. Motivating case: a multi-hour GLM audit that hit z.ai's _"Insufficient balance … Please recharge"_ 429 and was shown the misleading "switch models" prompt — classification (`classifyAnthropicCompatPlaceholderError`) reads the four causes off the placeholder text, the same spot the Consumer-Terms gate already reads. See [provider-spawn-model-availability-contract.md](../../.claude/memory/contracts/provider-spawn-model-availability-contract.md) `vendor-error-is-classified-and-named`.

## For agents

### How it works under the hood

**Spawn-env redirect, no alternate manager.** Zhipu publishes an Anthropic-compatible API for GLM — same request/response shape as Anthropic's Messages API, just at a different base URL. So Omniscio reuses the existing `claude` CLI binary (the same one Claude sessions use) and redirects every HTTP call at spawn time by setting two environment variables on the child process:

- `ANTHROPIC_BASE_URL=https://api.z.ai/api/anthropic` — sends every HTTP request to z.ai's compat endpoint instead of `api.anthropic.com` (the CLI appends `/v1/messages`).
- `ANTHROPIC_AUTH_TOKEN=<your z.ai API key>` — supersedes the Claude OAuth/API-key for this child only.

The child process never knows it isn't talking to Anthropic. Streaming, tool calls, `--resume` for multi-turn, `--output-format stream-json` parsing all "just work" because they're handled by the standard `claude` binary against an upstream that speaks the same protocol.

No alternate session manager exists for GLM — readiness gates, the `spawn_non_claude_session` feature event fires, then it falls through to the same `createSessionWithPrompt()` path Claude sessions use. The `provider: 'glm'` value is stored on the session row in the database; [spawn-cluster-manager.ts](../../src/main/process/spawn-cluster-manager.ts) reads that column and injects the right spawn-env on every CLI invocation (initial spawn, resume, aside-runner).

Because GLM **runs the `claude` binary**, it is eligible for the same crash/restart/suspended/re-arm recovery as native Claude (`claude --resume` re-applies the z.ai env overlay from the session row) — distinct from Anthropic _account_ machinery (rate-limit recovery, load-balancing), which it stays out of since it authenticates with your z.ai key, not an Anthropic login.

Compared to:

- **DeepSeek / Kimi** — same shape exactly (Anthropic-compat, no binary, spawn-env redirect). The only differences are the vendor base URL and the credential field. GLM is the third member of this `anthropic-compat` family and shares every code path with them.
- **Codex / Anti-Gravity** — separate CLI binaries with their own protocols; need dedicated `*-session-manager.ts` files. GLM does not.
- **Gemini** — per-turn one-shot subprocess. GLM keeps the long-lived `claude` child process; resume via `claude --resume <uuid>`.
- **OpenClaw** — remote WebSocket gateway. GLM is plain HTTP to a vendor-hosted compat endpoint.

### Cost tracking

Spend hits the standard `api_cost_log` table because the `claude` CLI emits per-turn cost events in its `stream-json` output and the spawn layer already plumbs those into the cost tracker. The `source` field on the cost row carries the provider ID (`glm`), so the Stats → Spend dashboard breaks down GLM spend separately from Claude/Codex/Gemini.

**Cost is re-priced, not taken verbatim.** The `claude` binary has no z.ai price table, so its `total_cost_usd` bills GLM models at Claude rates. GLM is therefore `costReporting: 'estimated'`, and Omniscio recomputes each turn from the token counts × GLM's real rate (`MODEL_PRICING`) via `applyAnthropicCompatReprice` — the path shared by all four anthropic-compat vendors (DeepSeek/Kimi/GLM/MiniMax). See [central-ai-spend-contract.md](../../.claude/memory/contracts/central-ai-spend-contract.md) `anthropic-compat-cost-is-repriced`. (The company-credit gateway path meters its own real cost separately for credit debiting.)

**Prompt-cache reads bill at z.ai's real $0.26/M.** GLM caches heavily — on a long session the prompt-cache reads are typically 90%+ of the input — and z.ai charges those cached-input tokens at **$0.26/M** for GLM-5.x (its official rate, ~0.19× the $1.40/M fresh-input rate). The GLM rows in `MODEL_PRICING` carry a `cacheRead` override so `calculateCost` prices cache reads at that real rate instead of the flat 0.1×-input default ($0.14/M), which had under-counted a cached GLM session's spend ~46% (e.g. a live 95%-cache-hit session showed $2.11 when the real z.ai charge was ~$3.18). z.ai reports no separate cache-write tier — a cache MISS is billed as ordinary fresh input.

Telemetry: every successful GLM spawn fires the `spawn_non_claude_session` feature event, recording `{ provider: 'glm' }` only — no session ID, project ID, or prompt content.

### Files

- [src/shared/providers/registry.ts](../../src/shared/providers/registry.ts) — the `glm` descriptor (`anthropic-compat` runtimeKind, capabilities, label, pickerOrder)
- [src/main/services/providers/main-registry.ts](../../src/main/services/providers/main-registry.ts) — the GLM readiness wiring (toggle + API-key gates, no binary gate)
- [src/main/services/anthropic-compat-provider.ts](../../src/main/services/anthropic-compat-provider.ts) — the `z.ai` base URL + credential accessors (`ANTHROPIC_COMPAT_PROVIDERS` membership)
- [src/main/services/glm-credential-store.ts](../../src/main/services/glm-credential-store.ts) — the encrypted `glmApiKey` store
- [src/main/process/spawn-cluster-manager.ts](../../src/main/process/spawn-cluster-manager.ts) — injects `ANTHROPIC_BASE_URL` + `ANTHROPIC_AUTH_TOKEN` per session row's `provider` column
- [src/shared/types/provider-readiness.ts](../../src/shared/types/provider-readiness.ts) — `ProviderId` union
- [src/shared/types/settings/accounts-providers-settings.ts](../../src/shared/types/settings/accounts-providers-settings.ts) — `allowGlmSessionSpawn` setting
- [src/shared/types/settings/ai-features-settings.ts](../../src/shared/types/settings/ai-features-settings.ts) — `glmApiKey` setting
- [src/renderer/src/features/settings/sections/accounts/AccountSettings.tsx](../../src/renderer/src/features/settings/sections/accounts/AccountSettings.tsx) — GLM section under "Show alternative AI providers" (keys and accounts only; who pays is the Who pays & who serves card)
- [src/main/services/gateway-routing.ts](../../src/main/services/gateway-routing.ts) — `buildCompanyGatewayEnv`, the gateway spawn-env overlay a credits row connects through
- [src/main/services/providers/supply-connect.ts](../../src/main/services/providers/supply-connect.ts) — connects a GLM session on the first row of GLM's supply list that can serve: a personal account (or the backup key once latched), or the Omniscio-credits row
- [src/shared/types/settings/accounts-providers-settings.ts](../../src/shared/types/settings/accounts-providers-settings.ts) — `supplyLists.glm`, GLM's supply list (the older `providerFunding.glm` choice is read once, to build it)
- [gateway/gateway/routing.ts](../../gateway/gateway/routing.ts) — the gateway `glm` provider route + the `v1/messages` path the `claude` binary uses

## Related

- [kimi-provider.md](kimi-provider.md) — same shape (Anthropic-compat, no binary, spawn-env redirect), different vendor + key
- [deepseek-provider.md](deepseek-provider.md) — the other `anthropic-compat` sibling
- [ai-providers.md](ai-providers.md) — landing page for the full provider matrix
- [provider-registry-contract.md](../../.claude/memory/contracts/provider-registry-contract.md) — the DeepSeek + Kimi + GLM (`anthropic-compat`) invariants
- [company-glm-gateway-contract.md](../../.claude/memory/contracts/company-glm-gateway-contract.md) — the invariants of GLM's Omniscio credits row (gateway-routed company credit)
- [model-vendors.md](model-vendors.md) — who pays for a GLM session and which company serves it (the supply list)
- [api-keys.md](api-keys.md) — the "My API Keys" gateway + prepaid-credit balance the company-credit path draws down
