@acosmi/sdk-ts 1.0.1 → 1.1.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/CHANGELOG.md CHANGED
@@ -5,8 +5,98 @@ All notable changes to `@acosmi/sdk-ts` will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.1.0] — 2026-05-06
9
+
10
+ ### Added
11
+
12
+ - **Agent Runs SDK Gateway** — 新增 `client.agentRuns` namespace,作为 CrabDesign、CrabCode、CrabClaw 等下游产品接入 Acosmi 云端智能体循环的正式 SDK 协议边界:
13
+ - `create(req, signal?)`
14
+ - `stream(runId, opts?, signal?)`
15
+ - `run(req, opts?, signal?)`
16
+ - `cancel(runId, signal?)`
17
+ - `get(runId, signal?)`
18
+ - `listArtifacts(runId, signal?)`
19
+ - `downloadArtifact(runId, artifactId, signal?)`
20
+ - `submitLocalToolResult(runId, result, signal?)`
21
+ - `runWithLocalTools(req, handlers, opts?, signal?)`
22
+
23
+ - **Agent Run protocol types** — 新增 `AgentRunCreateRequest`、`AgentRunStreamEvent` discriminated union、`AgentRunArtifact`、`AgentRunStreamError` 等公开类型。SDK public API 使用 camelCase,HTTP wire-format 使用 snake_case。
24
+
25
+ - **Local tool bridge protocol** — SDK 不内置产品专属本地文件读取逻辑;`local_tool_request` 由下游处理,并通过 `submitLocalToolResult({ requestId, ok, content | error })` 返回。便捷封装 `runWithLocalTools` 支持 handler 超时、拒绝和取消。
26
+
27
+ - **Durable Agent Run Gateway contract** — Nexus Agent Run Gateway 使用租户隔离的 durable run store 持久化 run 状态、SSE event、artifact 和 local tool result;stream 支持断线后的 durable event replay,并将 local tool request 真正桥接到 ADK function call 等待点。
28
+
29
+ - **Exact usage settlement** — Agent Runs 结算只接受 provider/ADK 透传的 `exact: true` usage,并通过 tk-dist `SettlePrecise(input/output/cacheRead/cacheCreate)` 精算;若 provider 未返回精确 usage,服务端会释放 hold 并返回 `usage_missing_released`,不会用字符数或 token 估算扣费。
30
+
31
+ ### Changed
32
+
33
+ - **401 retry policy for Agent Runs** — Agent Runs 客户端只对 GET/stream/download 等安全查询做单次 401 refresh retry;`create`、`submitLocalToolResult` 等可能产生副作用的 POST 不自动重放,避免重复创建 run 或重复计费。
34
+
35
+ ### Tests
36
+
37
+ - 新增 `test/agent-runs.test.ts`,覆盖 create 字段序列化、完整流事件解析、401 refresh 策略、error 事件结构化抛出、local tool result payload、artifact 下载文件名/content-type 解析。
38
+ - `scripts/smoke-pack.mjs` 增加 consumer 视角的 `client.agentRuns` 类型调用验证。
39
+
40
+ ## [1.0.2] — 2026-05-06
41
+
42
+ ### Fixed
43
+
44
+ - **多进程共享 `~/.acosmi/tokens.json` 撞 `HTTP 400: refresh token not found` 根治** —
45
+ `Client.ensureToken` / `Client.forceRefresh` 在 `withMu` 临界区内从不 reload 磁盘,
46
+ 导致 P1 完成 refresh token rotation 写盘后, P2 内存仍持旧 R0,下一次 refresh 必然
47
+ 撞网关 400 invalid_grant。CrabCode TUI 多窗口 / `crabclawskill` 并发等典型场景命中。
48
+
49
+ 双层修复:
50
+
51
+ - **Layer 1 — reload-before-refresh** (`src/client.ts`):新增 `Client.syncFromDisk()`
52
+ 在 `ensureToken` / `forceRefresh` 进入临界区后立刻 `store.load()`,若磁盘
53
+ `refresh_token` 与内存不同则采纳磁盘新版,重判过期 — 未过期直接 fast-return
54
+ (跳过本进程多余 refresh, 同时避免拿已 invalidated 的 R0 撞网关)。
55
+ - **Layer 2 — 跨进程临界区** (`src/store.ts`):`TokenStore` 加可选
56
+ `withLock?<T>(fn): Promise<T>` 方法(向后兼容,自定义 store 不实现自动回退到 L1
57
+ 窄窗口);`FileTokenStore` 实现 sidecar `<path>.lock` + `O_EXCL` 创建语义 + 60s
58
+ 旧锁回收 + 30s 获取超时 + 30+jitter ms backoff,真正消除残余 TOCTOU。
59
+ `Client.storeWithLock(fn)` helper 把整段 `load → check → refresh → save` 包进
60
+ 跨进程临界区。
61
+
62
+ - **`FileTokenStore.save` 改 atomic rename** (`src/store.ts`) — 写到
63
+ `<path>.tmp.<pid>.<ts>.<rand>` 后 `fs.rename` 到正式路径,POSIX 上 `rename(2)`
64
+ 同分区原子, Windows 上 `ReplaceFile`。读端永远看到完整旧/新 JSON,不会读到截断半
65
+ 文件(`Client.create.store.load` 在另一进程写入中间触发也不会 JSON parse 失败)。
66
+
67
+ ### Added
68
+
69
+ - **`fileLockDefaults` 公开常量** (`src/store.ts`) — 暴露 `acquireTimeoutMs` (30s) /
70
+ `staleMs` (60s) / `retryBaseMs` (30) / `retryJitterMs` (70),便于测试与诊断。
71
+
72
+ - **回归测试 7 项**:
73
+ - `test/auth/multi-process-refresh.test.ts` (4 项):双 Client 共享 FileTokenStore
74
+ 的 rotation 竞态核心回归 / P2 磁盘新 RT 也过期需 refresh / forceRefresh 也走
75
+ syncFromDisk / 无 rotation 时 0 影响 v1.0.1 行为。
76
+ - `test/store/file-token-store.test.ts` (3 项 + 4 子):atomic save 终态完整 /
77
+ 并发 save 不混合 / 双 store 实例临界区互斥 / 旧锁自动 break / 错误路径释放锁 /
78
+ in-process 串行化。
79
+
80
+ ### Compatibility
81
+
82
+ - **无破坏性变更**:`TokenStore.withLock` 是可选方法,1.0.x 自定义 store 实现 0 改动。
83
+ - **API 兼容**:`Client.ensureToken` / `forceRefresh` 签名不变,`FileTokenStore` 构造器
84
+ 不变。
85
+ - **行为兼容**:单进程场景与 v1.0.1 完全一致(磁盘 RT 与内存一致时 syncFromDisk 早返,
86
+ flock 单进程零竞争 ~1ms 开销)。
87
+
88
+ ### Notes
89
+
90
+ - **Go SDK 镜像修复待发**:`acosmi-sdk-go` 同根因(`client.go:316-383` `ensureToken` +
91
+ `:2143-2168` `forceRefresh` + `store.go` `FileTokenStore` 缺 flock),将在 v0.19.1
92
+ 对齐修复。
93
+ - **NFS / 跨机共享警告**:`O_EXCL` 在 NFS 上不保证原子。FileTokenStore 设计目标是本地
94
+ 文件系统(用户家目录)。真要跨机共享 token, 应实现自定义 Keychain / 数据库 store。
95
+
8
96
  ## [1.0.1] — 2026-05-01
