@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
|
@@ -14,7 +14,6 @@ var BusinessError = class extends Error {
|
|
|
14
14
|
this.code = code;
|
|
15
15
|
}
|
|
16
16
|
};
|
|
17
|
-
new OpenAIAdapter();
|
|
18
17
|
|
|
19
18
|
// src/models/adapters/openai.ts
|
|
20
19
|
var OpenAIAdapter = class {
|
|
@@ -182,16 +181,19 @@ function convertOpenAIToChatResponse(oai) {
|
|
|
182
181
|
role: "assistant",
|
|
183
182
|
content: [],
|
|
184
183
|
stop_reason: "",
|
|
184
|
+
// [W-SDK-OPENAI-PARITY] 缺 usage 的响应按 0 计数, 而不是让 `oai.usage.prompt_tokens`
|
|
185
|
+
// 抛 TypeError 把整条响应打死 —— 上游不计量是常见形态, 正文照样是有效的。
|
|
186
|
+
// 与 Go 侧同名函数逐字同语义 (那里 Usage 是值类型, 缺席即零值)。
|
|
185
187
|
usage: {
|
|
186
|
-
input_tokens: oai.usage
|
|
187
|
-
output_tokens: oai.usage
|
|
188
|
+
input_tokens: oai.usage?.prompt_tokens ?? 0,
|
|
189
|
+
output_tokens: oai.usage?.completion_tokens ?? 0
|
|
188
190
|
},
|
|
189
191
|
tokenRemaining: -1,
|
|
190
192
|
callRemaining: -1,
|
|
191
193
|
modelTokenRemaining: -1,
|
|
192
194
|
modelTokenRemainingETU: -1
|
|
193
195
|
};
|
|
194
|
-
if (oai.choices.length > 0) {
|
|
196
|
+
if (Array.isArray(oai.choices) && oai.choices.length > 0) {
|
|
195
197
|
const choice = oai.choices[0];
|
|
196
198
|
switch (choice.finish_reason) {
|
|
197
199
|
case "stop":
|
|
@@ -265,12 +267,14 @@ function parseOpenAIResponseToAnthropic(raw) {
|
|
|
265
267
|
content: [],
|
|
266
268
|
model: oaiResp.model,
|
|
267
269
|
stop_reason: "",
|
|
270
|
+
// [W-SDK-OPENAI-PARITY] 同 convertOpenAIToChatResponse: 缺 usage 按 0 计数, 不抛。
|
|
271
|
+
// 两处都要改 —— 只改一处等于「非流式路径修好了一半」, 而两条路径是同一族上游响应。
|
|
268
272
|
usage: {
|
|
269
|
-
input_tokens: oaiResp.usage
|
|
270
|
-
output_tokens: oaiResp.usage
|
|
273
|
+
input_tokens: oaiResp.usage?.prompt_tokens ?? 0,
|
|
274
|
+
output_tokens: oaiResp.usage?.completion_tokens ?? 0
|
|
271
275
|
}
|
|
272
276
|
};
|
|
273
|
-
if (oaiResp.choices.length > 0) {
|
|
277
|
+
if (Array.isArray(oaiResp.choices) && oaiResp.choices.length > 0) {
|
|
274
278
|
const choice = oaiResp.choices[0];
|
|
275
279
|
switch (choice.finish_reason) {
|
|
276
280
|
case "stop":
|
|
@@ -308,6 +312,21 @@ function parseOpenAIResponseToAnthropic(raw) {
|
|
|
308
312
|
}
|
|
309
313
|
return resp;
|
|
310
314
|
}
|
|
315
|
+
function readStreamUsage(chunk) {
|
|
316
|
+
const raw = chunk.usage;
|
|
317
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
|
|
318
|
+
return null;
|
|
319
|
+
}
|
|
320
|
+
const { prompt_tokens: promptTokens, completion_tokens: completionTokens } = raw;
|
|
321
|
+
const usage = {};
|
|
322
|
+
if (typeof promptTokens === "number" && Number.isFinite(promptTokens)) {
|
|
323
|
+
usage.input_tokens = promptTokens;
|
|
324
|
+
}
|
|
325
|
+
if (typeof completionTokens === "number" && Number.isFinite(completionTokens)) {
|
|
326
|
+
usage.output_tokens = completionTokens;
|
|
327
|
+
}
|
|
328
|
+
return usage;
|
|
329
|
+
}
|
|
311
330
|
var OpenAIStreamConverter = class {
|
|
312
331
|
messageStarted = false;
|
|
313
332
|
thinkingStarted = false;
|
|
@@ -326,8 +345,18 @@ var OpenAIStreamConverter = class {
|
|
|
326
345
|
toolArgsAccum = /* @__PURE__ */ new Map();
|
|
327
346
|
/** 上一次解析出的 tool 键,供缺 index 且缺 id 的后续增量沿用。 */
|
|
328
347
|
lastToolKey = null;
|
|
329
|
-
/** 已发出 message_delta/message_stop,避免 finish_reason
|
|
348
|
+
/** 已发出 message_delta/message_stop,避免 finish_reason、usage 尾帧、`[DONE]` 与
|
|
349
|
+
* {@link flush} 重复收口 —— 整条流恰好一个 message_stop。 */
|
|
330
350
|
messageClosed = false;
|
|
351
|
+
/** 已为仍打开的块发出 content_block_stop。与 {@link messageClosed} 分开记:块在
|
|
352
|
+
* finish_reason 帧上就关,message_delta/message_stop 却可能推迟到之后的帧。 */
|
|
353
|
+
blocksClosed = false;
|
|
354
|
+
/** [W-QUAD-CHAIN-20260914] finish_reason 已到、但 message_delta/message_stop 因等待
|
|
355
|
+
* usage 尾帧而推迟时记下的 stop_reason;没有推迟中的收尾时为 null。 */
|
|
356
|
+
pendingStopReason = null;
|
|
357
|
+
/** [W-QUAD-CHAIN-20260914] 最近一次带 usage 对象的帧搬出的 usage(后到覆盖先到);
|
|
358
|
+
* 整条流从未出现 usage 对象时为 null,收尾 message_delta 据此不写 usage 键。 */
|
|
359
|
+
usage = null;
|
|
331
360
|
/**
|
|
332
361
|
* 解析一条 tool_call delta 归属的块键。
|
|
333
362
|
*
|
|
@@ -352,15 +381,18 @@ var OpenAIStreamConverter = class {
|
|
|
352
381
|
return 0;
|
|
353
382
|
}
|
|
354
383
|
/**
|
|
355
|
-
* 关闭仍打开的 text / thinking / tool
|
|
384
|
+
* 关闭仍打开的 text / thinking / tool 块(整条流只关一次)。
|
|
356
385
|
*
|
|
357
386
|
* 由 `finish_reason` 分支与 `[DONE]` 分支共用:上游断流或只发 `[DONE]` 而不发
|
|
358
387
|
* `finish_reason` 时,此前一个 `content_block_stop` 都不会发,下游拿到的是一个
|
|
359
388
|
* 永不闭合的 tool_use 块。
|
|
389
|
+
*
|
|
390
|
+
* [W-QUAD-CHAIN-20260914] 此前关块与 message_delta/message_stop 是同一个方法里同一时刻
|
|
391
|
+
* 的事;usage 尾帧要求收尾推迟,于是拆成本方法与 {@link emitMessageEnd} 两段,各自防重。
|
|
360
392
|
*/
|
|
361
|
-
|
|
362
|
-
if (this.
|
|
363
|
-
this.
|
|
393
|
+
closeContentBlocks(events) {
|
|
394
|
+
if (this.blocksClosed) return;
|
|
395
|
+
this.blocksClosed = true;
|
|
364
396
|
if (this.textStarted) {
|
|
365
397
|
events.push({
|
|
366
398
|
event: "content_block_stop",
|
|
@@ -380,10 +412,23 @@ var OpenAIStreamConverter = class {
|
|
|
380
412
|
data: JSON.stringify({ type: "content_block_stop", index: idx })
|
|
381
413
|
});
|
|
382
414
|
}
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* 发出 message_delta + message_stop,整条流只发一次;同时清掉推迟中的收尾。
|
|
418
|
+
*
|
|
419
|
+
* 见过 usage 对象就把它放进 message_delta:usage 必须出现在唯一的 message_stop 之前,
|
|
420
|
+
* message_stop 之后下游已无处安放用量。整条流从未出现 usage 对象时不写 usage 键 ——
|
|
421
|
+
* 缺席表示「上游没给」,不是 0。
|
|
422
|
+
*/
|
|
423
|
+
emitMessageEnd(events, stopReason) {
|
|
424
|
+
if (this.messageClosed) return;
|
|
425
|
+
this.messageClosed = true;
|
|
426
|
+
this.pendingStopReason = null;
|
|
427
|
+
const messageDelta = { type: "message_delta", delta: { stop_reason: stopReason } };
|
|
428
|
+
if (this.usage !== null) {
|
|
429
|
+
messageDelta.usage = this.usage;
|
|
430
|
+
}
|
|
431
|
+
events.push({ event: "message_delta", data: JSON.stringify(messageDelta) });
|
|
387
432
|
events.push({
|
|
388
433
|
event: "message_stop",
|
|
389
434
|
data: JSON.stringify({ type: "message_stop" })
|
|
@@ -396,8 +441,11 @@ var OpenAIStreamConverter = class {
|
|
|
396
441
|
convert(data) {
|
|
397
442
|
if (data === "[DONE]") {
|
|
398
443
|
const events2 = [];
|
|
399
|
-
if (this.
|
|
400
|
-
this.
|
|
444
|
+
if (this.pendingStopReason !== null) {
|
|
445
|
+
this.emitMessageEnd(events2, this.pendingStopReason);
|
|
446
|
+
} else if (this.messageStarted) {
|
|
447
|
+
this.closeContentBlocks(events2);
|
|
448
|
+
this.emitMessageEnd(events2, "end_turn");
|
|
401
449
|
}
|
|
402
450
|
return { events: events2, done: true };
|
|
403
451
|
}
|
|
@@ -408,10 +456,18 @@ var OpenAIStreamConverter = class {
|
|
|
408
456
|
throw new Error(`parse openai stream chunk: ${e instanceof Error ? e.message : String(e)}`);
|
|
409
457
|
}
|
|
410
458
|
const events = [];
|
|
459
|
+
const frameUsage = readStreamUsage(chunk);
|
|
460
|
+
if (frameUsage !== null) {
|
|
461
|
+
this.usage = frameUsage;
|
|
462
|
+
}
|
|
411
463
|
if (!Array.isArray(chunk.choices) || chunk.choices.length === 0) {
|
|
464
|
+
if (frameUsage !== null && this.pendingStopReason !== null) {
|
|
465
|
+
this.emitMessageEnd(events, this.pendingStopReason);
|
|
466
|
+
}
|
|
412
467
|
return { events, done: false };
|
|
413
468
|
}
|
|
414
469
|
const choice = chunk.choices[0];
|
|
470
|
+
const delta = choice.delta ?? {};
|
|
415
471
|
if (!this.messageStarted) {
|
|
416
472
|
this.messageStarted = true;
|
|
417
473
|
const msgJSON = JSON.stringify({
|
|
@@ -426,8 +482,17 @@ var OpenAIStreamConverter = class {
|
|
|
426
482
|
});
|
|
427
483
|
events.push({ event: "message_start", data: msgJSON });
|
|
428
484
|
}
|
|
429
|
-
if (
|
|
485
|
+
if (delta.reasoning_content && delta.reasoning_content !== "") {
|
|
430
486
|
if (!this.thinkingStarted) {
|
|
487
|
+
if (this.textStarted) {
|
|
488
|
+
const stopJSON = JSON.stringify({
|
|
489
|
+
type: "content_block_stop",
|
|
490
|
+
index: this.blockIndex
|
|
491
|
+
});
|
|
492
|
+
events.push({ event: "content_block_stop", data: stopJSON });
|
|
493
|
+
this.blockIndex++;
|
|
494
|
+
this.textStarted = false;
|
|
495
|
+
}
|
|
431
496
|
this.thinkingStarted = true;
|
|
432
497
|
this.thinkingBlockIndex = this.blockIndex;
|
|
433
498
|
const blockJSON = JSON.stringify({
|
|
@@ -440,11 +505,11 @@ var OpenAIStreamConverter = class {
|
|
|
440
505
|
const deltaJSON = JSON.stringify({
|
|
441
506
|
type: "content_block_delta",
|
|
442
507
|
index: this.thinkingBlockIndex,
|
|
443
|
-
delta: { type: "thinking_delta", thinking:
|
|
508
|
+
delta: { type: "thinking_delta", thinking: delta.reasoning_content }
|
|
444
509
|
});
|
|
445
510
|
events.push({ event: "content_block_delta", data: deltaJSON });
|
|
446
511
|
}
|
|
447
|
-
if (
|
|
512
|
+
if (delta.content && delta.content !== "") {
|
|
448
513
|
if (this.thinkingStarted && !this.thinkingStopped) {
|
|
449
514
|
this.thinkingStopped = true;
|
|
450
515
|
const stopJSON = JSON.stringify({
|
|
@@ -466,11 +531,11 @@ var OpenAIStreamConverter = class {
|
|
|
466
531
|
const deltaJSON = JSON.stringify({
|
|
467
532
|
type: "content_block_delta",
|
|
468
533
|
index: this.blockIndex,
|
|
469
|
-
delta: { type: "text_delta", text:
|
|
534
|
+
delta: { type: "text_delta", text: delta.content }
|
|
470
535
|
});
|
|
471
536
|
events.push({ event: "content_block_delta", data: deltaJSON });
|
|
472
537
|
}
|
|
473
|
-
for (const tc of
|
|
538
|
+
for (const tc of delta.tool_calls ?? []) {
|
|
474
539
|
const fn = tc.function;
|
|
475
540
|
const toolKey = this.resolveToolKey(tc);
|
|
476
541
|
if (!this.toolBlockIndex.has(toolKey)) {
|
|
@@ -545,10 +610,35 @@ var OpenAIStreamConverter = class {
|
|
|
545
610
|
default:
|
|
546
611
|
stopReason = choice.finish_reason;
|
|
547
612
|
}
|
|
548
|
-
this.
|
|
613
|
+
if (!this.messageClosed && this.pendingStopReason === null) {
|
|
614
|
+
this.closeContentBlocks(events);
|
|
615
|
+
if (this.usage !== null) {
|
|
616
|
+
this.emitMessageEnd(events, stopReason);
|
|
617
|
+
} else {
|
|
618
|
+
this.pendingStopReason = stopReason;
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
if (frameUsage !== null && this.pendingStopReason !== null) {
|
|
623
|
+
this.emitMessageEnd(events, this.pendingStopReason);
|
|
549
624
|
}
|
|
550
625
|
return { events, done: false };
|
|
551
626
|
}
|
|
627
|
+
/**
|
|
628
|
+
* 流在**没有** `[DONE]` 的情况下结束 (EOF) 时, 由驱动方在读循环结束后调用一次。
|
|
629
|
+
*
|
|
630
|
+
* 只补发「finish_reason 已到、仅因等待 usage 尾帧而推迟」的 message_delta + message_stop。
|
|
631
|
+
* 从未收到 finish_reason 的流是被截断的流, 这里刻意**不**替它伪造正常结束 —— 一个
|
|
632
|
+
* `end_turn` 的 message_stop 会把被截断的回答当成完整回答交给下游。
|
|
633
|
+
* 已经收口 (usage 帧 / `[DONE]` / 上一次 flush) 后再调用返回空数组, 不会发出第二个 message_stop。
|
|
634
|
+
*/
|
|
635
|
+
flush() {
|
|
636
|
+
const events = [];
|
|
637
|
+
if (this.pendingStopReason !== null) {
|
|
638
|
+
this.emitMessageEnd(events, this.pendingStopReason);
|
|
639
|
+
}
|
|
640
|
+
return events;
|
|
641
|
+
}
|
|
552
642
|
};
|
|
553
643
|
function newOpenAIStreamConverter() {
|
|
554
644
|
return new OpenAIStreamConverter();
|