@agent-proxy/zcode 0.17.0 → 0.19.0

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/config.d.ts CHANGED
@@ -52,6 +52,8 @@ export interface ZcodeCacheDependencies {
52
52
  onSelectionChange?: () => void;
53
53
  /** 内存快照(渠道/套餐/模型集合/凭据)变化后触发,用于重建对外目录。 */
54
54
  onSnapshotChange?: (snapshot: ZcodeProviderSnapshot) => void;
55
+ /** 个人套餐项目令牌签发上报的客户端版本;缺省按 unknown 上报。 */
56
+ clientVersion?: string;
55
57
  }
56
58
  export interface ZcodePlanSelections {
57
59
  /** 当前渠道(providerFamilyDomain);失败根因优先取它的解析错误。 */
package/dist/config.js CHANGED
@@ -2,6 +2,7 @@ import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { decryptZcodeCredential } from "./credential-cipher.js";
4
4
  import { readZcodeIndividualCredential } from "./individual-credentials.js";
5
+ import { readZcodeIndividualProjectLocation, resolveZcodeIndividualApiKey, zcodeIndividualTokenFingerprint, ZcodeProjectTokenError, } from "./individual-project-token.js";
5
6
  import { readZcodeTeamCredentialInputs, resolveZcodeTeamApiKey, } from "./team-credentials.js";
6
7
  export class ZcodeConfigError extends Error {
7
8
  }
@@ -95,6 +96,18 @@ function startPlanApiKey(credentials, _family, env = process.env) {
95
96
  invalid("Start Plan 凭据解密失败");
96
97
  }
97
98
  }
99
+ /** 个人套餐项目令牌签发的登录态来源;解密失败必须明确报错,不能静默降级。 */
100
+ function oauthAccessToken(credentials, family, env = process.env) {
101
+ const value = credentials[`oauth:${family}:access_token`];
102
+ if (typeof value !== "string" || !value.trim())
103
+ return "";
104
+ try {
105
+ return decryptZcodeCredential(value, env).trim();
106
+ }
107
+ catch {
108
+ invalid("ZCode 个人套餐 OAuth 凭据解密失败;请在 ZCode 中重新登录当前账号");
109
+ }
110
+ }
98
111
  /**
99
112
  * ZCode 3.12.3 起切换套餐只写 `providerFamilyConnectionSelections[family].kind`,
100
113
  * legacy `modelProviderFamilySelectedKeys` 已冻结。在入口把已知 kind 归一化为与 legacy
@@ -461,6 +474,13 @@ export function createZcodeConfigCache(homeDirectory, dependencies = {}) {
461
474
  let teamCredential;
462
475
  let teamCredentialFingerprint;
463
476
  let teamForceRefresh = false;
477
+ /** 个人套餐项目令牌(ZCode 3.16+):落盘 Key 缺失时按 OAuth 在线签发并缓存。 */
478
+ let individualMint;
479
+ let individualMintFingerprint = "";
480
+ let individualMintMode = false;
481
+ let individualIdentity = "";
482
+ let individualOauth = "";
483
+ let individualLocation;
464
484
  let credentialGeneration = 0;
465
485
  let resolving;
466
486
  let firstEventAt;
