@agent-proxy/zcode 0.20.0 → 0.20.2

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/catalog.d.ts CHANGED
@@ -13,7 +13,12 @@ export declare function zcodeAPIProviderIDFromModel(model: string): string | und
13
13
  export declare function zcodeUpstreamModel(model: string, snapshot: ZcodeProviderSnapshot): string | undefined;
14
14
  /**
15
15
  * 仅由模型包的厂商预设合成全量裸 ID 目录。纯内存计算,
16
- * 重建成本与模型条数同级,不需要磁盘缓存,也不携带任何套餐或授权信息。
16
+ * 重建成本与模型条数同级,不需要磁盘缓存,不携带任何套餐或授权信息。
17
+ * 合成策略与其余 agent 插件(agy/qoder/zen/codebuddy)一致:以 Codex 基线整包
18
+ * 克隆为底座,厂商预设作为自定义替换层逐字段覆盖——tool_mode、multi_agent_version、
19
+ * use_responses_lite 等 Codex 行为字段默认继承基线(客户端据此组装 additional_tools
20
+ * 与全量 MCP 工具集),z.ai 预设声明真实能力(context_window、reasoning 档位)并以
21
+ * 空值中和不适用的基线字段(base_instructions 置空、experimental_supported_tools 清空)。
17
22
  */
18
23
  export declare function buildZcodeVendorCatalog(): ModelCatalog;
19
24
  /** /models 只按快照套餐的支持集合筛选并加套餐作用域前缀,不重新合成厂商元数据。 */
package/dist/catalog.js CHANGED
@@ -1,7 +1,6 @@
1
1
  import fs from "node:fs";
2
2
  import { createHash } from "node:crypto";
3
- import { getVendorModelPresets } from "./resources/models.js";
4
- import { compileModelOverrides, synthesizeModelEntry } from "@agent-proxy/plugin-api";
3
+ import { getCodexBaselineEntry, getVendorModelPresets } from "./resources/models.js";
5
4
  import { atomicWrite } from "@agent-proxy/plugin-api";
6
5
  const vendorModelIds = () => (getVendorModelPresets()["z.ai"] ?? []).map((model) => model.name);
7
6
  /** 旧版单 API Key 命名空间;新版多 Key 目录不再生成该前缀。 */
@@ -84,24 +83,30 @@ function stripZcodeChannelLabel(name) {
84
83
  }
85
84
  /**
86
85
  * 仅由模型包的厂商预设合成全量裸 ID 目录。纯内存计算,
87
- * 重建成本与模型条数同级,不需要磁盘缓存,也不携带任何套餐或授权信息。
86
+ * 重建成本与模型条数同级,不需要磁盘缓存,不携带任何套餐或授权信息。
87
+ * 合成策略与其余 agent 插件(agy/qoder/zen/codebuddy)一致:以 Codex 基线整包
88
+ * 克隆为底座,厂商预设作为自定义替换层逐字段覆盖——tool_mode、multi_agent_version、
89
+ * use_responses_lite 等 Codex 行为字段默认继承基线(客户端据此组装 additional_tools
90
+ * 与全量 MCP 工具集),z.ai 预设声明真实能力(context_window、reasoning 档位)并以
91
+ * 空值中和不适用的基线字段(base_instructions 置空、experimental_supported_tools 清空)。
88
92
  */
89
93
  export function buildZcodeVendorCatalog() {
90
- const vendorModels = getVendorModelPresets();
91
- const modelIds = (vendorModels["z.ai"] ?? []).map((model) => model.name);
92
- const vendors = compileModelOverrides(vendorModels, "vendor_models.json");
93
- const preset = { models: modelIds.map((id, index) => synthesizeModelEntry(`z.ai/${id}`, index, { models: [] }, vendors)) };
94
- return { models: preset.models.map((entry) => {
95
- const slug = entry.slug.slice("z.ai/".length);
96
- return {
97
- ...entry,
98
- slug,
99
- display_name: entry.display_name ?? slug,
100
- prefer_websockets: false,
101
- // zcode 链路已把 Responses web_search 原生映射为 web_search_20250305(实测 z.ai 支持)。
102
- supports_search_tool: true,
103
- };
104
- }) };
94
+ const presets = getVendorModelPresets()["z.ai"] ?? [];
95
+ const base = getCodexBaselineEntry();
96
+ const models = presets.map((preset, index) => {
97
+ const { name, display_name, ...overrides } = preset;
98
+ const entry = { ...structuredClone(base), ...overrides };
99
+ entry.slug = name;
100
+ entry.display_name = typeof display_name === "string" && display_name ? display_name : name;
101
+ entry.priority = index;
102
+ return entry;
103
+ });
104
+ return { models: models.map((entry) => ({
105
+ ...entry,
106
+ prefer_websockets: false,
107
+ // zcode 链路已把 Responses web_search 原生映射为 web_search_20250305(实测 z.ai 支持)。
108
+ supports_search_tool: true,
109
+ })) };
105
110
  }