9
97
 
98
+ > **Released**: sdk 仓 commit `0d8c0a9` + tag `v1.0.1` → release.yml CI 全自动 npm publish。已通过 audit Part 2 实拉验证 (`npm i @acosmi/sdk-ts@1.0.1` consumer 视角 smoke `tsc --noEmit` 全绿,9 处 declare module 在 dist/node/index.d.ts 行 548/571/592/601/645/654/677/720/761 全包名)。
99
+
10
100
  ### Fixed
11
101
 
12
102
  - **Layer 1 — packaging**:`tsup.config.ts` 三 entry 显式声明 `outExtension: ({ format }) => ({ js: format === 'esm' ? '.mjs' : '.cjs' })`,让产物与 `package.json.exports` 8 处 `.mjs` 引用对账。修复 1.0.0 在 bun / Node ESM 下 `Cannot find module '@acosmi/sdk-ts'`。
@@ -38,5 +128,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
38
128
  - 36/36 vitest 全绿,源码 typecheck/lint/build 0 错误
39
129
  - 翻车机制:`prepublishOnly` 仅跑源码 typecheck/vitest/build,不验证 packed product 在 consumer 视角能否解析
40
130
 
131
+ [1.1.0]: https://github.com/acosmi/sdk-ts/releases/tag/v1.1.0
132
+ [1.0.2]: https://github.com/acosmi/sdk-ts/releases/tag/v1.0.2
41
133
  [1.0.1]: https://github.com/acosmi/sdk-ts/releases/tag/v1.0.1
42
134
  [1.0.0]: https://www.npmjs.com/package/@acosmi/sdk-ts/v/1.0.0
package/README.md CHANGED
@@ -1,14 +1,15 @@
1
1
  # @acosmi/sdk-ts
2
2
 
