@sema-agent/sdk 0.0.113 → 0.0.114
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/registry/auth.d.ts +55 -0
- package/dist/registry/auth.d.ts.map +1 -0
- package/dist/registry/auth.js +106 -0
- package/dist/registry/auth.js.map +1 -0
- package/dist/registry/client.d.ts +32 -0
- package/dist/registry/client.d.ts.map +1 -0
- package/dist/registry/client.js +77 -0
- package/dist/registry/client.js.map +1 -0
- package/dist/registry/errors.d.ts +24 -0
- package/dist/registry/errors.d.ts.map +1 -0
- package/dist/registry/errors.js +29 -0
- package/dist/registry/errors.js.map +1 -0
- package/dist/registry/health.d.ts +17 -0
- package/dist/registry/health.d.ts.map +1 -0
- package/dist/registry/health.js +26 -0
- package/dist/registry/health.js.map +1 -0
- package/dist/registry/index.d.ts +17 -0
- package/dist/registry/index.d.ts.map +1 -0
- package/dist/registry/index.js +17 -0
- package/dist/registry/index.js.map +1 -0
- package/dist/registry/token-provider.d.ts +27 -0
- package/dist/registry/token-provider.d.ts.map +1 -0
- package/dist/registry/token-provider.js +18 -0
- package/dist/registry/token-provider.js.map +1 -0
- package/package.json +9 -2
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RFC 8628 device flow 驱动(G17 R1;registry-core api/auth.ts 是 wire 契约真身)。
|
|
3
|
+
*
|
|
4
|
+
* 契约单源纪律:SDK 保持**零运行时依赖**(拆包验字节门)⇒ 下面的路径/grant_type 是对
|
|
5
|
+
* `@sema-agent/registry-core` 的**值拷贝**,由 registry-auth.test.ts 的同源锚测试机器对账
|
|
6
|
+
* (registry-core 升键 ⇒ 锚红,不靠人肉)。类型用 type-only import(零运行时)。
|
|
7
|
+
*
|
|
8
|
+
* WIRE CASING:OAuth 标准端点,wire 字段 snake_case(access_token/device_code/…)——故意破本包
|
|
9
|
+
* camelCase 习惯,勿"修"(registry-core 同句)。
|
|
10
|
+
*/
|
|
11
|
+
import type { DeviceCodeResponse, TokenGrantResponse } from "@sema-agent/registry-core";
|
|
12
|
+
import type { TokenProvider } from "./token-provider.js";
|
|
13
|
+
/** 值拷贝 ← registry-core AUTH_V1_PATHS(同源锚测试对账)。 */
|
|
14
|
+
export declare const REGISTRY_AUTH_PATHS: {
|
|
15
|
+
readonly deviceCode: "/api/v1/auth/device/code";
|
|
16
|
+
readonly deviceToken: "/api/v1/auth/device/token";
|
|
17
|
+
readonly deviceApprove: "/api/v1/auth/device/approve";
|
|
18
|
+
readonly tokenRefresh: "/api/v1/auth/token/refresh";
|
|
19
|
+
readonly logout: "/api/v1/auth/logout";
|
|
20
|
+
};
|
|
21
|
+
/** 值拷贝 ← registry-core DEVICE_GRANT_TYPE(同源锚测试对账)。 */
|
|
22
|
+
export declare const REGISTRY_DEVICE_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:device_code";
|
|
23
|
+
export type { DeviceCodeResponse, TokenGrantResponse };
|
|
24
|
+
/** 步骤 1:POST device/code(设计上无凭证)。`clientName` 是同意屏的展示提示(非秘密)。 */
|
|
25
|
+
export declare function requestDeviceCode(baseUrl: string, opts?: {
|
|
26
|
+
clientName?: string;
|
|
27
|
+
fetch?: typeof fetch;
|
|
28
|
+
signal?: AbortSignal;
|
|
29
|
+
}): Promise<DeviceCodeResponse>;
|
|
30
|
+
/** 步骤 3:轮询 device/token 直到 approved(RFC §3.5:pending=保持 interval;slow_down=+5s——语义
|
|
31
|
+
* 真身是 registry-core `nextPollInterval`,此处按同判据实现,同源锚测试双向对账)。终态错
|
|
32
|
+
* (expired_token/access_denied)typed 抛出。成功先 `tokens.set` 再返回(persist-before-return)。
|
|
33
|
+
* `sleep` 可注入(测试);`signal` 中断轮询。 */
|
|
34
|
+
export declare function pollUntilApproved(baseUrl: string, device: Pick<DeviceCodeResponse, "device_code" | "interval" | "expires_in">, opts: {
|
|
35
|
+
tokens: TokenProvider;
|
|
36
|
+
fetch?: typeof fetch;
|
|
37
|
+
sleep?: (ms: number) => Promise<void>;
|
|
38
|
+
signal?: AbortSignal;
|
|
39
|
+
}): Promise<TokenGrantResponse>;
|
|
40
|
+
/** 步骤 4:refresh 轮换(STRICT single-use:成功即旧 grant 作废)。成功先 set 后返回;
|
|
41
|
+
* `invalid_grant`(replay/revoked/expired)⇒ **clear + typed**——本地令牌已不可再生,留着只会
|
|
42
|
+
* 永远循环失败,清掉让宿主直接进登录流。网络层错误原样抛(可重试,令牌不动)。 */
|
|
43
|
+
export declare function refreshTokens(baseUrl: string, opts: {
|
|
44
|
+
tokens: TokenProvider;
|
|
45
|
+
fetch?: typeof fetch;
|
|
46
|
+
signal?: AbortSignal;
|
|
47
|
+
}): Promise<TokenGrantResponse>;
|
|
48
|
+
/** 步骤 5:logout(RFC 7009:服务端恒 200 不披露存在性)。本地令牌**无条件清**(服务端失败也清——
|
|
49
|
+
* logout 的用户意图是"这台机器不再持有";renewal 路径已断或将断,残留本地令牌只是风险)。 */
|
|
50
|
+
export declare function logoutRegistry(baseUrl: string, opts: {
|
|
51
|
+
tokens: TokenProvider;
|
|
52
|
+
fetch?: typeof fetch;
|
|
53
|
+
signal?: AbortSignal;
|
|
54
|
+
}): Promise<void>;
|
|
55
|
+
//# sourceMappingURL=auth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/registry/auth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,KAAK,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAExF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD,kDAAkD;AAClD,eAAO,MAAM,mBAAmB;;;;;;CAMtB,CAAC;AAEX,sDAAsD;AACtD,eAAO,MAAM,0BAA0B,iDAAiD,CAAC;AAEzF,YAAY,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,CAAC;AAyBvD,iEAAiE;AACjE,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,MAAM,EACf,IAAI,GAAE;IAAE,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;IAAC,MAAM,CAAC,EAAE,WAAW,CAAA;CAAO,GAC7E,OAAO,CAAC,kBAAkB,CAAC,CAS7B;AAED;;;qCAGqC;AACrC,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,IAAI,CAAC,kBAAkB,EAAE,aAAa,GAAG,UAAU,GAAG,YAAY,CAAC,EAC3E,IAAI,EAAE;IAAE,MAAM,EAAE,aAAa,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;IAAC,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAAC,MAAM,CAAC,EAAE,WAAW,CAAA;CAAE,GACjH,OAAO,CAAC,kBAAkB,CAAC,CAsB7B;AAED;;6CAE6C;AAC7C,wBAAsB,aAAa,CACjC,OAAO,EAAE,MAAM,EACf,IAAI,EAAE;IAAE,MAAM,EAAE,aAAa,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;IAAC,MAAM,CAAC,EAAE,WAAW,CAAA;CAAE,GAC1E,OAAO,CAAC,kBAAkB,CAAC,CAiB7B;AAED;2DAC2D;AAC3D,wBAAsB,cAAc,CAClC,OAAO,EAAE,MAAM,EACf,IAAI,EAAE;IAAE,MAAM,EAAE,aAAa,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;IAAC,MAAM,CAAC,EAAE,WAAW,CAAA;CAAE,GAC1E,OAAO,CAAC,IAAI,CAAC,CAUf"}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { OAuthFlowError, RegistryApiError } from "./errors.js";
|
|
2
|
+
/** 值拷贝 ← registry-core AUTH_V1_PATHS(同源锚测试对账)。 */
|
|
3
|
+
export const REGISTRY_AUTH_PATHS = {
|
|
4
|
+
deviceCode: "/api/v1/auth/device/code",
|
|
5
|
+
deviceToken: "/api/v1/auth/device/token",
|
|
6
|
+
deviceApprove: "/api/v1/auth/device/approve",
|
|
7
|
+
tokenRefresh: "/api/v1/auth/token/refresh",
|
|
8
|
+
logout: "/api/v1/auth/logout",
|
|
9
|
+
};
|
|
10
|
+
/** 值拷贝 ← registry-core DEVICE_GRANT_TYPE(同源锚测试对账)。 */
|
|
11
|
+
export const REGISTRY_DEVICE_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:device_code";
|
|
12
|
+
const joinUrl = (baseUrl, path) => baseUrl.replace(/\/+$/, "") + path;
|
|
13
|
+
const postJson = async (fetchImpl, url, body, signal) => {
|
|
14
|
+
const res = await fetchImpl(url, {
|
|
15
|
+
method: "POST",
|
|
16
|
+
headers: { "content-type": "application/json" },
|
|
17
|
+
body: JSON.stringify(body),
|
|
18
|
+
...(signal ? { signal } : {}),
|
|
19
|
+
});
|
|
20
|
+
const parsed = await res.json().catch(() => undefined);
|
|
21
|
+
return { status: res.status, body: parsed };
|
|
22
|
+
};
|
|
23
|
+
const oauthCodeOf = (body) => {
|
|
24
|
+
const e = body?.error;
|
|
25
|
+
return typeof e === "string" ? e : undefined;
|
|
26
|
+
};
|
|
27
|
+
/** 步骤 1:POST device/code(设计上无凭证)。`clientName` 是同意屏的展示提示(非秘密)。 */
|
|
28
|
+
export async function requestDeviceCode(baseUrl, opts = {}) {
|
|
29
|
+
const f = opts.fetch ?? fetch;
|
|
30
|
+
const { status, body } = await postJson(f, joinUrl(baseUrl, REGISTRY_AUTH_PATHS.deviceCode), opts.clientName ? { client_name: opts.clientName } : {}, opts.signal);
|
|
31
|
+
if (status !== 200) {
|
|
32
|
+
const code = oauthCodeOf(body);
|
|
33
|
+
if (code)
|
|
34
|
+
throw new OAuthFlowError(code, body.error_description);
|
|
35
|
+
throw new RegistryApiError(status, `device/code failed (HTTP ${status})`, body);
|
|
36
|
+
}
|
|
37
|
+
return body;
|
|
38
|
+
}
|
|
39
|
+
/** 步骤 3:轮询 device/token 直到 approved(RFC §3.5:pending=保持 interval;slow_down=+5s——语义
|
|
40
|
+
* 真身是 registry-core `nextPollInterval`,此处按同判据实现,同源锚测试双向对账)。终态错
|
|
41
|
+
* (expired_token/access_denied)typed 抛出。成功先 `tokens.set` 再返回(persist-before-return)。
|
|
42
|
+
* `sleep` 可注入(测试);`signal` 中断轮询。 */
|
|
43
|
+
export async function pollUntilApproved(baseUrl, device, opts) {
|
|
44
|
+
const f = opts.fetch ?? fetch;
|
|
45
|
+
const sleep = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
46
|
+
let intervalSeconds = device.interval;
|
|
47
|
+
for (;;) {
|
|
48
|
+
if (opts.signal?.aborted)
|
|
49
|
+
throw new OAuthFlowError("expired_token", "polling aborted by caller");
|
|
50
|
+
await sleep(intervalSeconds * 1000);
|
|
51
|
+
const { status, body } = await postJson(f, joinUrl(baseUrl, REGISTRY_AUTH_PATHS.deviceToken), { grant_type: REGISTRY_DEVICE_GRANT_TYPE, device_code: device.device_code }, opts.signal);
|
|
52
|
+
if (status === 200) {
|
|
53
|
+
const grant = body;
|
|
54
|
+
await opts.tokens.set({ accessToken: grant.access_token, refreshToken: grant.refresh_token });
|
|
55
|
+
return grant;
|
|
56
|
+
}
|
|
57
|
+
const code = oauthCodeOf(body);
|
|
58
|
+
if (code === "authorization_pending")
|
|
59
|
+
continue; // keep polling at the current interval
|
|
60
|
+
if (code === "slow_down") {
|
|
61
|
+
intervalSeconds += 5; // RFC 8628 §3.5 mandated increment
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
if (code)
|
|
65
|
+
throw new OAuthFlowError(code, body.error_description);
|
|
66
|
+
throw new RegistryApiError(status, `device/token failed (HTTP ${status})`, body);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/** 步骤 4:refresh 轮换(STRICT single-use:成功即旧 grant 作废)。成功先 set 后返回;
|
|
70
|
+
* `invalid_grant`(replay/revoked/expired)⇒ **clear + typed**——本地令牌已不可再生,留着只会
|
|
71
|
+
* 永远循环失败,清掉让宿主直接进登录流。网络层错误原样抛(可重试,令牌不动)。 */
|
|
72
|
+
export async function refreshTokens(baseUrl, opts) {
|
|
73
|
+
const current = await opts.tokens.get();
|
|
74
|
+
if (!current)
|
|
75
|
+
throw new OAuthFlowError("invalid_grant", "no refresh token held (not logged in)");
|
|
76
|
+
const f = opts.fetch ?? fetch;
|
|
77
|
+
const { status, body } = await postJson(f, joinUrl(baseUrl, REGISTRY_AUTH_PATHS.tokenRefresh), { refresh_token: current.refreshToken }, opts.signal);
|
|
78
|
+
if (status === 200) {
|
|
79
|
+
const grant = body;
|
|
80
|
+
await opts.tokens.set({ accessToken: grant.access_token, refreshToken: grant.refresh_token });
|
|
81
|
+
return grant;
|
|
82
|
+
}
|
|
83
|
+
const code = oauthCodeOf(body);
|
|
84
|
+
if (code === "invalid_grant") {
|
|
85
|
+
await opts.tokens.clear();
|
|
86
|
+
throw new OAuthFlowError("invalid_grant", "refresh grant is dead (revoked/replayed/expired) — local tokens cleared; re-run the device flow");
|
|
87
|
+
}
|
|
88
|
+
if (code)
|
|
89
|
+
throw new OAuthFlowError(code, body.error_description);
|
|
90
|
+
throw new RegistryApiError(status, `token/refresh failed (HTTP ${status})`, body);
|
|
91
|
+
}
|
|
92
|
+
/** 步骤 5:logout(RFC 7009:服务端恒 200 不披露存在性)。本地令牌**无条件清**(服务端失败也清——
|
|
93
|
+
* logout 的用户意图是"这台机器不再持有";renewal 路径已断或将断,残留本地令牌只是风险)。 */
|
|
94
|
+
export async function logoutRegistry(baseUrl, opts) {
|
|
95
|
+
const current = await opts.tokens.get();
|
|
96
|
+
try {
|
|
97
|
+
if (current) {
|
|
98
|
+
const f = opts.fetch ?? fetch;
|
|
99
|
+
await postJson(f, joinUrl(baseUrl, REGISTRY_AUTH_PATHS.logout), { refresh_token: current.refreshToken }, opts.signal);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
finally {
|
|
103
|
+
await opts.tokens.clear();
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=auth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/registry/auth.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { TokenProvider } from "./token-provider.js";
|
|
2
|
+
export interface RegistryClientConfig {
|
|
3
|
+
/** registry base URL(如 `https://registry.example.com`;同源 BFF 形可相对)。 */
|
|
4
|
+
baseUrl: string;
|
|
5
|
+
/** 令牌存储(宿主实现;见 token-provider.ts 的 persist-before-return 契约)。 */
|
|
6
|
+
tokens: TokenProvider;
|
|
7
|
+
timeoutMs?: number;
|
|
8
|
+
fetch?: typeof fetch;
|
|
9
|
+
}
|
|
10
|
+
export interface RegistryRequestOptions {
|
|
11
|
+
method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
|
|
12
|
+
path: string;
|
|
13
|
+
body?: unknown;
|
|
14
|
+
/** 条件请求头位(R2 的 effective ETag / me.config CAS 走这里)。 */
|
|
15
|
+
headers?: Record<string, string>;
|
|
16
|
+
signal?: AbortSignal;
|
|
17
|
+
}
|
|
18
|
+
export declare class RegistryClient {
|
|
19
|
+
private readonly baseUrl;
|
|
20
|
+
private readonly tokens;
|
|
21
|
+
private readonly fetchImpl;
|
|
22
|
+
private readonly timeoutMs;
|
|
23
|
+
/** single-flight refresh:并发 401 共享同一次 refresh。🔴 必要而非优化——rotation 是 STRICT
|
|
24
|
+
* single-use,两个并发 refresh 里后到的那个必吃 invalid_grant 并把刚续上的会话整个清掉。 */
|
|
25
|
+
private refreshInFlight;
|
|
26
|
+
constructor(config: RegistryClientConfig);
|
|
27
|
+
request<T>(opts: RegistryRequestOptions): Promise<T>;
|
|
28
|
+
private refreshOnce;
|
|
29
|
+
private rawRequest;
|
|
30
|
+
private settle;
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/registry/client.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD,MAAM,WAAW,oBAAoB;IACnC,uEAAuE;IACvE,OAAO,EAAE,MAAM,CAAC;IAChB,iEAAiE;IACjE,MAAM,EAAE,aAAa,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;CACtB;AAED,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,OAAO,GAAG,QAAQ,CAAC;IACpD,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,uDAAuD;IACvD,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAgB;IACvC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAe;IACzC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC;uEACmE;IACnE,OAAO,CAAC,eAAe,CAA+B;gBAE1C,MAAM,EAAE,oBAAoB;IAOlC,OAAO,CAAC,CAAC,EAAE,IAAI,EAAE,sBAAsB,GAAG,OAAO,CAAC,CAAC,CAAC;IAQ1D,OAAO,CAAC,WAAW;YASL,UAAU;IA0BxB,OAAO,CAAC,MAAM;CAKf"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RegistryClient(G17 R1)——registry user-plane 的授权请求引擎:Bearer 注入 + 401 时 single-flight
|
|
3
|
+
* refresh + 原请求重试一次。R2 的资源族(scopes/effective/me.config)挂在 `request()` 上。
|
|
4
|
+
*
|
|
5
|
+
* 为什么不复用 worker 的 Transport:①错误族不同源(worker 的 toApiError 按 errorCode envelope,
|
|
6
|
+
* registry 是 OAuth/REST 混合);②重试语义不同(这里的 401→refresh→retry 是**凭证生命周期**,不是
|
|
7
|
+
* 网络重试——放进 Transport 的 RETRYABLE_STATUS 会把语义搅在一起);③worker Transport 的
|
|
8
|
+
* buildHeaders 闭包捕获静态 token,而这里 token 每次请求都可能已被 refresh 换新(必须 per-request
|
|
9
|
+
* 从 TokenProvider 读)。故 R1 用一个薄 fetch 引擎;若 R2 发现网络重试确有需要,再评估共享底座。
|
|
10
|
+
*/
|
|
11
|
+
import { OAuthFlowError, RegistryApiError } from "./errors.js";
|
|
12
|
+
import { refreshTokens } from "./auth.js";
|
|
13
|
+
export class RegistryClient {
|
|
14
|
+
baseUrl;
|
|
15
|
+
tokens;
|
|
16
|
+
fetchImpl;
|
|
17
|
+
timeoutMs;
|
|
18
|
+
/** single-flight refresh:并发 401 共享同一次 refresh。🔴 必要而非优化——rotation 是 STRICT
|
|
19
|
+
* single-use,两个并发 refresh 里后到的那个必吃 invalid_grant 并把刚续上的会话整个清掉。 */
|
|
20
|
+
refreshInFlight;
|
|
21
|
+
constructor(config) {
|
|
22
|
+
this.baseUrl = config.baseUrl;
|
|
23
|
+
this.tokens = config.tokens;
|
|
24
|
+
this.fetchImpl = config.fetch ?? fetch;
|
|
25
|
+
this.timeoutMs = config.timeoutMs ?? 30_000;
|
|
26
|
+
}
|
|
27
|
+
async request(opts) {
|
|
28
|
+
const first = await this.rawRequest(opts);
|
|
29
|
+
if (first.status !== 401)
|
|
30
|
+
return this.settle(first);
|
|
31
|
+
// 401 → single-flight refresh → 原请求重试一次(仅一次:重试仍 401 = 服务端真拒,typed 直出)。
|
|
32
|
+
await this.refreshOnce(opts.signal);
|
|
33
|
+
return this.settle(await this.rawRequest(opts));
|
|
34
|
+
}
|
|
35
|
+
refreshOnce(signal) {
|
|
36
|
+
if (!this.refreshInFlight) {
|
|
37
|
+
this.refreshInFlight = refreshTokens(this.baseUrl, { tokens: this.tokens, fetch: this.fetchImpl, ...(signal ? { signal } : {}) }).finally(() => {
|
|
38
|
+
this.refreshInFlight = undefined;
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
return this.refreshInFlight;
|
|
42
|
+
}
|
|
43
|
+
async rawRequest(opts) {
|
|
44
|
+
const current = await this.tokens.get();
|
|
45
|
+
if (!current)
|
|
46
|
+
throw new OAuthFlowError("invalid_grant", "not logged in (TokenProvider is empty) — run the device flow first");
|
|
47
|
+
const ac = new AbortController();
|
|
48
|
+
const timer = setTimeout(() => ac.abort(), this.timeoutMs);
|
|
49
|
+
const onOuterAbort = () => ac.abort();
|
|
50
|
+
opts.signal?.addEventListener("abort", onOuterAbort, { once: true });
|
|
51
|
+
try {
|
|
52
|
+
const res = await this.fetchImpl(this.baseUrl.replace(/\/+$/, "") + opts.path, {
|
|
53
|
+
method: opts.method,
|
|
54
|
+
headers: {
|
|
55
|
+
authorization: `Bearer ${current.accessToken}`,
|
|
56
|
+
...(opts.body !== undefined ? { "content-type": "application/json" } : {}),
|
|
57
|
+
...(opts.headers ?? {}),
|
|
58
|
+
},
|
|
59
|
+
...(opts.body !== undefined ? { body: JSON.stringify(opts.body) } : {}),
|
|
60
|
+
signal: ac.signal,
|
|
61
|
+
});
|
|
62
|
+
const body = await res.json().catch(() => undefined);
|
|
63
|
+
return { status: res.status, body, headers: res.headers };
|
|
64
|
+
}
|
|
65
|
+
finally {
|
|
66
|
+
clearTimeout(timer);
|
|
67
|
+
opts.signal?.removeEventListener("abort", onOuterAbort);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
settle(r) {
|
|
71
|
+
if (r.status >= 200 && r.status < 300)
|
|
72
|
+
return r.body;
|
|
73
|
+
const msg = r.body?.error;
|
|
74
|
+
throw new RegistryApiError(r.status, typeof msg === "string" ? msg : `registry request failed (HTTP ${r.status})`, r.body);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/registry/client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* registry user-plane 错误族(G17,设计稿 §2.3:worker 面的 toApiError 家族**不复用**——status 语义
|
|
3
|
+
* 不同源;OAuth 错误按 RFC 是 400 + `error` 码,worker 的 400 是 validation)。
|
|
4
|
+
*/
|
|
5
|
+
/** registry HTTP 面的通用 typed 错误(非 OAuth 端点,或 OAuth 端点的非 OAuth 形失败)。 */
|
|
6
|
+
export declare class RegistryApiError extends Error {
|
|
7
|
+
readonly status: number;
|
|
8
|
+
/** 服务端 body(已 parse;parse 失败为 undefined)。诊断用,分支请用 status。 */
|
|
9
|
+
readonly body?: unknown | undefined;
|
|
10
|
+
constructor(status: number, message: string,
|
|
11
|
+
/** 服务端 body(已 parse;parse 失败为 undefined)。诊断用,分支请用 status。 */
|
|
12
|
+
body?: unknown | undefined);
|
|
13
|
+
}
|
|
14
|
+
/** RFC 8628/OAuth 错误码集(值拷贝自 `@sema-agent/registry-core` OAUTH_ERROR_CODES——真身在那边,
|
|
15
|
+
* registry-auth.test.ts 的同源锚机器对账)。 */
|
|
16
|
+
export type OAuthFlowErrorCode = "authorization_pending" | "slow_down" | "expired_token" | "access_denied" | "invalid_request" | "unsupported_grant_type" | "invalid_grant" | "server_error";
|
|
17
|
+
/** OAuth/device-flow 语义错误——`code` 是唯一分支判据(RFC:`error_description` 只是人读诊断)。
|
|
18
|
+
* 终态语义:`expired_token`(重开流程)/`access_denied`(用户拒绝,别静默重开)/
|
|
19
|
+
* `invalid_grant`(refresh 已废,回登录——SDK 已顺手清了 TokenProvider)。 */
|
|
20
|
+
export declare class OAuthFlowError extends Error {
|
|
21
|
+
readonly code: OAuthFlowErrorCode;
|
|
22
|
+
constructor(code: OAuthFlowErrorCode, description?: string);
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/registry/errors.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,sEAAsE;AACtE,qBAAa,gBAAiB,SAAQ,KAAK;IAEvC,QAAQ,CAAC,MAAM,EAAE,MAAM;IAEvB,6DAA6D;IAC7D,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO;gBAHd,MAAM,EAAE,MAAM,EACvB,OAAO,EAAE,MAAM;IACf,6DAA6D;IACpD,IAAI,CAAC,EAAE,OAAO,YAAA;CAK1B;AAED;uCACuC;AACvC,MAAM,MAAM,kBAAkB,GAC1B,uBAAuB,GACvB,WAAW,GACX,eAAe,GACf,eAAe,GACf,iBAAiB,GACjB,wBAAwB,GACxB,eAAe,GACf,cAAc,CAAC;AAEnB;;gEAEgE;AAChE,qBAAa,cAAe,SAAQ,KAAK;IAErC,QAAQ,CAAC,IAAI,EAAE,kBAAkB;gBAAxB,IAAI,EAAE,kBAAkB,EACjC,WAAW,CAAC,EAAE,MAAM;CAKvB"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* registry user-plane 错误族(G17,设计稿 §2.3:worker 面的 toApiError 家族**不复用**——status 语义
|
|
3
|
+
* 不同源;OAuth 错误按 RFC 是 400 + `error` 码,worker 的 400 是 validation)。
|
|
4
|
+
*/
|
|
5
|
+
/** registry HTTP 面的通用 typed 错误(非 OAuth 端点,或 OAuth 端点的非 OAuth 形失败)。 */
|
|
6
|
+
export class RegistryApiError extends Error {
|
|
7
|
+
status;
|
|
8
|
+
body;
|
|
9
|
+
constructor(status, message,
|
|
10
|
+
/** 服务端 body(已 parse;parse 失败为 undefined)。诊断用,分支请用 status。 */
|
|
11
|
+
body) {
|
|
12
|
+
super(message);
|
|
13
|
+
this.status = status;
|
|
14
|
+
this.body = body;
|
|
15
|
+
this.name = "RegistryApiError";
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
/** OAuth/device-flow 语义错误——`code` 是唯一分支判据(RFC:`error_description` 只是人读诊断)。
|
|
19
|
+
* 终态语义:`expired_token`(重开流程)/`access_denied`(用户拒绝,别静默重开)/
|
|
20
|
+
* `invalid_grant`(refresh 已废,回登录——SDK 已顺手清了 TokenProvider)。 */
|
|
21
|
+
export class OAuthFlowError extends Error {
|
|
22
|
+
code;
|
|
23
|
+
constructor(code, description) {
|
|
24
|
+
super(description ? `${code}: ${description}` : code);
|
|
25
|
+
this.code = code;
|
|
26
|
+
this.name = "OAuthFlowError";
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/registry/errors.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,sEAAsE;AACtE,MAAM,OAAO,gBAAiB,SAAQ,KAAK;IAE9B;IAGA;IAJX,YACW,MAAc,EACvB,OAAe;IACf,6DAA6D;IACpD,IAAc;QAEvB,KAAK,CAAC,OAAO,CAAC,CAAC;QALN,WAAM,GAAN,MAAM,CAAQ;QAGd,SAAI,GAAJ,IAAI,CAAU;QAGvB,IAAI,CAAC,IAAI,GAAG,kBAAkB,CAAC;IACjC,CAAC;CACF;AAcD;;gEAEgE;AAChE,MAAM,OAAO,cAAe,SAAQ,KAAK;IAE5B;IADX,YACW,IAAwB,EACjC,WAAoB;QAEpB,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC,GAAG,IAAI,KAAK,WAAW,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QAH7C,SAAI,GAAJ,IAAI,CAAoB;QAIjC,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAC;IAC/B,CAAC;CACF"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* probeRegistryHealth(G18 顺并进 R1):zero-throw 健康探针——`/api/healthz` 优先,404 回落旧
|
|
3
|
+
* `/healthz`(老部署),回落命中如实标 `legacy`(消费方可提示升级)。与 worker 面的 probeAuth
|
|
4
|
+
* 同族姿势:judgement-typed,永不抛。
|
|
5
|
+
*/
|
|
6
|
+
export interface RegistryHealthResult {
|
|
7
|
+
ok: boolean;
|
|
8
|
+
/** 回落旧 /healthz 命中时 true(新端点 404 的老部署)。 */
|
|
9
|
+
legacy?: boolean;
|
|
10
|
+
/** ok:false 时的原因(网络错文本 / 末次 HTTP status)。 */
|
|
11
|
+
reason?: string;
|
|
12
|
+
}
|
|
13
|
+
export declare function probeRegistryHealth(baseUrl: string, opts?: {
|
|
14
|
+
fetch?: typeof fetch;
|
|
15
|
+
timeoutMs?: number;
|
|
16
|
+
}): Promise<RegistryHealthResult>;
|
|
17
|
+
//# sourceMappingURL=health.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"health.d.ts","sourceRoot":"","sources":["../../src/registry/health.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,WAAW,oBAAoB;IACnC,EAAE,EAAE,OAAO,CAAC;IACZ,2CAA2C;IAC3C,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,6CAA6C;IAC7C,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,MAAM,EACf,IAAI,GAAE;IAAE,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAO,GACtD,OAAO,CAAC,oBAAoB,CAAC,CAoB/B"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export async function probeRegistryHealth(baseUrl, opts = {}) {
|
|
2
|
+
const f = opts.fetch ?? fetch;
|
|
3
|
+
const base = baseUrl.replace(/\/+$/, "");
|
|
4
|
+
const tryOne = async (path) => {
|
|
5
|
+
const ac = new AbortController();
|
|
6
|
+
const timer = setTimeout(() => ac.abort(), opts.timeoutMs ?? 5_000);
|
|
7
|
+
try {
|
|
8
|
+
const res = await f(base + path, { method: "GET", signal: ac.signal });
|
|
9
|
+
return { ok: res.status === 200, status: res.status };
|
|
10
|
+
}
|
|
11
|
+
catch (err) {
|
|
12
|
+
return { ok: false, reason: err instanceof Error ? err.message : String(err) };
|
|
13
|
+
}
|
|
14
|
+
finally {
|
|
15
|
+
clearTimeout(timer);
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
const modern = await tryOne("/api/healthz");
|
|
19
|
+
if (modern.ok)
|
|
20
|
+
return { ok: true };
|
|
21
|
+
const legacy = await tryOne("/healthz");
|
|
22
|
+
if (legacy.ok)
|
|
23
|
+
return { ok: true, legacy: true };
|
|
24
|
+
return { ok: false, reason: modern.reason ?? legacy.reason ?? `HTTP ${modern.status ?? "?"} (/api/healthz) / ${legacy.status ?? "?"} (/healthz)` };
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=health.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"health.js","sourceRoot":"","sources":["../../src/registry/health.ts"],"names":[],"mappings":"AAaA,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,OAAe,EACf,OAAqD,EAAE;IAEvD,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC;IAC9B,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IACzC,MAAM,MAAM,GAAG,KAAK,EAAE,IAAY,EAA8D,EAAE;QAChG,MAAM,EAAE,GAAG,IAAI,eAAe,EAAE,CAAC;QACjC,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,KAAK,EAAE,EAAE,IAAI,CAAC,SAAS,IAAI,KAAK,CAAC,CAAC;QACpE,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,CAAC,CAAC,IAAI,GAAG,IAAI,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC,CAAC;YACvE,OAAO,EAAE,EAAE,EAAE,GAAG,CAAC,MAAM,KAAK,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC;QACxD,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;QACjF,CAAC;gBAAS,CAAC;YACT,YAAY,CAAC,KAAK,CAAC,CAAC;QACtB,CAAC;IACH,CAAC,CAAC;IACF,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,cAAc,CAAC,CAAC;IAC5C,IAAI,MAAM,CAAC,EAAE;QAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;IACnC,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,UAAU,CAAC,CAAC;IACxC,IAAI,MAAM,CAAC,EAAE;QAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;IACjD,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,IAAI,MAAM,CAAC,MAAM,IAAI,QAAQ,MAAM,CAAC,MAAM,IAAI,GAAG,qBAAqB,MAAM,CAAC,MAAM,IAAI,GAAG,aAAa,EAAE,CAAC;AACrJ,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@sema-agent/sdk/registry` — registry user-plane client 族(G17,设计稿
|
|
3
|
+
* DESIGN-sdk-userplane-and-relay.md §二;subpath export,不进主入口)。
|
|
4
|
+
*
|
|
5
|
+
* 主体=user JWT(device flow 取得),与主入口的 worker client(SERVICE_AUTH_TOKEN 主体)刻意分开
|
|
6
|
+
* ——两套凭证/刷新/错误语义不混构造器。R1=device flow + TokenProvider + healthz + RegistryClient
|
|
7
|
+
* 骨架;R2=scopes/effective(ETag)/me.config(CAS);R3=壳 12 条路由族收编。
|
|
8
|
+
*
|
|
9
|
+
* 契约单源:wire 真身在 `@sema-agent/registry-core`(type-only import + 常量值拷贝,同源锚测试
|
|
10
|
+
* 机器对账)——SDK 运行时零依赖不破。
|
|
11
|
+
*/
|
|
12
|
+
export { RegistryClient, type RegistryClientConfig, type RegistryRequestOptions } from "./client.js";
|
|
13
|
+
export { InMemoryTokenProvider, type TokenProvider, type RegistryTokens } from "./token-provider.js";
|
|
14
|
+
export { requestDeviceCode, pollUntilApproved, refreshTokens, logoutRegistry, REGISTRY_AUTH_PATHS, REGISTRY_DEVICE_GRANT_TYPE, type DeviceCodeResponse, type TokenGrantResponse, } from "./auth.js";
|
|
15
|
+
export { probeRegistryHealth, type RegistryHealthResult } from "./health.js";
|
|
16
|
+
export { RegistryApiError, OAuthFlowError, type OAuthFlowErrorCode } from "./errors.js";
|
|
17
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/registry/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAE,cAAc,EAAE,KAAK,oBAAoB,EAAE,KAAK,sBAAsB,EAAE,MAAM,aAAa,CAAC;AACrG,OAAO,EAAE,qBAAqB,EAAE,KAAK,aAAa,EAAE,KAAK,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrG,OAAO,EACL,iBAAiB,EACjB,iBAAiB,EACjB,aAAa,EACb,cAAc,EACd,mBAAmB,EACnB,0BAA0B,EAC1B,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,GACxB,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,mBAAmB,EAAE,KAAK,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAC7E,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,KAAK,kBAAkB,EAAE,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@sema-agent/sdk/registry` — registry user-plane client 族(G17,设计稿
|
|
3
|
+
* DESIGN-sdk-userplane-and-relay.md §二;subpath export,不进主入口)。
|
|
4
|
+
*
|
|
5
|
+
* 主体=user JWT(device flow 取得),与主入口的 worker client(SERVICE_AUTH_TOKEN 主体)刻意分开
|
|
6
|
+
* ——两套凭证/刷新/错误语义不混构造器。R1=device flow + TokenProvider + healthz + RegistryClient
|
|
7
|
+
* 骨架;R2=scopes/effective(ETag)/me.config(CAS);R3=壳 12 条路由族收编。
|
|
8
|
+
*
|
|
9
|
+
* 契约单源:wire 真身在 `@sema-agent/registry-core`(type-only import + 常量值拷贝,同源锚测试
|
|
10
|
+
* 机器对账)——SDK 运行时零依赖不破。
|
|
11
|
+
*/
|
|
12
|
+
export { RegistryClient } from "./client.js";
|
|
13
|
+
export { InMemoryTokenProvider } from "./token-provider.js";
|
|
14
|
+
export { requestDeviceCode, pollUntilApproved, refreshTokens, logoutRegistry, REGISTRY_AUTH_PATHS, REGISTRY_DEVICE_GRANT_TYPE, } from "./auth.js";
|
|
15
|
+
export { probeRegistryHealth } from "./health.js";
|
|
16
|
+
export { RegistryApiError, OAuthFlowError } from "./errors.js";
|
|
17
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/registry/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAE,cAAc,EAA0D,MAAM,aAAa,CAAC;AACrG,OAAO,EAAE,qBAAqB,EAA2C,MAAM,qBAAqB,CAAC;AACrG,OAAO,EACL,iBAAiB,EACjB,iBAAiB,EACjB,aAAa,EACb,cAAc,EACd,mBAAmB,EACnB,0BAA0B,GAG3B,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,mBAAmB,EAA6B,MAAM,aAAa,CAAC;AAC7E,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAA2B,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TokenProvider(G17 设计稿 §2.2):token 的**存储归宿主**(桌面 keychain / BFF session /测试内存),
|
|
3
|
+
* SDK 只定读写契约。zero-ambient:无模块级单例——多账户/多 registry 并存各持各的 provider。
|
|
4
|
+
*/
|
|
5
|
+
export interface RegistryTokens {
|
|
6
|
+
/** RS256 JWT(1h TTL,过期靠 refresh 换新)。 */
|
|
7
|
+
accessToken: string;
|
|
8
|
+
/** 旋转式一次性 refresh token(每次 refresh 换新;replay = invalid_grant)。0600/keychain 级敏感。 */
|
|
9
|
+
refreshToken: string;
|
|
10
|
+
}
|
|
11
|
+
export interface TokenProvider {
|
|
12
|
+
/** 当前令牌对;null = 未登录/已登出。 */
|
|
13
|
+
get(): Promise<RegistryTokens | null>;
|
|
14
|
+
/** 持久化新令牌对。🔴 rotation 契约:refresh 成功后 SDK **先 set 后返回**——宿主实现必须在
|
|
15
|
+
* resolve 前真正落盘(keychain 写完),否则进程崩在窗口里 = 旧 refresh 已被服务端废、新的没存下。 */
|
|
16
|
+
set(tokens: RegistryTokens): Promise<void>;
|
|
17
|
+
/** 清空(logout / invalid_grant 后 SDK 自动调)。幂等。 */
|
|
18
|
+
clear(): Promise<void>;
|
|
19
|
+
}
|
|
20
|
+
/** 内存实现(测试/短命进程)。真宿主请用 keychain/session 存储实现同一接口。 */
|
|
21
|
+
export declare class InMemoryTokenProvider implements TokenProvider {
|
|
22
|
+
private tokens;
|
|
23
|
+
get(): Promise<RegistryTokens | null>;
|
|
24
|
+
set(tokens: RegistryTokens): Promise<void>;
|
|
25
|
+
clear(): Promise<void>;
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=token-provider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token-provider.d.ts","sourceRoot":"","sources":["../../src/registry/token-provider.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,WAAW,cAAc;IAC7B,wCAAwC;IACxC,WAAW,EAAE,MAAM,CAAC;IACpB,oFAAoF;IACpF,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,aAAa;IAC5B,4BAA4B;IAC5B,GAAG,IAAI,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;IACtC;yEACqE;IACrE,GAAG,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3C,+CAA+C;IAC/C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED,qDAAqD;AACrD,qBAAa,qBAAsB,YAAW,aAAa;IACzD,OAAO,CAAC,MAAM,CAA+B;IACvC,GAAG,IAAI,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC;IAGrC,GAAG,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;IAG1C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAG7B"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TokenProvider(G17 设计稿 §2.2):token 的**存储归宿主**(桌面 keychain / BFF session /测试内存),
|
|
3
|
+
* SDK 只定读写契约。zero-ambient:无模块级单例——多账户/多 registry 并存各持各的 provider。
|
|
4
|
+
*/
|
|
5
|
+
/** 内存实现(测试/短命进程)。真宿主请用 keychain/session 存储实现同一接口。 */
|
|
6
|
+
export class InMemoryTokenProvider {
|
|
7
|
+
tokens = null;
|
|
8
|
+
async get() {
|
|
9
|
+
return this.tokens;
|
|
10
|
+
}
|
|
11
|
+
async set(tokens) {
|
|
12
|
+
this.tokens = { ...tokens };
|
|
13
|
+
}
|
|
14
|
+
async clear() {
|
|
15
|
+
this.tokens = null;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=token-provider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token-provider.js","sourceRoot":"","sources":["../../src/registry/token-provider.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAmBH,qDAAqD;AACrD,MAAM,OAAO,qBAAqB;IACxB,MAAM,GAA0B,IAAI,CAAC;IAC7C,KAAK,CAAC,GAAG;QACP,OAAO,IAAI,CAAC,MAAM,CAAC;IACrB,CAAC;IACD,KAAK,CAAC,GAAG,CAAC,MAAsB;QAC9B,IAAI,CAAC,MAAM,GAAG,EAAE,GAAG,MAAM,EAAE,CAAC;IAC9B,CAAC;IACD,KAAK,CAAC,KAAK;QACT,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;IACrB,CAAC;CACF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sema-agent/sdk",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.114",
|
|
4
4
|
"description": "Typed, zero-runtime-dependency SDK for the Sema agent fleet usage plane. The shared substrate for all doors (CC/Codex MCP façade + web). Server-side only — tokens never enter the browser.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "BUSL-1.1",
|
|
@@ -23,7 +23,11 @@
|
|
|
23
23
|
"types": "./dist/index.d.ts",
|
|
24
24
|
"import": "./dist/index.js"
|
|
25
25
|
},
|
|
26
|
-
"./openapi.yaml": "./openapi.yaml"
|
|
26
|
+
"./openapi.yaml": "./openapi.yaml",
|
|
27
|
+
"./registry": {
|
|
28
|
+
"types": "./dist/registry/index.d.ts",
|
|
29
|
+
"import": "./dist/registry/index.js"
|
|
30
|
+
}
|
|
27
31
|
},
|
|
28
32
|
"files": [
|
|
29
33
|
"dist",
|
|
@@ -40,5 +44,8 @@
|
|
|
40
44
|
"prepack": "node -e \"require('fs').copyFileSync('../../spec/openapi.yaml','openapi.yaml');require('fs').copyFileSync('../../LICENSE','LICENSE')\"",
|
|
41
45
|
"publish:npm": "npm publish --access public --@sema-agent:registry=https://registry.npmjs.org/ --\"//registry.npmjs.org/:_authToken=${NPM_TOKEN}\"",
|
|
42
46
|
"prepublishOnly": "npm run build"
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"@sema-agent/registry-core": "^0.10.21"
|
|
43
50
|
}
|
|
44
51
|
}
|