@@ -520,6 +540,12 @@ export function createZcodeConfigCache(homeDirectory, dependencies = {}) {
520
540
  teamCredential = undefined;
521
541
  teamCredentialFingerprint = "";
522
542
  teamForceRefresh = false;
543
+ individualMint = undefined;
544
+ individualMintFingerprint = "";
545
+ individualMintMode = false;
546
+ individualIdentity = "";
547
+ individualOauth = "";
548
+ individualLocation = undefined;
523
549
  credential = undefined;
524
550
  snapshot = undefined;
525
551
  expiryCheckedKey = undefined;
@@ -650,6 +676,12 @@ export function createZcodeConfigCache(homeDirectory, dependencies = {}) {
650
676
  teamCredential = undefined;
651
677
  teamCredentialFingerprint = "";
652
678
  teamForceRefresh = false;
679
+ individualMint = undefined;
680
+ individualMintFingerprint = "";
681
+ individualMintMode = false;
682
+ individualIdentity = "";
683
+ individualOauth = "";
684
+ individualLocation = undefined;
653
685
  credential = undefined;
654
686
  snapshot = undefined;
655
687
  expiryCheckedKey = undefined;
@@ -680,28 +712,47 @@ export function createZcodeConfigCache(homeDirectory, dependencies = {}) {
680
712
  const individual = selection.kind === "individual-coding-plan"
681
713
  ? readZcodeIndividualCredential(credentials, selection.family, dependencies.env)
682
714
  : undefined;
683
- const key = individual?.state === "available"
684
- ? individual.apiKey
685
- : individual?.state === "unavailable"
686
- ? ""
715
+ if (individual?.state === "unavailable") {
716
+ // ZCode 3.16 起个人套餐 Key 不再落盘 credentials.json:账号身份与
717
+ // OAuth 登录态仍可解析时改为在线签发项目令牌;镜像回退禁令不变,
718
+ // config.json 里的旧 Key 可能属于上一账号。
719
+ const identity = individual.identity ?? "";
720
+ const oauth = identity ? oauthAccessToken(credentials, selection.family, dependencies.env) : "";
721
+ if (identity && oauth) {
722
+ individualMintMode = true;
723
+ individualIdentity = identity;
724
+ individualOauth = oauth;
725
+ individualLocation = readZcodeIndividualProjectLocation(credentials, selection.family, identity, dependencies.env);
726
+ const nextFingerprint = zcodeIndividualTokenFingerprint(oauth, identity, individualLocation);
727
+ const mintChanged = individualMintFingerprint !== nextFingerprint;
728
+ individualMintFingerprint = nextFingerprint;
729
+ const projection = JSON.stringify(["project-token", nextFingerprint]);
730
+ oauthChanged = mintChanged || projection !== oauthProjection;
731
+ oauthProjection = projection;
732
+ }
733
+ else {
734
+ individualMintMode = false;
735
+ throw new ZcodeConfigError("ZCode 个人套餐凭据不可用;请在 ZCode 中重新连接当前账号");
736
+ }
737
+ }
738
+ else {
739
+ individualMintMode = false;
740
+ const key = individual?.state === "available"
741
+ ? individual.apiKey
687
742
  : selection.kind === "start-plan"
688
743
  ? startPlanApiKey(credentials, selection.family, dependencies.env)
689
744
  : "";
690
- // 账号身份存在却拿不到该账号的 Key 时禁止镜像回退:切换账号后
691
- // config.json 里的旧 Key 可能属于上一账号。
692
- if (individual?.state === "unavailable") {
693
- throw new ZcodeConfigError("ZCode 个人套餐凭据不可用;请在 ZCode 中重新连接当前账号");
745
+ personalApiKey = key || undefined;
746
+ const projection = JSON.stringify([
747
+ credentials[`oauth:${selection.family}:access_token`],
748
+ credentials[`oauth:${selection.family}:refresh_token`],
749
+ credentials.zcodejwttoken,
750
+ individual?.state === "available" ? individual.identity : individual?.state ?? null,
751
+ key,
752
+ ]);
753
+ oauthChanged = projection !== oauthProjection;
754
+ oauthProjection = projection;
694
755
  }
695
- personalApiKey = key || undefined;
696
- const projection = JSON.stringify([
697
- credentials[`oauth:${selection.family}:access_token`],
698
- credentials[`oauth:${selection.family}:refresh_token`],
699
- credentials.zcodejwttoken,
700
- individual?.state === "available" ? individual.identity : individual?.state ?? null,
701
- key,
702
- ]);
703
- oauthChanged = projection !== oauthProjection;
704
- oauthProjection = projection;
705
756
  }
706
757
  }
707
758
  catch (error) {
@@ -816,6 +867,77 @@ export function createZcodeConfigCache(homeDirectory, dependencies = {}) {
816
867
  armExpiry();
817
868
  return;
818
869
  }
870
+ if (selection.kind === "individual-coding-plan" && individualMintMode) {
871
+ // ZCode 3.16 个人套餐项目令牌:OAuth/账号/指针指纹变化或到期时重签;
872
+ // 机制与团队套餐一致(resolving 代际隔离 + 到期定时器)。
873
+ const fingerprint = individualMintFingerprint;
874
+ const expired = credential?.expiresAt !== undefined && credential.expiresAt <= now();
875
+ const mustResolve = !individualMint
876
+ || individualMint.identity !== individualIdentity
877
+ || individualMint.fingerprint !== fingerprint
878
+ || expired;
879
+ if (resolving) {
880
+ snapshot = undefined;
881
+ return;
882
+ }
883
+ if (mustResolve) {
884
+ const generation = ++credentialGeneration;
885
+ const oauth = individualOauth;
886
+ const mintIdentity = individualIdentity;
887
+ const location = individualLocation;
888
+ snapshot = undefined;
889
+ credential = undefined;
890
+ expiryCheckedKey = undefined;
891
+ clearTimeout(expiryTimer);
892
+ const promise = (async () => {
893
+ try {
894
+ const minted = await resolveZcodeIndividualApiKey(selection.family, { accessToken: oauth, identity: mintIdentity, fingerprint }, location, { fetch: dependencies.fetch, env: dependencies.env, clientVersion: dependencies.clientVersion, now });
895
+ if (credentialGeneration !== generation || selection.kind !== "individual-coding-plan")
896
+ return;
897
+ dependencies.onCredentialBuild?.();
898
+ credential = { apiKey: minted.apiKey, expiresAt: minted.expiresAt };
899
+ individualMint = { identity: mintIdentity, fingerprint, apiKey: minted.apiKey, expiresAt: minted.expiresAt };
900
+ const currentRoute = route;
901
+ if (!currentRoute)
902
+ invalid("ZCode 当前路由不可用");
903
+ snapshot = Object.freeze({ ...currentRoute, apiKey: minted.apiKey, expiresAt: minted.expiresAt });
904
+ failure = undefined;
905
+ armExpiry();
906
+ }
907
+ catch (error) {
908
+ if (credentialGeneration !== generation)
909
+ return;
910
+ snapshot = undefined;
911
+ credential = undefined;
912
+ individualMint = undefined;
913
+ failure = error instanceof ZcodeProjectTokenError || error instanceof ZcodeConfigError
914
+ ? new ZcodeConfigError(error.message)
915
+ : new ZcodeConfigError("无法签发 ZCode 个人套餐项目令牌;请在 ZCode 中重新登录当前账号后重试");
916
+ clearTimeout(expiryTimer);
917
+ }
918
+ finally {
919
+ if (credentialGeneration === generation)
920
+ resolving = undefined;
921
+ finish();
922
+ }
923
+ })();
924
+ resolving = { generation, promise };
925
+ return;
926
+ }
927
+ const cachedMint = individualMint;
928
+ if (!cachedMint)
929
+ invalid("ZCode 个人套餐项目令牌不可用");
930
+ credential = { apiKey: cachedMint.apiKey, ...(cachedMint.expiresAt === undefined ? {} : { expiresAt: cachedMint.expiresAt }) };
931
+ if (cachedMint.expiresAt !== undefined && cachedMint.expiresAt <= now()) {
932
+ expiryCheckedKey = cachedMint.apiKey;
933
+ invalid("ZCode 当前业务 Key 已过期");
934
+ }
935
+ snapshot = Object.freeze({ ...next, apiKey: cachedMint.apiKey,
936
+ ...(cachedMint.expiresAt === undefined ? {} : { expiresAt: cachedMint.expiresAt }) });
937
+ failure = undefined;
938
+ armExpiry();
939
+ return;
940
+ }
819
941
  // start-plan:上次套餐凭据优先于镜像 JWT(镜像只是无 zcodejwttoken 时的引导回退),
820
942
  // 否则不重读凭据的检查轮次会把 key 静默换成可能冻结的镜像值。
821
943
  const routeApiKey = personalApiKey
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@ import path from "node:path";
3
3
  import { isIP } from "node:net";
4
4
  import { resolvePaths } from "@agent-proxy/plugin-api";
5
5
  import { createZcodeConfigCache, readZcodeAPIKeyProviders, zcodeConfigPresent, ZcodeConfigError } from "./config.js";
6
- import { clearModelsCacheEntries, invalidateModelsCache, withAgentSystemPrompt } from "@agent-proxy/plugin-api";
6
+ import { clearModelsCacheEntries, invalidateModelsCache } 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
9
  import { createZcodeClientResponse } from "./response.js";
@@ -11,7 +11,7 @@ import { executeZcodeAnalyzeImage, matchZcodeAnalyzeImage } from "./vision.js";
11
11
  import { ZcodeEndpointRouting } from "./endpoint-routing.js";
12
12
  import { clientSigningVerifyRejection, ZcodeClientSigning } from "./client-signing.js";
13
13
  import { isZcodeRecord } from "./wire.js";
14
- import { buildZcodeModelHeaders, createZcodeContexts, decorateZcodeBody, readZcodeIdentity, zcodePlan, ZCODE_AGENT_SYSTEM_PROMPT } from "./request-context.js";
14
+ import { buildZcodeModelHeaders, createZcodeContexts, decorateZcodeBody, readZcodeIdentity, zcodePlan } 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
17
  export function validateZcodeConfig(config) {
@@ -52,10 +52,10 @@ function buildZcodeAdapter(config, dependencies = {}) {
52
52
  return clearModelsCacheEntries(dependencies.codexModelsCacheFile, (slug) => isZcodeModel(slug) && !current.has(slug.toLowerCase()));
53
53
  };
54
54
  const publishServedCatalog = (catalog) => {
55
- // 系统提示词在发布成品时替换(含 model_messages 模板):zcode-catalog.json 落盘即终态,
56
- // 与其它适配器的合成期替换对齐;Codex 客户端优先按模板渲染系统提示词,只改
57
- // base_instructions 无效(2026-10-09 实测)。
58
- servedCatalog = { models: catalog.models.map((entry) => withAgentSystemPrompt(entry, ZCODE_AGENT_SYSTEM_PROMPT)) };
55
+ // 目录条目保持 vendor 预设基底(base_instructions 为空串、无 model_messages 模板);
56
+ // 官方 ZCode 提示词由请求转换层整体注入(request.ts,任何客户端协议进入均生效),
57
+ // catalog 不再改写提示词。
58
+ servedCatalog = { models: catalog.models };
59
59
  const changed = writeZcodeServedCatalog(servedCatalogFile, servedCatalog);
60
60
  if (!changed || !dependencies.codexModelsCacheFile)
61
61
  return;
@@ -67,6 +67,7 @@ function buildZcodeAdapter(config, dependencies = {}) {
67
67
  { plan: "individual-coding-plan" },
68
68
  { plan: "team-coding-plan" },
69
69
  ];
70
+ const identity = dependencies.identity ?? readZcodeIdentity();
70
71
  let synchronizeAPIKeyRoutes = () => { };
71
72
  const republishFromPlans = (force = false) => {
72
73
  const current = planRoutes.map((route) => route.snapshot);
@@ -99,6 +100,8 @@ function buildZcodeAdapter(config, dependencies = {}) {
99
100
  ?? (injected ? undefined : createZcodeConfigCache(home, {
100
101
  plan: route.plan,
101
102
  refreshOnStart: false,
103
+ // 个人套餐项目令牌签发按官方客户端上报版本。
104
+ clientVersion: identity.appVersion,
102
105
  // 选择变化只撤下本套餐的快照并重发并集;另一个套餐的条目不受影响。
103
106
  onSelectionChange: () => {
104
107
  if (manual)
@@ -183,7 +186,6 @@ function buildZcodeAdapter(config, dependencies = {}) {
183
186
  }
184
187
  const fetchUpstream = dependencies.fetch ?? ((url, init) => fetch(url, init));
185
188
  const activeRequests = new Set();
186
- const identity = dependencies.identity ?? readZcodeIdentity();
187
189
  const endpointRouting = dependencies.endpointRouting !== null
188
190
  ? dependencies.endpointRouting ?? new ZcodeEndpointRouting({
189
191
  identity,
@@ -0,0 +1,39 @@
1
+ import type { ZcodeFamily } from "./config.ts";
2
+ /** 项目令牌签发失败;消息面向用户,可直接透传为配置错误。 */
3
+ export declare class ZcodeProjectTokenError extends Error {
4
+ }
5
+ export interface ZcodeIndividualProjectLocation {
6
+ organizationId: string;
7
+ projectId: string;
8
+ apiKeyId: string;
9
+ }
10
+ export interface ZcodeIndividualTokenInputs {
11
+ accessToken: string;
12
+ identity: string;
13
+ fingerprint: string;
14
+ }
15
+ export interface ZcodeIndividualTokenDependencies {
16
+ fetch?: typeof fetch;
17
+ env?: NodeJS.ProcessEnv;
18
+ clientVersion?: string;
19
+ now?: () => number;
20
+ }
21
+ export interface ZcodeIndividualApiKey {
22
+ apiKey: string;
23
+ expiresAt: number;
24
+ }
25
+ /**
26
+ * ZCode 3.16 起个人套餐业务 Key 不再落盘,credentials.json 只保留指向
27
+ * `organization/project/apiKey` 的指针(按账号隔离)。这里按当前账号身份与
28
+ * 渠道解析同账号、非团队、Coding Plan 场景的指针;无法解密或形状不符按缺失
29
+ * 处理,交给发现流程重建,与官方客户端的可恢复语义一致。
30
+ */
31
+ export declare function readZcodeIndividualProjectLocation(credentials: Record<string, unknown>, family: ZcodeFamily, identity: string, env?: NodeJS.ProcessEnv): ZcodeIndividualProjectLocation | undefined;
32
+ /** 令牌签发输入的指纹:OAuth、账号身份或指针任一变化都需要重新签发。 */
33
+ export declare function zcodeIndividualTokenFingerprint(accessToken: string, identity: string, location: ZcodeIndividualProjectLocation | undefined): string;
34
+ /**
35
+ * 复刻 ZCode 3.16 客户端的个人套餐项目令牌签发:优先按 credentials.json 的
36
+ * 指针签发短时令牌;指针缺失或失效(404)时经 getCustomerInfo → 项目 API Key
37
+ * 列表发现兜底。签发全程不落盘,结果由调用方缓存并按过期时间重签。
38
+ */
39
+ export declare function resolveZcodeIndividualApiKey(family: ZcodeFamily, inputs: ZcodeIndividualTokenInputs, location: ZcodeIndividualProjectLocation | undefined, dependencies?: ZcodeIndividualTokenDependencies): Promise<ZcodeIndividualApiKey>;
@@ -0,0 +1,196 @@
1
+ import { createHash } from "node:crypto";
2
+ import { decryptZcodeCredential } from "./credential-cipher.js";
3
+ const REQUEST_TIMEOUT_MS = 15_000;
4
+ const LOCATION_PREFIX = "project-key-location:v1:";
5
+ /** 指针元数据里 Coding Plan 场景的固定序号(客户端 di.CODING_PLAN)。 */
6
+ const CODING_PLAN_SCENE = 1;
7
+ const NON_TEAM_SELECTION = "non-team";
8
+ const API_KEY_TYPE = 2;
9
+ /** 项目令牌签发失败;消息面向用户,可直接透传为配置错误。 */
10
+ export class ZcodeProjectTokenError extends Error {
11
+ }
12
+ function invalid(message) {
13
+ throw new ZcodeProjectTokenError(message);
14
+ }
15
+ function isRecord(value) {
16
+ return value !== null && typeof value === "object" && !Array.isArray(value);
17
+ }
18
+ function host(family) {
19
+ return family === "zai" ? "https://api.z.ai" : "https://open.bigmodel.cn";
20
+ }
21
+ /**
22
+ * ZCode 3.16 起个人套餐业务 Key 不再落盘,credentials.json 只保留指向
23
+ * `organization/project/apiKey` 的指针(按账号隔离)。这里按当前账号身份与
24
+ * 渠道解析同账号、非团队、Coding Plan 场景的指针;无法解密或形状不符按缺失
25
+ * 处理,交给发现流程重建,与官方客户端的可恢复语义一致。
26
+ */
27
+ export function readZcodeIndividualProjectLocation(credentials, family, identity, env = process.env) {
28
+ if (!identity)
29
+ return undefined;
30
+ const expected = host(family);
31
+ for (const key of Object.keys(credentials)) {
32
+ if (!key.startsWith(LOCATION_PREFIX))
33
+ continue;
34
+ let meta;
35
+ try {
36
+ meta = JSON.parse(decodeURIComponent(key.slice(LOCATION_PREFIX.length)));
37
+ }
38
+ catch {
39
+ continue;
40
+ }
41
+ if (!Array.isArray(meta) || meta.length !== 6)
42
+ continue;
43
+ const [origin, metaFamily, accountId, team, scene, selection] = meta;
44
+ if (origin !== expected || metaFamily !== family || accountId !== identity)
45
+ continue;
46
+ if (team !== null || scene !== CODING_PLAN_SCENE || selection !== NON_TEAM_SELECTION)
47
+ continue;
48
+ const value = credentials[key];
49
+ if (typeof value !== "string" || !value.trim())
50
+ continue;
51
+ let decoded;
52
+ try {
53
+ decoded = JSON.parse(decryptZcodeCredential(value, env));
54
+ }
55
+ catch {
56
+ continue;
57
+ }
58
+ if (!isRecord(decoded))
59
+ continue;
60
+ const organizationId = typeof decoded.organizationId === "string" ? decoded.organizationId.trim() : "";
61
+ const projectId = typeof decoded.projectId === "string" ? decoded.projectId.trim() : "";
62
+ const apiKeyId = typeof decoded.apiKeyId === "string" ? decoded.apiKeyId.trim() : "";
63
+ if (organizationId && projectId && apiKeyId)
64
+ return { organizationId, projectId, apiKeyId };
65
+ }
66
+ return undefined;
67
+ }
68
+ /** 令牌签发输入的指纹:OAuth、账号身份或指针任一变化都需要重新签发。 */
69
+ export function zcodeIndividualTokenFingerprint(accessToken, identity, location) {
70
+ return createHash("sha256").update(JSON.stringify([accessToken, identity, location ?? null])).digest("hex");
71
+ }
72
+ async function requestJson(url, init, fetchImpl) {
73
+ let response;
74
+ try {
75
+ response = await fetchImpl(url, { ...init, signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS) });
76
+ }
77
+ catch {
78
+ throw new Error("无法访问 ZCode 个人套餐令牌接口");
79
+ }
80
+ if (!response.ok)
81
+ throw new Error("ZCode 个人套餐令牌接口请求失败");
82
+ let value;
83
+ try {
84
+ value = await response.json();
85
+ }
86
+ catch {
87
+ throw new Error("ZCode 个人套餐令牌接口响应无效");
88
+ }
89
+ if (!isRecord(value))
90
+ throw new Error("ZCode 个人套餐令牌接口响应无效");
91
+ return value;
92
+ }
93
+ function envelopeCode(envelope) {
94
+ const code = envelope.code;
95
+ return typeof code === "number" && Number.isFinite(code) ? code : undefined;
96
+ }
97
+ function responseData(envelope) {
98
+ const code = envelopeCode(envelope);
99
+ if (envelope.success === false || (code !== undefined && code !== 0 && code !== 200)) {
100
+ const message = typeof envelope.msg === "string" && envelope.msg.trim() ? `:${envelope.msg.trim().slice(0, 120)}` : "";
101
+ throw new ZcodeProjectTokenError(`ZCode 个人套餐令牌接口返回业务错误(code ${code ?? "unknown"})${message}`);
102
+ }
103
+ if (envelope.success !== true && !Object.hasOwn(envelope, "data")) {
104
+ throw new Error("ZCode 个人套餐令牌接口响应缺少数据");
105
+ }
106
+ return envelope.data;
107
+ }
108
+ async function mintProjectToken(family, inputs, location, dependencies) {
109
+ const fetchImpl = dependencies.fetch ?? fetch;
110
+ const now = dependencies.now ?? Date.now;
111
+ const url = `${host(family)}/api/biz/v1/organization/${encodeURIComponent(location.organizationId)}`
112
+ + `/projects/${encodeURIComponent(location.projectId)}`
113
+ + `/api_keys/${encodeURIComponent(location.apiKeyId)}/access_tokens`;
114
+ const envelope = await requestJson(url, {
115
+ method: "POST",
116
+ headers: { Authorization: `Bearer ${inputs.accessToken}`, "Content-Type": "application/json" },
117
+ body: JSON.stringify({ clientType: "zcode", clientVersion: dependencies.clientVersion ?? "unknown" }),
118
+ }, fetchImpl);
119
+ if (envelopeCode(envelope) === 404)
120
+ invalid("ZCODE_PROJECT_KEY_UNAVAILABLE");
121
+ const data = responseData(envelope);
122
+ // 与官方客户端一致:data 缺省 enable 字段时按已启用处理,显式 false 才是停用。
123
+ if (!isRecord(data) || data.enable === false)
124
+ throw new Error("ZCode 个人套餐项目令牌未启用");
125
+ const token = typeof data.accessToken === "string" ? data.accessToken.trim() : "";
126
+ if (!token)
127
+ throw new Error("ZCode 个人套餐项目令牌响应缺少 accessToken");
128
+ const nowMs = now();
129
+ const relative = typeof data.expiresIn === "number" && Number.isFinite(data.expiresIn) && data.expiresIn > 0
130
+ ? nowMs + data.expiresIn * 1000 : undefined;
131
+ const absolute = typeof data.expiresAt === "number" && Number.isFinite(data.expiresAt) ? data.expiresAt * 1000 : undefined;
132
+ const expiresAt = [relative, absolute].filter((value) => value !== undefined)
133
+ .reduce((left, right) => Math.min(left, right), Number.POSITIVE_INFINITY);
134
+ if (!Number.isFinite(expiresAt) || expiresAt <= nowMs)
135
+ throw new Error("ZCode 个人套餐项目令牌过期时间无效");
136
+ return { apiKey: token, expiresAt };
137
+ }
138
+ async function discoverProjectLocation(family, accessToken, dependencies) {
139
+ const fetchImpl = dependencies.fetch ?? fetch;
140
+ const apiHost = host(family);
141
+ const customer = await requestJson(`${apiHost}/api/biz/customer/getCustomerInfo`, {
142
+ method: "GET",
143
+ headers: { Authorization: accessToken, "Content-Type": "application/json" },
144
+ }, fetchImpl);
145
+ const data = responseData(customer);
146
+ const organizations = isRecord(data) && Array.isArray(data.organizations) ? data.organizations : [];
147
+ const projectIdOf = (item) => isRecord(item) && typeof item.projectId === "string" ? item.projectId.trim() : "";
148
+ // 个人(非团队)场景:与客户端一致取默认机构下第一个业务类型项目,无类型标注时取首个项目。
149
+ const project = organizations.flatMap((organization) => {
150
+ if (!isRecord(organization))
151
+ return [];
152
+ const organizationId = typeof organization.organizationId === "string" ? organization.organizationId.trim() : "";
153
+ const projects = Array.isArray(organization.projects) ? organization.projects.filter(isRecord) : [];
154
+ const selected = projects.find((item) => projectIdOf(item) && String(item.projectType ?? "").trim())
155
+ ?? projects.find((item) => projectIdOf(item));
156
+ const projectId = selected ? projectIdOf(selected) : "";
157
+ return organizationId && projectId ? [{ organizationId, projectId }] : [];
158
+ })[0];
159
+ if (!project)
160
+ invalid("ZCode 个人套餐缺少可用的默认项目;请在 ZCode 中打开一次个人套餐");
161
+ const list = await requestJson(`${apiHost}/api/biz/v1/organization/${encodeURIComponent(project.organizationId)}`
162
+ + `/projects/${encodeURIComponent(project.projectId)}/api_keys`, {
163
+ method: "GET",
164
+ headers: { Authorization: accessToken, "Content-Type": "application/json" },
165
+ }, fetchImpl);
166
+ const keys = responseData(list);
167
+ const entries = Array.isArray(keys) ? keys.filter(isRecord) : [];
168
+ const keyEntry = entries.find((item) => item.keyType === API_KEY_TYPE
169
+ && typeof item.apiKey === "string" && item.apiKey.trim());
170
+ const apiKeyId = typeof keyEntry?.apiKey === "string" ? keyEntry.apiKey.trim() : "";
171
+ if (!apiKeyId)
172
+ invalid("ZCode 个人套餐项目缺少可用的 API Key;请在 ZCode 中打开一次个人套餐重建");
173
+ return { ...project, apiKeyId };
174
+ }
175
+ /**
176
+ * 复刻 ZCode 3.16 客户端的个人套餐项目令牌签发:优先按 credentials.json 的
177
+ * 指针签发短时令牌;指针缺失或失效(404)时经 getCustomerInfo → 项目 API Key
178
+ * 列表发现兜底。签发全程不落盘,结果由调用方缓存并按过期时间重签。
179
+ */
180
+ export async function resolveZcodeIndividualApiKey(family, inputs, location, dependencies = {}) {
181
+ if (location) {
182
+ try {
183
+ return await mintProjectToken(family, inputs, location, dependencies);
184
+ }
185
+ catch (error) {
186
+ if (error instanceof ZcodeProjectTokenError && error.message === "ZCODE_PROJECT_KEY_UNAVAILABLE") {
187
+ // 指针已失效(服务端 404):与客户端一致转入发现流程重建。
188
+ }
189
+ else {
190
+ throw error;
191
+ }
192
+ }
193
+ }
194
+ const discovered = await discoverProjectLocation(family, inputs.accessToken, dependencies);
195
+ return mintProjectToken(family, inputs, discovered, dependencies);
196
+ }
package/dist/request.js CHANGED
@@ -1,5 +1,6 @@
1
- import { BridgeConversionError, convertRequest, normalizeResponsesToolDeclarations } from "@agent-proxy/llm-bridge";
1
+ import { BridgeConversionError, convertRequest, normalizeResponsesToolDeclarations, stripClientSystemPrompt } from "@agent-proxy/llm-bridge";
2
2
  import { createHash } from "node:crypto";
3
+ import { ZCODE_AGENT_SYSTEM_PROMPT } from "./request-context.js";
3
4
  import { decodeZcodeThinking, isZcodeRecord } from "./wire.js";
4
5
  import { ANALYZE_IMAGE_SYSTEM_NOTE, ANALYZE_IMAGE_TOOL_DECLARATION, ANALYZE_IMAGE_TOOL_NAME, collectZcodeImages } from "./vision.js";
5
6
  /**
@@ -267,6 +268,9 @@ export async function translateZcodeRequest(body, upstreamModel, to = "responses
267
268
  }
268
269
  if (body.instructions !== undefined && typeof body.instructions !== "string")
269
270
  fail("instructions 必须是字符串");
271
+ // 官方 ZCode 提示词整体替换客户端系统内容(catalog 不再注入,任何客户端协议
272
+ // 进入都在转换层统一注入):入口先剥离 instructions 与 system/developer 内容。
273
+ body = stripClientSystemPrompt(to, body);
270
274
  // 上游 token 上限按来源取参:responses 用 max_output_tokens,chat-completions /
271
275
  // anthropic 用 max_tokens,gemini 用 generationConfig.maxOutputTokens;缺省走服务默认 16384。
272
276
  const sourceLimit = to === "responses"
@@ -281,9 +285,9 @@ export async function translateZcodeRequest(body, upstreamModel, to = "responses
281
285
  const clientModel = body.model === undefined ? upstreamModel : string(body.model, "model");
282
286
  const identities = new Map();
283
287
  const messages = [];
284
- const system = [];
285
- if (typeof body.instructions === "string" && body.instructions)
286
- system.push(body.instructions);
288
+ // 官方 ZCode 提示词为上游系统槽唯一基底;客户端系统内容已入口剥离,
289
+ // 网关旁白(dropped 工具通告、视觉适配说明)随后拼装。
290
+ const system = [ZCODE_AGENT_SYSTEM_PROMPT];
287
291
  let passthroughChoice;
288
292
  let passthroughStopSequences;
289
293
  let passthroughTools;
@@ -301,8 +305,6 @@ export async function translateZcodeRequest(body, upstreamModel, to = "responses
301
305
  identities.set(mapped, identity);
302
306
  for (const message of anthropicMessagesOf(converted))
303
307
  addMessage(messages, message.role, message.content);
304
- if (typeof converted.body.system === "string" && converted.body.system)
305
- system.push(converted.body.system);
306
308
  passthroughTools = Array.isArray(converted.body.tools) ? converted.body.tools.filter(record) : undefined;
307
309
  passthroughChoice = converted.body.tool_choice;
308
310
  if (Array.isArray(converted.body.stop_sequences) && converted.body.stop_sequences.every((entry) => typeof entry === "string")) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-proxy/zcode",
3
- "version": "0.17.0",
3
+ "version": "0.19.0",
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.17.0"
38
+ "@agent-proxy/models": "0.19.0"
39
39
  },
40
40
  "dependencies": {
41
- "@agent-proxy/llm-bridge": "0.17.0",
42
- "@agent-proxy/plugin-api": "0.17.0"
41
+ "@agent-proxy/llm-bridge": "0.19.0",
42
+ "@agent-proxy/plugin-api": "0.19.0"
43
43
  },
44
- "gitHead": "7dc26953606d78cc982692de2f830f9f92061adc"
44
+ "gitHead": "20dff92139f68670155c173ba94849415c7ef21e"
45
45
  }