mirror of
https://github.com/hansjone/oclaw.git
synced 2026-10-11 09:20:49 +08:00
重构主控编排与运行时预热链路,统一工作区提示词/专家调度协议并补齐 wiki 记忆注入与写回闭环。
同时收敛启动与运维脚本默认行为(含 wiki worker)、更新 Admin 可观测性与相关测试,降低首轮时延并提高运行稳定性。 Made-with: Cursor
This commit is contained in:
parent
4a23b715a2
commit
dbbe3add6a
14438 changed files with 2693620 additions and 2546 deletions
11
openclaw/extensions/zai/api.ts
Normal file
11
openclaw/extensions/zai/api.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
export { applyZaiConfig, applyZaiProviderConfig, ZAI_DEFAULT_MODEL_REF } from "./onboard.js";
|
||||
export {
|
||||
buildZaiModelDefinition,
|
||||
resolveZaiBaseUrl,
|
||||
ZAI_CN_BASE_URL,
|
||||
ZAI_CODING_CN_BASE_URL,
|
||||
ZAI_CODING_GLOBAL_BASE_URL,
|
||||
ZAI_DEFAULT_COST,
|
||||
ZAI_DEFAULT_MODEL_ID,
|
||||
ZAI_GLOBAL_BASE_URL,
|
||||
} from "./model-definitions.js";
|
||||
12
openclaw/extensions/zai/config-api.ts
Normal file
12
openclaw/extensions/zai/config-api.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
// Narrow config barrel for core/test consumers.
|
||||
// Keep this separate from provider registration/runtime imports.
|
||||
|
||||
export { applyZaiConfig, applyZaiProviderConfig, ZAI_DEFAULT_MODEL_REF } from "./onboard.js";
|
||||
export {
|
||||
ZAI_CN_BASE_URL,
|
||||
ZAI_CODING_CN_BASE_URL,
|
||||
ZAI_CODING_GLOBAL_BASE_URL,
|
||||
ZAI_DEFAULT_COST,
|
||||
ZAI_DEFAULT_MODEL_ID,
|
||||
ZAI_GLOBAL_BASE_URL,
|
||||
} from "./model-definitions.js";
|
||||
21
openclaw/extensions/zai/detect.ts
Normal file
21
openclaw/extensions/zai/detect.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
import {
|
||||
detectZaiEndpoint as detectZaiEndpointCore,
|
||||
type ZaiDetectedEndpoint,
|
||||
type ZaiEndpointId,
|
||||
} from "./runtime-api.js";
|
||||
|
||||
type DetectZaiEndpointFn = typeof detectZaiEndpointCore;
|
||||
|
||||
let detectZaiEndpointImpl: DetectZaiEndpointFn = detectZaiEndpointCore;
|
||||
|
||||
export function setDetectZaiEndpointForTesting(fn?: DetectZaiEndpointFn): void {
|
||||
detectZaiEndpointImpl = fn ?? detectZaiEndpointCore;
|
||||
}
|
||||
|
||||
export async function detectZaiEndpoint(
|
||||
...args: Parameters<DetectZaiEndpointFn>
|
||||
): ReturnType<DetectZaiEndpointFn> {
|
||||
return await detectZaiEndpointImpl(...args);
|
||||
}
|
||||
|
||||
export type { ZaiDetectedEndpoint, ZaiEndpointId };
|
||||
242
openclaw/extensions/zai/index.test.ts
Normal file
242
openclaw/extensions/zai/index.test.ts
Normal file
|
|
@ -0,0 +1,242 @@
|
|||
import type { StreamFn } from "@mariozechner/pi-agent-core";
|
||||
import type { Context, Model } from "@mariozechner/pi-ai";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { registerSingleProviderPlugin } from "../../test/helpers/plugins/plugin-registration.js";
|
||||
import plugin from "./index.js";
|
||||
|
||||
describe("zai provider plugin", () => {
|
||||
it("owns replay policy for OpenAI-compatible Z.ai transports", async () => {
|
||||
const provider = await registerSingleProviderPlugin(plugin);
|
||||
|
||||
expect(
|
||||
provider.buildReplayPolicy?.({
|
||||
provider: "zai",
|
||||
modelApi: "openai-completions",
|
||||
modelId: "glm-5.1",
|
||||
} as never),
|
||||
).toMatchObject({
|
||||
sanitizeToolCallIds: true,
|
||||
toolCallIdMode: "strict",
|
||||
applyAssistantFirstOrderingFix: true,
|
||||
validateGeminiTurns: true,
|
||||
validateAnthropicTurns: true,
|
||||
});
|
||||
|
||||
expect(
|
||||
provider.buildReplayPolicy?.({
|
||||
provider: "zai",
|
||||
modelApi: "openai-responses",
|
||||
modelId: "glm-5.1",
|
||||
} as never),
|
||||
).toMatchObject({
|
||||
sanitizeToolCallIds: true,
|
||||
toolCallIdMode: "strict",
|
||||
applyAssistantFirstOrderingFix: false,
|
||||
validateGeminiTurns: false,
|
||||
validateAnthropicTurns: false,
|
||||
});
|
||||
});
|
||||
|
||||
it("resolves persisted GLM-5 family models with provider-owned metadata", async () => {
|
||||
const provider = await registerSingleProviderPlugin(plugin);
|
||||
const template = {
|
||||
id: "glm-4.7",
|
||||
name: "GLM-4.7",
|
||||
provider: "zai",
|
||||
api: "openai-completions",
|
||||
baseUrl: "https://api.z.ai/api/paas/v4",
|
||||
reasoning: true,
|
||||
input: ["text"],
|
||||
cost: { input: 0.6, output: 2.2, cacheRead: 0.11, cacheWrite: 0 },
|
||||
contextWindow: 204800,
|
||||
maxTokens: 131072,
|
||||
};
|
||||
|
||||
const cases = [
|
||||
{
|
||||
modelId: "glm-5.1",
|
||||
expected: {
|
||||
input: ["text"],
|
||||
reasoning: true,
|
||||
contextWindow: 202800,
|
||||
maxTokens: 131100,
|
||||
},
|
||||
},
|
||||
{
|
||||
modelId: "glm-5v-turbo",
|
||||
expected: {
|
||||
input: ["text", "image"],
|
||||
reasoning: true,
|
||||
contextWindow: 202800,
|
||||
maxTokens: 131100,
|
||||
},
|
||||
},
|
||||
] as const;
|
||||
|
||||
for (const testCase of cases) {
|
||||
expect(
|
||||
provider.resolveDynamicModel?.({
|
||||
provider: "zai",
|
||||
modelId: testCase.modelId,
|
||||
modelRegistry: {
|
||||
find: (_provider: string, modelId: string) => (modelId === "glm-4.7" ? template : null),
|
||||
},
|
||||
} as never),
|
||||
).toMatchObject({
|
||||
provider: "zai",
|
||||
api: "openai-completions",
|
||||
baseUrl: "https://api.z.ai/api/paas/v4",
|
||||
id: testCase.modelId,
|
||||
...testCase.expected,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
it("returns an already-registered GLM-5 variant as-is", async () => {
|
||||
const provider = await registerSingleProviderPlugin(plugin);
|
||||
const registered = {
|
||||
id: "glm-5-turbo",
|
||||
name: "GLM-5-Turbo",
|
||||
provider: "zai",
|
||||
api: "openai-completions",
|
||||
baseUrl: "https://api.z.ai/api/paas/v4",
|
||||
reasoning: false,
|
||||
input: ["text"],
|
||||
cost: { input: 0.1, output: 0.2, cacheRead: 0, cacheWrite: 0 },
|
||||
contextWindow: 123456,
|
||||
maxTokens: 54321,
|
||||
};
|
||||
const template = {
|
||||
id: "glm-4.7",
|
||||
name: "GLM-4.7",
|
||||
provider: "zai",
|
||||
api: "openai-completions",
|
||||
baseUrl: "https://api.z.ai/api/paas/v4",
|
||||
reasoning: true,
|
||||
input: ["text"],
|
||||
cost: { input: 0.6, output: 2.2, cacheRead: 0.11, cacheWrite: 0 },
|
||||
contextWindow: 204800,
|
||||
maxTokens: 131072,
|
||||
};
|
||||
|
||||
expect(
|
||||
provider.resolveDynamicModel?.({
|
||||
provider: "zai",
|
||||
modelId: "glm-5-turbo",
|
||||
modelRegistry: {
|
||||
find: (_provider: string, modelId: string) =>
|
||||
modelId === "glm-5-turbo" ? registered : modelId === "glm-4.7" ? template : null,
|
||||
},
|
||||
} as never),
|
||||
).toEqual(registered);
|
||||
});
|
||||
|
||||
it("still synthesizes unknown GLM-5 variants from the GLM-4.7 template", async () => {
|
||||
const provider = await registerSingleProviderPlugin(plugin);
|
||||
const template = {
|
||||
id: "glm-4.7",
|
||||
name: "GLM-4.7",
|
||||
provider: "zai",
|
||||
api: "openai-completions",
|
||||
baseUrl: "https://api.z.ai/api/paas/v4",
|
||||
reasoning: true,
|
||||
input: ["text"],
|
||||
cost: { input: 0.6, output: 2.2, cacheRead: 0.11, cacheWrite: 0 },
|
||||
contextWindow: 204800,
|
||||
maxTokens: 131072,
|
||||
};
|
||||
|
||||
expect(
|
||||
provider.resolveDynamicModel?.({
|
||||
provider: "zai",
|
||||
modelId: "glm-5-turbo",
|
||||
modelRegistry: {
|
||||
find: (_provider: string, modelId: string) => (modelId === "glm-4.7" ? template : null),
|
||||
},
|
||||
} as never),
|
||||
).toMatchObject({
|
||||
id: "glm-5-turbo",
|
||||
name: "GLM-5 Turbo",
|
||||
provider: "zai",
|
||||
api: "openai-completions",
|
||||
baseUrl: "https://api.z.ai/api/paas/v4",
|
||||
reasoning: true,
|
||||
input: ["text"],
|
||||
});
|
||||
});
|
||||
|
||||
it("wires tool-stream defaults through the shared stream family hook", async () => {
|
||||
const provider = await registerSingleProviderPlugin(plugin);
|
||||
let capturedPayload: Record<string, unknown> | undefined;
|
||||
const baseStreamFn: StreamFn = (model, _context, options) => {
|
||||
const payload: Record<string, unknown> = {};
|
||||
options?.onPayload?.(payload as never, model as never);
|
||||
capturedPayload = payload;
|
||||
return {} as ReturnType<StreamFn>;
|
||||
};
|
||||
|
||||
const defaultWrapped = provider.wrapStreamFn?.({
|
||||
provider: "zai",
|
||||
modelId: "glm-5.1",
|
||||
extraParams: {},
|
||||
streamFn: baseStreamFn,
|
||||
} as never);
|
||||
|
||||
void defaultWrapped?.(
|
||||
{
|
||||
api: "openai-completions",
|
||||
provider: "zai",
|
||||
id: "glm-5.1",
|
||||
} as Model<"openai-completions">,
|
||||
{ messages: [] } as Context,
|
||||
{},
|
||||
);
|
||||
|
||||
expect(capturedPayload).toMatchObject({
|
||||
tool_stream: true,
|
||||
});
|
||||
|
||||
const disabledWrapped = provider.wrapStreamFn?.({
|
||||
provider: "zai",
|
||||
modelId: "glm-5.1",
|
||||
extraParams: { tool_stream: false },
|
||||
streamFn: baseStreamFn,
|
||||
} as never);
|
||||
|
||||
void disabledWrapped?.(
|
||||
{
|
||||
api: "openai-completions",
|
||||
provider: "zai",
|
||||
id: "glm-5.1",
|
||||
} as Model<"openai-completions">,
|
||||
{ messages: [] } as Context,
|
||||
{},
|
||||
);
|
||||
|
||||
expect(capturedPayload).not.toHaveProperty("tool_stream");
|
||||
});
|
||||
|
||||
it("defaults tool_stream extra params but preserves explicit values", async () => {
|
||||
const provider = await registerSingleProviderPlugin(plugin);
|
||||
|
||||
expect(
|
||||
provider.prepareExtraParams?.({
|
||||
provider: "zai",
|
||||
modelId: "glm-4.7",
|
||||
extraParams: { endpoint: "global" },
|
||||
} as never),
|
||||
).toEqual({
|
||||
endpoint: "global",
|
||||
tool_stream: true,
|
||||
});
|
||||
|
||||
const explicit = { endpoint: "global", tool_stream: false };
|
||||
expect(
|
||||
provider.prepareExtraParams?.({
|
||||
provider: "zai",
|
||||
modelId: "glm-4.7",
|
||||
extraParams: explicit,
|
||||
} as never),
|
||||
).toBe(explicit);
|
||||
});
|
||||
});
|
||||
309
openclaw/extensions/zai/index.ts
Normal file
309
openclaw/extensions/zai/index.ts
Normal file
|
|
@ -0,0 +1,309 @@
|
|||
import {
|
||||
definePluginEntry,
|
||||
type ProviderAuthContext,
|
||||
type ProviderAuthMethod,
|
||||
type ProviderAuthMethodNonInteractiveContext,
|
||||
type ProviderResolveDynamicModelContext,
|
||||
type ProviderRuntimeModel,
|
||||
} from "openclaw/plugin-sdk/plugin-entry";
|
||||
import {
|
||||
applyAuthProfileConfig,
|
||||
buildApiKeyCredential,
|
||||
ensureApiKeyFromOptionEnvOrPrompt,
|
||||
normalizeApiKeyInput,
|
||||
normalizeOptionalSecretInput,
|
||||
type SecretInput,
|
||||
upsertAuthProfile,
|
||||
validateApiKeyInput,
|
||||
} from "openclaw/plugin-sdk/provider-auth-api-key";
|
||||
import {
|
||||
normalizeModelCompat,
|
||||
OPENAI_COMPATIBLE_REPLAY_HOOKS,
|
||||
} from "openclaw/plugin-sdk/provider-model-shared";
|
||||
import { TOOL_STREAM_DEFAULT_ON_HOOKS } from "openclaw/plugin-sdk/provider-stream-family";
|
||||
import { defaultToolStreamExtraParams } from "openclaw/plugin-sdk/provider-stream-shared";
|
||||
import { fetchZaiUsage, resolveLegacyPiAgentAccessToken } from "openclaw/plugin-sdk/provider-usage";
|
||||
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
|
||||
import { detectZaiEndpoint, type ZaiEndpointId } from "./detect.js";
|
||||
import { zaiMediaUnderstandingProvider } from "./media-understanding-provider.js";
|
||||
import { buildZaiModelDefinition } from "./model-definitions.js";
|
||||
import { applyZaiConfig, applyZaiProviderConfig, ZAI_DEFAULT_MODEL_REF } from "./onboard.js";
|
||||
|
||||
const PROVIDER_ID = "zai";
|
||||
const GLM5_TEMPLATE_MODEL_ID = "glm-4.7";
|
||||
const PROFILE_ID = "zai:default";
|
||||
|
||||
function resolveGlm5ForwardCompatModel(
|
||||
ctx: ProviderResolveDynamicModelContext,
|
||||
): ProviderRuntimeModel | undefined {
|
||||
const trimmedModelId = ctx.modelId.trim();
|
||||
if (!normalizeLowercaseStringOrEmpty(trimmedModelId).startsWith("glm-5")) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const existing = ctx.modelRegistry.find(
|
||||
PROVIDER_ID,
|
||||
trimmedModelId,
|
||||
) as ProviderRuntimeModel | null;
|
||||
if (existing) {
|
||||
return existing;
|
||||
}
|
||||
|
||||
const def = buildZaiModelDefinition({ id: trimmedModelId });
|
||||
const template = ctx.modelRegistry.find(
|
||||
PROVIDER_ID,
|
||||
GLM5_TEMPLATE_MODEL_ID,
|
||||
) as ProviderRuntimeModel | null;
|
||||
return normalizeModelCompat({
|
||||
...template,
|
||||
id: def.id,
|
||||
name: def.name,
|
||||
api: "openai-completions",
|
||||
provider: PROVIDER_ID,
|
||||
reasoning: def.reasoning,
|
||||
input: def.input,
|
||||
cost: def.cost,
|
||||
contextWindow: def.contextWindow,
|
||||
maxTokens: def.maxTokens,
|
||||
} as ProviderRuntimeModel);
|
||||
}
|
||||
|
||||
function resolveZaiDefaultModel(modelIdOverride?: string): string {
|
||||
return modelIdOverride ? `zai/${modelIdOverride}` : ZAI_DEFAULT_MODEL_REF;
|
||||
}
|
||||
|
||||
async function promptForZaiEndpoint(ctx: ProviderAuthContext): Promise<ZaiEndpointId> {
|
||||
return await ctx.prompter.select<ZaiEndpointId>({
|
||||
message: "Select Z.AI endpoint",
|
||||
initialValue: "global",
|
||||
options: [
|
||||
{ value: "global", label: "Global", hint: "Z.AI Global (api.z.ai)" },
|
||||
{ value: "cn", label: "CN", hint: "Z.AI CN (open.bigmodel.cn)" },
|
||||
{
|
||||
value: "coding-global",
|
||||
label: "Coding-Plan-Global",
|
||||
hint: "GLM Coding Plan Global (api.z.ai)",
|
||||
},
|
||||
{
|
||||
value: "coding-cn",
|
||||
label: "Coding-Plan-CN",
|
||||
hint: "GLM Coding Plan CN (open.bigmodel.cn)",
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
async function runZaiApiKeyAuth(
|
||||
ctx: ProviderAuthContext,
|
||||
endpoint?: ZaiEndpointId,
|
||||
): Promise<{
|
||||
profiles: Array<{ profileId: string; credential: ReturnType<typeof buildApiKeyCredential> }>;
|
||||
configPatch: ReturnType<typeof applyZaiProviderConfig>;
|
||||
defaultModel: string;
|
||||
notes?: string[];
|
||||
}> {
|
||||
let capturedSecretInput: SecretInput | undefined;
|
||||
let capturedCredential = false;
|
||||
let capturedMode: "plaintext" | "ref" | undefined;
|
||||
const apiKey = await ensureApiKeyFromOptionEnvOrPrompt({
|
||||
token:
|
||||
normalizeOptionalSecretInput(ctx.opts?.zaiApiKey) ??
|
||||
normalizeOptionalSecretInput(ctx.opts?.token),
|
||||
tokenProvider: normalizeOptionalSecretInput(ctx.opts?.zaiApiKey)
|
||||
? PROVIDER_ID
|
||||
: normalizeOptionalSecretInput(ctx.opts?.tokenProvider),
|
||||
secretInputMode:
|
||||
ctx.allowSecretRefPrompt === false
|
||||
? (ctx.secretInputMode ?? "plaintext")
|
||||
: ctx.secretInputMode,
|
||||
config: ctx.config,
|
||||
expectedProviders: [PROVIDER_ID, "z-ai"],
|
||||
provider: PROVIDER_ID,
|
||||
envLabel: "ZAI_API_KEY",
|
||||
promptMessage: "Enter Z.AI API key",
|
||||
normalize: normalizeApiKeyInput,
|
||||
validate: validateApiKeyInput,
|
||||
prompter: ctx.prompter,
|
||||
setCredential: async (key, mode) => {
|
||||
capturedSecretInput = key;
|
||||
capturedCredential = true;
|
||||
capturedMode = mode;
|
||||
},
|
||||
});
|
||||
if (!capturedCredential) {
|
||||
throw new Error("Missing Z.AI API key.");
|
||||
}
|
||||
const credentialInput = capturedSecretInput ?? "";
|
||||
|
||||
const detected = await detectZaiEndpoint({ apiKey, ...(endpoint ? { endpoint } : {}) });
|
||||
const modelIdOverride = detected?.modelId;
|
||||
const nextEndpoint = detected?.endpoint ?? endpoint ?? (await promptForZaiEndpoint(ctx));
|
||||
return {
|
||||
profiles: [
|
||||
{
|
||||
profileId: PROFILE_ID,
|
||||
credential: buildApiKeyCredential(
|
||||
PROVIDER_ID,
|
||||
credentialInput,
|
||||
undefined,
|
||||
capturedMode ? { secretInputMode: capturedMode } : undefined,
|
||||
),
|
||||
},
|
||||
],
|
||||
configPatch: applyZaiProviderConfig(ctx.config, {
|
||||
...(nextEndpoint ? { endpoint: nextEndpoint } : {}),
|
||||
...(modelIdOverride ? { modelId: modelIdOverride } : {}),
|
||||
}),
|
||||
defaultModel: resolveZaiDefaultModel(modelIdOverride),
|
||||
...(detected?.note ? { notes: [detected.note] } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
async function runZaiApiKeyAuthNonInteractive(
|
||||
ctx: ProviderAuthMethodNonInteractiveContext,
|
||||
endpoint?: ZaiEndpointId,
|
||||
) {
|
||||
const resolved = await ctx.resolveApiKey({
|
||||
provider: PROVIDER_ID,
|
||||
flagValue: normalizeOptionalSecretInput(ctx.opts.zaiApiKey),
|
||||
flagName: "--zai-api-key",
|
||||
envVar: "ZAI_API_KEY",
|
||||
});
|
||||
if (!resolved) {
|
||||
return null;
|
||||
}
|
||||
const detected = await detectZaiEndpoint({
|
||||
apiKey: resolved.key,
|
||||
...(endpoint ? { endpoint } : {}),
|
||||
});
|
||||
const modelIdOverride = detected?.modelId;
|
||||
const nextEndpoint = detected?.endpoint ?? endpoint;
|
||||
|
||||
if (resolved.source !== "profile") {
|
||||
const credential = ctx.toApiKeyCredential({
|
||||
provider: PROVIDER_ID,
|
||||
resolved,
|
||||
});
|
||||
if (!credential) {
|
||||
return null;
|
||||
}
|
||||
upsertAuthProfile({
|
||||
profileId: PROFILE_ID,
|
||||
credential,
|
||||
agentDir: ctx.agentDir,
|
||||
});
|
||||
}
|
||||
|
||||
const next = applyAuthProfileConfig(ctx.config, {
|
||||
profileId: PROFILE_ID,
|
||||
provider: PROVIDER_ID,
|
||||
mode: "api_key",
|
||||
});
|
||||
return applyZaiConfig(next, {
|
||||
...(nextEndpoint ? { endpoint: nextEndpoint } : {}),
|
||||
...(modelIdOverride ? { modelId: modelIdOverride } : {}),
|
||||
});
|
||||
}
|
||||
|
||||
function buildZaiApiKeyMethod(params: {
|
||||
id: string;
|
||||
choiceId: string;
|
||||
choiceLabel: string;
|
||||
choiceHint?: string;
|
||||
endpoint?: ZaiEndpointId;
|
||||
}): ProviderAuthMethod {
|
||||
return {
|
||||
id: params.id,
|
||||
label: params.choiceLabel,
|
||||
hint: params.choiceHint,
|
||||
kind: "api_key",
|
||||
wizard: {
|
||||
choiceId: params.choiceId,
|
||||
choiceLabel: params.choiceLabel,
|
||||
...(params.choiceHint ? { choiceHint: params.choiceHint } : {}),
|
||||
groupId: "zai",
|
||||
groupLabel: "Z.AI",
|
||||
groupHint: "GLM Coding Plan / Global / CN",
|
||||
},
|
||||
run: async (ctx) => await runZaiApiKeyAuth(ctx, params.endpoint),
|
||||
runNonInteractive: async (ctx) => await runZaiApiKeyAuthNonInteractive(ctx, params.endpoint),
|
||||
};
|
||||
}
|
||||
|
||||
export default definePluginEntry({
|
||||
id: PROVIDER_ID,
|
||||
name: "Z.AI Provider",
|
||||
description: "Bundled Z.AI provider plugin",
|
||||
register(api) {
|
||||
api.registerProvider({
|
||||
id: PROVIDER_ID,
|
||||
label: "Z.AI",
|
||||
aliases: ["z-ai", "z.ai"],
|
||||
docsPath: "/providers/models",
|
||||
envVars: ["ZAI_API_KEY", "Z_AI_API_KEY"],
|
||||
auth: [
|
||||
buildZaiApiKeyMethod({
|
||||
id: "api-key",
|
||||
choiceId: "zai-api-key",
|
||||
choiceLabel: "Z.AI API key",
|
||||
}),
|
||||
buildZaiApiKeyMethod({
|
||||
id: "coding-global",
|
||||
choiceId: "zai-coding-global",
|
||||
choiceLabel: "Coding-Plan-Global",
|
||||
choiceHint: "GLM Coding Plan Global (api.z.ai)",
|
||||
endpoint: "coding-global",
|
||||
}),
|
||||
buildZaiApiKeyMethod({
|
||||
id: "coding-cn",
|
||||
choiceId: "zai-coding-cn",
|
||||
choiceLabel: "Coding-Plan-CN",
|
||||
choiceHint: "GLM Coding Plan CN (open.bigmodel.cn)",
|
||||
endpoint: "coding-cn",
|
||||
}),
|
||||
buildZaiApiKeyMethod({
|
||||
id: "global",
|
||||
choiceId: "zai-global",
|
||||
choiceLabel: "Global",
|
||||
choiceHint: "Z.AI Global (api.z.ai)",
|
||||
endpoint: "global",
|
||||
}),
|
||||
buildZaiApiKeyMethod({
|
||||
id: "cn",
|
||||
choiceId: "zai-cn",
|
||||
choiceLabel: "CN",
|
||||
choiceHint: "Z.AI CN (open.bigmodel.cn)",
|
||||
endpoint: "cn",
|
||||
}),
|
||||
],
|
||||
resolveDynamicModel: (ctx) => resolveGlm5ForwardCompatModel(ctx),
|
||||
...OPENAI_COMPATIBLE_REPLAY_HOOKS,
|
||||
prepareExtraParams: (ctx) => defaultToolStreamExtraParams(ctx.extraParams),
|
||||
...TOOL_STREAM_DEFAULT_ON_HOOKS,
|
||||
isBinaryThinking: () => true,
|
||||
isModernModelRef: ({ modelId }) => {
|
||||
const lower = normalizeLowercaseStringOrEmpty(modelId);
|
||||
return (
|
||||
lower.startsWith("glm-5") ||
|
||||
lower.startsWith("glm-4.7") ||
|
||||
lower.startsWith("glm-4.7-flash") ||
|
||||
lower.startsWith("glm-4.7-flashx")
|
||||
);
|
||||
},
|
||||
resolveUsageAuth: async (ctx) => {
|
||||
const apiKey = ctx.resolveApiKeyFromConfigAndStore({
|
||||
providerIds: [PROVIDER_ID, "z-ai"],
|
||||
envDirect: [ctx.env.ZAI_API_KEY, ctx.env.Z_AI_API_KEY],
|
||||
});
|
||||
if (apiKey) {
|
||||
return { token: apiKey };
|
||||
}
|
||||
const legacyToken = resolveLegacyPiAgentAccessToken(ctx.env, ["z-ai", "zai"]);
|
||||
return legacyToken ? { token: legacyToken } : null;
|
||||
},
|
||||
fetchUsageSnapshot: async (ctx) => await fetchZaiUsage(ctx.token, ctx.timeoutMs, ctx.fetchFn),
|
||||
isCacheTtlEligible: () => true,
|
||||
});
|
||||
api.registerMediaUnderstandingProvider(zaiMediaUnderstandingProvider);
|
||||
},
|
||||
});
|
||||
14
openclaw/extensions/zai/media-understanding-provider.ts
Normal file
14
openclaw/extensions/zai/media-understanding-provider.ts
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import {
|
||||
describeImageWithModel,
|
||||
describeImagesWithModel,
|
||||
type MediaUnderstandingProvider,
|
||||
} from "openclaw/plugin-sdk/media-understanding";
|
||||
|
||||
export const zaiMediaUnderstandingProvider: MediaUnderstandingProvider = {
|
||||
id: "zai",
|
||||
capabilities: ["image"],
|
||||
defaultModels: { image: "glm-4.6v" },
|
||||
autoPriority: { image: 60 },
|
||||
describeImage: describeImageWithModel,
|
||||
describeImages: describeImagesWithModel,
|
||||
};
|
||||
75
openclaw/extensions/zai/model-definitions.test.ts
Normal file
75
openclaw/extensions/zai/model-definitions.test.ts
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
import { buildZaiModelDefinition, ZAI_DEFAULT_COST } from "./model-definitions.js";
|
||||
|
||||
describe("zai model definitions", () => {
|
||||
it("uses current Pi metadata for the new GLM-5.1 model", () => {
|
||||
expect(buildZaiModelDefinition({ id: "glm-5.1" })).toMatchObject({
|
||||
id: "glm-5.1",
|
||||
reasoning: true,
|
||||
input: ["text"],
|
||||
contextWindow: 202800,
|
||||
maxTokens: 131100,
|
||||
cost: { input: 1.2, output: 4, cacheRead: 0.24, cacheWrite: 0 },
|
||||
});
|
||||
});
|
||||
|
||||
it("uses current Pi metadata for the new GLM-5V Turbo model", () => {
|
||||
expect(buildZaiModelDefinition({ id: "glm-5v-turbo" })).toMatchObject({
|
||||
id: "glm-5v-turbo",
|
||||
reasoning: true,
|
||||
input: ["text", "image"],
|
||||
contextWindow: 202800,
|
||||
maxTokens: 131100,
|
||||
cost: { input: 1.2, output: 4, cacheRead: 0.24, cacheWrite: 0 },
|
||||
});
|
||||
});
|
||||
|
||||
it("uses current Pi metadata for the GLM-5 model", () => {
|
||||
expect(buildZaiModelDefinition({ id: "glm-5" })).toMatchObject({
|
||||
id: "glm-5",
|
||||
reasoning: true,
|
||||
input: ["text"],
|
||||
contextWindow: 202800,
|
||||
maxTokens: 131100,
|
||||
cost: ZAI_DEFAULT_COST,
|
||||
});
|
||||
});
|
||||
|
||||
it("publishes newer GLM 4.5/4.6 family metadata from Pi", () => {
|
||||
expect(buildZaiModelDefinition({ id: "glm-4.6v" })).toMatchObject({
|
||||
id: "glm-4.6v",
|
||||
input: ["text", "image"],
|
||||
contextWindow: 128000,
|
||||
maxTokens: 32768,
|
||||
cost: { input: 0.3, output: 0.9, cacheRead: 0, cacheWrite: 0 },
|
||||
});
|
||||
expect(buildZaiModelDefinition({ id: "glm-4.5-air" })).toMatchObject({
|
||||
id: "glm-4.5-air",
|
||||
input: ["text"],
|
||||
contextWindow: 131072,
|
||||
maxTokens: 98304,
|
||||
cost: { input: 0.2, output: 1.1, cacheRead: 0.03, cacheWrite: 0 },
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps the remaining GLM 4.7/5 pricing and token limits aligned with Pi", () => {
|
||||
expect(buildZaiModelDefinition({ id: "glm-4.7-flash" })).toMatchObject({
|
||||
id: "glm-4.7-flash",
|
||||
cost: { input: 0.07, output: 0.4, cacheRead: 0, cacheWrite: 0 },
|
||||
contextWindow: 200000,
|
||||
maxTokens: 131072,
|
||||
});
|
||||
expect(buildZaiModelDefinition({ id: "glm-4.7-flashx" })).toMatchObject({
|
||||
id: "glm-4.7-flashx",
|
||||
cost: { input: 0.06, output: 0.4, cacheRead: 0.01, cacheWrite: 0 },
|
||||
contextWindow: 200000,
|
||||
maxTokens: 128000,
|
||||
});
|
||||
expect(buildZaiModelDefinition({ id: "glm-5-turbo" })).toMatchObject({
|
||||
id: "glm-5-turbo",
|
||||
contextWindow: 202800,
|
||||
maxTokens: 131100,
|
||||
cost: { input: 1.2, output: 4, cacheRead: 0.24, cacheWrite: 0 },
|
||||
});
|
||||
});
|
||||
});
|
||||
170
openclaw/extensions/zai/model-definitions.ts
Normal file
170
openclaw/extensions/zai/model-definitions.ts
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
import type { ModelDefinitionConfig } from "openclaw/plugin-sdk/provider-model-shared";
|
||||
|
||||
export const ZAI_CODING_GLOBAL_BASE_URL = "https://api.z.ai/api/coding/paas/v4";
|
||||
export const ZAI_CODING_CN_BASE_URL = "https://open.bigmodel.cn/api/coding/paas/v4";
|
||||
export const ZAI_GLOBAL_BASE_URL = "https://api.z.ai/api/paas/v4";
|
||||
export const ZAI_CN_BASE_URL = "https://open.bigmodel.cn/api/paas/v4";
|
||||
export const ZAI_DEFAULT_MODEL_ID = "glm-5.1";
|
||||
export const ZAI_DEFAULT_MODEL_REF = `zai/${ZAI_DEFAULT_MODEL_ID}`;
|
||||
|
||||
type ZaiCatalogEntry = {
|
||||
name: string;
|
||||
reasoning: boolean;
|
||||
input: ModelDefinitionConfig["input"];
|
||||
contextWindow: number;
|
||||
maxTokens: number;
|
||||
cost: ModelDefinitionConfig["cost"];
|
||||
};
|
||||
|
||||
export const ZAI_DEFAULT_COST = {
|
||||
input: 1,
|
||||
output: 3.2,
|
||||
cacheRead: 0.2,
|
||||
cacheWrite: 0,
|
||||
} satisfies ModelDefinitionConfig["cost"];
|
||||
|
||||
const ZAI_MODEL_CATALOG = {
|
||||
"glm-5.1": {
|
||||
name: "GLM-5.1",
|
||||
reasoning: true,
|
||||
input: ["text"],
|
||||
contextWindow: 202800,
|
||||
maxTokens: 131100,
|
||||
cost: { input: 1.2, output: 4, cacheRead: 0.24, cacheWrite: 0 },
|
||||
},
|
||||
"glm-5": {
|
||||
name: "GLM-5",
|
||||
reasoning: true,
|
||||
input: ["text"],
|
||||
contextWindow: 202800,
|
||||
maxTokens: 131100,
|
||||
cost: ZAI_DEFAULT_COST,
|
||||
},
|
||||
"glm-5-turbo": {
|
||||
name: "GLM-5 Turbo",
|
||||
reasoning: true,
|
||||
input: ["text"],
|
||||
contextWindow: 202800,
|
||||
maxTokens: 131100,
|
||||
cost: { input: 1.2, output: 4, cacheRead: 0.24, cacheWrite: 0 },
|
||||
},
|
||||
"glm-5v-turbo": {
|
||||
name: "GLM-5V Turbo",
|
||||
reasoning: true,
|
||||
input: ["text", "image"],
|
||||
contextWindow: 202800,
|
||||
maxTokens: 131100,
|
||||
cost: { input: 1.2, output: 4, cacheRead: 0.24, cacheWrite: 0 },
|
||||
},
|
||||
"glm-4.7": {
|
||||
name: "GLM-4.7",
|
||||
reasoning: true,
|
||||
input: ["text"],
|
||||
contextWindow: 204800,
|
||||
maxTokens: 131072,
|
||||
cost: { input: 0.6, output: 2.2, cacheRead: 0.11, cacheWrite: 0 },
|
||||
},
|
||||
"glm-4.7-flash": {
|
||||
name: "GLM-4.7 Flash",
|
||||
reasoning: true,
|
||||
input: ["text"],
|
||||
contextWindow: 200000,
|
||||
maxTokens: 131072,
|
||||
cost: { input: 0.07, output: 0.4, cacheRead: 0, cacheWrite: 0 },
|
||||
},
|
||||
"glm-4.7-flashx": {
|
||||
name: "GLM-4.7 FlashX",
|
||||
reasoning: true,
|
||||
input: ["text"],
|
||||
contextWindow: 200000,
|
||||
maxTokens: 128000,
|
||||
cost: { input: 0.06, output: 0.4, cacheRead: 0.01, cacheWrite: 0 },
|
||||
},
|
||||
"glm-4.6": {
|
||||
name: "GLM-4.6",
|
||||
reasoning: true,
|
||||
input: ["text"],
|
||||
contextWindow: 204800,
|
||||
maxTokens: 131072,
|
||||
cost: { input: 0.6, output: 2.2, cacheRead: 0.11, cacheWrite: 0 },
|
||||
},
|
||||
"glm-4.6v": {
|
||||
name: "GLM-4.6V",
|
||||
reasoning: true,
|
||||
input: ["text", "image"],
|
||||
contextWindow: 128000,
|
||||
maxTokens: 32768,
|
||||
cost: { input: 0.3, output: 0.9, cacheRead: 0, cacheWrite: 0 },
|
||||
},
|
||||
"glm-4.5": {
|
||||
name: "GLM-4.5",
|
||||
reasoning: true,
|
||||
input: ["text"],
|
||||
contextWindow: 131072,
|
||||
maxTokens: 98304,
|
||||
cost: { input: 0.6, output: 2.2, cacheRead: 0.11, cacheWrite: 0 },
|
||||
},
|
||||
"glm-4.5-air": {
|
||||
name: "GLM-4.5 Air",
|
||||
reasoning: true,
|
||||
input: ["text"],
|
||||
contextWindow: 131072,
|
||||
maxTokens: 98304,
|
||||
cost: { input: 0.2, output: 1.1, cacheRead: 0.03, cacheWrite: 0 },
|
||||
},
|
||||
"glm-4.5-flash": {
|
||||
name: "GLM-4.5 Flash",
|
||||
reasoning: true,
|
||||
input: ["text"],
|
||||
contextWindow: 131072,
|
||||
maxTokens: 98304,
|
||||
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||
},
|
||||
"glm-4.5v": {
|
||||
name: "GLM-4.5V",
|
||||
reasoning: true,
|
||||
input: ["text", "image"],
|
||||
contextWindow: 64000,
|
||||
maxTokens: 16384,
|
||||
cost: { input: 0.6, output: 1.8, cacheRead: 0, cacheWrite: 0 },
|
||||
},
|
||||
} as const satisfies Record<string, ZaiCatalogEntry>;
|
||||
|
||||
type ZaiCatalogId = keyof typeof ZAI_MODEL_CATALOG;
|
||||
|
||||
export function resolveZaiBaseUrl(endpoint?: string): string {
|
||||
switch (endpoint) {
|
||||
case "coding-cn":
|
||||
return ZAI_CODING_CN_BASE_URL;
|
||||
case "global":
|
||||
return ZAI_GLOBAL_BASE_URL;
|
||||
case "cn":
|
||||
return ZAI_CN_BASE_URL;
|
||||
case "coding-global":
|
||||
return ZAI_CODING_GLOBAL_BASE_URL;
|
||||
default:
|
||||
return ZAI_GLOBAL_BASE_URL;
|
||||
}
|
||||
}
|
||||
|
||||
export function buildZaiModelDefinition(params: {
|
||||
id: string;
|
||||
name?: string;
|
||||
reasoning?: boolean;
|
||||
input?: ModelDefinitionConfig["input"];
|
||||
cost?: ModelDefinitionConfig["cost"];
|
||||
contextWindow?: number;
|
||||
maxTokens?: number;
|
||||
}): ModelDefinitionConfig {
|
||||
const catalog = ZAI_MODEL_CATALOG[params.id as ZaiCatalogId];
|
||||
return {
|
||||
id: params.id,
|
||||
name: params.name ?? catalog?.name ?? `GLM ${params.id}`,
|
||||
reasoning: params.reasoning ?? catalog?.reasoning ?? true,
|
||||
input:
|
||||
params.input ?? (catalog?.input ? ([...catalog.input] as ("text" | "image")[]) : ["text"]),
|
||||
cost: params.cost ?? catalog?.cost ?? ZAI_DEFAULT_COST,
|
||||
contextWindow: params.contextWindow ?? catalog?.contextWindow ?? 202800,
|
||||
maxTokens: params.maxTokens ?? catalog?.maxTokens ?? 131100,
|
||||
};
|
||||
}
|
||||
36
openclaw/extensions/zai/onboard.test.ts
Normal file
36
openclaw/extensions/zai/onboard.test.ts
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
import { resolveAgentModelPrimaryValue } from "openclaw/plugin-sdk/provider-onboard";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { expectProviderOnboardPreservesPrimary } from "../../test/helpers/plugins/provider-onboard.js";
|
||||
import { ZAI_CODING_CN_BASE_URL, ZAI_GLOBAL_BASE_URL } from "./model-definitions.js";
|
||||
import { applyZaiConfig, applyZaiProviderConfig } from "./onboard.js";
|
||||
|
||||
describe("zai onboard", () => {
|
||||
it("adds zai provider with correct settings", () => {
|
||||
const cfg = applyZaiConfig({});
|
||||
expect(cfg.models?.providers?.zai).toMatchObject({
|
||||
baseUrl: ZAI_GLOBAL_BASE_URL,
|
||||
api: "openai-completions",
|
||||
});
|
||||
const ids = cfg.models?.providers?.zai?.models?.map((m) => m.id);
|
||||
expect(ids).toContain("glm-5");
|
||||
expect(ids).toContain("glm-5-turbo");
|
||||
expect(ids).toContain("glm-4.7");
|
||||
expect(ids).toContain("glm-4.7-flash");
|
||||
expect(ids).toContain("glm-4.7-flashx");
|
||||
});
|
||||
|
||||
it("supports CN endpoint for supported coding models", () => {
|
||||
for (const modelId of ["glm-4.7-flash", "glm-4.7-flashx"] as const) {
|
||||
const cfg = applyZaiConfig({}, { endpoint: "coding-cn", modelId });
|
||||
expect(cfg.models?.providers?.zai?.baseUrl).toBe(ZAI_CODING_CN_BASE_URL);
|
||||
expect(resolveAgentModelPrimaryValue(cfg.agents?.defaults?.model)).toBe(`zai/${modelId}`);
|
||||
}
|
||||
});
|
||||
|
||||
it("does not overwrite existing primary model in provider-only mode", () => {
|
||||
expectProviderOnboardPreservesPrimary({
|
||||
applyProviderConfig: applyZaiProviderConfig,
|
||||
primaryModelRef: "anthropic/claude-opus-4-5",
|
||||
});
|
||||
});
|
||||
});
|
||||
67
openclaw/extensions/zai/onboard.ts
Normal file
67
openclaw/extensions/zai/onboard.ts
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
import {
|
||||
applyProviderConfigWithModelCatalogPreset,
|
||||
type OpenClawConfig,
|
||||
} from "openclaw/plugin-sdk/provider-onboard";
|
||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
|
||||
import {
|
||||
buildZaiModelDefinition,
|
||||
resolveZaiBaseUrl,
|
||||
ZAI_DEFAULT_MODEL_ID,
|
||||
} from "./model-definitions.js";
|
||||
|
||||
export const ZAI_DEFAULT_MODEL_REF = `zai/${ZAI_DEFAULT_MODEL_ID}`;
|
||||
|
||||
const ZAI_DEFAULT_MODELS = [
|
||||
buildZaiModelDefinition({ id: "glm-5.1" }),
|
||||
buildZaiModelDefinition({ id: "glm-5" }),
|
||||
buildZaiModelDefinition({ id: "glm-5-turbo" }),
|
||||
buildZaiModelDefinition({ id: "glm-5v-turbo" }),
|
||||
buildZaiModelDefinition({ id: "glm-4.7" }),
|
||||
buildZaiModelDefinition({ id: "glm-4.7-flash" }),
|
||||
buildZaiModelDefinition({ id: "glm-4.7-flashx" }),
|
||||
buildZaiModelDefinition({ id: "glm-4.6" }),
|
||||
buildZaiModelDefinition({ id: "glm-4.6v" }),
|
||||
buildZaiModelDefinition({ id: "glm-4.5" }),
|
||||
buildZaiModelDefinition({ id: "glm-4.5-air" }),
|
||||
buildZaiModelDefinition({ id: "glm-4.5-flash" }),
|
||||
buildZaiModelDefinition({ id: "glm-4.5v" }),
|
||||
];
|
||||
|
||||
function resolveZaiPresetBaseUrl(cfg: OpenClawConfig, endpoint?: string): string {
|
||||
const existingProvider = cfg.models?.providers?.zai;
|
||||
const existingBaseUrl = normalizeOptionalString(existingProvider?.baseUrl) ?? "";
|
||||
return endpoint ? resolveZaiBaseUrl(endpoint) : existingBaseUrl || resolveZaiBaseUrl();
|
||||
}
|
||||
|
||||
function applyZaiPreset(
|
||||
cfg: OpenClawConfig,
|
||||
params?: { endpoint?: string; modelId?: string },
|
||||
primaryModelRef?: string,
|
||||
): OpenClawConfig {
|
||||
const modelId = normalizeOptionalString(params?.modelId) ?? ZAI_DEFAULT_MODEL_ID;
|
||||
const modelRef = `zai/${modelId}`;
|
||||
return applyProviderConfigWithModelCatalogPreset(cfg, {
|
||||
providerId: "zai",
|
||||
api: "openai-completions",
|
||||
baseUrl: resolveZaiPresetBaseUrl(cfg, params?.endpoint),
|
||||
catalogModels: ZAI_DEFAULT_MODELS,
|
||||
aliases: [{ modelRef, alias: "GLM" }],
|
||||
primaryModelRef,
|
||||
});
|
||||
}
|
||||
|
||||
export function applyZaiProviderConfig(
|
||||
cfg: OpenClawConfig,
|
||||
params?: { endpoint?: string; modelId?: string },
|
||||
): OpenClawConfig {
|
||||
return applyZaiPreset(cfg, params);
|
||||
}
|
||||
|
||||
export function applyZaiConfig(
|
||||
cfg: OpenClawConfig,
|
||||
params?: { endpoint?: string; modelId?: string },
|
||||
): OpenClawConfig {
|
||||
const modelId = normalizeOptionalString(params?.modelId) ?? ZAI_DEFAULT_MODEL_ID;
|
||||
const modelRef = modelId === ZAI_DEFAULT_MODEL_ID ? ZAI_DEFAULT_MODEL_REF : `zai/${modelId}`;
|
||||
return applyZaiPreset(cfg, params, modelRef);
|
||||
}
|
||||
87
openclaw/extensions/zai/openclaw.plugin.json
Normal file
87
openclaw/extensions/zai/openclaw.plugin.json
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
{
|
||||
"id": "zai",
|
||||
"enabledByDefault": true,
|
||||
"providers": ["zai"],
|
||||
"providerAuthEnvVars": {
|
||||
"zai": ["ZAI_API_KEY", "Z_AI_API_KEY"]
|
||||
},
|
||||
"providerAuthChoices": [
|
||||
{
|
||||
"provider": "zai",
|
||||
"method": "api-key",
|
||||
"choiceId": "zai-api-key",
|
||||
"choiceLabel": "Z.AI API key",
|
||||
"groupId": "zai",
|
||||
"groupLabel": "Z.AI",
|
||||
"groupHint": "GLM Coding Plan / Global / CN",
|
||||
"optionKey": "zaiApiKey",
|
||||
"cliFlag": "--zai-api-key",
|
||||
"cliOption": "--zai-api-key <key>",
|
||||
"cliDescription": "Z.AI API key"
|
||||
},
|
||||
{
|
||||
"provider": "zai",
|
||||
"method": "coding-global",
|
||||
"choiceId": "zai-coding-global",
|
||||
"choiceLabel": "Coding-Plan-Global",
|
||||
"choiceHint": "GLM Coding Plan Global (api.z.ai)",
|
||||
"groupId": "zai",
|
||||
"groupLabel": "Z.AI",
|
||||
"groupHint": "GLM Coding Plan / Global / CN",
|
||||
"optionKey": "zaiApiKey",
|
||||
"cliFlag": "--zai-api-key",
|
||||
"cliOption": "--zai-api-key <key>",
|
||||
"cliDescription": "Z.AI API key"
|
||||
},
|
||||
{
|
||||
"provider": "zai",
|
||||
"method": "coding-cn",
|
||||
"choiceId": "zai-coding-cn",
|
||||
"choiceLabel": "Coding-Plan-CN",
|
||||
"choiceHint": "GLM Coding Plan CN (open.bigmodel.cn)",
|
||||
"groupId": "zai",
|
||||
"groupLabel": "Z.AI",
|
||||
"groupHint": "GLM Coding Plan / Global / CN",
|
||||
"optionKey": "zaiApiKey",
|
||||
"cliFlag": "--zai-api-key",
|
||||
"cliOption": "--zai-api-key <key>",
|
||||
"cliDescription": "Z.AI API key"
|
||||
},
|
||||
{
|
||||
"provider": "zai",
|
||||
"method": "global",
|
||||
"choiceId": "zai-global",
|
||||
"choiceLabel": "Global",
|
||||
"choiceHint": "Z.AI Global (api.z.ai)",
|
||||
"groupId": "zai",
|
||||
"groupLabel": "Z.AI",
|
||||
"groupHint": "GLM Coding Plan / Global / CN",
|
||||
"optionKey": "zaiApiKey",
|
||||
"cliFlag": "--zai-api-key",
|
||||
"cliOption": "--zai-api-key <key>",
|
||||
"cliDescription": "Z.AI API key"
|
||||
},
|
||||
{
|
||||
"provider": "zai",
|
||||
"method": "cn",
|
||||
"choiceId": "zai-cn",
|
||||
"choiceLabel": "CN",
|
||||
"choiceHint": "Z.AI CN (open.bigmodel.cn)",
|
||||
"groupId": "zai",
|
||||
"groupLabel": "Z.AI",
|
||||
"groupHint": "GLM Coding Plan / Global / CN",
|
||||
"optionKey": "zaiApiKey",
|
||||
"cliFlag": "--zai-api-key",
|
||||
"cliOption": "--zai-api-key <key>",
|
||||
"cliDescription": "Z.AI API key"
|
||||
}
|
||||
],
|
||||
"contracts": {
|
||||
"mediaUnderstandingProviders": ["zai"]
|
||||
},
|
||||
"configSchema": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {}
|
||||
}
|
||||
}
|
||||
15
openclaw/extensions/zai/package.json
Normal file
15
openclaw/extensions/zai/package.json
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"name": "@openclaw/zai-provider",
|
||||
"version": "2026.4.20",
|
||||
"private": true,
|
||||
"description": "OpenClaw Z.AI provider plugin",
|
||||
"type": "module",
|
||||
"devDependencies": {
|
||||
"@openclaw/plugin-sdk": "workspace:*"
|
||||
},
|
||||
"openclaw": {
|
||||
"extensions": [
|
||||
"./index.ts"
|
||||
]
|
||||
}
|
||||
}
|
||||
5
openclaw/extensions/zai/runtime-api.ts
Normal file
5
openclaw/extensions/zai/runtime-api.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
export {
|
||||
detectZaiEndpoint,
|
||||
type ZaiDetectedEndpoint,
|
||||
type ZaiEndpointId,
|
||||
} from "openclaw/plugin-sdk/provider-zai-endpoint";
|
||||
1
openclaw/extensions/zai/test-api.ts
Normal file
1
openclaw/extensions/zai/test-api.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export { zaiMediaUnderstandingProvider } from "./media-understanding-provider.js";
|
||||
16
openclaw/extensions/zai/tsconfig.json
Normal file
16
openclaw/extensions/zai/tsconfig.json
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"extends": "../tsconfig.package-boundary.base.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "."
|
||||
},
|
||||
"include": ["./*.ts", "./src/**/*.ts"],
|
||||
"exclude": [
|
||||
"./**/*.test.ts",
|
||||
"./dist/**",
|
||||
"./node_modules/**",
|
||||
"./src/test-support/**",
|
||||
"./src/**/*test-helpers.ts",
|
||||
"./src/**/*test-harness.ts",
|
||||
"./src/**/*test-support.ts"
|
||||
]
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue