openclaw/extensions/zai/index.ts

129 lines
3.9 KiB
TypeScript

import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import {
emptyPluginConfigSchema,
type OpenClawPluginApi,
type ProviderResolveDynamicModelContext,
type ProviderRuntimeModel,
} from "openclaw/plugin-sdk/core";
import { DEFAULT_CONTEXT_TOKENS } from "../../src/agents/defaults.js";
import { normalizeModelCompat } from "../../src/agents/model-compat.js";
import { createZaiToolStreamWrapper } from "../../src/agents/pi-embedded-runner/zai-stream-wrappers.js";
import { resolveRequiredHomeDir } from "../../src/infra/home-dir.js";
import { fetchZaiUsage } from "../../src/infra/provider-usage.fetch.js";
const PROVIDER_ID = "zai";
const GLM5_MODEL_ID = "glm-5";
const GLM5_TEMPLATE_MODEL_ID = "glm-4.7";
function resolveGlm5ForwardCompatModel(
ctx: ProviderResolveDynamicModelContext,
): ProviderRuntimeModel | undefined {
const trimmedModelId = ctx.modelId.trim();
const lower = trimmedModelId.toLowerCase();
if (lower !== GLM5_MODEL_ID && !lower.startsWith(`${GLM5_MODEL_ID}-`)) {
return undefined;
}
const template = ctx.modelRegistry.find(
PROVIDER_ID,
GLM5_TEMPLATE_MODEL_ID,
) as ProviderRuntimeModel | null;
if (template) {
return normalizeModelCompat({
...template,
id: trimmedModelId,
name: trimmedModelId,
reasoning: true,
} as ProviderRuntimeModel);
}
return normalizeModelCompat({
id: trimmedModelId,
name: trimmedModelId,
api: "openai-completions",
provider: PROVIDER_ID,
reasoning: true,
input: ["text"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: DEFAULT_CONTEXT_TOKENS,
maxTokens: DEFAULT_CONTEXT_TOKENS,
} as ProviderRuntimeModel);
}
function resolveLegacyZaiUsageToken(env: NodeJS.ProcessEnv): string | undefined {
try {
const authPath = path.join(
resolveRequiredHomeDir(env, os.homedir),
".pi",
"agent",
"auth.json",
);
if (!fs.existsSync(authPath)) {
return undefined;
}
const parsed = JSON.parse(fs.readFileSync(authPath, "utf8")) as Record<
string,
{ access?: string }
>;
return parsed["z-ai"]?.access || parsed.zai?.access;
} catch {
return undefined;
}
}
const zaiPlugin = {
id: PROVIDER_ID,
name: "Z.AI Provider",
description: "Bundled Z.AI provider plugin",
configSchema: emptyPluginConfigSchema(),
register(api: OpenClawPluginApi) {
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: [],
resolveDynamicModel: (ctx) => resolveGlm5ForwardCompatModel(ctx),
prepareExtraParams: (ctx) => {
if (ctx.extraParams?.tool_stream !== undefined) {
return ctx.extraParams;
}
return {
...ctx.extraParams,
tool_stream: true,
};
},
wrapStreamFn: (ctx) =>
createZaiToolStreamWrapper(ctx.streamFn, ctx.extraParams?.tool_stream !== false),
isBinaryThinking: () => true,
isModernModelRef: ({ modelId }) => {
const lower = modelId.trim().toLowerCase();
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 = resolveLegacyZaiUsageToken(ctx.env);
return legacyToken ? { token: legacyToken } : null;
},
fetchUsageSnapshot: async (ctx) => await fetchZaiUsage(ctx.token, ctx.timeoutMs, ctx.fetchFn),
isCacheTtlEligible: () => true,
});
},
};
export default zaiPlugin;