3
- > Acosmi 模型网关 TypeScript SDK — 双格式(Anthropic + OpenAI)多端(浏览器 / Node ≥18 / Deno / Bun)
3
+ > Acosmi 模型网关 + Agent Run Gateway TypeScript SDK — 双格式(Anthropic + OpenAI)多端(浏览器 / Node ≥18 / Deno / Bun)
4
4
 
5
5
  [![npm](https://img.shields.io/npm/v/%40acosmi%2Fsdk-ts.svg)](https://www.npmjs.com/package/@acosmi/sdk-ts)
6
6
 
7
7
  ## 状态
8
8
 
9
9
  - 端口源:[acosmi-sdk-go](https://github.com/acosmi/acosmi-sdk-go) v1.0.0(与 Go SDK 联动稳定测试版)
10
- - 当前版本:1.0.1(稳定测试版,与 Go SDK 联动 1.0.x;1.0.0 deprecate due to broken packaging,详见 CHANGELOG)
11
- - 测试:36/36 vitest 全绿,typecheck/lint/build 0 错误
10
+ - 当前版本:**1.1.0**(新增 SDK-facing Agent Runs 公开 API;详见 [CHANGELOG](./CHANGELOG.md)
11
+ - 测试:56/56 vitest 全绿,typecheck/lint/build 0 错误;packed-tarball smoke (`npm run test:pack`) 在 prepublishOnly 闸内
12
+ - 包链接:[npm](https://www.npmjs.com/package/@acosmi/sdk-ts/v/1.1.0) · [tarball](https://registry.npmjs.org/@acosmi/sdk-ts/-/sdk-ts-1.1.0.tgz) · [GitHub Release](https://github.com/acosmi/sdk-ts/releases/tag/v1.1.0)
12
13
 
13
14
  ## 安装
14
15
 
@@ -77,6 +78,69 @@ for await (const ev of stream) {
77
78
 
78
79
  `chatStreamWithUsage()` 返回带 usage/error/sources 标签的 AsyncIterable,便于聚合统计(详见 `src/client.ts`)。
79
80
 
81
+ ## Agent Runs
82
+
83
+ `client.agentRuns` 是下游产品接入 Acosmi 云端智能体循环的正式 SDK 边界。CrabDesign、CrabCode、CrabClaw 等产品应通过这里创建、流式消费、取消、查询和下载智能体任务,不要直连 Nexus 内部 `/api/v4/chat/completions`。
84
+
85
+ 服务端 Agent Run Gateway 会按认证上下文做 `tenantId + userId` 隔离,run 状态、SSE event、artifact 和 local tool result 都是 durable store;任务执行会进入 Acosmi 统一 entitlement 预扣/结算/释放链路。结算只使用 provider/ADK 透传的精确 usage;如果 provider 未返回 `exact: true` usage,服务端会释放 hold,不会使用估算 token 扣费。
86
+
87
+ ```ts
88
+ import { Client, allScopes } from '@acosmi/sdk-ts';
89
+
90
+ const client = new Client({ serverURL: 'https://acosmi.com' });
91
+ await client.login('CrabDesign', allScopes());
92
+
93
+ const run = await client.agentRuns.create({
94
+ appId: 'crabdesign',
95
+ mode: 'design',
96
+ sessionId: 'session-optional',
97
+ input: 'Create a landing page mockup for a fintech dashboard',
98
+ activeSkillIds: ['brand-system'],
99
+ knowledgeBaseIds: ['kb-product'],
100
+ localContextPolicy: {
101
+ enabled: true,
102
+ readonly: true,
103
+ maxBytes: 128_000,
104
+ allowedTools: ['read_file'],
105
+ },
106
+ artifactPolicy: { enabled: true, maxFiles: 10 },
107
+ });
108
+
109
+ for await (const event of client.agentRuns.stream(run.runId, { throwOnError: false })) {
110
+ switch (event.type) {
111
+ case 'text_delta':
112
+ process.stdout.write(event.text);
113
+ break;
114
+ case 'reasoning_delta':
115
+ console.debug('[reasoning]', event.text);
116
+ break;
117
+ case 'local_tool_request': {
118
+ // SDK only defines the protocol. Product code owns local read-only tools.
119
+ const content = await readReadonlyLocalContext(event.name, event.input);
120
+ await client.agentRuns.submitLocalToolResult(run.runId, {
121
+ requestId: event.requestId,
122
+ ok: true,
123
+ content,
124
+ });
125
+ break;
126
+ }
127
+ case 'artifact': {
128
+ const file = await client.agentRuns.downloadArtifact(run.runId, event.artifact.id);
129
+ console.log('artifact', file.filename, file.contentType, file.data.byteLength);
130
+ break;
131
+ }
132
+ case 'error':
133
+ throw new Error(event.error.message);
134
+ }
135
+ }
136
+
137
+ await client.agentRuns.cancel(run.runId); // safe to call from UI cancel buttons
138
+ ```
139
+
140
+ 本地工具桥是显式 opt-in:SDK 不内置任何 CrabDesign/CrabCode 专属文件读取逻辑。`local_tool_request` 由下游处理,结果通过 `submitLocalToolResult({ requestId, ok, content | error })` 返回;拒绝、超时和取消都由下游 handler 控制。`allowedTools` 必须使用稳定的 ASCII function name,例如 `read_file`。
141
+
142
+ `stream(runId)` 支持 durable replay:断线后重新连接同一个 run,会先回放已持久化的 Agent Run SSE 事件,再继续消费运行中的事件。`usage` / `settle` 事件会暴露 `exact`、`cacheReadTokens`、`cacheCreateTokens` 等字段,便于下游展示真实结算状态。
143
+
80
144
  ## 认证
81
145
 
82
146
  ### 浏览器内 / 自动 OAuth(推荐)
@@ -115,6 +179,7 @@ const client = new Client({ serverURL: 'https://acosmi.com', tokenStore: new Fil
115
179
  | 模块 | 主要方法 |
116
180
  | ------------ | ------------------------------------------------------------------------------------ |
117
181
  | **Chat** | `chat`, `chatStream`, `chatStreamWithUsage` |
182
+ | **Agent Runs** | `agentRuns.create`, `agentRuns.stream`, `agentRuns.run`, `agentRuns.cancel`, `agentRuns.get`, `agentRuns.listArtifacts`, `agentRuns.downloadArtifact`, `agentRuns.submitLocalToolResult`, `agentRuns.runWithLocalTools` |
118
183
  | **Auth** | `login`, `logout`, `ensureToken`, `forceRefresh`, `discover`, `authorize`, `exchangeCode`, `refreshToken` |
119
184
  | **Models** | `listModels`, `listModelsWithStatus`, `getModelCapabilities`, `getQuotaSummary` |
120
185
  | **Skills** | `browseSkills`, `browseSkillsList`, `getSkillDetail`, `resolveSkill`, `installSkill`, `downloadSkill`, `uploadSkill`, `generateSkill`, `optimizeSkill`, `validateSkill` |
@@ -205,6 +270,7 @@ const view = await client.getBugReport(result.feedback_id);
205
270
  | `HTTPError` | 4xx/5xx,含 `status` / `body` / `requestID` |
206
271
  | `NetworkError` | TCP/DNS/TLS 失败 |
207
272
  | `StreamError` | SSE 流解析失败 |
273
+ | `AgentRunStreamError` | Agent Runs 流返回 `error` 事件(默认抛出;可设 `throwOnError:false` 自行消费) |
208
274
  | `BusinessError` | 网关返回 `code !== 0`,含 `code` / `bizMsg` |
209
275
  | `RateLimitError` | 429(含 `retryAfter`) |
210
276
  | `OrderTerminalError` | `waitForPayment` 终态失败 |
@@ -248,7 +314,9 @@ npm run build
248
314
 
249
315
  | 版本 | 状态 | 概要 |
250
316
  | --- | --- | --- |
251
- | 1.0.1 | 当前稳定版 | 修复 1.0.0 双层 broken packaging:tsup 输出 `.mjs+.cjs` 与 exports 字段对齐;9 处 `declare module` 绑包名 `@acosmi/sdk-ts` 让 d.ts augmentation 在 consumer 视角合并;prepublishOnly 加 packed-tarball 烟测拦截"源码过 / 打包后 broken"。 |
317
+ | 1.1.0 | 当前稳定版 | 新增 SDK-facing `agentRuns` 网关客户端,覆盖 create/stream/cancel/get/artifacts/local-tool-result,并提供本地只读工具桥协议。 |
318
+ | 1.0.2 | 稳定版 | 修复多进程共享 token refresh rotation 竞态。 |
319
+ | 1.0.1 | 历史稳定版 | 修复 1.0.0 双层 broken packaging:tsup 输出 `.mjs+.cjs` 与 exports 字段对齐;9 处 `declare module` 绑包名 `@acosmi/sdk-ts` 让 d.ts augmentation 在 consumer 视角合并;prepublishOnly 加 packed-tarball 烟测拦截"源码过 / 打包后 broken"。 |
252
320
  | 1.0.0 | **deprecated** | 双层 broken:(1) `package.json.exports` 8 处 `.mjs` 引用与 tsup 默认 `.js+.cjs` 错位 → bun/Node ESM `Cannot find module`;(2) 9 处 `declare module` 用相对路径,consumer 视角断链 → 50+ 方法 TS2339。`npm install @acosmi/sdk-ts` 自动跳到 1.0.1。 |
253
321
 
254
322
  ## License