@agent-proxy/zcode 0.17.0 → 0.18.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 +2 -0
- package/dist/config.js +140 -18
- package/dist/index.js +3 -1
- package/dist/individual-project-token.d.ts +39 -0
- package/dist/individual-project-token.js +196 -0
- package/package.json +5 -5
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
|
-
|
|
684
|
-
|
|
685
|
-
|
|
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
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
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
|
@@ -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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-proxy/zcode",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.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.
|
|
38
|
+
"@agent-proxy/models": "0.18.0"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@agent-proxy/llm-bridge": "0.
|
|
42
|
-
"@agent-proxy/plugin-api": "0.
|
|
41
|
+
"@agent-proxy/llm-bridge": "0.18.0",
|
|
42
|
+
"@agent-proxy/plugin-api": "0.18.0"
|
|
43
43
|
},
|
|
44
|
-
"gitHead": "
|
|
44
|
+
"gitHead": "952c260940541ec72d0859d3d66bb39738e1c40f"
|
|
45
45
|
}
|