106
111
  /** /models 只按快照套餐的支持集合筛选并加套餐作用域前缀,不重新合成厂商元数据。 */
107
112
  export function createZcodeCatalog(snapshot, cached) {
package/dist/index.js CHANGED
@@ -13,6 +13,7 @@ import { clientSigningVerifyRejection, ZcodeClientSigning } from "./client-signi
13
13
  import { isZcodeRecord } from "./wire.js";
14
14
  import { buildZcodeModelHeaders, createZcodeContexts, decorateZcodeBody, readZcodeIdentity, zcodePlan } from "./request-context.js";
15
15
  import { createStartPlanFetch, dressZcodeStartPlanBody, startPlanUpstreamUrl, zcodeStartPlanRefusalHint } from "./start-plan-request.js";
16
+ import { zcodeStartPlanEntitledFromLog } from "./plans.js";
16
17
  import { localTime, logExchange, logGroupFromPath, requestLogDir } from "@agent-proxy/plugin-api";
17
18
  import { logGatewayError, logRequestSummary } from "@agent-proxy/plugin-api";
18
19
  export function validateZcodeConfig(config) {
@@ -71,19 +72,33 @@ function buildZcodeAdapter(config, dependencies = {}) {
71
72
  ];
72
73
  const identity = dependencies.identity ?? readZcodeIdentity(home);
73
74
  let synchronizeAPIKeyRoutes = () => { };
75
+ /** 上次发布时的 Start Plan 门禁结果;undefined(无日志信号)不参与变化判定。 */
76
+ let publishedStartPlanGate;
74
77
  const republishFromPlans = (force = false) => {
75
78
  const current = planRoutes.map((route) => route.snapshot);
76
79
  const previous = publishedSnapshots;
80
+ // Start Plan 权益由 ZCode 宿主日志判定:无记录为 undefined(不拦截发布,沿用上游
81
+ // 405/3012 兜底);快照不变而门禁翻转(计划开始/结束)同样要重投影,让 get 路径
82
+ // 也能即时增删免费档条目。
83
+ const startPlanSnapshot = planRoutes.find((route) => route.plan === "start-plan")?.snapshot;
84
+ const startPlanGate = startPlanSnapshot
85
+ ? zcodeStartPlanEntitledFromLog(home, startPlanSnapshot.family)
86
+ : undefined;
77
87
  if (!force && previous && current.length === previous.length
78
- && current.every((snapshot, index) => snapshot === previous[index]))
88
+ && current.every((snapshot, index) => snapshot === previous[index])
89
+ && startPlanGate === publishedStartPlanGate)
79
90
  return;
80
91
  publishedSnapshots = current;
92
+ publishedStartPlanGate = startPlanGate;
81
93
  // 各可用套餐目录的并集:单套餐失效只撤下自己的条目,其余套餐继续可选。
82
94
  const models = [];
83
95
  const seen = new Set();
84
96
  for (const route of planRoutes) {
85
97
  if (!route.snapshot)
86
98
  continue;
99
+ // 日志判定无权益:撤下免费档条目;会话点名已撤模型仍由上游 405/3012 拒绝。
100
+ if (route.plan === "start-plan" && startPlanGate === false)
101
+ continue;
87
102
  for (const entry of createZcodeCatalog(route.snapshot, vendorCatalog).models) {
88
103
  const slug = entry.slug.toLowerCase();
89
104
  if (seen.has(slug))
package/dist/plans.d.ts CHANGED
@@ -17,3 +17,17 @@ export declare function isZcodeStartPlanIdentity(planId: unknown, name: unknown)
17
17
  export declare function parseZcodeBillingPlans(value: unknown): ZcodeBillingPlanModels[];
18
18
  /** 汇总多个有效套餐的模型能力,按模型 ID 大小写不敏感去重。 */
19
19
  export declare function mergeZcodeBillingModelIds(plans: readonly ZcodeBillingPlanModels[]): string[];
20
+ /**
21
+ * 读取 `~/.zcode/v2/logs/<当天>.log` 尾部最后一条 Start Plan 的 billing/balance 响应。
22
+ * ZCode 宿主进程周期性拉取 billing 并把完整 payload 落盘(权威的权益判定结果,与
23
+ * LocalQuotaBar 同源),这里只消费该公开元数据,不触碰凭据。返回 undefined 表示当天
24
+ * 无可用信号(宿主未运行、日志缺失或尾部窗口内无匹配行),调用方应保持现状。
25
+ */
26
+ export declare function latestZcodeStartPlanBillingPayload(home: string, family: string): unknown;
27
+ /**
28
+ * 由宿主日志判定 Start Plan 当前是否仍有有效权益实例:
29
+ * - 记录存在且能解析出 active 且带模型能力的计划 → true(发布免费档目录);
30
+ * - 记录存在但解析为空(计划到期后宿主拉到空 plans/balances)→ false(撤下条目);
31
+ * - 无记录(undefined)→ 不干预,沿用「条目常驻、上游 405/3012 拒绝」的现状。
32
+ */
33
+ export declare function zcodeStartPlanEntitledFromLog(home: string, family: string): boolean | undefined;
package/dist/plans.js CHANGED
@@ -1,3 +1,5 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
1
3
  /** ZCode 客户端用于把 billing 计划归入 Start Plan 的字符串规则。 */
2
4
  export function isZcodeStartPlanIdentity(planId, name) {
3
5
  const id = typeof planId === "string" ? planId.trim().toLowerCase() : "";
@@ -72,3 +74,90 @@ export function mergeZcodeBillingModelIds(plans) {
72
74
  }
73
75
  return result;
74
76
  }
77
+ /**
78
+ * 宿主日志尾部扫描窗口:ZCode 宿主分钟级追加 billing 记录,1MB 覆盖全天记录量;
79
+ * 窗口外更早的记录不参与判定(视为无信号,保持现状)。有界读取,避免整文件扫描。
80
+ */
81
+ const HOST_LOG_TAIL_BYTES = 1024 * 1024;
82
+ /** 当天日志文件名(宿主按本地时区日期滚动,与 LocalQuotaBar 实证路径一致)。 */
83
+ function hostLogFileName(date = new Date()) {
84
+ return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}.log`;
85
+ }
86
+ /** 只读文件尾部;返回文本与是否发生截断,文件缺失或不可读返回 undefined。 */
87
+ function readFileTail(file, bytes) {
88
+ try {
89
+ const stat = fs.statSync(file);
90
+ if (!stat.isFile() || stat.size === 0)
91
+ return undefined;
92
+ const length = Math.min(stat.size, bytes);
93
+ const buffer = Buffer.alloc(length);
94
+ const fd = fs.openSync(file, "r");
95
+ try {
96
+ fs.readSync(fd, buffer, 0, length, stat.size - length);
97
+ }
98
+ finally {
99
+ fs.closeSync(fd);
100
+ }
101
+ return { text: buffer.toString("utf8"), truncated: length < stat.size };
102
+ }
103
+ catch {
104
+ return undefined;
105
+ }
106
+ }
107
+ /** 宿主日志里 Start Plan 记录的 providerId 形态:builtin:<family>-start-plan 或 account:<family>-start-plan。 */
108
+ function isStartPlanLogProviderID(providerID, family) {
109
+ if (typeof providerID !== "string")
110
+ return false;
111
+ const lowered = providerID.trim().toLowerCase();
112
+ return lowered === `builtin:${family}-start-plan` || lowered === `account:${family}-start-plan`;
113
+ }
114
+ /**
115
+ * 读取 `~/.zcode/v2/logs/<当天>.log` 尾部最后一条 Start Plan 的 billing/balance 响应。
116
+ * ZCode 宿主进程周期性拉取 billing 并把完整 payload 落盘(权威的权益判定结果,与
117
+ * LocalQuotaBar 同源),这里只消费该公开元数据,不触碰凭据。返回 undefined 表示当天
118
+ * 无可用信号(宿主未运行、日志缺失或尾部窗口内无匹配行),调用方应保持现状。
119
+ */
120
+ export function latestZcodeStartPlanBillingPayload(home, family) {
121
+ const tail = readFileTail(path.join(home, "v2", "logs", hostLogFileName()), HOST_LOG_TAIL_BYTES);
122
+ if (!tail)
123
+ return undefined;
124
+ const lines = tail.text.split("\n");
125
+ // 窗口截断时首行必是被拦腰截断的半行,丢弃;其余行倒序找最新记录。
126
+ for (let index = lines.length - 1; index >= (tail.truncated ? 1 : 0); index--) {
127
+ const line = lines[index];
128
+ const marker = line.indexOf("billing/balance");
129
+ if (marker < 0)
130
+ continue;
131
+ const body = line.indexOf("{", marker);
132
+ if (body < 0)
133
+ continue;
134
+ let record;
135
+ try {
136
+ record = JSON.parse(line.slice(body));
137
+ }
138
+ catch {
139
+ continue;
140
+ }
141
+ if (!record || typeof record !== "object" || Array.isArray(record))
142
+ continue;
143
+ const fields = record;
144
+ if (!isStartPlanLogProviderID(fields.providerId, family))
145
+ continue;
146
+ if (fields.payload === undefined || fields.payload === null)
147
+ continue;
148
+ return fields.payload;
149
+ }
150
+ return undefined;
151
+ }
152
+ /**
153
+ * 由宿主日志判定 Start Plan 当前是否仍有有效权益实例:
154
+ * - 记录存在且能解析出 active 且带模型能力的计划 → true(发布免费档目录);
155
+ * - 记录存在但解析为空(计划到期后宿主拉到空 plans/balances)→ false(撤下条目);
156
+ * - 无记录(undefined)→ 不干预,沿用「条目常驻、上游 405/3012 拒绝」的现状。
157
+ */
158
+ export function zcodeStartPlanEntitledFromLog(home, family) {
159
+ const payload = latestZcodeStartPlanBillingPayload(home, family);
160
+ if (payload === undefined)
161
+ return undefined;
162
+ return parseZcodeBillingPlans(payload).length > 0;
163
+ }
@@ -5871,7 +5871,6 @@ var vendor_models_default = {
5871
5871
  max_context_window: 1048576,
5872
5872
  effective_context_window_percent: 95,
5873
5873
  supports_parallel_tool_calls: true,
5874
- experimental_supported_tools: [],
5875
5874
  input_modalities: ["text", "image"]
5876
5875
  },
5877
5876
  {
@@ -5910,7 +5909,6 @@ var vendor_models_default = {
5910
5909
  max_context_window: 1048576,
5911
5910
  effective_context_window_percent: 95,
5912
5911
  supports_parallel_tool_calls: true,
5913
- experimental_supported_tools: [],
5914
5912
  input_modalities: ["text", "image"]
5915
5913
  },
5916
5914
  {
@@ -5949,7 +5947,6 @@ var vendor_models_default = {
5949
5947
  max_context_window: 1048576,
5950
5948
  effective_context_window_percent: 95,
5951
5949
  supports_parallel_tool_calls: true,
5952
- experimental_supported_tools: [],
5953
5950
  input_modalities: ["text", "image"]
5954
5951
  },
5955
5952
  {
@@ -5975,7 +5972,6 @@ var vendor_models_default = {
5975
5972
  max_context_window: 204800,
5976
5973
  effective_context_window_percent: 95,
5977
5974
  supports_parallel_tool_calls: true,
5978
- experimental_supported_tools: [],
5979
5975
  input_modalities: ["text"]
5980
5976
  }
5981
5977
  ],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-proxy/zcode",
3
- "version": "0.20.0",
3
+ "version": "0.20.2",
4
4
  "description": "Agent Proxy zcode 插件;保留基线转换行为。",
5
5
  "type": "module",
6
6
  "exports": {
@@ -35,11 +35,11 @@
35
35
  "url": "git+https://github.com/aceHubert/codex-cliproxy-gateway.git"
36
36
  },
37
37
  "devDependencies": {
38
- "@agent-proxy/models": "0.20.0"
38
+ "@agent-proxy/models": "0.20.2"
39
39
  },
40
40
  "dependencies": {
41
- "@agent-proxy/llm-bridge": "0.20.0",
42
- "@agent-proxy/plugin-api": "0.20.0"
41
+ "@agent-proxy/llm-bridge": "0.20.2",
42
+ "@agent-proxy/plugin-api": "0.20.2"
43
43
  },
44
- "gitHead": "52d36d18816a077ef2861bf3b5479a7dbd95108c"
44
+ "gitHead": "da33f92fc5cb05404b8f7ef7a91eb6321382cfc1"
45
45
  }