@acosmi/sdk-ts 2.19.3 → 2.19.5
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 +22 -0
- package/README.md +27 -2
- package/dist/browser/index.mjs +537 -372
- package/dist/browser/index.mjs.map +1 -1
- package/dist/index.mjs +537 -372
- package/dist/index.mjs.map +1 -1
- package/dist/node/adapters/anthropic.cjs +0 -7
- package/dist/node/adapters/anthropic.cjs.map +1 -1
- package/dist/node/adapters/anthropic.d.cts +1 -1
- package/dist/node/adapters/anthropic.d.ts +1 -1
- package/dist/node/adapters/anthropic.mjs +0 -7
- package/dist/node/adapters/anthropic.mjs.map +1 -1
- package/dist/node/adapters/openai.cjs +114 -24
- package/dist/node/adapters/openai.cjs.map +1 -1
- package/dist/node/adapters/openai.d.cts +2 -2
- package/dist/node/adapters/openai.d.ts +2 -2
- package/dist/node/adapters/openai.mjs +114 -24
- package/dist/node/adapters/openai.mjs.map +1 -1
- package/dist/node/{index-CqxPqbsl.d.cts → format-DWA12pVT.d.cts} +35 -27
- package/dist/node/{index-CqxPqbsl.d.ts → format-DWA12pVT.d.ts} +35 -27
- package/dist/node/index.cjs +538 -371
- package/dist/node/index.cjs.map +1 -1
- package/dist/node/index.d.cts +157 -16
- package/dist/node/index.d.ts +157 -16
- package/dist/node/index.mjs +537 -372
- package/dist/node/index.mjs.map +1 -1
- package/dist/node/{openai-mkTSir6E.d.ts → openai-Cuu_DPxM.d.cts} +34 -4
- package/dist/node/{openai-Ce85qh14.d.cts → openai-DsbJUOOW.d.ts} +34 -4
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,25 @@
|
|
|
1
|
+
## 2.19.5
|
|
2
|
+
|
|
3
|
+
- `StreamError` 新增 `serverRetryable?: boolean` 与 `retryAfterSecs?: number`(追加字段,解析点仍只有 `parseStreamError` 一个,`/chat` 线 `event: failed` 与 `/anthropic` 线 `event: error` 同表)。`serverRetryable` 是帧上 `retryable` 的**原值**,不与 `requestDisposition` 合取:既有的 `retryable` 是两者的合取,而流内错误帧并不总下发 `requestDisposition`,于是「服务端明说可以重试」会被合取成 `false` —— 两端各自正确,合起来恒否,消费方分不出「服务端明说不可重试」(一票否决)与「服务端没说」(该回落到本地分类表)。`retryAfterSecs` 只收**有限非负数**,`null` / 负数 / NaN / Infinity / 非数字一律 `undefined`:宁可不给,绝不编造等待时间。两个字段缺席即 `undefined`,绝不补默认值;`retryable` 的合取语义一字未动。
|
|
4
|
+
- 新增导出 `isStreamError(err): err is StreamError` —— `name === 'StreamError'` ∧ `errorCode` 是字符串的**结构**判据。消费方在 worker / `vm` 上下文里,或 SDK 被打进第二份 bundle 时,`instanceof StreamError` 会静默为 `false`,症状与「这段分类代码压根没写」逐字相同。`HTTPError` 也带可选 `errorCode`,靠 `name` 分开。
|
|
5
|
+
- 新增 `ChatStreamOptions`,作为 `chatStream` / `chatMessagesStream` 的**末位**可选实参。`requestTimeoutMs` 声明这一次流的总时长安全网(从开始迭代算起,与调用方 `signal` 先到者生效,`break` 提前退出也会释放计时器);`onResponseHeaders` 在拿到响应之后、判断 `resp.ok` **之前**交出整份响应头,用于读网关随头下发的流控提示(如 `X-Acosmi-Stream-Keepalive`,秒)—— 那个数只在头里,等流内事件时已经晚了,而最需要它的恰恰是一个事件都不来的流。与既有两个流式回调同构:旁路信号,回调抛错被吞掉且不中断流;401 刷新重试后由真正开始流的那次响应触发。
|
|
6
|
+
- `FileTokenStore.withLock` 不再把 Windows 的 delete-pending 当成致命错误。NT 在 `unlink` 之后、最后一个句柄关闭之前把文件置为 `STATUS_DELETE_PENDING`,这期间 `open(lockPath, 'wx')` 抛的是 `EPERM`(实测原文 `EPERM: operation not permitted, open '...tokens.json.lock'`;`EBUSY` 是同机理的第二种表现)而**不是** `EEXIST`;判据只认 `EEXIST`,于是同一把锁上的一次正常交接被 `throw e` 报成永久失败 —— 同进程 5 个并发 `withLock` 就能稳定复现,受害者是 refresh token rotation 那段临界区。现在「锁被占」的判据是 `EEXIST` ∪(`win32` 且 code ∈ {`EPERM`, `EBUSY`}),命中后者时**跳过 stale 检测**直接退避重试(delete-pending 的文件 `stat` 要么失败要么给出前一持有者的旧 mtime,两种都会把「正在消失的锁」误判成「崩溃残留」,而 `stat` 失败那条腿是 `continue`、不过 `acquireTimeoutMs` 检查,会退化成不退出的忙循环),重试仍受 `acquireTimeoutMs` 上界约束。`EACCES` **不在**这个集合里:它在 Windows 上更常见的产地是锁目录只读 / 被策略拒绝这类真配置错误,收进来等于把「立刻报 `EACCES`」拖成「30 秒后报 acquire timeout」。**非 Windows 平台控制流逐字节不变** —— POSIX 的 `EPERM` 同样是真权限错误,一律不收。
|
|
7
|
+
- **零破坏性变更**:前三项全部是追加式(新可选字段 / 新导出 / 末位新可选实参),2.19.4 的调用方式一字不改仍然成立。`requestTimeoutMs` 缺席或非有限正数时**不武装任何计时器**,与 2.19.4 逐字一致 —— 流式链路历来只受调用方 `signal` 约束(`CHAT_REQUEST_TIMEOUT_MS` 的注释此前误把两个流式方法也算了进去,本版同步勘误:那两条路径从来没有武装过它)。给历来无上限的流凭空补一道默认墙钟,第一个被杀的就是健康的长任务。
|
|
8
|
+
|
|
9
|
+
## 2.19.4
|
|
10
|
+
|
|
11
|
+
- Stop dropping the OpenAI-line streaming usage tail frame. With `stream_options.include_usage: true` the upstream sends content frames, then the `finish_reason` frame, then `{"choices":[],"usage":{...}}`, then `data: [DONE]`. The stream converter discarded every frame without `choices`, and it had already emitted `message_delta` (without usage) and `message_stop` on the `finish_reason` frame, so no OpenAI-line turn streamed through `chatMessagesStream` ever carried usage and consumers counted it as zero. The converter now records the usage object of any frame, with or without `choices` (later frames override earlier ones), and puts it in the `message_delta` that immediately precedes the single `message_stop`.
|
|
12
|
+
- Content blocks are still closed on the `finish_reason` frame, but when no usage has been seen yet, `message_delta` and `message_stop` wait for the first of: a frame carrying usage, `data: [DONE]`, or the end of the body. The new `OpenAIStreamConverter.flush()` emits that deferred close at end of stream; `chatMessagesStream` calls it when the body ends without `[DONE]`, and code that drives the converter directly should do the same. `flush()` never fabricates a close for a stream that never delivered `finish_reason`, and no path emits `message_stop` twice.
|
|
13
|
+
- The streaming usage mapping is exactly the non-streaming one: `prompt_tokens` to `input_tokens` and `completion_tokens` to `output_tokens`. Cached-token and reasoning-token details are neither mapped nor netted out; normalizing usage semantics belongs to the gateway. A count that is missing or not a finite number is omitted rather than written as 0. `OpenAIStreamChunk.usage` is now typed as `OpenAIStreamUsage | null`, with optional counts and the detail objects.
|
|
14
|
+
- Fix the `@acosmi/sdk-ts/adapters/openai` and `@acosmi/sdk-ts/adapters/anthropic` subpath entries, which threw as soon as they were loaded: `OpenAIAdapter is not a constructor` / `AnthropicAdapter is not a constructor` under Node ESM and CJS, `undefined is not a constructor` under Bun. Both adapter implementations took `ProviderFormat` from the adapters barrel, and the barrel constructs both adapters at module top level. With a subpath as the entry, the adapter module was evaluated first and pulled in the barrel, whose top-level `new` then ran before the adapter class had been defined. The root entry `@acosmi/sdk-ts` loads the barrel first and was not affected; the published 2.19.3 package fails the same way. `ProviderFormat` and `ProviderAdapter` now live in a leaf module that imports no adapter implementation, and every entry exports the same names as before. `test:pack` now also loads every `exports` subpath that declares an `import` or `require` condition, each in a fresh Node process, with `import()` and `require()` respectively; before, it only type-checked from the consumer side and never loaded any entry at runtime.
|
|
15
|
+
- Tolerate a stream `choice` that carries no `delta`. Some compatible implementations send `{"choices":[{"index":0}]}` to say "channel 0 is still there" on a frame with no increment; the converter read `choice.delta.reasoning_content` straight away, and the resulting `TypeError` tore through the whole `for-await` chain and failed the turn. Such a choice is now treated as an empty delta: zero events, no error, the stream keeps running, and `finish_reason` / `usage` on the same frame are still handled. `OpenAIStreamChoice.delta` is now optional, matching what the wire actually sends.
|
|
16
|
+
- Stop throwing on a non-streaming response that carries no `choices`. Both `parseResponse` and `parseOpenAIResponseToAnthropic` read `oai.choices.length` without a guard, so the whole response died - including the `id`, `model` and `usage` that had in fact arrived. Missing `choices` is now treated as an empty array on both paths: no content blocks, no error, `stop_reason` left empty rather than invented, and every other field converted as before. `OpenAIChatResponse.choices` is now optional. This is the fourth known site of one recurring mistake - a wire type declaring a key required that the wire genuinely omits - after `OpenAIStreamChunk.choices`, `OpenAIStreamChoice.delta` and `OpenAIChatResponse.usage`.
|
|
17
|
+
- Stop throwing on a non-streaming response that carries no `usage`. Both `parseResponse` and `parseOpenAIResponseToAnthropic` read `oai.usage.prompt_tokens` without a guard, so a response whose content, `finish_reason` and `tool_calls` were all perfectly usable was killed outright just because the upstream does not meter. Missing usage now counts as 0 on both paths and everything else converts as before; `OpenAIChatResponse.usage` is now optional.
|
|
18
|
+
- Fix block indexes when text arrives before `reasoning_content`. The thinking branch neither closed an open text block nor advanced the block index, so both `content_block_start` events landed on index 0 while the `textStarted / else if thinking` close only ever reached the text block - the thinking block was never closed. The thinking branch now mirrors the text and `tool_calls` branches and closes an open text block first, so at most one non-tool block is open at a time. This is the other half of the invariant the `thinking` then `tool_calls` fix established.
|
|
19
|
+
- Add `WindowLimitStatus.fiveHourSoftContinuing?: boolean`, the gateway's server-synthesized flag meaning the window is used up but the user's "continue" switch is letting requests through. New gateways always send it and older gateways omit it, so absence does not mean `false`. Type only.
|
|
20
|
+
- Add `WindowLimitStatus.inflightHeldCredits?: number | null`, the micro-Credits in that window currently held by in-progress requests and not yet settled. `usedCredits` counts settled usage only, while admission counts settled usage plus in-flight holds; this field is the difference. Absent or `null` means unknown (an older gateway, or the lookup failed), not 0; `0` means nothing is in flight. Type only.
|
|
21
|
+
- Add `QuotaSummary.subscriptionPool?: QuotaSummarySubscriptionPool` (`quota`, `used`, `remaining`, optional `expiresAt`, `unit`), derived by the gateway from alive COMMERCIAL buckets. Amounts are in micro-Credits (divide by 1000 for Credits). The field is absent on older gateways and when there is no alive paid bucket; do not read absence as 0. Type only.
|
|
22
|
+
|
|
1
23
|
## 2.19.3
|
|
2
24
|
|
|
3
25
|
- Surface gateway stream failures on the OpenAI line instead of crashing on them. The gateway emits its structured error frame with `event: failed`, but the OpenAI branch of `chatMessagesStream` captured the SSE event name and never read it, so the error frame reached the OpenAI chunk converter and died on `chunk.choices.length` - replacing the real upstream cause with an unrelated TypeError. That branch now routes `failed` and `error` frames through the existing `parseStreamError`, exactly as `chatStreamWithUsage` already did.
|
package/README.md
CHANGED
|
@@ -358,6 +358,30 @@ const stream = client.chatMessagesStream(
|
|
|
358
358
|
|
|
359
359
|
网关没下发(旧版本 / 非托管路径)时回调**一次都不触发**,SDK 绝不合成占位值;流照常出。浏览器端消费方还需网关 CORS `Access-Control-Expose-Headers` 放行该头(网关 v2026-09-01 起已放行)。
|
|
360
360
|
|
|
361
|
+
### 流式可选项 `ChatStreamOptions`(v2.19.5+)
|
|
362
|
+
|
|
363
|
+
`chatStream` / `chatMessagesStream` 的**末位**可选实参。两项都是追加语义,不传时行为与既往逐字一致。
|
|
364
|
+
|
|
365
|
+
```ts
|
|
366
|
+
const stream = client.chatMessagesStream(
|
|
367
|
+
modelId,
|
|
368
|
+
{ messages, max_tokens: 4096 },
|
|
369
|
+
abortSignal,
|
|
370
|
+
onUpstreamActivity,
|
|
371
|
+
onGatewayRequestID,
|
|
372
|
+
{
|
|
373
|
+
requestTimeoutMs: 60 * 60 * 1000, // ← 安全网,不是主判据
|
|
374
|
+
onResponseHeaders: h => {
|
|
375
|
+
const keepalive = h.get('X-Acosmi-Stream-Keepalive'); // 秒;缺失为 null
|
|
376
|
+
if (keepalive) armIdleWatchdog(Number(keepalive));
|
|
377
|
+
},
|
|
378
|
+
},
|
|
379
|
+
);
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
- **`requestTimeoutMs`** —— 这一次流的**总时长**预算,从开始迭代算到流结束;与 `signal` 先到者生效,调用方 `break` 提前退出也会释放计时器。**缺席或非有限正数 = 不武装任何计时器**(流式链路历来只受调用方 `signal` 约束;`CHAT_REQUEST_TIMEOUT_MS` 只作用于非流式的 `chat` / `chatMessages`)。杀一条流的判据应当是**失去活性证据**(见上面的 `onUpstreamActivity`)而不是「活得太久」—— 这一条的职责只是给「看门狗本身坏掉」兜底,因此应当显著长于活性预算。
|
|
383
|
+
- **`onResponseHeaders`** —— 在拿到响应之后、判断 `resp.ok` **之前**触发,因此 HTTP 错误响应上同样触发;401 刷新重试后由真正开始流的那次响应触发。网关的流控提示只存在于响应头里,等流内事件已经晚了,而最需要它的恰恰是一个事件都不来的流。与其它两个流式回调同构:旁路信号,回调抛错被吞掉且不中断流。
|
|
384
|
+
|
|
361
385
|
### Sources SSE 四态分类(v2.15+)
|
|
362
386
|
|
|
363
387
|
`sources: []` 是检索成功但没有可展示引用的合法零结果,不是传输损坏。需要精确区分状态的新 consumer 使用加性接口 `classifySourcesEvent()`:
|
|
@@ -878,7 +902,7 @@ const view = await client.getBugReport(result.feedback_id);
|
|
|
878
902
|
| -------------------- | ---------------------------------------------------------------------- |
|
|
879
903
|
| `HTTPError` | 4xx/5xx,含 `statusCode` / `body` / `type` / `retryAfter` |
|
|
880
904
|
| `NetworkError` | TCP/DNS/TLS 失败;含 `isTimeout()` / `isEOF()` 便捷判定 |
|
|
881
|
-
| `StreamError` | gateway `managed_model_stream_failed` 事件解析(含 `code` / `stage` / `retryable`) |
|
|
905
|
+
| `StreamError` | gateway `managed_model_stream_failed` 事件解析(含 `code` / `stage` / `retryable`;v2.19.5 起另带 `serverRetryable` = 帧上 `retryable` 原值、`retryAfterSecs` = 建议等待秒数,缺席均为 `undefined`。跨 realm 用 `isStreamError()` 判,别用 `instanceof`) |
|
|
882
906
|
| `AgentRunStreamError` | Agent Runs 流返回 `error` 事件(默认抛出;可设 `throwOnError:false` 自行消费) |
|
|
883
907
|
| `BusinessError` | 网关返回 `code !== 0`,含 `code` (number) / `message` (字符串) |
|
|
884
908
|
| `RateLimitError` | 429(含 `retryAfter`) |
|
|
@@ -1170,7 +1194,8 @@ npm run docs # 经 TypeDoc 生成 API 参考到 docs/api/
|
|
|
1170
1194
|
|
|
1171
1195
|
| 版本 | 状态 | 概要 |
|
|
1172
1196
|
| --- | --- | --- |
|
|
1173
|
-
| 2.19.
|
|
1197
|
+
| 2.19.5 | **当前版本** | **流内重试提示透出 + 流式可选项(2026-09-19)**。`StreamError` 加性新增 `serverRetryable`(帧上 `retryable` **原值**,不与 `requestDisposition` 合取 —— 合取会把「服务端明说可重试」与「服务端没说」压成同一个 `false`)与 `retryAfterSecs`(只收有限非负数,非法值一律 `undefined`);加性导出结构判据 `isStreamError()`(worker / `vm` / 二次打包下 `instanceof` 静默为假);`chatStream` / `chatMessagesStream` 新增**末位**可选实参 `ChatStreamOptions`(`requestTimeoutMs` 总时长安全网、`onResponseHeaders` 在 `resp.ok` 判断前交出响应头,用于读 `X-Acosmi-Stream-Keepalive` 等流控提示)。全部追加式;不传 `requestTimeoutMs` 时**不武装任何计时器**,流式链路行为与 2.19.4 逐字一致。 |
|
|
1198
|
+
| 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
1199
|
| 2.18.0 | 稳定版 | **`ManagedModel.thinking_levels` 类型面对齐(2026-08-29)**。加性新增可选字段 `thinking_levels?: string[]`:网关下发的升序思考档位 id 列表(`'off'`/`'high'`/`'max'` 的子集,按「admin 声明 ∩ wire 层真投递」派生)。`listModels` 原样透传,无归一化、无新方法、公开签名零变化。`[]` = 该模型无思考档;`undefined` = 旧网关未播报,调用方按"未知"处理,严禁按模型名推档。 |
|
|
1175
1200
|
| 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 签名零变化。 |
|
|
1176
1201
|
| 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 行)能抵达消费方的空闲看门狗。不传回调时行为逐字节不变。 |
|