feat(providers): add local ZCode ACP backend (#10184)

* feat(providers): add local ZCode ACP backend

* test(snapshots): regenerate translate-path golden for zcode provider

The new local ZCode ACP backend (zcode://app-server/stdio) was added to the
provider catalog but the translate-path golden snapshot was not regenerated,
so the combined suite (provider-translate-path-golden.test.ts) failed on the
merged tip. Regenerate the snapshot to include the zcode translate-path entry.

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

* docs(env): document ZCODE_* vars for the local zcode provider

Registers the 11 ZCODE_* env vars read by the zcode executor (.env.example
+ docs/reference/ENVIRONMENT.md) so the env-doc-sync gate stays green.

Co-authored-by: Diego Souza <8016841+diegosouzapw@users.noreply.github.com>

* test(autoCombo): include zcode in the glm-family provider set

#10184's local zcode backend advertises the full GLM_SHARED_MODELS
line-up (registry/zcode, authType none) — same documented case as auggie
and devin-cli-agentic. Update auto/glm provider-set assertion to include
it.

Co-authored-by: Diego Souza <8016841+diegosouzapw@users.noreply.github.com>

---------

Co-authored-by: roomhacker <roomhacker@bezrabotnyi.com>
Co-authored-by: adevwithpurpose <adevwithpurpose@users.noreply.github.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
This commit is contained in:
Bezrabotnyi
2026-08-16 06:13:36 +03:00
committed by GitHub
parent aa5b77eb6e
commit 595d04dad9
18 changed files with 1243 additions and 1 deletions

View File

@@ -735,6 +735,21 @@ NEXT_PUBLIC_ENABLE_SOCKS5_PROXY=true
# CLI_AUGGIE_BIN=auggie
# AUGGIE_BIN=auggie
# ── ZCode (Z.ai GLM coding-plan CLI) local provider ──
# The local "zcode" provider talks to the authenticated ZCode app-server over a
# custom framed stdio protocol. Overrides below tune that stdio lifecycle.
# ZCODE_BIN=zcode
# ZCODE_ARGS=["--some-flag"]
# ZCODE_CWD=
# ZCODE_PROVIDER_ID=builtin:zai-coding-plan
# ZCODE_SERVER_RUNTIME_ROOT=~/.zcode/server
# ZCODE_SERVER_NODE=~/.zcode/server/node
# ZCODE_SERVER_ENTRY=~/.zcode/server/zcode-server.cjs
# ZCODE_STARTUP_TIMEOUT_MS=10000
# ZCODE_RPC_TIMEOUT_MS=30000
# ZCODE_TURN_TIMEOUT_MS=120000
# ZCODE_POLL_INTERVAL_MS=250
# Override the Hermes Agent home directory (where OmniRoute reads/writes the
# Hermes CLI config). Matches the env var the Hermes PowerShell installer sets
# on Windows (%LOCALAPPDATA%\hermes); defaults to ~/.hermes when unset.

View File

@@ -400,6 +400,17 @@ Controls how OmniRoute discovers and launches CLI sidecars (Claude Code, Codex,
| `DEVIN_BRIDGE_SUBAGENT_MODEL` | `DEVIN_BRIDGE_MODEL` | `docker/devin-bridge/compose.yml` | Isolated bridge alias used for Claude Code subagents. |
| `AUGGIE_BIN` | `auggie` | `open-sse/executors/auggie.ts` | Absolute-path override for the Augment (Auggie) CLI binary used by the local `auggie` provider. Falls back to `CLI_AUGGIE_BIN`, then a PATH lookup. |
| `CLI_AUGGIE_BIN` | `auggie` | `open-sse/executors/auggie.ts` | Alias override for the Augment (Auggie) CLI binary path (checked after `AUGGIE_BIN`). |
| `ZCODE_BIN` | `zcode` | `open-sse/executors/zcode.ts` | Binary used for the local `zcode` provider's stdio client. Falls back to `zcode` on PATH. |
| `ZCODE_ARGS` | — | `open-sse/executors/zcode.ts` | JSON array (≤16 strings) of extra arguments passed to the `zcode` binary when launched via `cliTools`. |
| `ZCODE_CWD` | `process.cwd()` | `open-sse/executors/zcode.ts` | Working directory for the ZCode app-server subprocess. |
| `ZCODE_PROVIDER_ID` | `builtin:zai-coding-plan` | `open-sse/executors/zcode.ts` | Override for the provider id sent to the app-server. |
| `ZCODE_SERVER_RUNTIME_ROOT` | `~/.zcode/server` | `open-sse/executors/zcode.ts` | Root of the ZCode app-server runtime (where the bundled `node` and `zcode-server.cjs` live). |
| `ZCODE_SERVER_NODE` | `<runtimeRoot>/node` | `open-sse/executors/zcode.ts` | Node executable used to host the ZCode app-server. |
| `ZCODE_SERVER_ENTRY` | `<runtimeRoot>/zcode-server.cjs` | `open-sse/executors/zcode.ts` | App-server entry script used to host the ZCode server. |
| `ZCODE_STARTUP_TIMEOUT_MS` | `10000` | `open-sse/executors/zcode.ts` | Startup timeout (ms) before a ZCode app-server launch is considered failed. |
| `ZCODE_RPC_TIMEOUT_MS` | `30000` | `open-sse/executors/zcode.ts` | Per-request RPC timeout (ms) for a ZCode app-server call. |
| `ZCODE_TURN_TIMEOUT_MS` | `120000` | `open-sse/executors/zcode.ts` | Maximum duration (ms) of one ZCode turn before the supervisor times it out. |
| `ZCODE_POLL_INTERVAL_MS` | `250` | `open-sse/executors/zcode.ts` | Polling interval (ms) for ZCode turn completion. |
| `HERMES_HOME` | `~/.hermes` | `src/lib/cli-helper/config-generator/hermesHome.ts` | Hermes Agent home directory where OmniRoute reads/writes the Hermes CLI config. Matches the env var the Hermes PowerShell installer sets on Windows (`%LOCALAPPDATA%\hermes`). |
### CLI Profile Auto-Sync

View File

@@ -154,6 +154,7 @@ import { command_codeProvider } from "./registry/command-code/index.ts";
import { novitaProvider } from "./registry/novita/index.ts";
import { regoloProvider } from "./registry/regolo/index.ts";
import { devin_desktopProvider } from "./registry/devin-desktop/index.ts";
import { zcodeProvider } from "./registry/zcode/index.ts";
import { zed_hostedProvider } from "./registry/zed-hosted/index.ts";
import { nanogptProvider } from "./registry/nanogpt/index.ts";
import { scalewayProvider } from "./registry/scaleway/index.ts";
@@ -412,6 +413,7 @@ export const REGISTRY: Record<string, RegistryEntry> = {
novita: novitaProvider,
regolo: regoloProvider,
"devin-desktop": devin_desktopProvider,
zcode: zcodeProvider,
"zed-hosted": zed_hostedProvider,
nanogpt: nanogptProvider,
scaleway: scalewayProvider,

View File

@@ -0,0 +1,18 @@
import type { RegistryEntry } from "../../shared.ts";
import { GLM_SHARED_MODELS } from "../../../glmProvider.ts";
/**
* Local ZCode app-server backend. Authentication remains in the user's local
* ZCode profile (`builtin:zai-coding-plan`); OmniRoute does not receive or
* persist the Z.ai credential.
*/
export const zcodeProvider: RegistryEntry = {
id: "zcode",
alias: "zc",
format: "openai",
executor: "zcode",
baseUrl: "zcode://app-server/stdio",
authType: "none",
authHeader: "none",
models: [...GLM_SHARED_MODELS],
};

View File

@@ -33,6 +33,7 @@ import { NlpCloudExecutor } from "./nlpcloud.ts";
import { DevinDesktopExecutor } from "./devin-desktop.ts";
import { ZedHostedExecutor } from "./zed-hosted.ts";
import { DevinCliExecutor } from "./devin-cli.ts";
import { ZcodeExecutor } from "./zcode.ts";
import { DevinCliAgenticExecutor } from "./devin-cli-agentic.ts";
import { AuggieExecutor } from "./auggie.ts";
import { DeepSeekWebExecutor } from "./deepseek-web.ts";
@@ -134,6 +135,8 @@ const executors = {
"devin-desktop": new DevinDesktopExecutor(),
"zed-hosted": new ZedHostedExecutor(),
"devin-cli": new DevinCliExecutor(),
zcode: new ZcodeExecutor(),
zc: new ZcodeExecutor(), // Alias
"devin-cli-agentic": new DevinCliAgenticExecutor(),
devin: new DevinCliExecutor(), // Alias
"deepseek-web": new DeepSeekWebWithAutoRefreshExecutor(),

375
open-sse/executors/zcode.ts Normal file
View File

@@ -0,0 +1,375 @@
import { randomUUID } from "node:crypto";
import { existsSync } from "node:fs";
import { homedir } from "node:os";
import { join, resolve } from "node:path";
import { GLM_SHARED_MODELS } from "../config/glmProvider.ts";
import { BaseExecutor, type ExecuteInput, type ExecutorExecuteResult, type ProviderCredentials } from "./base.ts";
import { ZcodeAppServerClient, type ZcodeClientLike } from "./zcodeProtocol.ts";
import { buildErrorBody, errorResponse, sanitizeErrorMessage } from "../utils/error.ts";
const ZCODE_URL = "zcode://app-server/stdio";
const DEFAULT_PROVIDER_ID = "builtin:zai-coding-plan";
const DEFAULT_TURN_TIMEOUT_MS = 120_000;
const DEFAULT_POLL_INTERVAL_MS = 250;
const TERMINAL_STATUSES = new Set(["completed", "idle", "paused", "error"]);
const ZCODE_MODEL_ALLOWLIST = new Set(GLM_SHARED_MODELS.map((model) => model.id));
const DEFAULT_ZCODE_MODEL = GLM_SHARED_MODELS[0]?.id || "glm-5.2";
type JsonRecord = Record<string, unknown>;
type OpenAIMsg = { role?: string; content?: unknown };
type ZcodeCommand = { command: string; args: string[] };
type ZcodeModelResolution = { ok: true; model: string } | { ok: false; error: string };
export interface ZcodeExecutorOptions {
command?: string;
args?: string[];
cwd?: string;
providerId?: string;
startupTimeoutMs?: number;
requestTimeoutMs?: number;
turnTimeoutMs?: number;
pollIntervalMs?: number;
clientFactory?: () => ZcodeClientLike;
}
function asRecord(value: unknown): JsonRecord {
return value && typeof value === "object" && !Array.isArray(value) ? value as JsonRecord : {};
}
function textFromContent(content: unknown): string {
if (typeof content === "string") return content;
if (!Array.isArray(content)) return "";
return content
.map((part) => {
if (typeof part === "string") return part;
const record = asRecord(part);
if (record.type === "text" || record.type === "input_text" || record.type === "output_text") {
return typeof record.text === "string" ? record.text : "";
}
return "";
})
.join("");
}
/** Convert an OpenAI conversation into one explicit ZCode coding turn. */
export function buildZcodePrompt(messages: OpenAIMsg[]): string {
const parts: string[] = [];
for (const message of messages) {
const text = textFromContent(message.content).trim();
if (!text) continue;
const role = String(message.role || "user");
const label = role === "system" ? "System" : role === "assistant" ? "Assistant" : "User";
parts.push(`[${label}]\n${text}`);
}
return parts.join("\n\n") || "(empty)";
}
export function resolveZcodeModel(model: unknown): ZcodeModelResolution {
const requested = typeof model === "string" ? model.trim() : "";
if (!requested) return { ok: true, model: DEFAULT_ZCODE_MODEL };
if (requested.startsWith("-")) {
return { ok: false, error: `Invalid ZCode model \"${requested}\": model must not start with \"-\".` };
}
const normalized = requested.startsWith("zcode/")
? requested.slice("zcode/".length)
: requested;
if (!ZCODE_MODEL_ALLOWLIST.has(normalized)) {
return {
ok: false,
error: `Unknown ZCode model \"${requested}\". Supported models: ${[...ZCODE_MODEL_ALLOWLIST].join(", ")}.`,
};
}
return { ok: true, model: normalized };
}
function parseArgs(raw: string | undefined): string[] {
if (!raw) return ["app-server"];
const parsed: unknown = JSON.parse(raw);
if (!Array.isArray(parsed) || parsed.length > 16 || !parsed.every((arg) => typeof arg === "string" && arg.length <= 4096)) {
throw new Error("ZCODE_ARGS must be a JSON array of at most 16 strings");
}
return parsed as string[];
}
function defaultCommand(): ZcodeCommand {
const runtimeRoot = process.env.ZCODE_SERVER_RUNTIME_ROOT || join(homedir(), ".zcode", "server");
const serverNode = process.env.ZCODE_SERVER_NODE || join(runtimeRoot, "node");
const serverEntry = process.env.ZCODE_SERVER_ENTRY || join(runtimeRoot, "zcode-server.cjs");
if (existsSync(serverNode) && existsSync(serverEntry)) {
return { command: serverNode, args: [serverEntry] };
}
return { command: process.env.ZCODE_BIN || "zcode", args: parseArgs(process.env.ZCODE_ARGS) };
}
function extractSessionId(value: unknown): string | undefined {
const root = asRecord(value);
const nested = asRecord(root.session);
const sessionId = nested.sessionId ?? root.sessionId;
return typeof sessionId === "string" && sessionId.trim() ? sessionId : undefined;
}
function extractStatus(value: unknown): string | undefined {
const root = asRecord(value);
const nested = asRecord(root.session);
const status = nested.status ?? root.status;
return typeof status === "string" ? status : undefined;
}
function extractTextFromMessage(value: unknown): { role?: string; text: string } {
const message = asRecord(value);
const info = asRecord(message.info);
const role = typeof info.role === "string" ? info.role : typeof message.role === "string" ? message.role : undefined;
const parts = Array.isArray(message.parts) ? message.parts : [];
const text = parts
.map((part) => {
const record = asRecord(part);
if (record.type === "text" && typeof record.text === "string") return record.text;
return "";
})
.join("");
return { role, text };
}
function extractAssistantText(value: unknown): string {
const root = asRecord(value);
const messages = Array.isArray(root.messages) ? root.messages : [];
for (let i = messages.length - 1; i >= 0; i -= 1) {
const message = extractTextFromMessage(messages[i]);
if (message.text && (!message.role || message.role === "assistant")) return message.text;
}
const nestedMessage = extractTextFromMessage(root.message);
if (nestedMessage.text) return nestedMessage.text;
for (const candidate of [root.content, root.text, root.output_text]) {
if (typeof candidate === "string" && candidate.trim()) return candidate;
}
return "";
}
function extractErrorMessage(value: unknown): string {
const root = asRecord(value);
const nested = asRecord(root.error);
for (const candidate of [nested.message, root.message, root.reason]) {
if (typeof candidate === "string" && candidate.trim()) return candidate;
}
return "ZCode app-server returned an error";
}
function makeWorkspace(cwd: string): JsonRecord {
return { workspacePath: cwd, workspaceIdentity: cwd };
}
function abortError(): Error {
return new Error("ZCode request aborted");
}
async function raceAbort<T>(promise: Promise<T>, signal?: AbortSignal | null): Promise<T> {
if (!signal) return promise;
if (signal.aborted) {
promise.catch(() => undefined);
throw abortError();
}
let onAbort: (() => void) | undefined;
const aborted = new Promise<T>((_, reject) => {
onAbort = () => reject(abortError());
signal.addEventListener("abort", onAbort, { once: true });
});
promise.catch(() => undefined);
try {
return await Promise.race([promise, aborted]);
} finally {
if (onAbort) signal.removeEventListener("abort", onAbort);
}
}
async function delay(ms: number, signal?: AbortSignal | null): Promise<void> {
if (ms <= 0) {
if (signal?.aborted) throw abortError();
return;
}
await raceAbort(new Promise<void>((resolveDelay) => {
const timer = setTimeout(resolveDelay, ms);
timer.unref?.();
}), signal);
}
function estimateTokens(text: string): number {
return Math.max(1, Math.ceil(text.length / 4));
}
function completionResponse(model: string, prompt: string, content: string): Response {
const promptTokens = estimateTokens(prompt);
const completionTokens = estimateTokens(content);
return new Response(JSON.stringify({
id: `chatcmpl-zcode-${Date.now()}`,
object: "chat.completion",
created: Math.floor(Date.now() / 1000),
model,
choices: [{ index: 0, message: { role: "assistant", content }, finish_reason: "stop" }],
usage: {
prompt_tokens: promptTokens,
completion_tokens: completionTokens,
total_tokens: promptTokens + completionTokens,
estimated: true,
},
}), { status: 200, headers: { "Content-Type": "application/json" } });
}
function sseResponse(model: string, content: string): Response {
const id = `chatcmpl-zcode-${Date.now()}`;
const created = Math.floor(Date.now() / 1000);
const chunks = [
{ id, object: "chat.completion.chunk", created, model, choices: [{ index: 0, delta: { role: "assistant", content: "" }, finish_reason: null }] },
{ id, object: "chat.completion.chunk", created, model, choices: [{ index: 0, delta: { content }, finish_reason: null }] },
{ id, object: "chat.completion.chunk", created, model, choices: [{ index: 0, delta: {}, finish_reason: "stop" }] },
];
const body = `${chunks.map((chunk) => `data: ${JSON.stringify(chunk)}\n\n`).join("")}data: [DONE]\n\n`;
return new Response(body, {
status: 200,
headers: { "Content-Type": "text/event-stream", "Cache-Control": "no-cache", Connection: "keep-alive" },
});
}
function sseErrorResponse(status: number, message: string): Response {
const body = `data: ${JSON.stringify(buildErrorBody(status, message))}\n\ndata: [DONE]\n\n`;
return new Response(body, {
status: 200,
headers: { "Content-Type": "text/event-stream", "Cache-Control": "no-cache", Connection: "keep-alive" },
});
}
export class ZcodeExecutor extends BaseExecutor {
private readonly options: ZcodeExecutorOptions;
constructor(options: ZcodeExecutorOptions = {}) {
super("zcode", { id: "zcode", baseUrl: ZCODE_URL, format: "openai" });
this.options = options;
}
buildUrl(): string {
return ZCODE_URL;
}
transformRequest(): null {
return null;
}
async execute(input: ExecuteInput): Promise<ExecutorExecuteResult> {
const resolution = resolveZcodeModel(input.model);
if (!resolution.ok) {
const message = "error" in resolution ? resolution.error : "Invalid ZCode model";
return input.stream ? sseErrorResponse(400, message) : errorResponse(400, message);
}
const body = asRecord(input.body);
const messages = Array.isArray(body.messages) ? body.messages as OpenAIMsg[] : [];
const prompt = buildZcodePrompt(messages);
input.log?.info?.("ZCODE", `local app-server turn started model=${resolution.model}`);
try {
const content = await this.runTurn(resolution.model, prompt, input.signal, input.log);
const response = input.stream
? sseResponse(resolution.model, content)
: completionResponse(resolution.model, prompt, content);
return {
response,
url: ZCODE_URL,
headers: {},
transformedBody: { model: resolution.model, promptLength: prompt.length, buffered: true },
transport: "local-zcode-app-server",
};
} catch (error) {
const message = sanitizeErrorMessage(error instanceof Error ? error.message : String(error));
input.log?.warn?.("ZCODE", message);
return input.stream ? sseErrorResponse(502, message) : errorResponse(502, message);
}
}
private createClient(): ZcodeClientLike {
if (this.options.clientFactory) return this.options.clientFactory();
const command = this.options.command || process.env.ZCODE_SERVER_NODE || defaultCommand().command;
const args = this.options.args || (process.env.ZCODE_SERVER_NODE
? [process.env.ZCODE_SERVER_ENTRY || join(process.env.ZCODE_SERVER_RUNTIME_ROOT || join(homedir(), ".zcode", "server"), "zcode-server.cjs")]
: defaultCommand().args);
return new ZcodeAppServerClient({
command,
args,
cwd: this.options.cwd || process.env.ZCODE_CWD || process.cwd(),
startupTimeoutMs: this.options.startupTimeoutMs ?? Number(process.env.ZCODE_STARTUP_TIMEOUT_MS || 10_000),
requestTimeoutMs: this.options.requestTimeoutMs ?? Number(process.env.ZCODE_RPC_TIMEOUT_MS || 30_000),
});
}
private async runTurn(
model: string,
prompt: string,
signal: AbortSignal | null | undefined,
log: ExecuteInput["log"]
): Promise<string> {
const client = this.createClient();
const cwd = resolve(this.options.cwd || process.env.ZCODE_CWD || process.cwd());
const workspace = makeWorkspace(cwd);
const providerId = this.options.providerId || process.env.ZCODE_PROVIDER_ID || DEFAULT_PROVIDER_ID;
const turnTimeoutMs = this.options.turnTimeoutMs ?? Number(process.env.ZCODE_TURN_TIMEOUT_MS || DEFAULT_TURN_TIMEOUT_MS);
const pollIntervalMs = this.options.pollIntervalMs ?? Number(process.env.ZCODE_POLL_INTERVAL_MS || DEFAULT_POLL_INTERVAL_MS);
let sessionId: string | undefined;
try {
await raceAbort(client.start(), signal);
const initialized = asRecord(await raceAbort(client.call("zcode-agent", "initialize", [workspace]), signal));
if (initialized.available !== true) {
throw new Error(extractErrorMessage(initialized));
}
const created = await raceAbort(client.call("zcode-agent", "createSession", [{
...workspace,
sessionTraceId: randomUUID(),
mode: "build",
persistence: "persistent",
}]), signal);
sessionId = extractSessionId(created);
if (!sessionId) throw new Error("ZCode createSession returned no sessionId");
await raceAbort(client.call("zcode-agent", "setModel", [{
...workspace,
sessionId,
model: { providerId, modelId: model },
}]), signal);
let state: unknown = await raceAbort(client.call("zcode-agent", "sendPrompt", [{
...workspace,
sessionId,
inputId: randomUUID(),
content: prompt,
}]), signal);
const deadline = Date.now() + Math.max(1, turnTimeoutMs);
while (Date.now() <= deadline) {
if (signal?.aborted) throw abortError();
const text = extractAssistantText(state);
const status = extractStatus(state);
if (text && (status === undefined || TERMINAL_STATUSES.has(status))) return text;
if (status === "error") throw new Error(extractErrorMessage(state));
await delay(Math.max(0, pollIntervalMs), signal);
state = await raceAbort(client.call("zcode-agent", "readSession", [{
...workspace,
sessionId,
messageLimit: 200,
}]), signal);
}
const finalText = extractAssistantText(state);
if (finalText) return finalText;
throw new Error("ZCode turn timed out before an assistant response was available");
} finally {
if (sessionId && !signal?.aborted) {
await client.call("zcode-agent", "closeSession", [{ ...workspace, sessionId }]).catch(() => undefined);
}
await client.close().catch((error) => log?.debug?.("ZCODE", `app-server close failed: ${sanitizeErrorMessage(error)}`));
}
}
// Credentials are intentionally ignored: the local ZCode profile owns auth.
override buildHeaders(_credentials: ProviderCredentials): Record<string, string> {
return {};
}
}

View File

@@ -0,0 +1,438 @@
import { spawn, type ChildProcessWithoutNullStreams } from "node:child_process";
const HEADER_SIZE = 13;
const REGULAR_MESSAGE = 1;
const INITIALIZE_MESSAGE = 200;
const RESPONSE_MESSAGE = 201;
const ERROR_MESSAGE = 202;
const CANCELED_MESSAGE = 203;
const MAX_FRAME_BYTES = 32 * 1024 * 1024;
type JsonRecord = Record<string, unknown>;
export interface ZcodeAppServerClientOptions {
command: string;
args?: string[];
cwd?: string;
env?: NodeJS.ProcessEnv;
startupTimeoutMs?: number;
requestTimeoutMs?: number;
}
export interface ZcodeClientLike {
start(): Promise<void>;
call(channel: string, method: string, args: unknown[]): Promise<unknown>;
close(): Promise<void>;
}
interface PendingRequest {
resolve: (value: unknown) => void;
reject: (error: Error) => void;
timer: ReturnType<typeof setTimeout>;
}
interface DecodedValue {
value: unknown;
offset: number;
}
function encodeVql(value: number): Buffer {
if (!Number.isSafeInteger(value) || value < 0) {
throw new Error(`ZCode protocol requires a non-negative integer, got ${String(value)}`);
}
const bytes: number[] = [];
let remaining = value;
do {
let next = remaining % 128;
remaining = Math.floor(remaining / 128);
if (remaining > 0) next |= 0x80;
bytes.push(next);
} while (remaining > 0);
return Buffer.from(bytes);
}
function decodeVql(data: Uint8Array, offset: number): { value: number; offset: number } {
let value = 0;
let multiplier = 1;
let cursor = offset;
for (let i = 0; i < 8; i += 1) {
if (cursor >= data.byteLength) throw new Error("Truncated ZCode variable-length quantity");
const next = data[cursor++];
value += (next & 0x7f) * multiplier;
if ((next & 0x80) === 0) return { value, offset: cursor };
multiplier *= 128;
}
throw new Error("Invalid ZCode variable-length quantity");
}
/** Serialize one value using ZCode's SocketProtocol value encoding. */
export function encodeZcodeValue(value: unknown): Buffer {
if (value === undefined) return Buffer.from([0]);
if (typeof value === "string") {
const bytes = Buffer.from(value, "utf8");
return Buffer.concat([Buffer.from([1]), encodeVql(bytes.byteLength), bytes]);
}
if (Buffer.isBuffer(value) || value instanceof Uint8Array) {
const bytes = Buffer.from(value);
return Buffer.concat([Buffer.from([2]), encodeVql(bytes.byteLength), bytes]);
}
if (Array.isArray(value)) {
return Buffer.concat([
Buffer.from([4]),
encodeVql(value.length),
...value.map((item) => encodeZcodeValue(item)),
]);
}
if (typeof value === "number" && Number.isSafeInteger(value) && value >= 0) {
return Buffer.concat([Buffer.from([6]), encodeVql(value)]);
}
if (typeof value === "bigint" || typeof value === "function" || typeof value === "symbol") {
throw new Error(`Unsupported ZCode protocol value type: ${typeof value}`);
}
const bytes = Buffer.from(JSON.stringify(value), "utf8");
return Buffer.concat([Buffer.from([5]), encodeVql(bytes.byteLength), bytes]);
}
/** Decode one value from ZCode's SocketProtocol value encoding. */
export function decodeZcodeValue(data: Uint8Array, offset = 0): DecodedValue {
if (offset >= data.byteLength) throw new Error("Truncated ZCode serialized value");
const type = data[offset++];
if (type === 0) return { value: undefined, offset };
if (type === 1 || type === 2) {
const length = decodeVql(data, offset);
const end = length.offset + length.value;
if (end > data.byteLength) throw new Error("Truncated ZCode byte/string value");
const bytes = data.slice(length.offset, end);
return {
value: type === 1 ? Buffer.from(bytes).toString("utf8") : Buffer.from(bytes),
offset: end,
};
}
if (type === 4) {
const length = decodeVql(data, offset);
const values: unknown[] = [];
let cursor = length.offset;
for (let i = 0; i < length.value; i += 1) {
const decoded = decodeZcodeValue(data, cursor);
values.push(decoded.value);
cursor = decoded.offset;
}
return { value: values, offset: cursor };
}
if (type === 5) {
const length = decodeVql(data, offset);
const end = length.offset + length.value;
if (end > data.byteLength) throw new Error("Truncated ZCode JSON value");
return {
value: JSON.parse(Buffer.from(data.slice(length.offset, end)).toString("utf8")),
offset: end,
};
}
if (type === 6) {
const decoded = decodeVql(data, offset);
return { value: decoded.value, offset: decoded.offset };
}
throw new Error(`Unknown ZCode serialized value type ${type}`);
}
export function encodeZcodeRpcCall(
id: number,
channel: string,
method: string,
args: unknown[]
): Buffer {
const body = Buffer.concat([
encodeZcodeValue([100, id, channel, method]),
encodeZcodeValue(args),
]);
const frame = Buffer.alloc(HEADER_SIZE + body.byteLength);
frame.writeUInt8(REGULAR_MESSAGE, 0);
frame.writeUInt32BE(0, 1);
frame.writeUInt32BE(0, 5);
frame.writeUInt32BE(body.byteLength, 9);
body.copy(frame, HEADER_SIZE);
return frame;
}
function errorFromPayload(payload: unknown, fallback: string): Error {
if (payload && typeof payload === "object") {
const record = payload as JsonRecord;
const message = typeof record.message === "string" ? record.message : fallback;
const error = new Error(message);
if (typeof record.code === "string") Object.assign(error, { code: record.code });
if (record.data !== undefined) Object.assign(error, { data: record.data });
return error;
}
return new Error(fallback);
}
/**
* Local stdio client for the ZCode app-server. The protocol starts with a JSON
* hello line and then switches to 13-byte length-prefixed binary frames.
*/
export class ZcodeAppServerClient implements ZcodeClientLike {
private readonly command: string;
private readonly args: string[];
private readonly cwd?: string;
private readonly env?: NodeJS.ProcessEnv;
private readonly startupTimeoutMs: number;
private readonly requestTimeoutMs: number;
private child?: ChildProcessWithoutNullStreams;
private outputBuffer = Buffer.alloc(0);
private handshakeDone = false;
private ready = false;
private startPromise?: Promise<void>;
private serverReady?: () => void;
private serverReadyError?: (error: Error) => void;
private nextRequestId = 1;
private readonly pending = new Map<number, PendingRequest>();
constructor(options: ZcodeAppServerClientOptions) {
this.command = options.command;
this.args = options.args ?? [];
this.cwd = options.cwd;
this.env = options.env;
this.startupTimeoutMs = options.startupTimeoutMs ?? 10_000;
this.requestTimeoutMs = options.requestTimeoutMs ?? 30_000;
}
async start(): Promise<void> {
if (this.ready) return;
if (this.startPromise) return this.startPromise;
this.startPromise = this.startInternal().finally(() => {
this.startPromise = undefined;
});
return this.startPromise;
}
private async startInternal(): Promise<void> {
let child: ChildProcessWithoutNullStreams;
try {
child = spawn(this.command, this.args, {
cwd: this.cwd,
env: this.env ? { ...process.env, ...this.env } : process.env,
stdio: ["pipe", "pipe", "pipe"],
shell: false,
windowsHide: true,
});
} catch (error) {
throw error instanceof Error ? error : new Error(String(error));
}
this.child = child;
this.outputBuffer = Buffer.alloc(0);
this.handshakeDone = false;
this.ready = false;
child.stdin.on("error", () => {
// EPIPE is expected when timeout/abort closes an already-exited runtime.
});
let settled = false;
const readyPromise = new Promise<void>((resolve, reject) => {
this.serverReady = () => {
if (settled) return;
settled = true;
resolve();
};
this.serverReadyError = (error) => {
if (settled) return;
settled = true;
reject(error);
};
});
child.stdout.on("data", (chunk: Buffer) => this.onStdout(chunk));
child.stderr.on("data", () => {
// ZCode stderr is intentionally not forwarded: it can contain provider
// diagnostics or credentials from the user's local runtime.
});
child.on("error", (error) => {
this.serverReadyError?.(error);
this.rejectPending(error);
});
child.on("exit", (code, signal) => {
const error = new Error(`ZCode app-server exited: ${code ?? signal ?? "unknown"}`);
this.ready = false;
this.handshakeDone = false;
this.serverReadyError?.(error);
this.rejectPending(error);
if (this.child === child) this.child = undefined;
});
try {
await this.withTimeout(readyPromise, this.startupTimeoutMs, "ZCode app-server handshake timed out");
this.ready = true;
} catch (error) {
await this.disposeChild(child);
throw error instanceof Error ? error : new Error(String(error));
} finally {
this.serverReady = undefined;
this.serverReadyError = undefined;
}
}
private onStdout(chunk: Buffer): void {
this.outputBuffer = Buffer.concat([this.outputBuffer, chunk]);
if (!this.handshakeDone) {
const newline = this.outputBuffer.indexOf(0x0a);
if (newline < 0) {
if (this.outputBuffer.byteLength > 64 * 1024) {
this.serverReadyError?.(new Error("ZCode hello line is too large"));
}
return;
}
const line = this.outputBuffer.subarray(0, newline).toString("utf8").trim();
this.outputBuffer = this.outputBuffer.subarray(newline + 1);
let hello: unknown;
try {
hello = JSON.parse(line);
} catch {
this.serverReadyError?.(new Error("Invalid ZCode app-server hello"));
return;
}
if (!hello || typeof hello !== "object" || (hello as JsonRecord).type !== "zcode-hello") {
this.serverReadyError?.(new Error("Unexpected ZCode app-server hello"));
return;
}
const child = this.child;
if (!child) return;
child.stdin.write(`${JSON.stringify({
type: "zcode-hello-ack",
version: "omniroute",
clientId: `omniroute-${process.pid}`,
})}\n`);
this.handshakeDone = true;
}
this.consumeFrames();
}
private consumeFrames(): void {
while (this.outputBuffer.byteLength >= HEADER_SIZE) {
const type = this.outputBuffer.readUInt8(0);
const length = this.outputBuffer.readUInt32BE(9);
if (length > MAX_FRAME_BYTES) {
const error = new Error("ZCode frame exceeds the configured safety limit");
this.serverReadyError?.(error);
this.rejectPending(error);
return;
}
const frameLength = HEADER_SIZE + length;
if (this.outputBuffer.byteLength < frameLength) return;
const body = this.outputBuffer.subarray(HEADER_SIZE, frameLength);
this.outputBuffer = this.outputBuffer.subarray(frameLength);
if (type !== REGULAR_MESSAGE) continue;
try {
const header = decodeZcodeValue(body, 0);
const payload = decodeZcodeValue(body, header.offset);
this.handleMessage(header.value, payload.value);
} catch (error) {
const normalized = error instanceof Error ? error : new Error(String(error));
this.serverReadyError?.(normalized);
this.rejectPending(normalized);
}
}
}
private handleMessage(headerValue: unknown, payload: unknown): void {
if (!Array.isArray(headerValue)) return;
const type = headerValue[0];
if (type === INITIALIZE_MESSAGE) {
this.serverReady?.();
return;
}
if (type !== RESPONSE_MESSAGE && type !== ERROR_MESSAGE && type !== CANCELED_MESSAGE) return;
const requestId = headerValue[1];
if (typeof requestId !== "number") return;
const request = this.pending.get(requestId);
if (!request) return;
this.pending.delete(requestId);
clearTimeout(request.timer);
if (type === RESPONSE_MESSAGE) {
request.resolve(payload);
} else {
request.reject(errorFromPayload(
payload,
type === ERROR_MESSAGE ? "ZCode RPC request failed" : "ZCode RPC request canceled"
));
}
}
async call(channel: string, method: string, args: unknown[]): Promise<unknown> {
await this.start();
const child = this.child;
if (!child || !this.ready) throw new Error("ZCode app-server is not ready");
const requestId = this.nextRequestId++;
return new Promise((resolve, reject) => {
const timer = setTimeout(() => {
this.pending.delete(requestId);
reject(new Error(`ZCode RPC request timed out: ${channel}.${method}`));
}, this.requestTimeoutMs);
timer.unref?.();
this.pending.set(requestId, { resolve, reject, timer });
try {
child.stdin.write(encodeZcodeRpcCall(requestId, channel, method, args));
} catch (error) {
clearTimeout(timer);
this.pending.delete(requestId);
reject(error instanceof Error ? error : new Error(String(error)));
}
});
}
async close(): Promise<void> {
const child = this.child;
this.ready = false;
this.handshakeDone = false;
this.child = undefined;
this.serverReadyError?.(new Error("ZCode app-server closed"));
this.rejectPending(new Error("ZCode app-server closed"));
if (child) await this.disposeChild(child);
}
private rejectPending(error: Error): void {
for (const [id, pending] of this.pending) {
clearTimeout(pending.timer);
pending.reject(error);
this.pending.delete(id);
}
}
private async disposeChild(child: ChildProcessWithoutNullStreams): Promise<void> {
if (child.exitCode !== null || child.signalCode !== null) return;
const exited = new Promise<void>((resolve) => child.once("close", () => resolve()));
try {
child.stdin.end();
} catch {
// The process may already have closed stdin.
}
if (!child.killed) child.kill("SIGTERM");
let timer: ReturnType<typeof setTimeout> | undefined;
await Promise.race([
exited,
new Promise<void>((resolve) => {
timer = setTimeout(resolve, 1500);
timer.unref?.();
}),
]);
if (timer) clearTimeout(timer);
if (child.exitCode === null && child.signalCode === null) {
child.kill("SIGKILL");
await exited;
}
}
private async withTimeout<T>(promise: Promise<T>, timeoutMs: number, message: string): Promise<T> {
let timer: ReturnType<typeof setTimeout> | undefined;
try {
return await Promise.race([
promise,
new Promise<T>((_, reject) => {
timer = setTimeout(() => reject(new Error(message)), timeoutMs);
timer.unref?.();
}),
]);
} finally {
if (timer) clearTimeout(timer);
}
}
}

View File

@@ -96,6 +96,15 @@ const AGENT_DEFINITIONS: Omit<CliAgentInfo, "version" | "installed">[] = [
spawnArgs: ["--no-auto-commits"],
protocol: "stdio",
},
{
id: "zcode",
name: "ZCode (GLM Coding Plan)",
binary: "zcode",
versionCommand: "zcode --version",
providerAlias: "zcode",
spawnArgs: ["app-server"],
protocol: "stdio",
},
{
id: "opencode",
name: "OpenCode",

View File

@@ -85,6 +85,25 @@ export const CLI_TOOLS: Record<string, CliCatalogEntry> = {
baseUrlSupport: "full",
defaultCommand: "codex",
},
zcode: {
id: "zcode",
name: "ZCode (GLM Coding Plan)",
color: "#3B82F6",
description: "Local ZCode app-server backend; auth remains in the user's ZCode profile",
docsUrl: "https://zcode.z.ai",
configType: "custom",
category: "code",
vendor: "Z.ai",
// ZCode's app-server is a native length-prefixed protocol, not ACP. The
// zcode provider executor owns its lifecycle instead of ACP spawning it.
acpSpawnable: false,
baseUrlSupport: "none",
defaultCommand: "zcode",
notes: [
{ type: "info", text: "Uses the local ZCode app-server and its existing builtin:zai-coding-plan login." },
{ type: "warning", text: "The response is buffered until the ZCode turn completes." },
],
},
droid: {
id: "droid",
name: "Factory Droid",

View File

@@ -38,6 +38,8 @@ export const FREE_APIKEY_PROVIDER_IDS = new Set([
// accepts an optional connection row for display/priority/testStatus tracking —
// no apiKey is ever required or sent upstream.
"auggie",
// zcode is a local app-server backend; auth stays in the ZCode profile.
"zcode",
]);
export function supportsApiKeyOnFreeProvider(providerId: unknown): boolean {

View File

@@ -157,6 +157,24 @@ export const NOAUTH_PROVIDERS = {
text: "Augment (Auggie CLI) requires the `auggie` binary installed and authenticated locally (`auggie login`). OmniRoute spawns it as a subprocess and never sees or stores your Augment credentials.",
},
},
zcode: {
id: "zcode",
alias: "zc",
name: "ZCode (GLM Coding Plan)",
icon: "terminal",
color: "#3B82F6",
textIcon: "ZC",
website: "https://zcode.z.ai",
noAuth: true,
hasFree: false,
serviceKinds: ["llm"],
isLocalCli: true,
authHint:
"No API key stored by OmniRoute. The local ZCode app-server uses the existing builtin:zai-coding-plan login.",
notice: {
text: "ZCode runs locally through its native app-server. OmniRoute never receives or stores the Z.ai credential.",
},
},
aihorde: {
id: "aihorde",
alias: "horde",

View File

@@ -93,6 +93,17 @@ const CLI_TOOLS: Record<string, any> = {
},
},
},
zcode: {
defaultCommand: "zcode",
envBinKey: "ZCODE_BIN",
requiresBinary: true,
// The app-server performs a local runtime handshake and can be slower on
// the first launch while the user's ZCode profile is loaded.
healthcheckTimeoutMs: 15000,
paths: {
config: ".zcode",
},
},
cline: {
defaultCommand: "cline",
envBinKey: "CLI_CLINE_BIN",

167
tests/fixtures/fake-zcode-app-server.mjs vendored Normal file
View File

@@ -0,0 +1,167 @@
const HEADER_SIZE = 13;
let input = Buffer.alloc(0);
let handshaken = false;
let sessionId = "fake-zcode-session";
let selectedModel = null;
function vql(value) {
if (!Number.isSafeInteger(value) || value < 0) throw new Error("invalid vql value");
const bytes = [];
let remaining = value;
do {
let next = remaining % 128;
remaining = Math.floor(remaining / 128);
if (remaining > 0) next |= 0x80;
bytes.push(next);
} while (remaining > 0);
return Buffer.from(bytes);
}
function encode(value) {
if (value === undefined) return Buffer.from([0]);
if (typeof value === "string") {
const bytes = Buffer.from(value, "utf8");
return Buffer.concat([Buffer.from([1]), vql(bytes.length), bytes]);
}
if (Buffer.isBuffer(value) || value instanceof Uint8Array) {
const bytes = Buffer.from(value);
return Buffer.concat([Buffer.from([2]), vql(bytes.length), bytes]);
}
if (Array.isArray(value)) {
return Buffer.concat([Buffer.from([4]), vql(value.length), ...value.map(encode)]);
}
if (typeof value === "number" && Number.isSafeInteger(value) && value >= 0) {
return Buffer.concat([Buffer.from([6]), vql(value)]);
}
const bytes = Buffer.from(JSON.stringify(value), "utf8");
return Buffer.concat([Buffer.from([5]), vql(bytes.length), bytes]);
}
function readVql(data, state) {
let value = 0;
let multiplier = 1;
for (let i = 0; i < 8; i += 1) {
if (state.offset >= data.length) throw new Error("truncated vql");
const next = data[state.offset++];
value += (next & 0x7f) * multiplier;
if ((next & 0x80) === 0) return value;
multiplier *= 128;
}
throw new Error("invalid vql");
}
function decode(data, state) {
const type = data[state.offset++];
if (type === 0) return undefined;
if (type === 1 || type === 2) {
const length = readVql(data, state);
const end = state.offset + length;
if (end > data.length) throw new Error("truncated bytes");
const bytes = data.subarray(state.offset, end);
state.offset = end;
return type === 1 ? bytes.toString("utf8") : bytes;
}
if (type === 4) {
const length = readVql(data, state);
return Array.from({ length }, () => decode(data, state));
}
if (type === 5) {
const length = readVql(data, state);
const end = state.offset + length;
const value = JSON.parse(data.subarray(state.offset, end).toString("utf8"));
state.offset = end;
return value;
}
if (type === 6) return readVql(data, state);
throw new Error(`unknown type ${type}`);
}
function frame(body) {
const result = Buffer.alloc(HEADER_SIZE + body.length);
result.writeUInt8(1, 0);
result.writeUInt32BE(0, 1);
result.writeUInt32BE(0, 5);
result.writeUInt32BE(body.length, 9);
body.copy(result, HEADER_SIZE);
return result;
}
function send(header, payload) {
const packet = frame(Buffer.concat([encode(header), encode(payload)]));
process.stdout.write(packet.subarray(0, 5));
setTimeout(() => process.stdout.write(packet.subarray(5)), 1);
}
function response(id, payload) {
send([201, id], payload);
}
function handleFrame(body) {
const state = { offset: 0 };
const header = decode(body, state);
const args = decode(body, state);
const id = Array.isArray(header) ? header[1] : undefined;
const method = Array.isArray(header) ? header[3] : undefined;
const request = Array.isArray(args) && args[0] && typeof args[0] === "object" ? args[0] : {};
switch (method) {
case "initialize":
response(id, { available: true, protocolName: "ZCode Protocol", protocolVersion: 1, transportKind: "stdio" });
break;
case "createSession":
sessionId = "fake-zcode-session";
response(id, { session: { sessionId, status: "idle", workspace: { workspacePath: request.workspacePath } }, messages: [] });
break;
case "setModel":
selectedModel = request.model;
response(id, { ok: true, model: selectedModel });
break;
case "sendPrompt":
response(id, { session: { sessionId, status: "running" }, accepted: true });
break;
case "readSession":
response(id, {
session: { sessionId, status: "completed", model: selectedModel },
messages: [
{ info: { messageId: "fake-user-message", role: "user" }, parts: [{ type: "text", text: request.content || "prompt" }] },
{ info: { messageId: "fake-assistant-message", role: "assistant" }, parts: [{ type: "text", text: "fake zcode response" }] },
],
});
break;
case "closeSession":
response(id, { ok: true });
break;
default:
response(id, { ok: true });
break;
}
}
function consumeFrames() {
while (input.length >= HEADER_SIZE) {
const length = input.readUInt32BE(9);
const total = HEADER_SIZE + length;
if (input.length < total) return;
const body = input.subarray(HEADER_SIZE, total);
input = input.subarray(total);
handleFrame(body);
}
}
process.stdout.write(`${JSON.stringify({ type: "zcode-hello", version: "fixture", platform: "test", arch: "test", pid: process.pid })}\n`);
process.stdin.on("data", (chunk) => {
input = Buffer.concat([input, chunk]);
if (!handshaken) {
const newline = input.indexOf(0x0a);
if (newline < 0) return;
const ack = JSON.parse(input.subarray(0, newline).toString("utf8"));
if (ack.type !== "zcode-hello-ack") throw new Error("missing ZCode hello ack");
input = input.subarray(newline + 1);
handshaken = true;
send([200], undefined);
}
consumeFrames();
});
process.stdin.on("end", () => process.exit(0));

View File

@@ -6156,6 +6156,29 @@
"stream": "https://chat.z.ai"
}
},
"zcode": {
"format": "openai",
"headers": {
"apiKey": {
"Accept": "text/event-stream",
"Authorization": "Bearer <TOK>",
"Content-Type": "application/json"
},
"nonStream": {
"Authorization": "Bearer <TOK>",
"Content-Type": "application/json"
},
"oauth": {
"Accept": "text/event-stream",
"Authorization": "Bearer <TOK>",
"Content-Type": "application/json"
}
},
"url": {
"nonStream": "zcode://app-server/stdio",
"stream": "zcode://app-server/stdio"
}
},
"zed-hosted": {
"format": "openai",
"headers": {

View File

@@ -141,7 +141,10 @@ describe("auto/<family> materialization (#6453)", () => {
// `devin-cli-agentic` joined for the same documented reason as `auggie`:
// #8914 added the Devin ACP bridge whose catalog (registry/devin/catalog.ts)
// advertises the glm-5-2* line, so it genuinely serves the family.
assert.deepEqual(providerIds, ["auggie", "devin-cli-agentic", "glm", "zai"]);
// `zcode` joined for the same documented reason too — #10184 added the local
// ZCode app-server backend whose registry (registry/zcode) advertises the
// full GLM_SHARED_MODELS line-up, so it genuinely serves the family.
assert.deepEqual(providerIds, ["auggie", "devin-cli-agentic", "glm", "zai", "zcode"]);
// Every candidate must be a glm-family model (the Cartesian pool now surfaces
// each backend's full glm line-up, not only the glm-5.2 default), and the
// connected openai/gpt-4o-mini backend must be excluded — same family

View File

@@ -0,0 +1,84 @@
import test from "node:test";
import assert from "node:assert/strict";
import { mkdtempSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
const fixture = join(process.cwd(), "tests/fixtures/fake-zcode-app-server.mjs");
const TEST_DATA_DIR = mkdtempSync(join(tmpdir(), "omniroute-zcode-"));
process.env.DATA_DIR = TEST_DATA_DIR;
test.after(() => rmSync(TEST_DATA_DIR, { recursive: true, force: true }));
async function loadZcodeExecutor() {
return import("../../open-sse/executors/zcode.ts");
}
function requestBody() {
return {
messages: [
{ role: "system", content: "You are a coding assistant." },
{ role: "user", content: "Reply with a short status." },
],
};
}
test("ZCode accepts GLM Coding Plan models and rejects unsafe/unknown ids", async () => {
const { resolveZcodeModel } = await loadZcodeExecutor();
assert.deepEqual(resolveZcodeModel("glm-5.2"), { ok: true, model: "glm-5.2" });
assert.equal(resolveZcodeModel("-unexpected").ok, false);
assert.equal(resolveZcodeModel("unknown-model").ok, false);
});
test("ZCode runs a local app-server turn and returns an OpenAI chat completion", async () => {
const { ZcodeExecutor } = await loadZcodeExecutor();
const executor = new ZcodeExecutor({
command: process.execPath,
args: [fixture],
cwd: process.cwd(),
requestTimeoutMs: 3000,
turnTimeoutMs: 3000,
pollIntervalMs: 1,
});
const result = await executor.execute({
model: "glm-5.2",
body: requestBody(),
stream: false,
credentials: {},
});
const response = "response" in result ? result.response : result;
assert.equal(response.status, 200);
assert.match(response.headers.get("content-type") || "", /application\/json/);
const body = await response.json();
assert.equal(body.object, "chat.completion");
assert.equal(body.model, "glm-5.2");
assert.equal(body.choices?.[0]?.message?.role, "assistant");
assert.equal(body.choices?.[0]?.message?.content, "fake zcode response");
assert.equal(body.choices?.[0]?.finish_reason, "stop");
});
test("ZCode buffers the completed turn into OpenAI SSE when stream=true", async () => {
const { ZcodeExecutor } = await loadZcodeExecutor();
const executor = new ZcodeExecutor({
command: process.execPath,
args: [fixture],
cwd: process.cwd(),
requestTimeoutMs: 3000,
turnTimeoutMs: 3000,
pollIntervalMs: 1,
});
const result = await executor.execute({
model: "glm-5.2-high",
body: requestBody(),
stream: true,
credentials: {},
});
const response = "response" in result ? result.response : result;
const text = await response.text();
assert.equal(response.status, 200);
assert.match(response.headers.get("content-type") || "", /text\/event-stream/);
assert.match(text, /fake zcode response/);
assert.match(text, /data: \[DONE\]/);
});

View File

@@ -0,0 +1,30 @@
import test from "node:test";
import assert from "node:assert/strict";
import { join } from "node:path";
import { ZcodeAppServerClient } from "../../open-sse/executors/zcodeProtocol.ts";
const fixture = join(process.cwd(), "tests/fixtures/fake-zcode-app-server.mjs");
test("ZCode protocol performs hello handshake and exchanges fragmented framed RPC", async () => {
const client = new ZcodeAppServerClient({
command: process.execPath,
args: [fixture],
cwd: process.cwd(),
startupTimeoutMs: 3000,
requestTimeoutMs: 3000,
});
try {
await client.start();
const result = await client.call("zcode-agent", "initialize", [
{ workspacePath: "/workspace", workspaceIdentity: "/workspace" },
]);
assert.deepEqual(result, {
available: true,
protocolName: "ZCode Protocol",
protocolVersion: 1,
transportKind: "stdio",
});
} finally {
await client.close();
}
});

View File

@@ -0,0 +1,14 @@
import test from "node:test";
import assert from "node:assert/strict";
import { zcodeProvider } from "../../open-sse/config/providers/registry/zcode/index.ts";
test("ZCode provider registry exposes a local no-auth GLM Coding Plan backend", () => {
assert.equal(zcodeProvider.id, "zcode");
assert.equal(zcodeProvider.alias, "zc");
assert.equal(zcodeProvider.executor, "zcode");
assert.equal(zcodeProvider.format, "openai");
assert.equal(zcodeProvider.baseUrl, "zcode://app-server/stdio");
assert.equal(zcodeProvider.authType, "none");
assert.equal(zcodeProvider.authHeader, "none");
assert.equal(zcodeProvider.models.some((model) => model.id === "glm-5.2"), true);
});