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