@agent-proxy/zcode 0.20.0 → 0.20.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.js +16 -1
- package/dist/plans.d.ts +14 -0
- package/dist/plans.js +89 -0
- package/package.json +2 -2
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
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-proxy/zcode",
|
|
3
|
-
"version": "0.20.
|
|
3
|
+
"version": "0.20.1",
|
|
4
4
|
"description": "Agent Proxy zcode 插件;保留基线转换行为。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -41,5 +41,5 @@
|
|
|
41
41
|
"@agent-proxy/llm-bridge": "0.20.0",
|
|
42
42
|
"@agent-proxy/plugin-api": "0.20.0"
|
|
43
43
|
},
|
|
44
|
-
"gitHead": "
|
|
44
|
+
"gitHead": "f8b3fdd82c5ac51c598f438d10aa1b3e29e1335c"
|
|
45
45
|
}
|