@acosmi/sdk-ts 2.18.0 → 2.19.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,34 @@ 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
+ ## [2.19.1] - 2026-09-08 — 认证生命周期与上游错误归属修复
9
+
10
+ - 新增显式 `credentialMode: 'versioned'`,通过调用方提供的 `VersionedCredentialStore` 原子读写凭据。SDK 统一负责登录、身份确认、刷新和本地退出;多个进程同时刷新时使用同一份持久状态,刷新结果不确定时要求重新登录。
11
+ - 新增 `credentialRequestOwner`,将请求绑定到构造时的存储实例、登录会话和已验证身份。同一身份正常刷新继续可用,已退出或被替换的客户端不能借用另一个账号的凭据。WebSocket 票据、连接与晚到事件遵守同一归属。
12
+ - 新增 `logoutCredential(signal?, expected?)` 本地提交结果与独立远端吊销结果。过期的退出操作不能清除新登录;调用方可订阅不含凭据的状态通知。
13
+ - 新增显式 `credentialMode: 'external'` 与 `accessTokenProvider`,外部凭据不进入 SDK 刷新存储。非 legacy 模式将凭据限制在配置的同一源。
14
+ - HTTP 401 只有完整错误合同明确归属用户访问凭据、且请求未被接收时,才触发一次刷新和重试。提供商拒绝、已接收请求及缺失合同的错误不再被误当成登录过期。OpenAI 流式错误作为终态保留。
15
+
16
+ 默认仍为 `legacy` 模式,现有存储接口继续可用。上述 401 判据适用于所有模式;旧网关未提供完整错误合同时,调用方会收到原错误而不会自动重放。启用 versioned 模式前,上游 OAuth metadata 必须声明 `crabcode_auth_contract_version=2` 与 `gateway_error_contract_version=1`;不支持时明确拒绝登录,不能静默降级。
17
+
18
+ ## [2.19.0] - 2026-09-01 — 网关消费请求 ID 透出(跨系统关联)
19
+
20
+ **客户端的失败与上游的成功之间此前没有任何共同标识符。** 2026-08-31 事故里,GUI 上每次托管 WebSearch 都失败,而同一时刻上游 6 次搜索全部成功、6 条计费行已落库;定位花掉整场审计,因为只能靠时间戳与模型名人工对齐。网关现在把 `consumeRequestID`(即 `managed_model_usage_logs.request_id`,能 join 到计费行的那个键)放进 `X-Acosmi-Request-Id` 响应头,本版把它透出给消费方。全部改动 additive,旧调用点零改动。
21
+
22
+ ### Added
23
+
24
+ - **`GATEWAY_REQUEST_ID_HEADER`** —— 头名常量(`'X-Acosmi-Request-Id'`)。**不是**网关的传输层 `X-Request-ID`:那一个独立生成、从不写进任何计费表,两者永不相等,混用的后果是恒空 join(不报错,只是下次事故照样查不动)。
25
+ - **`GatewayRequestIDCallback`** 与四个方法的可选回调形参 —— `chatMessagesStream` / `chatStream` 第 5 形参、`chatMessages` / `chat` 第 4 形参。与既有 `onUpstreamActivity` 完全同构:旁路信号、至多触发一次、消费方回调抛错被吞掉且不中断主链路。
26
+ - 流式路径在**首字节之前**触发,因此覆盖「流中段中断」「上游 200 但零事件」「HTTP 错误」全部形态 —— 而流内事件在「事件根本没来」的场景里恰恰不存在,那正是最需要诊断的那一种。HTTP 错误响应上同样触发(错误体未必带这个 ID)。
27
+
28
+ ### 兼容性
29
+
30
+ 旧网关 + 新 SDK:回调**一次都不触发**,流照常出(fail-open)。缺失或空白的头一律按"没下发"处理,**绝不合成占位值**。
31
+
32
+ ### 回归闸门
33
+
34
+ - `test/gateway-request-id.test.ts` —— 11 例。正向对照两条:网关不下发时回调零触发(防"没头就编一个")、ID 必须在第一个事件之前到手(防退化成从流内事件取值)。另含空白头、回调抛错、HTTP 错误、不传回调时逐字兼容,以及头名与网关侧常量逐字一致、且不等于 `x-request-id` 的契约断言。
35
+
8
36
  ## [2.18.0] - 2026-08-29 — `ManagedModel.thinking_levels` 类型面对齐
9
37
 
10
38
  网关 `ManagedModelPublicResponse` 新增 `thinking_levels: []string`(升序档位 id,按「admin 声明 ∩ wire 层真投递」读时派生)。`listModels` / `listModelsWithStatus` 对 `ManagedModel` 本就原样透传(唯一归一化是 `input_modalities` → `inputModalities`),所以本版**只补类型面与文档**,零运行时改动。
