@agent-proxy/zcode 0.11.3 → 0.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { HostServices, GatewayPlugin } from "@agent-proxy/plugin-api";
1
+ import type { HostServices, GatewayPlugin, ClientProtocol } from "@agent-proxy/plugin-api";
2
2
  import type { ZcodeConfigCache, ZcodeSelection } from "./config.ts";
3
3
  import { ZcodeEndpointRouting } from "./endpoint-routing.ts";
4
4
  import { ZcodeClientSigning } from "./client-signing.ts";
@@ -32,15 +32,13 @@ export interface ZcodeDependencies {
32
32
  export type GatewayHandler = ((request: Request) => Promise<Response>) & {
33
33
  close(): void;
34
34
  };
35
- /** 插件启停仅由 ZCode 开关决定,default 上游路由策略不参与。 */
36
- export declare function zcodeEnabled<Config extends AgentConfig>(config: Config): boolean;
37
- export declare function validateZcodeConfig<Config extends AgentConfig>(config: Config): void;
35
+ export declare function validateZcodeConfig(config: AgentConfig): void;
38
36
  export declare function zcodeError(status: number, message: string, type?: string): Response;
39
- export declare function createZcodeAdapter<Config extends AgentConfig>(config: Config, dependencies?: ZcodeDependencies): {
37
+ export declare function createZcodeAdapter(config: AgentConfig, dependencies?: ZcodeDependencies): {
40
38
  catalog(): Promise<ModelCatalog>;
41
39
  refreshCatalog: () => Promise<ModelCatalog>;
42
40
  reloadCatalog(): Promise<ModelCatalog>;
43
- forward(request: Request, input: Record<string, unknown>, mapResult?: (payload: Record<string, unknown>) => Response): Promise<Response>;
41
+ forward(request: Request, input: Record<string, unknown>, to?: ClientProtocol, mapResult?: (payload: Record<string, unknown>) => Response): Promise<Response>;
44
42
  close(): void;
45
43
  };
46
44
  export declare const ZCODE_MODEL_PATTERNS: readonly string[];
package/dist/index.js CHANGED
@@ -6,7 +6,7 @@ import { createZcodeConfigCache, readZcodeAPIKeyProviders, zcodeConfigPresent, Z
6
6
  import { clearModelsCacheEntries, invalidateModelsCache, withAgentSystemPrompt } from "@agent-proxy/plugin-api";
7
7
  import { buildZcodeVendorCatalog, createZcodeCatalog, isZcodeModel, writeZcodeServedCatalog, zcodeAPIProviderIDFromModel, zcodeModelPlan, zcodeUpstreamModel, } from "./catalog.js";
8
8
  import { translateZcodeRequest, ZcodeRequestError } from "./request.js";
9
- import { createZcodeResponse } from "./response.js";
9
+ import { createZcodeClientResponse } from "./response.js";
10
10
  import { executeZcodeAnalyzeImage, matchZcodeAnalyzeImage } from "./vision.js";
11
11
  import { ZcodeEndpointRouting } from "./endpoint-routing.js";
12
12
  import { clientSigningVerifyRejection, ZcodeClientSigning } from "./client-signing.js";
@@ -14,37 +14,22 @@ import { isZcodeRecord } from "./wire.js";
14
14
  import { buildZcodeModelHeaders, createZcodeContexts, decorateZcodeBody, readZcodeIdentity, zcodePlan, ZCODE_AGENT_SYSTEM_PROMPT } from "./request-context.js";
15
15
  import { localTime, logExchange, logGroupFromPath, requestLogDir } from "@agent-proxy/plugin-api";
16
16
  import { logGatewayError, logRequestSummary } from "@agent-proxy/plugin-api";
17
- /** 插件启停仅由 ZCode 开关决定,default 上游路由策略不参与。 */
18
- export function zcodeEnabled(config) {
19
- return config.zcode === undefined || config.zcode === true;
20
- }
21
17
  export function validateZcodeConfig(config) {
22
- if (config.zcode !== undefined && typeof config.zcode !== "boolean")
23
- throw new Error("zcode 必须为 boolean");
24
- if (!zcodeEnabled(config))
25
- return;
26
18
  const host = config.host;
27
19
  if (!(host === "localhost" || host === "::1" || host === "[::1]" || (isIP(host) === 4 && host.startsWith("127.")))) {
28
20
  throw new Error("启用 ZCode 时网关只能监听环回地址");
29
21
  }
30
- if (config.prefix && ("zcode/".startsWith(config.prefix) || config.prefix.startsWith("zcode/")
31
- || config.prefix.startsWith("zcode-"))) {
32
- throw new Error("启用 ZCode 时 zcode/ 与 zcode- 前缀保留给 ZCode,请调整第三方 prefix");
33
- }
34
22
  }
35
23
  export function zcodeError(status, message, type = "invalid_request_error") {
36
24
  return Response.json({ error: { type, message } }, { status });
37
25
  }
38
26
  function buildZcodeAdapter(config, dependencies = {}) {
39
27
  validateZcodeConfig(config);
40
- const enabled = zcodeEnabled(config);
41
28
  const manual = dependencies.catalogMode === "manual";
42
29
  // 自管目录文件一律放运行主目录,不从 catalogPath 位置倒推。
43
30
  const cacheDirectory = dependencies.cacheDirectory ?? resolvePaths().runtimeHome;
44
31
  // 厂商元数据只取模型包预设;账号或套餐刷新仅重新投影支持集合。
45
- const vendorCatalog = enabled
46
- ? buildZcodeVendorCatalog()
47
- : undefined;
32
+ const vendorCatalog = buildZcodeVendorCatalog();
48
33
  /** 套餐或选择变化时立即撤下 Codex 缓存里的旧 ZCode 条目,避免旧套餐模型继续可选。 */
49
34
  const dropCachedZcodeModels = () => {
50
35
  if (dependencies.codexModelsCacheFile) {
@@ -84,8 +69,6 @@ function buildZcodeAdapter(config, dependencies = {}) {
84
69
  ];
85
70
  let synchronizeAPIKeyRoutes = () => { };
86
71
  const republishFromPlans = (force = false) => {
87
- if (!vendorCatalog)
88
- return;
89
72
  const current = planRoutes.map((route) => route.snapshot);
90
73
  const previous = publishedSnapshots;
91
74
  if (!force && previous && current.length === previous.length
@@ -108,121 +91,119 @@ function buildZcodeAdapter(config, dependencies = {}) {
108
91
  }
109
92
  publishServedCatalog({ models });
110
93
  };
111
- if (enabled) {
112
- // 注入模式(测试)只为显式给出的套餐建缓存,避免读到真实的 ~/.zcode。
113
- const injected = dependencies.planCaches
114
- ?? (dependencies.apiKeyCaches ? {} : dependencies.configCache ? { "api-key": dependencies.configCache } : undefined);
115
- for (const route of planRoutes) {
116
- route.cache = injected?.[route.plan]
117
- ?? (injected ? undefined : createZcodeConfigCache(home, {
118
- plan: route.plan,
119
- refreshOnStart: false,
120
- // 选择变化只撤下本套餐的快照并重发并集;另一个套餐的条目不受影响。
121
- onSelectionChange: () => {
122
- if (manual)
123
- return;
124
- route.snapshot = undefined;
125
- republishFromPlans();
126
- },
127
- onSnapshotChange: (snapshot) => {
128
- if (manual)
129
- return;
130
- route.snapshot = snapshot;
131
- republishFromPlans();
132
- },
133
- }));
94
+ // 注入模式(测试)只为显式给出的套餐建缓存,避免读到真实的 ~/.zcode。
95
+ const injected = dependencies.planCaches
96
+ ?? (dependencies.apiKeyCaches ? {} : dependencies.configCache ? { "api-key": dependencies.configCache } : undefined);
97
+ for (const route of planRoutes) {
98
+ route.cache = injected?.[route.plan]
99
+ ?? (injected ? undefined : createZcodeConfigCache(home, {
100
+ plan: route.plan,
101
+ refreshOnStart: false,
102
+ // 选择变化只撤下本套餐的快照并重发并集;另一个套餐的条目不受影响。
103
+ onSelectionChange: () => {
104
+ if (manual)
105
+ return;
106
+ route.snapshot = undefined;
107
+ republishFromPlans();
108
+ },
109
+ onSnapshotChange: (snapshot) => {
110
+ if (manual)
111
+ return;
112
+ route.snapshot = snapshot;
113
+ republishFromPlans();
114
+ },
115
+ }));
116
+ }
117
+ if (injected) {
118
+ const apiCaches = dependencies.apiKeyCaches
119
+ ?? (dependencies.planCaches?.["api-key"] || dependencies.configCache
120
+ ? { current: dependencies.planCaches?.["api-key"] ?? dependencies.configCache }
121
+ : {});
122
+ for (const [apiProviderID, cache] of Object.entries(apiCaches)) {
123
+ planRoutes.push({ plan: "api-key", ...(apiProviderID === "current" ? {} : { apiProviderID }), cache });
134
124
  }
135
- if (injected) {
136
- const apiCaches = dependencies.apiKeyCaches
137
- ?? (dependencies.planCaches?.["api-key"] || dependencies.configCache
138
- ? { current: dependencies.planCaches?.["api-key"] ?? dependencies.configCache }
139
- : {});
140
- for (const [apiProviderID, cache] of Object.entries(apiCaches)) {
141
- planRoutes.push({ plan: "api-key", ...(apiProviderID === "current" ? {} : { apiProviderID }), cache });
125
+ }
126
+ else {
127
+ const createAPIRoute = (provider) => {
128
+ const route = { plan: "api-key", apiProviderID: provider.providerID };
129
+ route.cache = createZcodeConfigCache(home, {
130
+ plan: "api-key",
131
+ apiProviderID: provider.providerID,
132
+ refreshOnStart: false,
133
+ onSelectionChange: () => {
134
+ if (manual)
135
+ return;
136
+ route.snapshot = undefined;
137
+ republishFromPlans();
138
+ },
139
+ onSnapshotChange: (snapshot) => {
140
+ if (manual)
141
+ return;
142
+ route.snapshot = snapshot;
143
+ republishFromPlans();
144
+ },
145
+ });
146
+ return route;
147
+ };
148
+ const synchronize = (republish = true, strict = false) => {
149
+ let providers = [];
150
+ try {
151
+ providers = readZcodeAPIKeyProviders(home);
142
152
  }
143
- }
144
- else {
145
- const createAPIRoute = (provider) => {
146
- const route = { plan: "api-key", apiProviderID: provider.providerID };
147
- route.cache = createZcodeConfigCache(home, {
148
- plan: "api-key",
149
- apiProviderID: provider.providerID,
150
- refreshOnStart: false,
151
- onSelectionChange: () => {
152
- if (manual)
153
- return;
154
- route.snapshot = undefined;
155
- republishFromPlans();
156
- },
157
- onSnapshotChange: (snapshot) => {
158
- if (manual)
159
- return;
160
- route.snapshot = snapshot;
161
- republishFromPlans();
162
- },
163
- });
164
- return route;
165
- };
166
- const synchronize = (republish = true, strict = false) => {
167
- let providers = [];
168
- try {
169
- providers = readZcodeAPIKeyProviders(home);
170
- }
171
- catch (error) {
172
- if (strict)
173
- throw error;
174
- /* 动态目录撤下无效 API Key;套餐路由不受影响。 */
175
- }
176
- const targetIDs = new Set(providers.map((provider) => provider.providerID));
177
- let changed = false;
178
- const removed = planRoutes.filter((route) => route.plan === "api-key" && route.apiProviderID && !targetIDs.has(route.apiProviderID));
179
- for (const route of removed)
180
- route.cache?.close();
181
- if (removed.length) {
182
- for (let index = planRoutes.length - 1; index >= 0; index--) {
183
- if (removed.includes(planRoutes[index]))
184
- planRoutes.splice(index, 1);
185
- }
186
- changed = true;
187
- }
188
- for (const provider of providers) {
189
- if (planRoutes.some((route) => route.plan === "api-key" && route.apiProviderID === provider.providerID))
190
- continue;
191
- planRoutes.push(createAPIRoute(provider));
192
- changed = true;
153
+ catch (error) {
154
+ if (strict)
155
+ throw error;
156
+ /* 动态目录撤下无效 API Key;套餐路由不受影响。 */
157
+ }
158
+ const targetIDs = new Set(providers.map((provider) => provider.providerID));
159
+ let changed = false;
160
+ const removed = planRoutes.filter((route) => route.plan === "api-key" && route.apiProviderID && !targetIDs.has(route.apiProviderID));
161
+ for (const route of removed)
162
+ route.cache?.close();
163
+ if (removed.length) {
164
+ for (let index = planRoutes.length - 1; index >= 0; index--) {
165
+ if (removed.includes(planRoutes[index]))
166
+ planRoutes.splice(index, 1);
193
167
  }
194
- if (changed && republish)
195
- republishFromPlans();
196
- };
197
- synchronizeAPIKeyRoutes = (strict) => synchronize(!manual, strict);
198
- synchronize(false);
199
- // /models 与未知 API 前缀请求都会重新同步,支持新增/删除 provider;现有
200
- // provider 的 Key 与模型变化由各自 cache 的 provider_config watcher 驱动。
201
- }
168
+ changed = true;
169
+ }
170
+ for (const provider of providers) {
171
+ if (planRoutes.some((route) => route.plan === "api-key" && route.apiProviderID === provider.providerID))
172
+ continue;
173
+ planRoutes.push(createAPIRoute(provider));
174
+ changed = true;
175
+ }
176
+ if (changed && republish)
177
+ republishFromPlans();
178
+ };
179
+ synchronizeAPIKeyRoutes = (strict) => synchronize(!manual, strict);
180
+ synchronize(false);
181
+ // /models 与未知 API 前缀请求都会重新同步,支持新增/删除 provider;现有
182
+ // provider 的 Key 与模型变化由各自 cache 的 provider_config watcher 驱动。
202
183
  }
203
184
  const fetchUpstream = dependencies.fetch ?? ((url, init) => fetch(url, init));
204
185
  const activeRequests = new Set();
205
- const identity = enabled ? dependencies.identity ?? readZcodeIdentity() : undefined;
206
- const endpointRouting = enabled && dependencies.endpointRouting !== null
186
+ const identity = dependencies.identity ?? readZcodeIdentity();
187
+ const endpointRouting = dependencies.endpointRouting !== null
207
188
  ? dependencies.endpointRouting ?? new ZcodeEndpointRouting({
208
- identity: identity,
189
+ identity,
209
190
  fetch: (url, init) => fetchUpstream(url, init),
210
191
  })
211
192
  : undefined;
212
193
  // coding plan 官方客户端签名:逐请求补 X-Client-* 签名头;fail-open,失败按未签名继续。
213
- const clientSigning = enabled && dependencies.clientSigning !== null
194
+ const clientSigning = dependencies.clientSigning !== null
214
195
  ? dependencies.clientSigning ?? new ZcodeClientSigning({ fetch: (url, init) => fetchUpstream(url, init) })
215
196
  : undefined;
216
197
  const contexts = createZcodeContexts();
217
198
  const sink = config.requestLogging === true ? {
218
- dir: requestLogDir(config),
199
+ dir: requestLogDir(),
219
200
  maxLogs: Math.max(0, Math.trunc(config.maxRequestLogs ?? 0)),
220
201
  processLog: dependencies.processLog,
221
202
  } : undefined;
222
203
  let closed = false;
223
204
  let refreshPromise;
224
205
  const readCatalog = async (mode) => {
225
- if (closed || !enabled)
206
+ if (closed)
226
207
  return { models: [] };
227
208
  synchronizeAPIKeyRoutes(manual && mode !== "get");
228
209
  const neverPublished = publishedSnapshots === undefined;
@@ -279,7 +260,7 @@ function buildZcodeAdapter(config, dependencies = {}) {
279
260
  return promise;
280
261
  };
281
262
  /** 启动首读仍允许凭据授权,手动策略仅停止后续目录自动发布。 */
282
- const startup = enabled && vendorCatalog && dependencies.refreshCatalogOnStart !== false
263
+ const startup = dependencies.refreshCatalogOnStart !== false
283
264
  ? refreshCatalog() : Promise.resolve({ models: [] });
284
265
  // 构造适配器与等待 modelsReady 之间允许异步间隙;失败由公开目录接口传递。
285
266
  void startup.catch(() => { });
@@ -297,7 +278,7 @@ function buildZcodeAdapter(config, dependencies = {}) {
297
278
  await refreshPromise;
298
279
  return readCatalog("reload");
299
280
  },
300
- async forward(request, input, mapResult) {
281
+ async forward(request, input, to = "responses", mapResult) {
301
282
  const start = Date.now();
302
283
  const requestTime = localTime();
303
284
  const incoming = new URL(request.url);
@@ -426,7 +407,7 @@ function buildZcodeAdapter(config, dependencies = {}) {
426
407
  cleanup();
427
408
  return fail(404, "此模型不属于 ZCode 当前套餐或厂商目录");
428
409
  }
429
- const translated = translateZcodeRequest(input, model);
410
+ const translated = await translateZcodeRequest(input, model, to);
430
411
  const baseUpstreamUrl = `${snapshot.baseURL}${snapshot.baseURL.endsWith("/v1") ? "/messages" : "/v1/messages"}`;
431
412
  const routedUrl = endpointRouting
432
413
  // 跟随 z.ai 服务端下发的端点映射(如 zcode.z.ai ultra 中转);resolve 内部 fail-open,失败保持原 URL。
@@ -562,7 +543,7 @@ function buildZcodeAdapter(config, dependencies = {}) {
562
543
  }
563
544
  let chunks = "";
564
545
  const stream = !mapResult && input.stream === true;
565
- const response = await createZcodeResponse(upstream, {
546
+ const response = await createZcodeClientResponse(to, upstream, {
566
547
  model: String(input.model), tools: translated.tools, stream, signal: abort.signal,
567
548
  gatewayTools,
568
549
  sanitizeError: (error) => ({
@@ -637,9 +618,7 @@ export function createZcodePlugin(host, dependencies = {}) {
637
618
  const input = await context.body.json();
638
619
  if (!input || typeof input !== "object" || Array.isArray(input))
639
620
  return Response.json({ error: { type: "invalid_request_error", message: "请求正文必须是 JSON 对象" } }, { status: 400 });
640
- if (context.from !== "responses")
641
- return Response.json({ error: { type: "invalid_request_error", message: "Zcode 模型仅支持 Responses API" } }, { status: 400 });
642
- return adapter.forward(context.request, input);
621
+ return adapter.forward(context.request, input, context.to);
643
622
  },
644
623
  getCatalog: () => adapter.catalog(),
645
624
  refreshCatalog: () => adapter.refreshCatalog(),
package/dist/request.d.ts CHANGED
@@ -1,5 +1,16 @@
1
+ import type { ClientProtocol } from "@agent-proxy/plugin-api";
1
2
  import type { ZcodeTranslatedRequest } from "./wire.ts";
3
+ /**
4
+ * ZCode 请求准备:标准消息/工具/参数统一经 @agent-proxy/llm-bridge 的
5
+ * convertRequest(to, "anthropic");z.ai 私有形状(web_search_20250305 服务端工具、
6
+ * thinking 预算表、zcode-thinking 信封回放、analyze_image 代执行)作为插件前后处理。
7
+ *
8
+ * Responses 源的私有 history 项(reasoning / web_search_call / 其它服务器调用)不能
9
+ * 进标准转换:按项把 input 切成「标准段 + 私有项」,标准段逐段调用公共入口,私有段
10
+ * 由本模块转换,最后按 main 的同角色合并语义拼装。
11
+ */
2
12
  export declare class ZcodeRequestError extends Error {
3
13
  constructor(message: string);
4
14
  }
5
- export declare function translateZcodeRequest(body: Record<string, unknown>, upstreamModel: string): ZcodeTranslatedRequest;
15
+ /** to 为路由端点(客户端入口)协议:请求方向 to → anthropic,响应方向 anthropic → to。 */
16
+ export declare function translateZcodeRequest(body: Record<string, unknown>, upstreamModel: string, to?: ClientProtocol): Promise<ZcodeTranslatedRequest>;