dskcode 0.1.20 → 0.1.22
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/dist/{chunk-BA7UJPKJ.js → chunk-I6HEIOSU.js} +242 -164
- package/dist/chunk-I6HEIOSU.js.map +1 -0
- package/dist/deepseek-YTT76XDE.js +7 -0
- package/dist/index.js +1000 -386
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-7QS2XBBX.js +0 -83
- package/dist/chunk-7QS2XBBX.js.map +0 -1
- package/dist/chunk-BA7UJPKJ.js.map +0 -1
- package/dist/deepseek-UJXV2D6K.js +0 -8
- package/dist/models-OL7DHYGK.js +0 -19
- package/dist/models-OL7DHYGK.js.map +0 -1
- /package/dist/{deepseek-UJXV2D6K.js.map → deepseek-YTT76XDE.js.map} +0 -0
|
@@ -1,7 +1,72 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
// src/provider/models.ts
|
|
2
|
+
var SUPPORTED_MODELS = {
|
|
3
|
+
"deepseek-v4-flash": {
|
|
4
|
+
id: "deepseek-v4-flash",
|
|
5
|
+
displayName: "DeepSeek V4 Flash",
|
|
6
|
+
/** 上下文窗口大小(token) */
|
|
7
|
+
contextWindow: 1e6,
|
|
8
|
+
/** 输入价格(元 / 百万 token,缓存未命中) */
|
|
9
|
+
inputPricePerMillion: 1,
|
|
10
|
+
/** 输出价格(元 / 百万 token) */
|
|
11
|
+
outputPricePerMillion: 2,
|
|
12
|
+
/** 输入价格(元 / 百万 token,缓存命中) */
|
|
13
|
+
cacheHitPricePerMillion: 0.02
|
|
14
|
+
},
|
|
15
|
+
"deepseek-v4-pro": {
|
|
16
|
+
id: "deepseek-v4-pro",
|
|
17
|
+
displayName: "DeepSeek V4 Pro",
|
|
18
|
+
/** 上下文窗口大小(token) */
|
|
19
|
+
contextWindow: 1e6,
|
|
20
|
+
/** 输入价格(元 / 百万 token,缓存未命中) */
|
|
21
|
+
inputPricePerMillion: 3,
|
|
22
|
+
/** 输出价格(元 / 百万 token) */
|
|
23
|
+
outputPricePerMillion: 6,
|
|
24
|
+
/** 输入价格(元 / 百万 token,缓存命中) */
|
|
25
|
+
cacheHitPricePerMillion: 0.025
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
var SUPPORTED_MODEL_IDS = Object.keys(SUPPORTED_MODELS);
|
|
29
|
+
function isSupportedModel(model) {
|
|
30
|
+
return model in SUPPORTED_MODELS;
|
|
31
|
+
}
|
|
32
|
+
function getModelMeta(model) {
|
|
33
|
+
return SUPPORTED_MODELS[model];
|
|
34
|
+
}
|
|
35
|
+
function isCJK(char) {
|
|
36
|
+
const code = char.codePointAt(0);
|
|
37
|
+
return code >= 19968 && code <= 40959 || // CJK 扩展 A
|
|
38
|
+
code >= 13312 && code <= 19903 || // CJK 扩展 B 及其他
|
|
39
|
+
code >= 131072 && code <= 173791 || // CJK 兼容汉字
|
|
40
|
+
code >= 63744 && code <= 64255 || // 全角字符
|
|
41
|
+
code >= 65281 && code <= 65376;
|
|
42
|
+
}
|
|
43
|
+
function estimateTokens(text) {
|
|
44
|
+
let cjkCount = 0;
|
|
45
|
+
let otherCount = 0;
|
|
46
|
+
for (const char of text) {
|
|
47
|
+
if (isCJK(char)) {
|
|
48
|
+
cjkCount++;
|
|
49
|
+
} else {
|
|
50
|
+
otherCount++;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
const tokens = cjkCount * 0.6 + otherCount * 0.3;
|
|
54
|
+
return Math.max(1, Math.ceil(tokens));
|
|
55
|
+
}
|
|
56
|
+
function calculateCost(usage, model) {
|
|
57
|
+
const meta = SUPPORTED_MODELS[model];
|
|
58
|
+
const cached = usage.cachedPromptTokens ?? 0;
|
|
59
|
+
const nonCached = usage.promptTokens - cached;
|
|
60
|
+
const inputCost = nonCached * meta.inputPricePerMillion / 1e6;
|
|
61
|
+
const cacheHitCost = cached * meta.cacheHitPricePerMillion / 1e6;
|
|
62
|
+
const outputCost = usage.completionTokens * meta.outputPricePerMillion / 1e6;
|
|
63
|
+
return {
|
|
64
|
+
inputCost,
|
|
65
|
+
cacheHitCost,
|
|
66
|
+
outputCost,
|
|
67
|
+
totalCost: inputCost + cacheHitCost + outputCost
|
|
68
|
+
};
|
|
69
|
+
}
|
|
5
70
|
|
|
6
71
|
// src/provider/errors.ts
|
|
7
72
|
var ProviderError = class extends Error {
|
|
@@ -415,13 +480,157 @@ function readWithTimeout(reader, idleMs, signal) {
|
|
|
415
480
|
});
|
|
416
481
|
}
|
|
417
482
|
|
|
483
|
+
// src/provider/deepseek-protocol.ts
|
|
484
|
+
async function* streamCompletion(client, baseUrl, apiKey, request, opts) {
|
|
485
|
+
const url = `${baseUrl}/chat/completions`;
|
|
486
|
+
const idleTimeoutMs = opts?.idleTimeoutMs ?? 6e4;
|
|
487
|
+
const response = await client.requestWithRetry(
|
|
488
|
+
url,
|
|
489
|
+
{
|
|
490
|
+
method: "POST",
|
|
491
|
+
headers: {
|
|
492
|
+
Authorization: `Bearer ${apiKey}`
|
|
493
|
+
},
|
|
494
|
+
body: JSON.stringify(request)
|
|
495
|
+
},
|
|
496
|
+
{ signal: opts?.signal }
|
|
497
|
+
);
|
|
498
|
+
for await (const evt of parseSSE(response, { idleTimeoutMs, signal: opts?.signal })) {
|
|
499
|
+
if (evt.data === "[DONE]") return;
|
|
500
|
+
let chunk;
|
|
501
|
+
try {
|
|
502
|
+
chunk = JSON.parse(evt.data);
|
|
503
|
+
} catch {
|
|
504
|
+
continue;
|
|
505
|
+
}
|
|
506
|
+
yield chunk;
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
async function getBalance(client, baseUrl, apiKey) {
|
|
510
|
+
const response = await client.request(`${baseUrl}/user/balance`, {
|
|
511
|
+
method: "GET",
|
|
512
|
+
headers: { Authorization: `Bearer ${apiKey}` }
|
|
513
|
+
});
|
|
514
|
+
const data = await response.json();
|
|
515
|
+
return {
|
|
516
|
+
isAvailable: data.is_available,
|
|
517
|
+
balances: data.balance_infos.map((b) => ({
|
|
518
|
+
currency: b.currency,
|
|
519
|
+
totalBalance: Number(b.total_balance),
|
|
520
|
+
grantedBalance: Number(b.granted_balance),
|
|
521
|
+
toppedUpBalance: Number(b.topped_up_balance)
|
|
522
|
+
}))
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
|
|
418
526
|
// src/provider/deepseek.ts
|
|
527
|
+
function intoDeepSeekMessages(messages) {
|
|
528
|
+
return messages.map((msg) => {
|
|
529
|
+
const mapped = {
|
|
530
|
+
role: msg.role,
|
|
531
|
+
content: msg.content || null
|
|
532
|
+
};
|
|
533
|
+
if (msg.name) mapped.name = msg.name;
|
|
534
|
+
if (msg.toolCallId) mapped.tool_call_id = msg.toolCallId;
|
|
535
|
+
if (msg.toolCalls && msg.toolCalls.length > 0) {
|
|
536
|
+
mapped.tool_calls = msg.toolCalls.map((tc) => ({
|
|
537
|
+
id: tc.id,
|
|
538
|
+
type: "function",
|
|
539
|
+
function: {
|
|
540
|
+
name: tc.name,
|
|
541
|
+
arguments: tc.arguments
|
|
542
|
+
}
|
|
543
|
+
}));
|
|
544
|
+
}
|
|
545
|
+
return mapped;
|
|
546
|
+
});
|
|
547
|
+
}
|
|
548
|
+
var DeepSeekEventMapper = class {
|
|
549
|
+
#toolCallsByIndex = /* @__PURE__ */ new Map();
|
|
550
|
+
/**
|
|
551
|
+
* 重置内部状态(每次新请求前调用)。
|
|
552
|
+
*/
|
|
553
|
+
reset() {
|
|
554
|
+
this.#toolCallsByIndex.clear();
|
|
555
|
+
}
|
|
556
|
+
/**
|
|
557
|
+
* 将 DeepSeek 原始流式块映射为 ChatChunk 数组。
|
|
558
|
+
* 一个 SSE 数据块可能生成 0~N 个 ChatChunk 事件。
|
|
559
|
+
*/
|
|
560
|
+
mapEvent(chunk) {
|
|
561
|
+
const choice = chunk.choices?.[0];
|
|
562
|
+
if (!choice) return [];
|
|
563
|
+
const events = [];
|
|
564
|
+
const delta = choice.delta;
|
|
565
|
+
const content = delta?.content ?? "";
|
|
566
|
+
const finishReason = this.#mapFinishReason(choice.finish_reason);
|
|
567
|
+
if (delta?.tool_calls) {
|
|
568
|
+
for (const tc of delta.tool_calls) {
|
|
569
|
+
this.#accumulateToolCall(tc);
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
let usage;
|
|
573
|
+
if (chunk.usage) {
|
|
574
|
+
usage = {
|
|
575
|
+
promptTokens: chunk.usage.prompt_tokens,
|
|
576
|
+
completionTokens: chunk.usage.completion_tokens,
|
|
577
|
+
cachedPromptTokens: chunk.usage.prompt_cache_hit_tokens
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
const shouldYieldToolCalls = finishReason === "tool_calls" && this.#toolCallsByIndex.size > 0;
|
|
581
|
+
if (!content && finishReason === null && !shouldYieldToolCalls && !usage) {
|
|
582
|
+
return [];
|
|
583
|
+
}
|
|
584
|
+
events.push({
|
|
585
|
+
content,
|
|
586
|
+
finishReason,
|
|
587
|
+
...shouldYieldToolCalls ? { toolCalls: [...this.#toolCallsByIndex.values()] } : {},
|
|
588
|
+
...usage ? { usage } : {}
|
|
589
|
+
});
|
|
590
|
+
if (shouldYieldToolCalls) {
|
|
591
|
+
this.#toolCallsByIndex.clear();
|
|
592
|
+
}
|
|
593
|
+
return events;
|
|
594
|
+
}
|
|
595
|
+
/** 将 DeepSeek 的 finish_reason 映射为内部类型 */
|
|
596
|
+
#mapFinishReason(reason) {
|
|
597
|
+
switch (reason) {
|
|
598
|
+
case "stop":
|
|
599
|
+
return "stop";
|
|
600
|
+
case "tool_calls":
|
|
601
|
+
return "tool_calls";
|
|
602
|
+
case "length":
|
|
603
|
+
return "length";
|
|
604
|
+
default:
|
|
605
|
+
return null;
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
/** 累积一个工具调用分块 */
|
|
609
|
+
#accumulateToolCall(tc) {
|
|
610
|
+
const idx = tc.index ?? 0;
|
|
611
|
+
const existing = this.#toolCallsByIndex.get(idx);
|
|
612
|
+
if (!existing) {
|
|
613
|
+
this.#toolCallsByIndex.set(idx, {
|
|
614
|
+
id: tc.id ?? "",
|
|
615
|
+
name: tc.function?.name ?? "",
|
|
616
|
+
arguments: tc.function?.arguments ?? ""
|
|
617
|
+
});
|
|
618
|
+
} else {
|
|
619
|
+
if (tc.id) existing.id = tc.id;
|
|
620
|
+
if (tc.function?.name) existing.name = tc.function.name;
|
|
621
|
+
if (tc.function?.arguments) {
|
|
622
|
+
existing.arguments += tc.function.arguments;
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
};
|
|
419
627
|
var DeepSeekProvider = class {
|
|
420
628
|
#apiKey;
|
|
421
629
|
#baseUrl;
|
|
422
630
|
#model;
|
|
423
631
|
#client;
|
|
424
632
|
#idleTimeoutMs;
|
|
633
|
+
#mapper = new DeepSeekEventMapper();
|
|
425
634
|
name = "deepseek";
|
|
426
635
|
constructor(config) {
|
|
427
636
|
this.#apiKey = config.apiKey;
|
|
@@ -443,187 +652,56 @@ var DeepSeekProvider = class {
|
|
|
443
652
|
}
|
|
444
653
|
/**
|
|
445
654
|
* 查询账户余额。
|
|
446
|
-
*
|
|
447
|
-
* 调用 DeepSeek /user/balance 接口,返回各币种的余额信息。
|
|
448
|
-
* 可用于在启动前检查 API Key 有效性和余额状态。
|
|
449
655
|
*/
|
|
450
656
|
async getBalance() {
|
|
451
|
-
|
|
452
|
-
const response = await this.#client.request(url, {
|
|
453
|
-
method: "GET",
|
|
454
|
-
headers: { Authorization: `Bearer ${this.#apiKey}` }
|
|
455
|
-
});
|
|
456
|
-
const data = await response.json();
|
|
457
|
-
return {
|
|
458
|
-
isAvailable: data.is_available,
|
|
459
|
-
balances: data.balance_infos.map((b) => ({
|
|
460
|
-
currency: b.currency,
|
|
461
|
-
totalBalance: Number(b.total_balance),
|
|
462
|
-
grantedBalance: Number(b.granted_balance),
|
|
463
|
-
toppedUpBalance: Number(b.topped_up_balance)
|
|
464
|
-
}))
|
|
465
|
-
};
|
|
657
|
+
return getBalance(this.#client, this.#baseUrl, this.#apiKey);
|
|
466
658
|
}
|
|
467
659
|
/**
|
|
468
660
|
* 发起聊天补全请求(流式)。
|
|
469
|
-
*
|
|
470
|
-
* 通过 HttpClient 发起请求(自动连接超时 + 429/5xx 指数退避重试),
|
|
471
|
-
* 用 parseSSE 解析事件流,逐块映射为 ChatChunk 并 yield。
|
|
472
661
|
*/
|
|
473
662
|
async *chat(messages, opts) {
|
|
474
|
-
const
|
|
475
|
-
const
|
|
476
|
-
const apiMessages = this.#mapMessages(messages);
|
|
477
|
-
const body = {
|
|
663
|
+
const apiMessages = intoDeepSeekMessages(messages);
|
|
664
|
+
const request = {
|
|
478
665
|
model: this.#model,
|
|
479
666
|
messages: apiMessages,
|
|
480
667
|
stream: true,
|
|
481
668
|
max_tokens: opts?.maxTokens,
|
|
482
|
-
temperature: opts?.temperature
|
|
669
|
+
temperature: opts?.temperature,
|
|
670
|
+
// 从 ChatOptions 映射 thinking / reasoning_effort
|
|
671
|
+
thinking: opts?.thinkingAllowed !== void 0 ? { type: opts.thinkingAllowed ? "enabled" : "disabled" } : void 0,
|
|
672
|
+
reasoning_effort: opts?.thinkingEffort,
|
|
673
|
+
// 从 ChatOptions 映射 response_format
|
|
674
|
+
response_format: opts?.responseFormat ? { type: opts.responseFormat } : void 0,
|
|
675
|
+
// 从 ChatOptions 映射 tool_choice
|
|
676
|
+
tool_choice: opts?.toolChoice
|
|
483
677
|
};
|
|
484
678
|
if (opts?.tools && opts.tools.length > 0) {
|
|
485
|
-
|
|
679
|
+
request.tools = opts.tools;
|
|
486
680
|
}
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
const response = await this.#client.requestWithRetry(
|
|
495
|
-
url,
|
|
496
|
-
{
|
|
497
|
-
method: "POST",
|
|
498
|
-
headers: { Authorization: `Bearer ${this.#apiKey}` },
|
|
499
|
-
body: JSON.stringify(body)
|
|
500
|
-
},
|
|
501
|
-
requestOptions
|
|
681
|
+
this.#mapper.reset();
|
|
682
|
+
const stream = streamCompletion(
|
|
683
|
+
this.#client,
|
|
684
|
+
this.#baseUrl,
|
|
685
|
+
this.#apiKey,
|
|
686
|
+
request,
|
|
687
|
+
{ signal: opts?.signal, idleTimeoutMs: this.#idleTimeoutMs }
|
|
502
688
|
);
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
// -----------------------------------------------------------------------
|
|
508
|
-
/**
|
|
509
|
-
* 将内部 ChatMessage 数组映射为 DeepSeek API 的请求消息格式。
|
|
510
|
-
*/
|
|
511
|
-
#mapMessages(messages) {
|
|
512
|
-
return messages.map((msg) => {
|
|
513
|
-
const mapped = {
|
|
514
|
-
role: msg.role,
|
|
515
|
-
content: msg.content || null
|
|
516
|
-
};
|
|
517
|
-
if (msg.name) {
|
|
518
|
-
mapped.name = msg.name;
|
|
519
|
-
}
|
|
520
|
-
if (msg.toolCallId) {
|
|
521
|
-
mapped.tool_call_id = msg.toolCallId;
|
|
522
|
-
}
|
|
523
|
-
if (msg.toolCalls && msg.toolCalls.length > 0) {
|
|
524
|
-
mapped.tool_calls = msg.toolCalls.map((tc) => ({
|
|
525
|
-
id: tc.id,
|
|
526
|
-
type: "function",
|
|
527
|
-
function: {
|
|
528
|
-
name: tc.name,
|
|
529
|
-
arguments: tc.arguments
|
|
530
|
-
}
|
|
531
|
-
}));
|
|
532
|
-
}
|
|
533
|
-
return mapped;
|
|
534
|
-
});
|
|
535
|
-
}
|
|
536
|
-
/**
|
|
537
|
-
* 解析 SSE 事件流并映射为 ChatChunk。
|
|
538
|
-
*
|
|
539
|
-
* 策略:
|
|
540
|
-
* - 文本内容增量立即 yield(支持实时流式渲染)
|
|
541
|
-
* - 工具调用在多个 SSE 块中逐步到达,内部累积拼接
|
|
542
|
-
* - 当 finishReason 为 "tool_calls" 时,yield 完整的工具调用列表
|
|
543
|
-
* - 最后一个块通常包含 usage 统计信息
|
|
544
|
-
*/
|
|
545
|
-
async *#parseStream(response, signal) {
|
|
546
|
-
const toolCallAccumulator = /* @__PURE__ */ new Map();
|
|
547
|
-
for await (const evt of parseSSE(response, {
|
|
548
|
-
idleTimeoutMs: this.#idleTimeoutMs,
|
|
549
|
-
signal
|
|
550
|
-
})) {
|
|
551
|
-
if (evt.data === "[DONE]") return;
|
|
552
|
-
let chunk;
|
|
553
|
-
try {
|
|
554
|
-
chunk = JSON.parse(evt.data);
|
|
555
|
-
} catch {
|
|
556
|
-
continue;
|
|
557
|
-
}
|
|
558
|
-
const choice = chunk.choices?.[0];
|
|
559
|
-
if (!choice) continue;
|
|
560
|
-
const delta = choice.delta;
|
|
561
|
-
const content = delta?.content ?? "";
|
|
562
|
-
const finishReason = choice.finish_reason;
|
|
563
|
-
if (delta?.tool_calls) {
|
|
564
|
-
for (const tc of delta.tool_calls) {
|
|
565
|
-
const idx = tc.index ?? 0;
|
|
566
|
-
const existing = toolCallAccumulator.get(idx);
|
|
567
|
-
if (!existing) {
|
|
568
|
-
toolCallAccumulator.set(idx, {
|
|
569
|
-
id: tc.id ?? "",
|
|
570
|
-
name: tc.function?.name ?? "",
|
|
571
|
-
arguments: tc.function?.arguments ?? ""
|
|
572
|
-
});
|
|
573
|
-
} else {
|
|
574
|
-
if (tc.id) existing.id = tc.id;
|
|
575
|
-
if (tc.function?.name) existing.name = tc.function.name;
|
|
576
|
-
if (tc.function?.arguments) {
|
|
577
|
-
existing.arguments += tc.function.arguments;
|
|
578
|
-
}
|
|
579
|
-
}
|
|
580
|
-
}
|
|
581
|
-
}
|
|
582
|
-
let usage;
|
|
583
|
-
if (chunk.usage) {
|
|
584
|
-
usage = {
|
|
585
|
-
promptTokens: chunk.usage.prompt_tokens,
|
|
586
|
-
completionTokens: chunk.usage.completion_tokens,
|
|
587
|
-
cachedPromptTokens: chunk.usage.prompt_cache_hit_tokens
|
|
588
|
-
};
|
|
689
|
+
for await (const rawChunk of stream) {
|
|
690
|
+
const events = this.#mapper.mapEvent(rawChunk);
|
|
691
|
+
for (const event of events) {
|
|
692
|
+
yield event;
|
|
589
693
|
}
|
|
590
|
-
const mappedFinishReason = this.#mapFinishReason(finishReason);
|
|
591
|
-
const shouldYieldToolCalls = mappedFinishReason === "tool_calls" && toolCallAccumulator.size > 0;
|
|
592
|
-
if (!content && mappedFinishReason === null && !shouldYieldToolCalls && !usage) {
|
|
593
|
-
continue;
|
|
594
|
-
}
|
|
595
|
-
yield {
|
|
596
|
-
content,
|
|
597
|
-
finishReason: mappedFinishReason,
|
|
598
|
-
...shouldYieldToolCalls ? {
|
|
599
|
-
toolCalls: [...toolCallAccumulator.values()]
|
|
600
|
-
} : {},
|
|
601
|
-
...usage ? { usage } : {}
|
|
602
|
-
};
|
|
603
|
-
if (shouldYieldToolCalls) {
|
|
604
|
-
toolCallAccumulator.clear();
|
|
605
|
-
}
|
|
606
|
-
}
|
|
607
|
-
}
|
|
608
|
-
/**
|
|
609
|
-
* 将 DeepSeek API 的 finish_reason 映射为内部类型。
|
|
610
|
-
*/
|
|
611
|
-
#mapFinishReason(reason) {
|
|
612
|
-
switch (reason) {
|
|
613
|
-
case "stop":
|
|
614
|
-
return "stop";
|
|
615
|
-
case "tool_calls":
|
|
616
|
-
return "tool_calls";
|
|
617
|
-
case "length":
|
|
618
|
-
return "length";
|
|
619
|
-
default:
|
|
620
|
-
return null;
|
|
621
694
|
}
|
|
622
695
|
}
|
|
623
696
|
};
|
|
624
697
|
|
|
625
698
|
export {
|
|
699
|
+
SUPPORTED_MODELS,
|
|
700
|
+
isSupportedModel,
|
|
701
|
+
getModelMeta,
|
|
702
|
+
estimateTokens,
|
|
703
|
+
calculateCost,
|
|
626
704
|
ModelNotSupportedError,
|
|
627
705
|
DeepSeekProvider
|
|
628
706
|
};
|
|
629
|
-
//# sourceMappingURL=chunk-
|
|
707
|
+
//# sourceMappingURL=chunk-I6HEIOSU.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/provider/models.ts","../src/provider/errors.ts","../src/provider/retry.ts","../src/provider/client.ts","../src/provider/sse.ts","../src/provider/deepseek-protocol.ts","../src/provider/deepseek.ts"],"sourcesContent":["// ---------------------------------------------------------------------------\r\n// 模型定义与校验\r\n// ---------------------------------------------------------------------------\r\n\r\nimport type { ModelId, ModelMeta } from \"./types.js\";\r\n\r\n/** 支持的模型及其元数据 */\r\nexport const SUPPORTED_MODELS: Record<ModelId, ModelMeta> = {\r\n \"deepseek-v4-flash\": {\r\n id: \"deepseek-v4-flash\",\r\n displayName: \"DeepSeek V4 Flash\",\r\n /** 上下文窗口大小(token) */\r\n contextWindow: 1_000_000,\r\n /** 输入价格(元 / 百万 token,缓存未命中) */\r\n inputPricePerMillion: 1,\r\n /** 输出价格(元 / 百万 token) */\r\n outputPricePerMillion: 2,\r\n /** 输入价格(元 / 百万 token,缓存命中) */\r\n cacheHitPricePerMillion: 0.02,\r\n },\r\n \"deepseek-v4-pro\": {\r\n id: \"deepseek-v4-pro\",\r\n displayName: \"DeepSeek V4 Pro\",\r\n /** 上下文窗口大小(token) */\r\n contextWindow: 1_000_000,\r\n /** 输入价格(元 / 百万 token,缓存未命中) */\r\n inputPricePerMillion: 3,\r\n /** 输出价格(元 / 百万 token) */\r\n outputPricePerMillion: 6,\r\n /** 输入价格(元 / 百万 token,缓存命中) */\r\n cacheHitPricePerMillion: 0.025,\r\n },\r\n};\r\n\r\n/** 支持的模型 ID 列表 */\r\nexport const SUPPORTED_MODEL_IDS: string[] = Object.keys(SUPPORTED_MODELS);\r\n\r\n/**\r\n * 判断模型标识是否受支持。\r\n * dskcode 仅支持 deepseek-v4-flash 和 deepseek-v4-pro 两个模型。\r\n */\r\nexport function isSupportedModel(model: string): model is ModelId {\r\n return model in SUPPORTED_MODELS;\r\n}\r\n\r\n/** 获取模型元数据(已校验) */\r\nexport function getModelMeta(model: ModelId): ModelMeta {\r\n return SUPPORTED_MODELS[model];\r\n}\r\n\r\n/**\r\n * CJK 字符 Unicode 范围检测。\r\n * 覆盖 CJK 统一汉字、扩展 A/B、兼容汉字等。\r\n */\r\nfunction isCJK(char: string): boolean {\r\n const code = char.codePointAt(0)!;\r\n // CJK 统一汉字\r\n return (\r\n (code >= 0x4e00 && code <= 0x9fff) ||\r\n // CJK 扩展 A\r\n (code >= 0x3400 && code <= 0x4dbf) ||\r\n // CJK 扩展 B 及其他\r\n (code >= 0x20000 && code <= 0x2a6df) ||\r\n // CJK 兼容汉字\r\n (code >= 0xf900 && code <= 0xfaff) ||\r\n // 全角字符\r\n (code >= 0xff01 && code <= 0xff60)\r\n );\r\n}\r\n\r\n/**\r\n * 估算文本的 token 数量。\r\n *\r\n * 根据 DeepSeek 官方文档的换算比例:\r\n * - 1 个英文字符 ≈ 0.3 个 token(即约 3.3 个字符 ≈ 1 token)\r\n * - 1 个中文字符 ≈ 0.6 个 token(即约 1.7 个字符 ≈ 1 token)\r\n *\r\n * 对文本中的 CJK 和非 CJK 字符分别估算后求和。\r\n */\r\nexport function estimateTokens(text: string): number {\r\n let cjkCount = 0;\r\n let otherCount = 0;\r\n for (const char of text) {\r\n if (isCJK(char)) {\r\n cjkCount++;\r\n } else {\r\n otherCount++;\r\n }\r\n }\r\n // CJK: 1 字符 ≈ 0.6 token → token 数 ≈ cjkCount × 0.6\r\n // 其他: 1 字符 ≈ 0.3 token → token 数 ≈ otherCount × 0.3\r\n const tokens = cjkCount * 0.6 + otherCount * 0.3;\r\n return Math.max(1, Math.ceil(tokens));\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// 费用计算\r\n// ---------------------------------------------------------------------------\r\n\r\nimport type { UsageInfo, CostInfo } from \"./types.js\";\r\n\r\n/**\r\n * 根据模型和 Token 使用量计算费用。\r\n *\r\n * 计费公式:\r\n * 输入费用(缓存未命中) = (promptTokens - cachedPromptTokens) × inputPrice / 1,000,000\r\n * 缓存命中费用 = cachedPromptTokens × cacheHitPrice / 1,000,000\r\n * 输出费用 = completionTokens × outputPrice / 1,000,000\r\n * 总费用 = 输入费用 + 缓存命中费用 + 输出费用\r\n *\r\n * @param usage Token 使用统计\r\n * @param model 模型标识\r\n * @returns 费用明细(单位:元)\r\n */\r\nexport function calculateCost(usage: UsageInfo, model: ModelId): CostInfo {\r\n const meta = SUPPORTED_MODELS[model];\r\n const cached = usage.cachedPromptTokens ?? 0;\r\n const nonCached = usage.promptTokens - cached;\r\n\r\n const inputCost = (nonCached * meta.inputPricePerMillion) / 1_000_000;\r\n const cacheHitCost = (cached * meta.cacheHitPricePerMillion) / 1_000_000;\r\n const outputCost = (usage.completionTokens * meta.outputPricePerMillion) / 1_000_000;\r\n\r\n return {\r\n inputCost,\r\n cacheHitCost,\r\n outputCost,\r\n totalCost: inputCost + cacheHitCost + outputCost,\r\n };\r\n}\r\n\r\n/**\r\n * 将费用格式化为易读的字符串,保留4位小数。\r\n */\r\nexport function formatCost(cost: CostInfo): string {\r\n return `≈¥${cost.totalCost.toFixed(4)}`;\r\n}","// ---------------------------------------------------------------------------\r\n// Provider 错误类型与 HTTP 状态映射\r\n// ---------------------------------------------------------------------------\r\n\r\n/** Provider 错误基类 */\r\nexport class ProviderError extends Error {\r\n constructor(\r\n message: string,\r\n public readonly code: string,\r\n public readonly statusCode?: number,\r\n ) {\r\n super(message);\r\n this.name = \"ProviderError\";\r\n }\r\n}\r\n\r\n/** 认证失败(401 / 403) */\r\nexport class AuthError extends ProviderError {\r\n constructor(message: string, statusCode?: number) {\r\n super(message, \"AUTH_ERROR\", statusCode);\r\n this.name = \"AuthError\";\r\n }\r\n}\r\n\r\n/** 速率限制(429) */\r\nexport class RateLimitError extends ProviderError {\r\n /** 建议等待的毫秒数 */\r\n public readonly retryAfterMs?: number;\r\n\r\n constructor(message: string, retryAfterMs?: number) {\r\n super(message, \"RATE_LIMIT\", 429);\r\n this.name = \"RateLimitError\";\r\n this.retryAfterMs = retryAfterMs;\r\n }\r\n}\r\n\r\n/** 服务端错误(5xx) */\r\nexport class ServerError extends ProviderError {\r\n constructor(message: string, statusCode?: number) {\r\n super(message, \"SERVER_ERROR\", statusCode);\r\n this.name = \"ServerError\";\r\n }\r\n}\r\n\r\n/** 网络连接错误 */\r\nexport class NetworkError extends ProviderError {\r\n /** 导致网络错误的底层错误 */\r\n readonly originalError?: Error;\r\n\r\n constructor(message: string, originalError?: Error) {\r\n super(message, \"NETWORK_ERROR\");\r\n this.name = \"NetworkError\";\r\n this.originalError = originalError;\r\n }\r\n}\r\n\r\n/** 连接超时错误 */\r\nexport class TimeoutError extends ProviderError {\r\n /** 触发超时的等待时间(毫秒) */\r\n public readonly timeoutMs: number;\r\n\r\n constructor(message: string, timeoutMs: number) {\r\n super(message, \"TIMEOUT\");\r\n this.name = \"TimeoutError\";\r\n this.timeoutMs = timeoutMs;\r\n }\r\n}\r\n\r\n/** 流式空闲超时错误——两个 SSE 数据块之间间隔过长 */\r\nexport class StreamIdleTimeoutError extends ProviderError {\r\n /** 触发空闲超时的最大间隔(毫秒) */\r\n public readonly idleMs: number;\r\n\r\n constructor(message: string, idleMs: number) {\r\n super(message, \"STREAM_IDLE_TIMEOUT\");\r\n this.name = \"StreamIdleTimeoutError\";\r\n this.idleMs = idleMs;\r\n }\r\n}\r\n\r\n/** 不支持的模型 */\r\nexport class ModelNotSupportedError extends ProviderError {\r\n constructor(model: string) {\r\n super(\r\n `不支持的模型: \"${model}\"。dskcode 仅支持: deepseek-v4-flash, deepseek-v4-pro`,\r\n \"MODEL_NOT_SUPPORTED\",\r\n );\r\n this.name = \"ModelNotSupportedError\";\r\n }\r\n}\r\n\r\n/**\r\n * 将 HTTP 状态码映射为结构化错误。\r\n *\r\n * 覆盖 DeepSeek API 常见的错误码:\r\n * - 400 → 请求参数错误\r\n * - 401/403 → 认证失败\r\n * - 429 → 速率限制\r\n * - 5xx → 服务端错误\r\n */\r\nexport function mapHttpError(status: number, body: string): ProviderError {\r\n switch (status) {\r\n case 401:\r\n case 403:\r\n return new AuthError(\r\n `认证失败:请检查 API Key 是否正确。(${status})`,\r\n status,\r\n );\r\n\r\n case 429: {\r\n // 尝试从响应体中解析重试等待时间\r\n let retryAfterMs: number | undefined;\r\n try {\r\n const parsed = JSON.parse(body) as { error?: { message?: string } };\r\n const msg = parsed.error?.message ?? \"\";\r\n const match = /(\\d+)\\s*second/i.exec(msg);\r\n if (match?.[1]) {\r\n retryAfterMs = Number(match[1]) * 1000;\r\n }\r\n } catch {\r\n // 解析失败 — 不提取重试时间\r\n }\r\n return new RateLimitError(\r\n `请求过于频繁,请稍后再试。${retryAfterMs ? `建议等待 ${Math.ceil(retryAfterMs / 1000)} 秒。` : \"\"}`,\r\n retryAfterMs,\r\n );\r\n }\r\n\r\n case 400: {\r\n let detail = \"\";\r\n try {\r\n const parsed = JSON.parse(body) as { error?: { message?: string } };\r\n detail = parsed.error?.message ?? \"\";\r\n } catch {\r\n // 解析失败 — 不提取详情\r\n }\r\n return new ProviderError(\r\n `请求参数错误${detail ? `: ${detail}` : \"\"}`,\r\n \"BAD_REQUEST\",\r\n status,\r\n );\r\n }\r\n\r\n default:\r\n if (status >= 500) {\r\n return new ServerError(\r\n `服务端错误 (${status}),请稍后再试。`,\r\n status,\r\n );\r\n }\r\n return new ProviderError(`请求失败 (${status})`, \"UNKNOWN_ERROR\", status);\r\n }\r\n}\r\n\r\n/**\r\n * 判断一个 ProviderError 是否可重试。\r\n *\r\n * 可重试的错误类型:\r\n * - RateLimitError(429)——服从 Retry-After,但也可退避\r\n * - ServerError(5xx)\r\n * - NetworkError(连接失败、DNS 错误等)\r\n */\r\nexport function isRetryableError(err: ProviderError): boolean {\r\n return (\r\n err instanceof RateLimitError ||\r\n err instanceof ServerError ||\r\n err instanceof NetworkError\r\n );\r\n}","// ---------------------------------------------------------------------------\r\n// 指数退避重试策略\r\n// ---------------------------------------------------------------------------\r\n//\r\n// 针对可重试错误(RateLimitError / ServerError / NetworkError)自动重试,\r\n// 重试间隔按指数退避 + 抖动增长。429 错误优先服从 Retry-After 提示。\r\n\r\nimport {\r\n ProviderError,\r\n RateLimitError,\r\n isRetryableError,\r\n} from \"./errors.js\";\r\n\r\n/** 重试策略配置 */\r\nexport interface RetryOptions {\r\n /** 最大重试次数(不含首次),默认 3 */\r\n maxRetries?: number;\r\n /** 指数退避基准延迟(毫秒),默认 1000 */\r\n baseDelayMs?: number;\r\n /** 单次重试最大延迟上限(毫秒),默认 30000 */\r\n maxDelayMs?: number;\r\n /**\r\n * 重试前的回调,可用于日志输出。\r\n * 收到 (attempt, error, delayMs) 三个参数。\r\n */\r\n onRetry?: (attempt: number, error: Error, delayMs: number) => void;\r\n}\r\n\r\n/** 默认重试配置 */\r\nexport const DEFAULT_RETRY_OPTIONS: Required<\r\n Omit<RetryOptions, \"onRetry\">\r\n> = {\r\n maxRetries: 3,\r\n baseDelayMs: 1000,\r\n maxDelayMs: 30_000,\r\n};\r\n\r\n/**\r\n * 计算第 attempt 次重试的退避延迟(毫秒)。\r\n *\r\n * 公式:base * 2^(attempt-1) + jitter\r\n * 其中 jitter 为 [0, base/2) 的随机抖动,避免重试洪峰。\r\n * 最终结果被 maxDelayMs 截断。\r\n *\r\n * @param attempt 重试序号,从 1 开始(第 1 次重试)\r\n */\r\nexport function computeBackoffDelay(\r\n attempt: number,\r\n baseDelayMs: number,\r\n maxDelayMs: number,\r\n): number {\r\n // 指数部分:1, 2, 4, 8, 16 ...\r\n const exp = Math.pow(2, attempt - 1);\r\n const jitter = Math.random() * (baseDelayMs / 2);\r\n const delay = baseDelayMs * exp + jitter;\r\n return Math.min(delay, maxDelayMs);\r\n}\r\n\r\n/**\r\n * 对一个异步操作执行指数退避重试。\r\n *\r\n * - 操作成功(返回值)→ 立即返回结果\r\n * - 抛出非 ProviderError → 立即向上抛出(不可重试)\r\n * - 抛出 ProviderError 且 isRetryableError → 按配置重试\r\n * - 超过 maxRetries 仍未成功 → 抛出最后一次的错误\r\n *\r\n * 对于 RateLimitError,若携带 retryAfterMs,则优先使用该值\r\n * 作为延迟(但仍受 maxDelayMs 截断约束)。\r\n *\r\n * @param fn 待执行的重试操作\r\n * @param options 重试配置\r\n */\r\nexport async function withRetry<T>(\r\n fn: () => Promise<T>,\r\n options: RetryOptions = {},\r\n): Promise<T> {\r\n const maxRetries = options.maxRetries ?? DEFAULT_RETRY_OPTIONS.maxRetries;\r\n const baseDelayMs =\r\n options.baseDelayMs ?? DEFAULT_RETRY_OPTIONS.baseDelayMs;\r\n const maxDelayMs =\r\n options.maxDelayMs ?? DEFAULT_RETRY_OPTIONS.maxDelayMs;\r\n\r\n let lastError: unknown;\r\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\r\n try {\r\n return await fn();\r\n } catch (err: unknown) {\r\n lastError = err;\r\n\r\n // 非可重试错误:立即抛出\r\n if (!(err instanceof ProviderError) || !isRetryableError(err)) {\r\n throw err;\r\n }\r\n\r\n // 已达最大尝试次数\r\n if (attempt >= maxRetries) break;\r\n\r\n // 计算延迟\r\n let delayMs: number;\r\n if (err instanceof RateLimitError && err.retryAfterMs !== undefined) {\r\n // 429 优先服从 Retry-After,但仍受 maxDelayMs 截断\r\n delayMs = Math.min(err.retryAfterMs, maxDelayMs);\r\n } else {\r\n delayMs = computeBackoffDelay(attempt + 1, baseDelayMs, maxDelayMs);\r\n }\r\n\r\n options.onRetry?.(attempt + 1, err, delayMs);\r\n await sleep(delayMs);\r\n }\r\n }\r\n // 理论上不会到达;保险起见抛出最后一次错误\r\n throw lastError instanceof Error\r\n ? lastError\r\n : new Error(\"withRetry: 未知错误\");\r\n}\r\n\r\n/** 可被外部替换的 sleep 函数,便于测试中加速 */\r\nexport let sleep = (ms: number): Promise<void> =>\r\n new Promise((resolve) => setTimeout(resolve, ms));\r\n\r\n/**\r\n * 替换模块内的 sleep 实现(仅供测试使用)。\r\n *\r\n * @param impl 自定义的 sleep 实现\r\n * @returns 一个函数,调用后恢复默认 sleep\r\n */\r\nexport function overrideSleep(\r\n impl: (ms: number) => Promise<void>,\r\n): () => void {\r\n const original = sleep;\r\n sleep = impl;\r\n return () => {\r\n sleep = original;\r\n };\r\n}","// ---------------------------------------------------------------------------\r\n// HttpClient — 通用 HTTP 传输层\r\n// ---------------------------------------------------------------------------\r\n//\r\n// 封装原生 fetch,提供:\r\n// - 连接超时(通过 AbortSignal.timeout 或外部 signal 合并实现)\r\n// - 流式空闲超时(由 SSE 解析器负责)\r\n// - 指数退避重试(针对 429 / 5xx)\r\n//\r\n// 与具体 Provider 解耦:Provider 负责「请求构造 / 响应映射」,\r\n// HttpClient 负责「传输稳定性」。\r\n\r\nimport type { ClientOptions } from \"./types.js\";\r\nimport {\r\n NetworkError,\r\n ProviderError,\r\n TimeoutError,\r\n mapHttpError,\r\n} from \"./errors.js\";\r\nimport { withRetry, type RetryOptions } from \"./retry.js\";\r\n\r\n/** HttpClient 配置 */\r\nexport interface HttpClientConfig extends ClientOptions {}\r\n\r\n/** 请求选项(扩展标准 fetch init) */\r\nexport interface RequestOptions {\r\n /** 外部中止信号,与连接超时信号合并 */\r\n signal?: AbortSignal;\r\n /**\r\n * 本次请求的连接超时(毫秒),覆盖 HttpClient 默认值。\r\n * 设为 0 表示不超时。\r\n */\r\n connectTimeoutMs?: number;\r\n /**\r\n * 本次请求的重试配置;为 false 则禁用重试。\r\n */\r\n retry?: RetryOptions | false;\r\n}\r\n\r\n/** 默认连接超时(30s) */\r\nconst DEFAULT_CONNECT_TIMEOUT_MS = 30_000;\r\n\r\n/**\r\n * 通用 HTTP 客户端。\r\n *\r\n * 不持有任何状态,可被多个 Provider 共享。\r\n */\r\nexport class HttpClient {\r\n readonly #connectTimeoutMs: number;\r\n readonly #maxRetries: number;\r\n readonly #retryBaseDelayMs: number;\r\n readonly #retryMaxDelayMs: number;\r\n\r\n constructor(config: HttpClientConfig = {}) {\r\n this.#connectTimeoutMs =\r\n config.connectTimeoutMs ?? DEFAULT_CONNECT_TIMEOUT_MS;\r\n this.#maxRetries = config.maxRetries ?? 3;\r\n this.#retryBaseDelayMs = config.retryBaseDelayMs ?? 1000;\r\n this.#retryMaxDelayMs = config.retryMaxDelayMs ?? 30_000;\r\n }\r\n\r\n /**\r\n * 发起单次 HTTP 请求(带连接超时,不重试)。\r\n *\r\n * - 自动附加 `Content-Type: application/json`(若 body 是字符串)\r\n * - 非 2xx 响应:抛出 mapHttpError 映射后的 ProviderError\r\n * - 网络错误:抛出 NetworkError\r\n * - 超时:抛出 TimeoutError\r\n *\r\n * @param url 请求 URL\r\n * @param init fetch 初始化(method/headers/body)\r\n * @param options 请求级选项(超时、中止信号)\r\n * @returns 成功(2xx)的 Response\r\n */\r\n async request(\r\n url: string,\r\n init: RequestInit = {},\r\n options: RequestOptions = {},\r\n ): Promise<Response> {\r\n const timeoutMs =\r\n options.connectTimeoutMs === undefined\r\n ? this.#connectTimeoutMs\r\n : options.connectTimeoutMs;\r\n\r\n // 合并 headers\r\n const headers = new Headers(init.headers);\r\n if (init.body && typeof init.body === \"string\" && !headers.has(\"Content-Type\")) {\r\n headers.set(\"Content-Type\", \"application/json\");\r\n }\r\n\r\n const { signal, cleanup } = this.#mergeSignals(\r\n options.signal,\r\n timeoutMs,\r\n );\r\n\r\n let response: Response;\r\n try {\r\n response = await fetch(url, {\r\n ...init,\r\n headers,\r\n signal,\r\n });\r\n } catch (err: unknown) {\r\n // 外部中止优先视为用户主动取消\r\n if (options.signal?.aborted) {\r\n throw new ProviderError(\"请求已取消\", \"ABORTED\");\r\n }\r\n // 超时(本客户端内部的超时信号先于外信号触发)\r\n if (signal?.aborted && !options.signal?.aborted) {\r\n throw new TimeoutError(\r\n `连接超时(${timeoutMs}ms): ${url}`,\r\n timeoutMs,\r\n );\r\n }\r\n throw new NetworkError(\r\n `网络错误:无法连接到 ${url}`,\r\n err instanceof Error ? err : undefined,\r\n );\r\n } finally {\r\n cleanup();\r\n }\r\n\r\n if (!response.ok) {\r\n const body = await response.text().catch(() => \"\");\r\n throw mapHttpError(response.status, body);\r\n }\r\n\r\n return response;\r\n }\r\n\r\n /**\r\n * 发起带重试的 HTTP 请求。\r\n *\r\n * 等价于 request,但用 withRetry 包装:429 / 5xx / 网络错误会自动重试。\r\n * 流式响应一旦获得 2xx,重试即停止,剩余流式稳定性交给 SSE 解析器。\r\n */\r\n async requestWithRetry(\r\n url: string,\r\n init: RequestInit = {},\r\n options: RequestOptions = {},\r\n ): Promise<Response> {\r\n const retryOptions: RetryOptions | undefined =\r\n options.retry === false\r\n ? { maxRetries: 0 }\r\n : (options.retry ?? {\r\n maxRetries: this.#maxRetries,\r\n baseDelayMs: this.#retryBaseDelayMs,\r\n maxDelayMs: this.#retryMaxDelayMs,\r\n });\r\n\r\n return withRetry(() => this.request(url, init, options), retryOptions);\r\n }\r\n\r\n /**\r\n * 合并外部 signal 与连接超时 signal。\r\n *\r\n * 策略:\r\n * - 若 timeoutMs <= 0 且无外部 signal → 不使用 signal\r\n * - 若已有外部 signal 且无超时 → 直接使用外部 signal\r\n * - 否则用 AbortSignal.any([外部, 超时]) 合并(Node 20+ 原生支持)\r\n *\r\n * @returns { signal, cleanup } 调用方应在 finally 调用 cleanup 取消监听\r\n */\r\n #mergeSignals(\r\n external: AbortSignal | undefined,\r\n timeoutMs: number,\r\n ): { signal: AbortSignal | undefined; cleanup: () => void } {\r\n const useTimeout = timeoutMs > 0;\r\n\r\n if (!external && !useTimeout) {\r\n return { signal: undefined, cleanup: () => {} };\r\n }\r\n if (!useTimeout) {\r\n return { signal: external, cleanup: () => {} };\r\n }\r\n\r\n const controller = new AbortController();\r\n const timer = setTimeout(() => controller.abort(), timeoutMs);\r\n\r\n // 监听外部 signal:触发时同步中止本控制器\r\n const onExternalAbort = () => controller.abort();\r\n if (external) {\r\n if (external.aborted) controller.abort();\r\n else external.addEventListener(\"abort\", onExternalAbort, { once: true });\r\n }\r\n\r\n return {\r\n signal: controller.signal,\r\n cleanup: () => {\r\n clearTimeout(timer);\r\n if (external) {\r\n external.removeEventListener(\"abort\", onExternalAbort);\r\n }\r\n },\r\n };\r\n }\r\n}","// ---------------------------------------------------------------------------\r\n// SSE(Server-Sent Events)流式解析器\r\n// ---------------------------------------------------------------------------\r\n//\r\n// 独立于 HTTP 传输的 SSE 协议解析模块。按照\r\n// [SSE 规范](https://html.spec.whatwg.org/multipage/server-sent-events.html)\r\n// 逐步解析事件流。\r\n//\r\n// 关键设计:\r\n// - 按 \\n 拆行,保留跨 chunk 的不完整行\r\n// - 同一事件内的多个 data: 块用 \\n 拼接\r\n// - 空行触发事件 emit\r\n// - 支持 idleTimeoutMs:单次 reader.read() 等待超时即抛 StreamIdleTimeoutError\r\n// - 支持外部 AbortSignal 中止读取\r\n\r\nimport type { SSEEvent } from \"./types.js\";\r\nimport { StreamIdleTimeoutError } from \"./errors.js\";\r\n\r\n/** parseSSE 的可选配置 */\r\nexport interface ParseSSEOptions {\r\n /**\r\n * 流式空闲超时(毫秒)。\r\n * 一次 reader.read() 等待超过此时间即抛出 StreamIdleTimeoutError。\r\n * 默认 60000。\r\n */\r\n idleTimeoutMs?: number;\r\n /**\r\n * 外部中止信号。中止后立即停止读取并释放 reader。\r\n */\r\n signal?: AbortSignal;\r\n /**\r\n * 遇到 data: [DONE] 时是否自动停止迭代。\r\n * SSE 规范并不要求解析器处理 [DONE],但 OpenAI/DeepSeek\r\n * 等兼容 API 使用它标记流结束。默认 true。\r\n */\r\n stopOnDone?: boolean;\r\n}\r\n\r\n/**\r\n * 解析 SSE 响应流,逐步 yield 事件。\r\n *\r\n * 调用方需保证传入的 response 为成功的 2xx 响应且 body 可读。\r\n *\r\n * @param response 已返回 2xx 的 fetch 响应\r\n * @param options 解析选项(超时、中止信号)\r\n * @yields SSEEvent — 已按 SSE 协议拼装好的事件\r\n */\r\nexport async function* parseSSE(\r\n response: Response,\r\n options: ParseSSEOptions = {},\r\n): AsyncIterable<SSEEvent> {\r\n const reader = response.body?.getReader();\r\n if (!reader) {\r\n throw new Error(\"SSE 响应体为空,无法解析\");\r\n }\r\n\r\n const decoder = new TextDecoder();\r\n let buffer = \"\";\r\n\r\n // 当前正在拼装的事件字段累积器\r\n let pendingData: string[] = [];\r\n let pendingEvent: string | undefined;\r\n let pendingId: string | undefined;\r\n let pendingRetry: number | undefined;\r\n\r\n const idleMs = options.idleTimeoutMs ?? 60_000;\r\n\r\n // 是否在遇到 [DONE] 时停止迭代\r\n const shouldStopOnDone = options.stopOnDone !== false;\r\n\r\n // [DONE] 标记:由 flush 设置,外部循环检查后停止迭代\r\n let streamDone = false;\r\n\r\n // 将当前累积的事件 flush 为一个 SSEEvent\r\n function* flush(): Generator<SSEEvent> {\r\n if (pendingData.length === 0) {\r\n pendingEvent = undefined;\r\n pendingId = undefined;\r\n pendingRetry = undefined;\r\n return;\r\n }\r\n const evt: SSEEvent = { data: pendingData.join(\"\\n\") };\r\n if (pendingEvent) evt.event = pendingEvent;\r\n if (pendingId) evt.id = pendingId;\r\n if (pendingRetry !== undefined) evt.retry = pendingRetry;\r\n pendingData = [];\r\n pendingEvent = undefined;\r\n pendingId = undefined;\r\n pendingRetry = undefined;\r\n\r\n // 遇到 [DONE] 标记时设置流结束标志\r\n if (shouldStopOnDone && evt.data === \"[DONE]\") {\r\n streamDone = true;\r\n }\r\n\r\n yield evt;\r\n }\r\n\r\n // 处理单行 SSE 内容\r\n function* processLine(rawLine: string): Generator<SSEEvent> {\r\n // 去掉行尾 \\r(CRLF 场景)\r\n const line = rawLine.endsWith(\"\\r\") ? rawLine.slice(0, -1) : rawLine;\r\n\r\n // 空行:事件分隔符,触发 emit\r\n if (line === \"\") {\r\n yield* flush();\r\n return;\r\n }\r\n // 注释行(以 \":\" 开头),忽略\r\n if (line.startsWith(\":\")) return;\r\n\r\n // 字段行:field: value(冒号后允许一个可选空格)\r\n const colonIdx = line.indexOf(\":\");\r\n if (colonIdx === -1) {\r\n applyField(line, \"\");\r\n return;\r\n }\r\n const field = line.slice(0, colonIdx);\r\n let value = line.slice(colonIdx + 1);\r\n if (value.startsWith(\" \")) value = value.slice(1);\r\n applyField(field, value);\r\n }\r\n\r\n function applyField(field: string, value: string): void {\r\n switch (field) {\r\n case \"event\":\r\n pendingEvent = value;\r\n break;\r\n case \"data\":\r\n pendingData.push(value);\r\n break;\r\n case \"id\":\r\n pendingId = value;\r\n break;\r\n case \"retry\": {\r\n const n = Number(value);\r\n if (Number.isFinite(n)) pendingRetry = n;\r\n break;\r\n }\r\n default:\r\n // SSE 规范要求忽略未识别字段\r\n break;\r\n }\r\n }\r\n\r\n try {\r\n while (true) {\r\n if (options.signal?.aborted) return;\r\n\r\n const readResult = await readWithTimeout(reader, idleMs, options.signal);\r\n if (readResult === null) return; // 中止\r\n const { done, value } = readResult;\r\n if (done) {\r\n // flush 解码器残留\r\n buffer += decoder.decode();\r\n if (buffer.length > 0) {\r\n const line = buffer.endsWith(\"\\r\") ? buffer.slice(0, -1) : buffer;\r\n if (line !== \"\") yield* processLine(line);\r\n buffer = \"\";\r\n }\r\n yield* flush();\r\n if (streamDone) return;\r\n return;\r\n }\r\n\r\n buffer += decoder.decode(value, { stream: true });\r\n\r\n let nl: number;\r\n while ((nl = buffer.indexOf(\"\\n\")) !== -1) {\r\n const line = buffer.slice(0, nl);\r\n buffer = buffer.slice(nl + 1);\r\n yield* processLine(line);\r\n if (streamDone) return;\r\n }\r\n }\r\n } finally {\r\n reader.releaseLock();\r\n }\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// 带超时的 reader.read()\r\n// ---------------------------------------------------------------------------\r\n\r\ninterface TimedReadResult {\r\n done: boolean;\r\n value: Uint8Array;\r\n}\r\n\r\n/**\r\n * 在超时范围内等待 reader.read(),超时抛出 StreamIdleTimeoutError。\r\n *\r\n * - signal 中止时立即返回 null(让调用方优雅退出)\r\n * - 超时抛出 StreamIdleTimeoutError(reader 由外层 finally 释放)\r\n */\r\nfunction readWithTimeout(\r\n reader: ReadableStreamDefaultReader<Uint8Array>,\r\n idleMs: number,\r\n signal?: AbortSignal,\r\n): Promise<TimedReadResult | null> {\r\n if (signal?.aborted) return Promise.resolve(null);\r\n\r\n return new Promise<TimedReadResult | null>((resolve, reject) => {\r\n let settled = false;\r\n\r\n const timer = setTimeout(() => {\r\n if (settled) return;\r\n settled = true;\r\n reject(new StreamIdleTimeoutError(`流式空闲超时(${idleMs}ms 无数据)`, idleMs));\r\n }, idleMs);\r\n\r\n const onAbort = () => {\r\n if (settled) return;\r\n settled = true;\r\n clearTimeout(timer);\r\n resolve(null);\r\n };\r\n if (signal) signal.addEventListener(\"abort\", onAbort, { once: true });\r\n\r\n reader\r\n .read()\r\n .then((result) => {\r\n if (settled) return;\r\n settled = true;\r\n clearTimeout(timer);\r\n if (signal) signal.removeEventListener(\"abort\", onAbort);\r\n resolve(result as TimedReadResult);\r\n })\r\n .catch((err: unknown) => {\r\n if (settled) return;\r\n settled = true;\r\n clearTimeout(timer);\r\n if (signal) signal.removeEventListener(\"abort\", onAbort);\r\n reject(err);\r\n });\r\n });\r\n}","// ---------------------------------------------------------------------------\r\n// DeepSeek API 协议层 — 纯数据模型 + HTTP 请求函数\r\n//\r\n// 职责:定义 DeepSeek Chat Completions API 的完整类型,提供纯函数发起请求。\r\n// 不依赖任何 Provider 框架类型,可被任何调用方复用。\r\n// ---------------------------------------------------------------------------\r\n\r\nimport { HttpClient, type RequestOptions } from \"./client.js\";\r\nimport { parseSSE } from \"./sse.js\";\r\nimport { mapHttpError } from \"./errors.js\";\r\n\r\n// ============================================================================\r\n// 类型定义 — 镜像 DeepSeek API 协议\r\n// ============================================================================\r\n\r\n/** 消息角色 */\r\nexport type DeepSeekRole = \"system\" | \"user\" | \"assistant\" | \"tool\";\r\n\r\n/** 请求消息 */\r\nexport interface DeepSeekMessage {\r\n role: DeepSeekRole;\r\n content: string | null;\r\n name?: string;\r\n tool_call_id?: string;\r\n tool_calls?: DeepSeekToolCall[];\r\n}\r\n\r\n/** 工具调用(请求中) */\r\nexport interface DeepSeekToolCall {\r\n id: string;\r\n type: \"function\";\r\n function: {\r\n name: string;\r\n arguments: string;\r\n };\r\n}\r\n\r\n/** 工具定义 */\r\nexport interface DeepSeekToolDefinition {\r\n type: \"function\";\r\n function: {\r\n name: string;\r\n description: string;\r\n parameters: Record<string, unknown>;\r\n };\r\n}\r\n\r\n/** 聊天补全请求体 */\r\n/** 深度思考模式 */\r\nexport interface DeepSeekThinking {\r\n type: \"enabled\" | \"disabled\";\r\n}\r\n\r\n/** 推理努力等级 */\r\nexport type DeepSeekReasoningEffort = \"high\" | \"max\";\r\n\r\n/** 响应格式 */\r\nexport interface DeepSeekResponseFormat {\r\n type: \"text\" | \"json_object\";\r\n}\r\n\r\n/** 工具调用策略 */\r\nexport type DeepSeekToolChoice = \"none\" | \"auto\" | \"required\";\r\n\r\n/** 聊天补全请求体 */\r\nexport interface DeepSeekRequest {\r\n model: string;\r\n messages: DeepSeekMessage[];\r\n stream: boolean;\r\n max_tokens?: number;\r\n temperature?: number;\r\n /** 启用深度思考模式,允许模型在回答前进行更长的推理 */\r\n thinking?: DeepSeekThinking;\r\n /** 推理努力等级(仅 thinking 启用时有效) */\r\n reasoning_effort?: DeepSeekReasoningEffort;\r\n /** 响应格式控制 */\r\n response_format?: DeepSeekResponseFormat;\r\n /** 工具调用策略 */\r\n tool_choice?: DeepSeekToolChoice;\r\n /** 可用工具定义 */\r\n tools?: DeepSeekToolDefinition[];\r\n}\r\n\r\n/** 非流式响应 */\r\nexport interface DeepSeekResponse {\r\n id: string;\r\n object: string;\r\n created: number;\r\n model: string;\r\n choices: DeepSeekChoice[];\r\n usage: DeepSeekUsage;\r\n}\r\n\r\nexport interface DeepSeekChoice {\r\n index: number;\r\n message: DeepSeekMessage;\r\n finish_reason: string | null;\r\n}\r\n\r\n/** Token 用量 */\r\nexport interface DeepSeekUsage {\r\n prompt_tokens: number;\r\n completion_tokens: number;\r\n total_tokens: number;\r\n /** DeepSeek Prefix Cache 命中的 token 数 */\r\n prompt_cache_hit_tokens?: number;\r\n}\r\n\r\n/** SSE 流中的单个数据块 */\r\nexport interface DeepSeekStreamChunk {\r\n id: string;\r\n object: string;\r\n created: number;\r\n model: string;\r\n choices: DeepSeekStreamChoice[];\r\n usage?: DeepSeekUsage;\r\n}\r\n\r\nexport interface DeepSeekStreamChoice {\r\n index: number;\r\n delta: DeepSeekStreamDelta;\r\n finish_reason: string | null;\r\n}\r\n\r\nexport interface DeepSeekStreamDelta {\r\n content?: string;\r\n tool_calls?: DeepSeekToolCallChunk[];\r\n}\r\n\r\n/** 流式 tool call 分块(按 index 标识,需要累积拼接) */\r\nexport interface DeepSeekToolCallChunk {\r\n index: number;\r\n id?: string;\r\n function?: {\r\n name?: string;\r\n arguments?: string;\r\n };\r\n}\r\n\r\n/** 余额响应 */\r\ninterface DeepSeekBalanceResponse {\r\n is_available: boolean;\r\n balance_infos: Array<{\r\n currency: string;\r\n total_balance: string;\r\n granted_balance: string;\r\n topped_up_balance: string;\r\n }>;\r\n}\r\n\r\n/** 余额查询结果 */\r\nexport interface BalanceResult {\r\n isAvailable: boolean;\r\n balances: Array<{\r\n currency: string;\r\n totalBalance: number;\r\n grantedBalance: number;\r\n toppedUpBalance: number;\r\n }>;\r\n}\r\n\r\n// ============================================================================\r\n// 协议层函数\r\n// ============================================================================\r\n\r\n/**\r\n * 发起 DeepSeek Chat Completions 流式请求。\r\n *\r\n * 纯协议层函数:只处理 HTTP + SSE 解析,返回原始 StreamChunk。\r\n * 不涉及任何框架类型映射。\r\n *\r\n * @param client HTTP 客户端\r\n * @param baseUrl API 地址(如 https://api.deepseek.com)\r\n * @param apiKey API Key\r\n * @param request 请求体\r\n * @param opts 请求选项(信号等)\r\n * @returns 原始流式数据块的异步迭代器\r\n */\r\nexport async function* streamCompletion(\r\n client: HttpClient,\r\n baseUrl: string,\r\n apiKey: string,\r\n request: DeepSeekRequest,\r\n opts?: { signal?: AbortSignal; idleTimeoutMs?: number },\r\n): AsyncIterable<DeepSeekStreamChunk> {\r\n const url = `${baseUrl}/chat/completions`;\r\n const idleTimeoutMs = opts?.idleTimeoutMs ?? 60_000;\r\n\r\n const response = await client.requestWithRetry(\r\n url,\r\n {\r\n method: \"POST\",\r\n headers: {\r\n Authorization: `Bearer ${apiKey}`,\r\n },\r\n body: JSON.stringify(request),\r\n },\r\n { signal: opts?.signal } satisfies RequestOptions,\r\n );\r\n\r\n for await (const evt of parseSSE(response, { idleTimeoutMs, signal: opts?.signal })) {\r\n if (evt.data === \"[DONE]\") return;\r\n\r\n let chunk: DeepSeekStreamChunk;\r\n try {\r\n chunk = JSON.parse(evt.data) as DeepSeekStreamChunk;\r\n } catch {\r\n continue;\r\n }\r\n\r\n yield chunk;\r\n }\r\n}\r\n\r\n/**\r\n * 查询 DeepSeek 账户余额。\r\n */\r\nexport async function getBalance(\r\n client: HttpClient,\r\n baseUrl: string,\r\n apiKey: string,\r\n): Promise<BalanceResult> {\r\n const response = await client.request(`${baseUrl}/user/balance`, {\r\n method: \"GET\",\r\n headers: { Authorization: `Bearer ${apiKey}` },\r\n });\r\n\r\n const data = (await response.json()) as DeepSeekBalanceResponse;\r\n\r\n return {\r\n isAvailable: data.is_available,\r\n balances: data.balance_infos.map((b) => ({\r\n currency: b.currency,\r\n totalBalance: Number(b.total_balance),\r\n grantedBalance: Number(b.granted_balance),\r\n toppedUpBalance: Number(b.topped_up_balance),\r\n })),\r\n };\r\n}\r\n","// ---------------------------------------------------------------------------\r\n// DeepSeek Provider 集成层 — 适配 DeepSeek API 到 Provider 框架\r\n//\r\n// 职责:\r\n// 1. 将内部 ChatMessage[] 转换为 DeepSeek 协议格式(intoDeepSeekMessages)\r\n// 2. 将 DeepSeek 流式响应映射为 ChatChunk 事件(DeepSeekEventMapper)\r\n// 3. 实现 Provider 接口,提供 DeepSeekProvider 类\r\n// ---------------------------------------------------------------------------\r\n\r\nimport type {\r\n ChatMessage,\r\n ChatOptions,\r\n ChatChunk,\r\n Provider,\r\n ProviderToolCall,\r\n UsageInfo,\r\n ModelId,\r\n ClientOptions,\r\n} from \"./types.js\";\r\nimport { getModelMeta, estimateTokens } from \"./models.js\";\r\nimport { HttpClient } from \"./client.js\";\r\nimport { mapHttpError } from \"./errors.js\";\r\nimport {\r\n type DeepSeekMessage,\r\n type DeepSeekRequest,\r\n type DeepSeekStreamChunk,\r\n type DeepSeekToolCallChunk,\r\n type DeepSeekThinking,\r\n type DeepSeekReasoningEffort,\r\n type DeepSeekResponseFormat,\r\n type DeepSeekToolChoice,\r\n streamCompletion as protocolStream,\r\n getBalance as protocolBalance,\r\n} from \"./deepseek-protocol.js\";\r\n\r\n// ============================================================================\r\n// 转换函数 — 内部类型 → DeepSeek 协议类型\r\n// ============================================================================\r\n\r\n/** 将内部消息格式映射为 DeepSeek API 请求格式 */\r\nfunction intoDeepSeekMessages(messages: ChatMessage[]): DeepSeekMessage[] {\r\n return messages.map((msg) => {\r\n const mapped: DeepSeekMessage = {\r\n role: msg.role,\r\n content: msg.content || null,\r\n };\r\n\r\n if (msg.name) mapped.name = msg.name;\r\n if (msg.toolCallId) mapped.tool_call_id = msg.toolCallId;\r\n\r\n if (msg.toolCalls && msg.toolCalls.length > 0) {\r\n mapped.tool_calls = msg.toolCalls.map((tc) => ({\r\n id: tc.id,\r\n type: \"function\" as const,\r\n function: {\r\n name: tc.name,\r\n arguments: tc.arguments,\r\n },\r\n }));\r\n }\r\n\r\n return mapped;\r\n });\r\n}\r\n\r\n// ============================================================================\r\n// DeepSeekEventMapper — 流式事件映射\r\n// ============================================================================\r\n\r\n/** 被累积的工具调用(跨多个 SSE 块拼接) */\r\ninterface AccumulatedToolCall {\r\n id: string;\r\n name: string;\r\n arguments: string;\r\n}\r\n\r\n/** 工具调用累积器:按 index 累积分块到达的 tool call */\r\nclass DeepSeekEventMapper {\r\n readonly #toolCallsByIndex = new Map<number, AccumulatedToolCall>();\r\n\r\n /**\r\n * 重置内部状态(每次新请求前调用)。\r\n */\r\n reset(): void {\r\n this.#toolCallsByIndex.clear();\r\n }\r\n\r\n /**\r\n * 将 DeepSeek 原始流式块映射为 ChatChunk 数组。\r\n * 一个 SSE 数据块可能生成 0~N 个 ChatChunk 事件。\r\n */\r\n mapEvent(chunk: DeepSeekStreamChunk): ChatChunk[] {\r\n const choice = chunk.choices?.[0];\r\n if (!choice) return [];\r\n\r\n const events: ChatChunk[] = [];\r\n const delta = choice.delta;\r\n const content = delta?.content ?? \"\";\r\n const finishReason = this.#mapFinishReason(choice.finish_reason);\r\n\r\n // 1. 累积工具调用片段\r\n if (delta?.tool_calls) {\r\n for (const tc of delta.tool_calls) {\r\n this.#accumulateToolCall(tc);\r\n }\r\n }\r\n\r\n // 2. 构建 usage\r\n let usage: UsageInfo | undefined;\r\n if (chunk.usage) {\r\n usage = {\r\n promptTokens: chunk.usage.prompt_tokens,\r\n completionTokens: chunk.usage.completion_tokens,\r\n cachedPromptTokens: chunk.usage.prompt_cache_hit_tokens,\r\n };\r\n }\r\n\r\n // 3. finish_reason = \"tool_calls\" 时,yield 完整工具调用列表\r\n const shouldYieldToolCalls =\r\n finishReason === \"tool_calls\" && this.#toolCallsByIndex.size > 0;\r\n\r\n // 跳过空块(无内容、无工具调用、无 usage、非结束)\r\n if (\r\n !content &&\r\n finishReason === null &&\r\n !shouldYieldToolCalls &&\r\n !usage\r\n ) {\r\n return [];\r\n }\r\n\r\n events.push({\r\n content,\r\n finishReason,\r\n ...(shouldYieldToolCalls\r\n ? { toolCalls: [...this.#toolCallsByIndex.values()] }\r\n : {}),\r\n ...(usage ? { usage } : {}),\r\n });\r\n\r\n // 清理已 yield 的工具调用\r\n if (shouldYieldToolCalls) {\r\n this.#toolCallsByIndex.clear();\r\n }\r\n\r\n return events;\r\n }\r\n\r\n /** 将 DeepSeek 的 finish_reason 映射为内部类型 */\r\n #mapFinishReason(\r\n reason: string | null,\r\n ): \"stop\" | \"tool_calls\" | \"length\" | null {\r\n switch (reason) {\r\n case \"stop\":\r\n return \"stop\";\r\n case \"tool_calls\":\r\n return \"tool_calls\";\r\n case \"length\":\r\n return \"length\";\r\n default:\r\n return null;\r\n }\r\n }\r\n\r\n /** 累积一个工具调用分块 */\r\n #accumulateToolCall(tc: DeepSeekToolCallChunk): void {\r\n const idx = tc.index ?? 0;\r\n const existing = this.#toolCallsByIndex.get(idx);\r\n\r\n if (!existing) {\r\n this.#toolCallsByIndex.set(idx, {\r\n id: tc.id ?? \"\",\r\n name: tc.function?.name ?? \"\",\r\n arguments: tc.function?.arguments ?? \"\",\r\n });\r\n } else {\r\n if (tc.id) existing.id = tc.id;\r\n if (tc.function?.name) existing.name = tc.function.name;\r\n if (tc.function?.arguments) {\r\n existing.arguments += tc.function.arguments;\r\n }\r\n }\r\n }\r\n}\r\n\r\n// ============================================================================\r\n// DeepSeekProvider\r\n// ============================================================================\r\n\r\n/** DeepSeek Provider 创建配置 */\r\nexport interface DeepSeekProviderConfig extends ClientOptions {\r\n apiKey: string;\r\n baseUrl: string;\r\n model: ModelId;\r\n /** 流式空闲超时(毫秒),默认 60000 */\r\n idleTimeoutMs?: number;\r\n}\r\n\r\n/**\r\n * DeepSeek LLM Provider 实现。\r\n *\r\n * 集成层:使用协议层的纯函数(streamCompletion / getBalance),\r\n * 通过 intoDeepSeekMessages 和 DeepSeekEventMapper 做类型转换。\r\n */\r\nexport class DeepSeekProvider implements Provider {\r\n readonly #apiKey: string;\r\n readonly #baseUrl: string;\r\n readonly #model: ModelId;\r\n readonly #client: HttpClient;\r\n readonly #idleTimeoutMs: number;\r\n readonly #mapper = new DeepSeekEventMapper();\r\n\r\n readonly name = \"deepseek\";\r\n\r\n constructor(config: DeepSeekProviderConfig) {\r\n this.#apiKey = config.apiKey;\r\n this.#baseUrl = config.baseUrl.replace(/\\/+$/, \"\");\r\n this.#model = config.model;\r\n this.#idleTimeoutMs = config.idleTimeoutMs ?? 60_000;\r\n this.#client = new HttpClient({\r\n connectTimeoutMs: config.connectTimeoutMs,\r\n maxRetries: config.maxRetries,\r\n retryBaseDelayMs: config.retryBaseDelayMs,\r\n retryMaxDelayMs: config.retryMaxDelayMs,\r\n });\r\n }\r\n\r\n model(): string {\r\n return this.#model;\r\n }\r\n\r\n countTokens(text: string): number {\r\n return estimateTokens(text);\r\n }\r\n\r\n /**\r\n * 查询账户余额。\r\n */\r\n async getBalance(): Promise<import(\"./types.js\").BalanceResult> {\r\n return protocolBalance(this.#client, this.#baseUrl, this.#apiKey);\r\n }\r\n\r\n /**\r\n * 发起聊天补全请求(流式)。\r\n */\r\n async *chat(\r\n messages: ChatMessage[],\r\n opts?: ChatOptions,\r\n ): AsyncIterable<ChatChunk> {\r\n // 1. 构建请求体\r\n const apiMessages = intoDeepSeekMessages(messages);\r\n\r\n const request: DeepSeekRequest = {\r\n model: this.#model,\r\n messages: apiMessages,\r\n stream: true,\r\n max_tokens: opts?.maxTokens,\r\n temperature: opts?.temperature,\r\n // 从 ChatOptions 映射 thinking / reasoning_effort\r\n thinking: opts?.thinkingAllowed !== undefined\r\n ? { type: opts.thinkingAllowed ? \"enabled\" : \"disabled\" }\r\n : undefined,\r\n reasoning_effort: opts?.thinkingEffort as DeepSeekReasoningEffort | undefined,\r\n // 从 ChatOptions 映射 response_format\r\n response_format: opts?.responseFormat\r\n ? { type: opts.responseFormat }\r\n : undefined,\r\n // 从 ChatOptions 映射 tool_choice\r\n tool_choice: opts?.toolChoice as DeepSeekToolChoice | undefined,\r\n };\r\n\r\n if (opts?.tools && opts.tools.length > 0) {\r\n request.tools = opts.tools;\r\n }\r\n\r\n // 2. 清空事件映射器状态\r\n this.#mapper.reset();\r\n\r\n // 3. 使用协议层函数发请求,并通过 mapper 映射事件\r\n const stream = protocolStream(\r\n this.#client,\r\n this.#baseUrl,\r\n this.#apiKey,\r\n request,\r\n { signal: opts?.signal, idleTimeoutMs: this.#idleTimeoutMs },\r\n );\r\n\r\n for await (const rawChunk of stream) {\r\n const events = this.#mapper.mapEvent(rawChunk);\r\n for (const event of events) {\r\n yield event;\r\n }\r\n }\r\n }\r\n}\r\n"],"mappings":"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","names":[]}
|