package/README.md CHANGED
@@ -7,7 +7,8 @@
7
7
  ## 状态
8
8
 
9
9
  - **主实现 / 事实标准**:本 TS SDK 现为 Acosmi SDK 的主力实现。Go SDK [acosmi-sdk-go](https://github.com/acosmi/acosmi-sdk-go) 已暂停维护,待 TS 稳定后再从 TS 反向翻译补齐。
10
- - **当前版本:`2.18.0`**(`ManagedModel.thinking_levels` 类型面对齐,2026-08-29)。
10
+ - **当前版本:`2.19.1`**(认证生命周期与上游错误归属修复,2026-09-08)。
11
+ - **`v2.19.1`**:可选 versioned 凭据存储、原子刷新与本地退出,支持将 HTTP / WebSocket 绑定到固定登录身份;外部凭据使用独立的 external 模式。所有模式仅在完整网关错误合同证明用户访问凭据失效且请求未接收时自动刷新重试,普通 401 原样返回。versioned 模式要求上游 metadata 的认证合同为 2、网关错误合同为 1;默认 legacy 存储接口继续可用。详见 [CHANGELOG](./CHANGELOG.md)。
11
12
  - **`v2.18.0`(加性类型发布)**:`ManagedModel` 新增可选字段 `thinking_levels?: string[]` —— 网关下发的**升序思考档位 id 列表**(`ThinkingOff`/`ThinkingHigh`/`ThinkingMax` 的子集)。`listModels` 对它原样透传,公开签名向后兼容、零行为变化;`[]` = 该模型无思考档,`undefined` = 旧网关未播报(按"未知"处理,严禁自行推档)。详见〈思考档位 `thinking_levels`〉。
12
13
  - **`v2.17.0`(安全修复发布)**:桌面 loopback `authorize()` 对 `/callback` 的**一切形态**(成功 / OAuth error / 畸形)先行校验 CSRF `state`,且必须"恰好一个"并严格等值 —— 缺失、重复(含重复的正确值)、错值一律以稳定错误码 `state_mismatch` 拒绝本次登录;此前携带 OAuth error 的回调绕过 state 直接结算 `auth_denied`,本机任意进程零知识即可打断/塑形等待中的登录,且 `?code=…&state=<正确值>&state=x` 这类重复参数可蒙混通过。`finally` 补 `closeIdleConnections()`,每条终止路径以端口完全关闭收尾。公开 API 签名零变化;回归闸门 `test/auth/desktop-loopback-state.test.ts` 十路终止矩阵。
13
14
  - **`v2.15.0`(加性兼容发布)**:新增 `classifySourcesEvent()`、`SourcesEventParseResult` 与稳定 issue code,把 `not_sources`、合法 `empty_sources`、非空 `sources`、`malformed_sources` 明确分开。既有 `parseSourcesEvent()` 的代码路径、宽松判定、`null` 条件和返回对象形状保持原样;现有 consumer 无需改动,新 consumer 才选择严格 API。
@@ -331,6 +332,32 @@ const stream = client.chatMessagesStream(
331
332
 
332
333
  语义是「链路刚刚有动静」,不是「来了一个事件」;对每条 SSE 行触发一次,早于任何过滤与解析。回调抛出的错误会被吞掉且不中断流(旁路信号不该有能力杀死主链路)。不传时行为逐字节不变。
333
334
 
335
+ ### 网关请求 ID 回调 `onGatewayRequestID`(v2.19+)
336
+
337
+ 如果你要把**用户可见的失败**关联回上游那一次网关调用与它产生的计费行,接这个回调。
338
+
339
+ 网关在响应头 `X-Acosmi-Request-Id`(常量 `GATEWAY_REQUEST_ID_HEADER`)里下发 `consumeRequestID` —— 它就是 `managed_model_usage_logs.request_id` 的值,是能 join 到计费行的那个键。
340
+
341
+ > ⚠️ 它**不是**网关的传输层 `X-Request-ID`。那一个独立生成、从不写进任何计费表,两者永不相等。用错的后果是恒空 join —— 不报错,只是下次事故照样查不动。
342
+
343
+ ```ts
344
+ let gatewayRequestId: string | undefined;
345
+
346
+ const stream = client.chatMessagesStream(
347
+ modelId,
348
+ { messages, max_tokens: 4096 },
349
+ abortSignal,
350
+ onUpstreamActivity,
351
+ id => { gatewayRequestId = id; }, // ← 响应头到达时触发,早于第一个事件
352
+ );
353
+ ```
354
+
355
+ 回调在响应头到达后触发**至多一次**,且在第一个 SSE 事件之前 —— 因此它覆盖「流中段被掐断」「上游 200 但零事件」「HTTP 错误」全部形态;流内事件在「事件根本没来」的场景里恰恰不存在,而那正是最需要诊断的那一种。
356
+
357
+ 同步路径 `chatMessages` / `chat` 的第 4 个实参是同一个回调。
358
+
359
+ 网关没下发(旧版本 / 非托管路径)时回调**一次都不触发**,SDK 绝不合成占位值;流照常出。浏览器端消费方还需网关 CORS `Access-Control-Expose-Headers` 放行该头(网关 v2026-09-01 起已放行)。
360
+
334
361
  ### Sources SSE 四态分类(v2.15+)
335
362
 
336
363
  `sources: []` 是检索成功但没有可展示引用的合法零结果,不是传输损坏。需要精确区分状态的新 consumer 使用加性接口 `classifySourcesEvent()`:
@@ -1143,7 +1170,8 @@ npm run docs # 经 TypeDoc 生成 API 参考到 docs/api/
1143
1170
 
1144
1171
  | 版本 | 状态 | 概要 |
1145
1172
  | --- | --- | --- |
1146
- | 2.18.0 | **当前版本** | **`ManagedModel.thinking_levels` 类型面对齐(2026-08-29)**。加性新增可选字段 `thinking_levels?: string[]`:网关下发的升序思考档位 id 列表(`'off'`/`'high'`/`'max'` 的子集,按「admin 声明 wire 层真投递」派生)。`listModels` 原样透传,无归一化、无新方法、公开签名零变化。`[]` = 该模型无思考档;`undefined` = 旧网关未播报,调用方按"未知"处理,严禁按模型名推档。 |
1173
+ | 2.19.0 | **当前版本** | **网关消费请求 ID 透出(2026-09-01)**。网关把 `consumeRequestID`(即 `managed_model_usage_logs.request_id`)放进 `X-Acosmi-Request-Id` 响应头;加性导出 `GATEWAY_REQUEST_ID_HEADER` `GatewayRequestIDCallback`,`chatMessagesStream` / `chatStream` 新增第 5 个可选实参、`chatMessages` / `chat` 新增第 4 个可选实参。响应头在首字节之前到达,因此覆盖流中段中断 / 零事件 / HTTP 错误全部形态。旧网关下回调零触发、绝不合成占位值,流照常出。不传回调时行为逐字节不变。 |
1174
+ | 2.18.0 | 稳定版 | **`ManagedModel.thinking_levels` 类型面对齐(2026-08-29)**。加性新增可选字段 `thinking_levels?: string[]`:网关下发的升序思考档位 id 列表(`'off'`/`'high'`/`'max'` 的子集,按「admin 声明 ∩ wire 层真投递」派生)。`listModels` 原样透传,无归一化、无新方法、公开签名零变化。`[]` = 该模型无思考档;`undefined` = 旧网关未播报,调用方按"未知"处理,严禁按模型名推档。 |
1147
1175
  | 2.17.0 | 稳定版 | **桌面 loopback OAuth state 全路径闸 + 端口确定性关闭(2026-08-15)**。`/callback` 一切形态(成功 / OAuth error / 畸形)先验 `state` 且必须恰好一个并严格等值;缺失 / 重复(含重复的正确值)/ 错值一律 `state_mismatch` 拒绝且不再被误结算为 `auth_denied`;错误信息只描述形态,不回显 code / state / token / 完整 callback query。`finally` 补 `closeIdleConnections()`(Node 18 上 `close()` 不关残留 idle keep-alive)。用户真拒绝(OAuth error + 正确 state)语义保留为 `auth_denied`。公开 API 签名零变化。 |
1148
1176
  | 2.16.0 | 稳定版 | **chat 超时预算真正下传 + 流式活性回调(2026-08-06)**。`chat` / `chatMessagesAnthropic` / `chatMessagesOpenAI` / `generateVideo` 此前漏传 `doJSONFullRaw` 的第 5 实参,内层 **30 秒**默认值恒先于外层 11 分钟预算触发 —— v1.6.0 那次"调整为 11min"一天都没生效过(生产实证:单日 29 条 latency≈30 000 ms 的 499,横跨 4 厂商 5 模型,受害最重的是默认主循环模型)。加性导出 `CHAT_REQUEST_TIMEOUT_MS`;`chatStream` / `chatMessagesStream` 新增第 4 个可选实参 `onUpstreamActivity`,让被 `isSSECommentLine` 吞掉的保活注释行(以及 OpenAI 格式下零事件的 data 行)能抵达消费方的空闲看门狗。不传回调时行为逐字节不变。 |
1149
1177
  | 2.15.0 | 稳定版 | **sources 四态分类与零结果契约(2026-08-02)**。加性新增 `classifySourcesEvent`、`SourcesEventParseResult` 与稳定 issue code,区分非 sources、合法空结果、有效结果和结构损坏;未知额外字段继续兼容。既有 `parseSourcesEvent` 的返回形状、宽松解析和 `null` 条件保持不变。 |