@deepwhale/llm 1.0.0
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/anthropic-client.d.ts +147 -0
- package/dist/anthropic-client.d.ts.map +1 -0
- package/dist/anthropic-client.js +443 -0
- package/dist/anthropic-client.js.map +1 -0
- package/dist/canonicalize-schema.d.ts +40 -0
- package/dist/canonicalize-schema.d.ts.map +1 -0
- package/dist/canonicalize-schema.js +93 -0
- package/dist/canonicalize-schema.js.map +1 -0
- package/dist/deepseek-client.d.ts +106 -0
- package/dist/deepseek-client.d.ts.map +1 -0
- package/dist/deepseek-client.js +395 -0
- package/dist/deepseek-client.js.map +1 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +27 -0
- package/dist/index.js.map +1 -0
- package/dist/parse.d.ts +53 -0
- package/dist/parse.d.ts.map +1 -0
- package/dist/parse.js +285 -0
- package/dist/parse.js.map +1 -0
- package/dist/pricing-config.d.ts +104 -0
- package/dist/pricing-config.d.ts.map +1 -0
- package/dist/pricing-config.js +186 -0
- package/dist/pricing-config.js.map +1 -0
- package/dist/pricing.default.toml +46 -0
- package/dist/types.d.ts +255 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +70 -0
- package/dist/types.js.map +1 -0
- package/package.json +28 -0
- package/src/pricing.default.toml +46 -0
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @deepwhale/llm — AnthropicClient
|
|
3
|
+
*
|
|
4
|
+
* Sprint 1b.5 Step 2: Anthropic provider, 走 /anthropic endpoint 薄包装方案 (D1 拍板).
|
|
5
|
+
*
|
|
6
|
+
* 关键设计 (R-D 拍板 2026-06-03):
|
|
7
|
+
* - 用官方 @anthropic-ai/sdk 实例发请求, 不手写 fetch
|
|
8
|
+
* - SDK opts.fetch 注入是设计意图内的 escape hatch (Cloudflare Workers / Deno
|
|
9
|
+
* proxy), 我们用同 pattern 注入 mock fetch 用于测试, 真实部署走 SDK 实际 fetch
|
|
10
|
+
* - baseURL 落 https://api.deepseek.com/anthropic, authToken 复用 DEEPSEEK_API_KEY
|
|
11
|
+
* (DeepSeek shim 接 /anthropic 路径, 同 key 验证) — **不**直连 api.anthropic.com
|
|
12
|
+
* - 响应是 Anthropic-shape Message, 写 parseAnthropicMessage 翻译成 ChatResult
|
|
13
|
+
* - SSE 走 SDK MessageStream, 写 parseAnthropicSseEvent 翻译 RawMessageStreamEvent
|
|
14
|
+
* → ChatChunk. 不复用 parseOai* (那是 OAI shape, 协议不同)
|
|
15
|
+
* - X3 拍板: Step 2 不接真 API, 测试用 mock fetch, 不碰 key
|
|
16
|
+
*
|
|
17
|
+
* Cache 字段 (B1 拍板): Anthropic 有 cache_creation_input_tokens (新建) +
|
|
18
|
+
* cache_read_input_tokens (命中) 两个**独立**字段. 我们合并到 cached_tokens:
|
|
19
|
+
* cached_tokens = (cache_creation ?? 0) + (cache_read ?? 0)
|
|
20
|
+
* cache_creation 详细拆解留 sprint 2 改进 (跟 cache_hit_rate / cost_turn 一起).
|
|
21
|
+
*
|
|
22
|
+
* StopReason 翻译 (Anthropic → OAI-shape finish_reason):
|
|
23
|
+
* 'end_turn' → 'stop'
|
|
24
|
+
* 'stop_sequence' → 'stop'
|
|
25
|
+
* 'max_tokens' → 'length'
|
|
26
|
+
* 'tool_use' → 'tool_calls'
|
|
27
|
+
* null (in-flight) → undefined
|
|
28
|
+
*/
|
|
29
|
+
import type { Message as AnthropicMessage, MessageDeltaUsage, RawMessageStreamEvent, Usage as AnthropicUsage } from '@anthropic-ai/sdk/resources/messages/messages.js';
|
|
30
|
+
import type { ChatChunk, ChatMessage, ChatResult, LLMClient, LLMToolSchema, ModelId, Usage } from './types.js';
|
|
31
|
+
import type { PricingConfig } from './pricing-config.js';
|
|
32
|
+
/** DeepSeek shim 提供的 /anthropic 兼容端点 (相对 OAI v1 端点). */
|
|
33
|
+
export declare const DEEPSEEK_ANTHROPIC_BASE_URL = "https://api.deepseek.com/anthropic";
|
|
34
|
+
/** Anthropic 默认 model (走 DeepSeek shim 时, 服务端映射到 Claude Sonnet 4.5). */
|
|
35
|
+
export declare const ANTHROPIC_DEFAULT_MODEL = "claude-sonnet-4-5";
|
|
36
|
+
export interface AnthropicClientOptions {
|
|
37
|
+
/** API key。优先于 process.env.ANTHROPIC_AUTH_TOKEN / DEEPSEEK_API_KEY。 */
|
|
38
|
+
apiKey?: string;
|
|
39
|
+
/** 模型 ID,默认 claude-sonnet-4-5。 */
|
|
40
|
+
model?: string;
|
|
41
|
+
/**
|
|
42
|
+
* Base URL。
|
|
43
|
+
*
|
|
44
|
+
* 拍板 1C (2026-06-04): **DeepSeek-first + Anthropic SDK 协议兼容 + 多 Provider Adapter**。
|
|
45
|
+
* 默认 `https://api.deepseek.com/anthropic` (DeepSeek 提供的 /anthropic 兼容端点, 单 key 走两家),
|
|
46
|
+
* 但 caller 可显式指定其他 Anthropic-兼容 provider:
|
|
47
|
+
* - `https://api.anthropic.com` — 真 Anthropic API (需 ANTHROPIC_AUTH_TOKEN 真 key, 不走 DEEPSEEK_API_KEY 退路)
|
|
48
|
+
* - `https://openrouter.ai/api/v1/anthropic` — OpenRouter Anthropic Route
|
|
49
|
+
* - 任何自定义 proxy / 兼容层
|
|
50
|
+
*
|
|
51
|
+
* Sprint 1d.5-B 揭示: DeepSeek /anthropic 端点**实际** routing 兜底到 OAI flash (server.model=deepseek-v4-flash),
|
|
52
|
+
* 行为稳定但 server 协议声明 mis-labeled. 1C 拍板**不**解决这个 routing 问题 — 现状保留, caller
|
|
53
|
+
* 自选 baseUrl 决定走哪条路.
|
|
54
|
+
*/
|
|
55
|
+
baseUrl?: string;
|
|
56
|
+
/** 自定义 fetch (注入 mock 用于测试). 默认走 SDK 内部 fetch. */
|
|
57
|
+
fetchImpl?: typeof fetch;
|
|
58
|
+
/** 单次 HTTP 调用的超时毫秒,默认 60s。 */
|
|
59
|
+
timeoutMs?: number;
|
|
60
|
+
/**
|
|
61
|
+
* pricing config override. 不传 → 走 ship-in default.toml.
|
|
62
|
+
* Sprint 1b.5 pricing 抽象层 (per-model currency). 详见 pricing-config.ts.
|
|
63
|
+
*/
|
|
64
|
+
pricing?: PricingConfig;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* AnthropicClient implements LLMClient.
|
|
68
|
+
*
|
|
69
|
+
* 内部包一个 @anthropic-ai/sdk 实例, .messages.create() / .messages.stream()
|
|
70
|
+
* 走 SDK 真实 HTTP 路径. 我们负责:
|
|
71
|
+
* - ChatMessage → Anthropic.MessageParam 转换 (Sprint 1c 再加 tool_use schema)
|
|
72
|
+
* - Anthropic.Message → ChatResult 转换 (parseAnthropicMessage)
|
|
73
|
+
* - RawMessageStreamEvent → ChatChunk 转换 (parseAnthropicSseEvent, 含 usage 翻译)
|
|
74
|
+
*
|
|
75
|
+
* 不**在**这里写 HTTP / SSE 解析 (SDK 负责), 不**在**这里做重试 (SDK 自带 maxRetries=2,
|
|
76
|
+
* 我们接受默认). Sprint 1c 集成测真接 shim 时可调整 SDK timeout / maxRetries.
|
|
77
|
+
*/
|
|
78
|
+
export declare class AnthropicClient implements LLMClient {
|
|
79
|
+
readonly model: ModelId;
|
|
80
|
+
private readonly apiKey;
|
|
81
|
+
private readonly baseUrl;
|
|
82
|
+
private readonly timeoutMs;
|
|
83
|
+
private readonly pricing;
|
|
84
|
+
private readonly sdk;
|
|
85
|
+
constructor(options?: AnthropicClientOptions);
|
|
86
|
+
chat(messages: ChatMessage[], options?: {
|
|
87
|
+
signal?: AbortSignal;
|
|
88
|
+
tools?: ReadonlyArray<LLMToolSchema>;
|
|
89
|
+
tool_choice?: 'auto' | 'none' | 'required';
|
|
90
|
+
}): Promise<ChatResult>;
|
|
91
|
+
stream(messages: ChatMessage[], options: {
|
|
92
|
+
signal?: AbortSignal;
|
|
93
|
+
tools?: ReadonlyArray<LLMToolSchema>;
|
|
94
|
+
tool_choice?: 'auto' | 'none' | 'required';
|
|
95
|
+
onChunk: (chunk: ChatChunk) => void;
|
|
96
|
+
}): Promise<ChatResult>;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* 把 Anthropic SDK 的 Message 翻译成 ChatResult.
|
|
100
|
+
*
|
|
101
|
+
* - content: 拼 text block, 跳过 tool_use (1c 实施) / thinking (1b.5 不暴露)
|
|
102
|
+
* - stop_reason → finish_reason 翻译
|
|
103
|
+
* - usage: cache_creation + cache_read → cached_tokens (B1 拍板). 算 cache_hit_rate
|
|
104
|
+
* + cost_turn + tokens_uncached 跟 OAI 路径一致.
|
|
105
|
+
*/
|
|
106
|
+
export declare function parseAnthropicMessage(message: AnthropicMessage, fallbackModel: ModelId, pricing?: PricingConfig): ChatResult;
|
|
107
|
+
/**
|
|
108
|
+
* 把 Anthropic Usage 翻译成标准化 Usage 结构 (带 cache/cost 字段).
|
|
109
|
+
*
|
|
110
|
+
* B1 拍板 (Sprint 1b.5 Step 2): cached_tokens = (cache_creation ?? 0) + (cache_read ?? 0).
|
|
111
|
+
*
|
|
112
|
+
* ⚠️ 重要语义修正 (F4 拍板 2026-06-03, review 找到):
|
|
113
|
+
* Anthropic 官方 prompt caching 文档: total input tokens = input_tokens + cache_creation_input_tokens
|
|
114
|
+
* + cache_read_input_tokens. 之前 Step 2 写时把 input_tokens 当"总 prompt" 是错的, 漏算 cache
|
|
115
|
+
* 字段对应的 token 总量. 修法:
|
|
116
|
+
*
|
|
117
|
+
* - total_prompt = input_tokens + cache_creation + cache_read
|
|
118
|
+
* - cached_tokens = cache_creation + cache_read (跟 Step 2 一致)
|
|
119
|
+
* - tokens_uncached = total_prompt - cached_tokens = input_tokens (不变量)
|
|
120
|
+
* - cache_hit_rate = cached_tokens / total_prompt (cache 命中率, 包括 write + read)
|
|
121
|
+
*
|
|
122
|
+
* **Cost 1b.5 保守策略** (F4 拍板): cache_creation 跟 cache_read 价格不同 (Sonnet 1h TTL write
|
|
123
|
+
* \$3.75/M, read \$0.30/M, 比例 12.5×), 1b.5 pricing 模型**不**拆 cache_write vs cache_read 字段.
|
|
124
|
+
* 为避免假装知道 cache_creation 价格, **保守**: cache_creation OR cache_read 任一非零 →
|
|
125
|
+
* cost_turn/cost_currency 字段 absent. 留 sprint 2 加 `cache_write_per_m` 字段.
|
|
126
|
+
* - 注意: tokens_uncached 仍**可**算 (是 input_tokens, 跟 cache 字段无关), 不受 cost 限制
|
|
127
|
+
*
|
|
128
|
+
* 接受 Usage | MessageDeltaUsage (后者无 cache_creation / cache_read, 视为 0).
|
|
129
|
+
* Sprint 1b.5 Step 2: 流末尾 message_delta event.usage 是 MessageDeltaUsage, 走 delta 路径
|
|
130
|
+
* 不算 cost, 真实生产靠 stream.finalMessage() 拿完整 Usage.
|
|
131
|
+
*/
|
|
132
|
+
export declare function parseAnthropicUsage(usage: AnthropicUsage | MessageDeltaUsage, fallbackModel: ModelId, pricing?: PricingConfig): Usage | undefined;
|
|
133
|
+
/**
|
|
134
|
+
* 把单个 RawMessageStreamEvent 翻译成 ChatChunk.
|
|
135
|
+
* 返 null 表示跳过 (heartbeat / ping / 不该透出的 event).
|
|
136
|
+
*
|
|
137
|
+
* Event 类型 (来自 SDK type):
|
|
138
|
+
* - message_start: 携带 message metadata (model, id, usage.input_tokens=0) — 不透出
|
|
139
|
+
* - content_block_start: 新的 text/tool_use/thinking block 起点 — 不透出 (避免 0 token 块)
|
|
140
|
+
* - content_block_delta: text 增量 (delta.text) 或 input_json_delta (tool_use) — 透出 content
|
|
141
|
+
* - content_block_stop: block 结束 — 不透出
|
|
142
|
+
* - message_delta: 顶层 delta (stop_reason + usage 更新) — 透出 finish_reason + 最终 usage
|
|
143
|
+
* - message_stop: 流结束 — 不透出 (caller 走 stream.finalMessage 拿 final Message)
|
|
144
|
+
* - ping: heartbeat — 不透出
|
|
145
|
+
*/
|
|
146
|
+
export declare function parseAnthropicSseEvent(event: RawMessageStreamEvent, fallbackModel: ModelId, pricing?: PricingConfig): ChatChunk | null;
|
|
147
|
+
//# sourceMappingURL=anthropic-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"anthropic-client.d.ts","sourceRoot":"","sources":["../src/anthropic-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAGH,OAAO,KAAK,EACV,OAAO,IAAI,gBAAgB,EAE3B,iBAAiB,EACjB,qBAAqB,EACrB,KAAK,IAAI,cAAc,EACxB,MAAM,kDAAkD,CAAC;AAM1D,OAAO,KAAK,EACV,SAAS,EACT,WAAW,EACX,UAAU,EACV,SAAS,EACT,aAAa,EACb,OAAO,EAEP,KAAK,EACN,MAAM,YAAY,CAAC;AAEpB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD,wDAAwD;AACxD,eAAO,MAAM,2BAA2B,uCAAuC,CAAC;AAEhF,wEAAwE;AACxE,eAAO,MAAM,uBAAuB,sBAAsB,CAAC;AAI3D,MAAM,WAAW,sBAAsB;IACrC,uEAAuE;IACvE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,kCAAkC;IAClC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,kDAAkD;IAClD,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IACzB,8BAA8B;IAC9B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,OAAO,CAAC,EAAE,aAAa,CAAC;CACzB;AAED;;;;;;;;;;;GAWG;AACH,qBAAa,eAAgB,YAAW,SAAS;IAC/C,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA4B;IACpD,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAY;gBAEpB,OAAO,GAAE,sBAA2B;IAwB1C,IAAI,CACR,QAAQ,EAAE,WAAW,EAAE,EACvB,OAAO,CAAC,EAAE;QACR,MAAM,CAAC,EAAE,WAAW,CAAC;QACrB,KAAK,CAAC,EAAE,aAAa,CAAC,aAAa,CAAC,CAAC;QACrC,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,UAAU,CAAC;KAC5C,GACA,OAAO,CAAC,UAAU,CAAC;IA0BhB,MAAM,CACV,QAAQ,EAAE,WAAW,EAAE,EACvB,OAAO,EAAE;QACP,MAAM,CAAC,EAAE,WAAW,CAAC;QACrB,KAAK,CAAC,EAAE,aAAa,CAAC,aAAa,CAAC,CAAC;QACrC,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,UAAU,CAAC;QAC3C,OAAO,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,IAAI,CAAC;KACrC,GACA,OAAO,CAAC,UAAU,CAAC;CAsCvB;AAqID;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,gBAAgB,EACzB,aAAa,EAAE,OAAO,EACtB,OAAO,CAAC,EAAE,aAAa,GACtB,UAAU,CA8BZ;AAiBD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,cAAc,GAAG,iBAAiB,EACzC,aAAa,EAAE,OAAO,EACtB,OAAO,CAAC,EAAE,aAAa,GACtB,KAAK,GAAG,SAAS,CA6CnB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,qBAAqB,EAC5B,aAAa,EAAE,OAAO,EACtB,OAAO,CAAC,EAAE,aAAa,GACtB,SAAS,GAAG,IAAI,CA2BlB"}
|
|
@@ -0,0 +1,443 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @deepwhale/llm — AnthropicClient
|
|
3
|
+
*
|
|
4
|
+
* Sprint 1b.5 Step 2: Anthropic provider, 走 /anthropic endpoint 薄包装方案 (D1 拍板).
|
|
5
|
+
*
|
|
6
|
+
* 关键设计 (R-D 拍板 2026-06-03):
|
|
7
|
+
* - 用官方 @anthropic-ai/sdk 实例发请求, 不手写 fetch
|
|
8
|
+
* - SDK opts.fetch 注入是设计意图内的 escape hatch (Cloudflare Workers / Deno
|
|
9
|
+
* proxy), 我们用同 pattern 注入 mock fetch 用于测试, 真实部署走 SDK 实际 fetch
|
|
10
|
+
* - baseURL 落 https://api.deepseek.com/anthropic, authToken 复用 DEEPSEEK_API_KEY
|
|
11
|
+
* (DeepSeek shim 接 /anthropic 路径, 同 key 验证) — **不**直连 api.anthropic.com
|
|
12
|
+
* - 响应是 Anthropic-shape Message, 写 parseAnthropicMessage 翻译成 ChatResult
|
|
13
|
+
* - SSE 走 SDK MessageStream, 写 parseAnthropicSseEvent 翻译 RawMessageStreamEvent
|
|
14
|
+
* → ChatChunk. 不复用 parseOai* (那是 OAI shape, 协议不同)
|
|
15
|
+
* - X3 拍板: Step 2 不接真 API, 测试用 mock fetch, 不碰 key
|
|
16
|
+
*
|
|
17
|
+
* Cache 字段 (B1 拍板): Anthropic 有 cache_creation_input_tokens (新建) +
|
|
18
|
+
* cache_read_input_tokens (命中) 两个**独立**字段. 我们合并到 cached_tokens:
|
|
19
|
+
* cached_tokens = (cache_creation ?? 0) + (cache_read ?? 0)
|
|
20
|
+
* cache_creation 详细拆解留 sprint 2 改进 (跟 cache_hit_rate / cost_turn 一起).
|
|
21
|
+
*
|
|
22
|
+
* StopReason 翻译 (Anthropic → OAI-shape finish_reason):
|
|
23
|
+
* 'end_turn' → 'stop'
|
|
24
|
+
* 'stop_sequence' → 'stop'
|
|
25
|
+
* 'max_tokens' → 'length'
|
|
26
|
+
* 'tool_use' → 'tool_calls'
|
|
27
|
+
* null (in-flight) → undefined
|
|
28
|
+
*/
|
|
29
|
+
import Anthropic from '@anthropic-ai/sdk';
|
|
30
|
+
import { readFileSync } from 'node:fs';
|
|
31
|
+
import { fileURLToPath } from 'node:url';
|
|
32
|
+
import { dirname, resolve } from 'node:path';
|
|
33
|
+
import process from 'node:process';
|
|
34
|
+
import { APIKeyMissingError, LLMUnknownError } from './types.js';
|
|
35
|
+
import { computeCost, parsePricingConfig } from './pricing-config.js';
|
|
36
|
+
/** DeepSeek shim 提供的 /anthropic 兼容端点 (相对 OAI v1 端点). */
|
|
37
|
+
export const DEEPSEEK_ANTHROPIC_BASE_URL = 'https://api.deepseek.com/anthropic';
|
|
38
|
+
/** Anthropic 默认 model (走 DeepSeek shim 时, 服务端映射到 Claude Sonnet 4.5). */
|
|
39
|
+
export const ANTHROPIC_DEFAULT_MODEL = 'claude-sonnet-4-5';
|
|
40
|
+
const DEFAULT_TIMEOUT_MS = 60_000;
|
|
41
|
+
/**
|
|
42
|
+
* AnthropicClient implements LLMClient.
|
|
43
|
+
*
|
|
44
|
+
* 内部包一个 @anthropic-ai/sdk 实例, .messages.create() / .messages.stream()
|
|
45
|
+
* 走 SDK 真实 HTTP 路径. 我们负责:
|
|
46
|
+
* - ChatMessage → Anthropic.MessageParam 转换 (Sprint 1c 再加 tool_use schema)
|
|
47
|
+
* - Anthropic.Message → ChatResult 转换 (parseAnthropicMessage)
|
|
48
|
+
* - RawMessageStreamEvent → ChatChunk 转换 (parseAnthropicSseEvent, 含 usage 翻译)
|
|
49
|
+
*
|
|
50
|
+
* 不**在**这里写 HTTP / SSE 解析 (SDK 负责), 不**在**这里做重试 (SDK 自带 maxRetries=2,
|
|
51
|
+
* 我们接受默认). Sprint 1c 集成测真接 shim 时可调整 SDK timeout / maxRetries.
|
|
52
|
+
*/
|
|
53
|
+
export class AnthropicClient {
|
|
54
|
+
model;
|
|
55
|
+
apiKey;
|
|
56
|
+
baseUrl;
|
|
57
|
+
timeoutMs;
|
|
58
|
+
pricing;
|
|
59
|
+
sdk;
|
|
60
|
+
constructor(options = {}) {
|
|
61
|
+
this.apiKey = options.apiKey ?? resolveApiKey();
|
|
62
|
+
if (this.apiKey === '') {
|
|
63
|
+
throw new APIKeyMissingError('Anthropic API key not set. Set ANTHROPIC_AUTH_TOKEN or DEEPSEEK_API_KEY env var, ' +
|
|
64
|
+
'or pass apiKey option.');
|
|
65
|
+
}
|
|
66
|
+
this.model = (options.model ?? ANTHROPIC_DEFAULT_MODEL);
|
|
67
|
+
this.baseUrl = options.baseUrl ?? DEEPSEEK_ANTHROPIC_BASE_URL;
|
|
68
|
+
this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
69
|
+
this.pricing = options.pricing ?? loadDefaultPricing();
|
|
70
|
+
// SDK opts.fetch 注入是设计意图内的 escape hatch (测试 mock + Cloudflare
|
|
71
|
+
// Workers / Deno proxy). 真实生产不传, SDK 走全局 fetch.
|
|
72
|
+
const sdkOptions = {
|
|
73
|
+
authToken: this.apiKey,
|
|
74
|
+
baseURL: this.baseUrl,
|
|
75
|
+
timeout: this.timeoutMs,
|
|
76
|
+
};
|
|
77
|
+
if (options.fetchImpl !== undefined)
|
|
78
|
+
sdkOptions.fetch = options.fetchImpl;
|
|
79
|
+
this.sdk = new Anthropic(sdkOptions);
|
|
80
|
+
}
|
|
81
|
+
async chat(messages, options) {
|
|
82
|
+
// Sprint 1c.5 拍板 (1c-revive-2-B-1, 2026-06-04): tool schema 转换 (OAI {parameters} → Anthropic
|
|
83
|
+
// {input_schema}), 跟 DeepSeekClient 同 LLMClient 契约 (5-7 行 production 改).
|
|
84
|
+
// 跟 pi-agent 4-layer 模式: model layer (AnthropicClient) 不知道 tool registry 细节, 只做协议转换.
|
|
85
|
+
const body = toAnthropicMessages(messages, options?.tools);
|
|
86
|
+
const createParams = {
|
|
87
|
+
model: this.model,
|
|
88
|
+
messages: body.messages,
|
|
89
|
+
max_tokens: 4096, // Anthropic API 必填, 4096 是合理默认 (Sprint 1c 让 caller 传)
|
|
90
|
+
...(body.system !== undefined ? { system: body.system } : {}),
|
|
91
|
+
...(body.tools !== undefined ? { tools: body.tools } : {}),
|
|
92
|
+
...(options?.tool_choice !== undefined ? { tool_choice: mapToolChoice(options.tool_choice) } : {}),
|
|
93
|
+
};
|
|
94
|
+
const sdkOptions = {
|
|
95
|
+
...(options?.signal !== undefined ? { signal: options.signal } : {}),
|
|
96
|
+
};
|
|
97
|
+
let response;
|
|
98
|
+
try {
|
|
99
|
+
response = await this.sdk.messages.create(createParams, sdkOptions);
|
|
100
|
+
}
|
|
101
|
+
catch (e) {
|
|
102
|
+
throw mapSdkError(e);
|
|
103
|
+
}
|
|
104
|
+
return parseAnthropicMessage(response, this.model, this.pricing);
|
|
105
|
+
}
|
|
106
|
+
async stream(messages, options) {
|
|
107
|
+
const body = toAnthropicMessages(messages, options.tools);
|
|
108
|
+
const streamParams = {
|
|
109
|
+
model: this.model,
|
|
110
|
+
messages: body.messages,
|
|
111
|
+
max_tokens: 4096,
|
|
112
|
+
...(body.system !== undefined ? { system: body.system } : {}),
|
|
113
|
+
...(body.tools !== undefined ? { tools: body.tools } : {}),
|
|
114
|
+
...(options.tool_choice !== undefined ? { tool_choice: mapToolChoice(options.tool_choice) } : {}),
|
|
115
|
+
};
|
|
116
|
+
const sdkOptions = {
|
|
117
|
+
...(options.signal !== undefined ? { signal: options.signal } : {}),
|
|
118
|
+
};
|
|
119
|
+
// SDK 的 messages.stream() 返 MessageStream (异步可迭代 + EventEmitter).
|
|
120
|
+
// 跟 for await 一起用, 跟 OAI SSE 处理模式不同 — 这里是 SDK 自家 stream,
|
|
121
|
+
// 不是 SSE 字节流. parseAnthropicSseEvent 负责把每个 RawMessageStreamEvent
|
|
122
|
+
// 翻译成 ChatChunk.
|
|
123
|
+
const stream = this.sdk.messages.stream(streamParams, sdkOptions);
|
|
124
|
+
let finalMessage;
|
|
125
|
+
for await (const event of stream) {
|
|
126
|
+
const chunk = parseAnthropicSseEvent(event, this.model, this.pricing);
|
|
127
|
+
if (chunk !== null) {
|
|
128
|
+
options.onChunk(chunk);
|
|
129
|
+
// 收到 message_stop 时, SDK 也提供 finalMessage(), 但我们靠 event 流本身
|
|
130
|
+
// 拼出 stop_reason + 完整 usage. 留 1c 集成测时优化 (现 8 tests 覆盖 main path).
|
|
131
|
+
}
|
|
132
|
+
// SDK 的 message_stop event 不携带 final message, 需调 stream.finalMessage()
|
|
133
|
+
// 才能拿到. 简化: 收完流后从 stream 实例读 final message.
|
|
134
|
+
if (event.type === 'message_stop') {
|
|
135
|
+
finalMessage = await stream.finalMessage();
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
if (finalMessage === undefined) {
|
|
139
|
+
throw new LLMUnknownError('Anthropic stream ended without message_stop event');
|
|
140
|
+
}
|
|
141
|
+
return parseAnthropicMessage(finalMessage, this.model, this.pricing);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
// ---- 私有 helper ----
|
|
145
|
+
function resolveApiKey() {
|
|
146
|
+
// 优先 ANTHROPIC_AUTH_TOKEN (Anthropic SDK 标准), 退到 DEEPSEEK_API_KEY (Sprint 1b.5 shim).
|
|
147
|
+
const anthropic = process.env['ANTHROPIC_AUTH_TOKEN'];
|
|
148
|
+
if (anthropic !== undefined && anthropic !== '')
|
|
149
|
+
return anthropic;
|
|
150
|
+
const deepseek = process.env['DEEPSEEK_API_KEY'];
|
|
151
|
+
if (deepseek !== undefined && deepseek !== '')
|
|
152
|
+
return deepseek;
|
|
153
|
+
return '';
|
|
154
|
+
}
|
|
155
|
+
function loadDefaultPricing() {
|
|
156
|
+
try {
|
|
157
|
+
// 跟 DeepSeekClient 走同 pattern: readFileSync pricing.default.toml relative to dist/.
|
|
158
|
+
// Sprint 1c 集成测时检查这条路径在 ESM 打包后是否仍 OK. Step 2 单测用 mock fetch,
|
|
159
|
+
// 不走 loadDefaultPricing (test fixture 显式传 pricing).
|
|
160
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
161
|
+
const defaultPath = resolve(here, 'pricing.default.toml');
|
|
162
|
+
const tomlText = readFileSync(defaultPath, 'utf-8');
|
|
163
|
+
return parsePricingConfig(tomlText);
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
return undefined;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
function mapSdkError(e) {
|
|
170
|
+
// SDK 错误分类: APIConnectionError / APIConnectionTimeoutError / RateLimitError /
|
|
171
|
+
// AuthenticationError / BadRequestError / InternalServerError 等. 简化: 透传
|
|
172
|
+
// SDK Error name + message 到 LLMUnknownError. Sprint 1c 集成测时细化 1:1 映射
|
|
173
|
+
// 到 LLMRateLimitError / LLMAuthError / LLMNetworkError (跟 DeepSeekClient 一致).
|
|
174
|
+
if (e instanceof Error)
|
|
175
|
+
return new LLMUnknownError(`Anthropic SDK: ${e.message}`, { cause: e });
|
|
176
|
+
return new LLMUnknownError(`Anthropic SDK: ${String(e)}`);
|
|
177
|
+
}
|
|
178
|
+
/** 拆分 system / 非 system messages (Anthropic 协议 system 是顶层字段). */
|
|
179
|
+
function toAnthropicMessages(messages, tools) {
|
|
180
|
+
const out = [];
|
|
181
|
+
let system;
|
|
182
|
+
// Sprint 1c-revive-2-D-4-1 (P38, 2026-06-04): 合并连续 tool 消息到 1 个 user 消息
|
|
183
|
+
// (Anthropic 协议要求 N 个 tool_use 紧跟 1 个 user 消息含 N 个 tool_result blocks).
|
|
184
|
+
// 1c.5 拍板时 1-tool-call 路径碰巧合法 (N=1 时独立 user 消息仍可), 多 tool_calls 揭示.
|
|
185
|
+
let pendingToolResults;
|
|
186
|
+
const flushToolResults = () => {
|
|
187
|
+
if (pendingToolResults !== undefined && pendingToolResults.length > 0) {
|
|
188
|
+
out.push({
|
|
189
|
+
role: 'user',
|
|
190
|
+
content: pendingToolResults,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
pendingToolResults = undefined;
|
|
194
|
+
};
|
|
195
|
+
for (const m of messages) {
|
|
196
|
+
if (m.role === 'system') {
|
|
197
|
+
// 多条 system 合并 (Anthropic system 是单 string, 重复 system 罕见)
|
|
198
|
+
system = system === undefined ? m.content : `${system}\n\n${m.content}`;
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
if (m.role === 'tool') {
|
|
202
|
+
// 拍板 (D-4-1): 累积 tool_result 进 pendingToolResults (跟下一个 tool 消息合并).
|
|
203
|
+
// flush 时机: 1) 遇到非 tool 角色, 2) loop 结束.
|
|
204
|
+
if (pendingToolResults === undefined) {
|
|
205
|
+
pendingToolResults = [];
|
|
206
|
+
}
|
|
207
|
+
pendingToolResults.push({
|
|
208
|
+
type: 'tool_result',
|
|
209
|
+
tool_use_id: m.tool_call_id ?? '',
|
|
210
|
+
content: m.content,
|
|
211
|
+
});
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
// 非 tool 角色: 先 flush pending tool_results (如果有)
|
|
215
|
+
flushToolResults();
|
|
216
|
+
if (m.role === 'user') {
|
|
217
|
+
out.push({ role: 'user', content: m.content });
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
// assistant: OAI tool_calls → Anthropic content blocks (text + tool_use)
|
|
221
|
+
if (m.role === 'assistant') {
|
|
222
|
+
if (m.tool_calls !== undefined && m.tool_calls.length > 0) {
|
|
223
|
+
const blocks = m.tool_calls.map((tc) => ({
|
|
224
|
+
type: 'tool_use',
|
|
225
|
+
id: tc.id,
|
|
226
|
+
name: tc.name,
|
|
227
|
+
input: tc.args,
|
|
228
|
+
}));
|
|
229
|
+
out.push({
|
|
230
|
+
role: 'assistant',
|
|
231
|
+
// ToolUseBlockParam[] 在 SDK 类型上是 ContentBlockParam[] 的子集, 但 TS 4.x 推断不到
|
|
232
|
+
// (SDK 用 union 反推, 编译期会失配). 显式 cast: 真实运行时 server 接受.
|
|
233
|
+
content: blocks,
|
|
234
|
+
});
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
out.push({ role: 'assistant', content: m.content });
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
// loop 结束: flush 最后一批 tool_results (避免末尾独立 tool 消息丢失)
|
|
242
|
+
flushToolResults();
|
|
243
|
+
// tool schema: OAI {name, description, parameters} → Anthropic {name, description, input_schema}
|
|
244
|
+
// 1c.5 拍板: 走 Tool 类型 (跟 SDK 对齐), 不拆 ToolUnion (Bash20250124 等 built-in 工具暂不用).
|
|
245
|
+
let anthropicTools;
|
|
246
|
+
if (tools !== undefined && tools.length > 0) {
|
|
247
|
+
anthropicTools = tools.map((t) => ({
|
|
248
|
+
name: t.name,
|
|
249
|
+
description: t.description,
|
|
250
|
+
input_schema: t.parameters,
|
|
251
|
+
}));
|
|
252
|
+
}
|
|
253
|
+
const out2 = { system, messages: out };
|
|
254
|
+
if (anthropicTools !== undefined)
|
|
255
|
+
out2.tools = anthropicTools;
|
|
256
|
+
return out2;
|
|
257
|
+
}
|
|
258
|
+
/** Map LLMClient 通用 tool_choice (OAI 风格) → Anthropic ToolChoice. */
|
|
259
|
+
function mapToolChoice(choice) {
|
|
260
|
+
switch (choice) {
|
|
261
|
+
case 'auto':
|
|
262
|
+
return { type: 'auto' };
|
|
263
|
+
case 'none':
|
|
264
|
+
return { type: 'none' };
|
|
265
|
+
case 'required':
|
|
266
|
+
return { type: 'any' }; // Anthropic 协议 'any' 强制至少调 1 个, 跟 OAI 'required' 等价
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
// ---- 解析层: Anthropic.Message / RawMessageStreamEvent → ChatResult / ChatChunk ----
|
|
270
|
+
/**
|
|
271
|
+
* 把 Anthropic SDK 的 Message 翻译成 ChatResult.
|
|
272
|
+
*
|
|
273
|
+
* - content: 拼 text block, 跳过 tool_use (1c 实施) / thinking (1b.5 不暴露)
|
|
274
|
+
* - stop_reason → finish_reason 翻译
|
|
275
|
+
* - usage: cache_creation + cache_read → cached_tokens (B1 拍板). 算 cache_hit_rate
|
|
276
|
+
* + cost_turn + tokens_uncached 跟 OAI 路径一致.
|
|
277
|
+
*/
|
|
278
|
+
export function parseAnthropicMessage(message, fallbackModel, pricing) {
|
|
279
|
+
// 拼 text + 提取 tool_use (1c.5 实施, 1b.5 留空)
|
|
280
|
+
let content = '';
|
|
281
|
+
const toolCalls = [];
|
|
282
|
+
for (const block of message.content) {
|
|
283
|
+
if (block.type === 'text') {
|
|
284
|
+
content = content === '' ? block.text : `${content}${block.text}`;
|
|
285
|
+
}
|
|
286
|
+
else if (block.type === 'tool_use') {
|
|
287
|
+
// Sprint 1c.5 (1c-revive-2-B-1): tool_use block → OAI-style ToolCall (跟 DeepSeek shape 对齐)
|
|
288
|
+
// Anthropic SDK 给 input: unknown, 我们假设是 parsed object (runToolLoop 给 args object)
|
|
289
|
+
const input = block.input;
|
|
290
|
+
const args = typeof input === 'object' && input !== null
|
|
291
|
+
? input
|
|
292
|
+
: {};
|
|
293
|
+
toolCalls.push({ id: block.id, name: block.name, args });
|
|
294
|
+
}
|
|
295
|
+
// thinking / redacted_thinking 跳过 (跟 1b.5 范围一致)
|
|
296
|
+
}
|
|
297
|
+
const finishReason = mapStopReason(message.stop_reason);
|
|
298
|
+
const usage = parseAnthropicUsage(message.usage, fallbackModel, pricing);
|
|
299
|
+
const model = message.model ?? fallbackModel;
|
|
300
|
+
const result = { model, content };
|
|
301
|
+
if (toolCalls.length > 0)
|
|
302
|
+
result.tool_calls = toolCalls;
|
|
303
|
+
if (usage !== undefined)
|
|
304
|
+
result.usage = usage;
|
|
305
|
+
if (finishReason !== undefined)
|
|
306
|
+
result.finish_reason = finishReason;
|
|
307
|
+
return result;
|
|
308
|
+
}
|
|
309
|
+
/** 翻译 Anthropic stop_reason → ChatResult['finish_reason']. */
|
|
310
|
+
function mapStopReason(stopReason) {
|
|
311
|
+
if (stopReason === null)
|
|
312
|
+
return undefined;
|
|
313
|
+
switch (stopReason) {
|
|
314
|
+
case 'end_turn':
|
|
315
|
+
return 'stop';
|
|
316
|
+
case 'stop_sequence':
|
|
317
|
+
return 'stop';
|
|
318
|
+
case 'max_tokens':
|
|
319
|
+
return 'length';
|
|
320
|
+
case 'tool_use':
|
|
321
|
+
return 'tool_calls';
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* 把 Anthropic Usage 翻译成标准化 Usage 结构 (带 cache/cost 字段).
|
|
326
|
+
*
|
|
327
|
+
* B1 拍板 (Sprint 1b.5 Step 2): cached_tokens = (cache_creation ?? 0) + (cache_read ?? 0).
|
|
328
|
+
*
|
|
329
|
+
* ⚠️ 重要语义修正 (F4 拍板 2026-06-03, review 找到):
|
|
330
|
+
* Anthropic 官方 prompt caching 文档: total input tokens = input_tokens + cache_creation_input_tokens
|
|
331
|
+
* + cache_read_input_tokens. 之前 Step 2 写时把 input_tokens 当"总 prompt" 是错的, 漏算 cache
|
|
332
|
+
* 字段对应的 token 总量. 修法:
|
|
333
|
+
*
|
|
334
|
+
* - total_prompt = input_tokens + cache_creation + cache_read
|
|
335
|
+
* - cached_tokens = cache_creation + cache_read (跟 Step 2 一致)
|
|
336
|
+
* - tokens_uncached = total_prompt - cached_tokens = input_tokens (不变量)
|
|
337
|
+
* - cache_hit_rate = cached_tokens / total_prompt (cache 命中率, 包括 write + read)
|
|
338
|
+
*
|
|
339
|
+
* **Cost 1b.5 保守策略** (F4 拍板): cache_creation 跟 cache_read 价格不同 (Sonnet 1h TTL write
|
|
340
|
+
* \$3.75/M, read \$0.30/M, 比例 12.5×), 1b.5 pricing 模型**不**拆 cache_write vs cache_read 字段.
|
|
341
|
+
* 为避免假装知道 cache_creation 价格, **保守**: cache_creation OR cache_read 任一非零 →
|
|
342
|
+
* cost_turn/cost_currency 字段 absent. 留 sprint 2 加 `cache_write_per_m` 字段.
|
|
343
|
+
* - 注意: tokens_uncached 仍**可**算 (是 input_tokens, 跟 cache 字段无关), 不受 cost 限制
|
|
344
|
+
*
|
|
345
|
+
* 接受 Usage | MessageDeltaUsage (后者无 cache_creation / cache_read, 视为 0).
|
|
346
|
+
* Sprint 1b.5 Step 2: 流末尾 message_delta event.usage 是 MessageDeltaUsage, 走 delta 路径
|
|
347
|
+
* 不算 cost, 真实生产靠 stream.finalMessage() 拿完整 Usage.
|
|
348
|
+
*/
|
|
349
|
+
export function parseAnthropicUsage(usage, fallbackModel, pricing) {
|
|
350
|
+
// MessageDeltaUsage (流末尾 message_delta 事件): 只有 output_tokens, 拿不到 input/cache.
|
|
351
|
+
// → 视为 delta 增量, 不算完整 cost. 真实生产靠 stream.finalMessage() 拿完整 Usage.
|
|
352
|
+
if (!('input_tokens' in usage)) {
|
|
353
|
+
return {
|
|
354
|
+
prompt_tokens: 0,
|
|
355
|
+
completion_tokens: usage.output_tokens,
|
|
356
|
+
total_tokens: usage.output_tokens,
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
// 此后 usage 一定是 AnthropicUsage (有 input_tokens + cache_creation + cache_read)
|
|
360
|
+
const full = usage;
|
|
361
|
+
// F4 修正: total_prompt = input + cache_creation + cache_read (官方文档)
|
|
362
|
+
const cacheCreation = full.cache_creation_input_tokens ?? 0;
|
|
363
|
+
const cacheRead = full.cache_read_input_tokens ?? 0;
|
|
364
|
+
const cached = cacheCreation + cacheRead;
|
|
365
|
+
const totalPrompt = full.input_tokens + cached;
|
|
366
|
+
const completion = full.output_tokens;
|
|
367
|
+
const total = totalPrompt + completion;
|
|
368
|
+
const out = {
|
|
369
|
+
prompt_tokens: totalPrompt, // 跟 DeepSeek OAI shape 字段对齐 (prompt = 全部输入, 含 cache)
|
|
370
|
+
completion_tokens: completion,
|
|
371
|
+
total_tokens: total,
|
|
372
|
+
};
|
|
373
|
+
if (cached > 0)
|
|
374
|
+
out.cached_tokens = cached;
|
|
375
|
+
// tokens_uncached 仍算 (跟 cache 字段无关, 跟 computeCost 内部算的 uncached 一致)
|
|
376
|
+
if (cached > 0)
|
|
377
|
+
out.tokens_uncached = full.input_tokens; // = totalPrompt - cached
|
|
378
|
+
// F4 保守: cache_creation OR cache_read 非零 → cost_turn 字段 absent. 留 sprint 2 加 cache_write_per_m 字段.
|
|
379
|
+
// 1b.5 pricing 模型只有 cache_miss / cache_hit / completion, 不能区分 cache_creation 跟
|
|
380
|
+
// cache_read 的不同价. 假装按 cache_hit 价算 cache_creation 会**低估** Sonnet 12.5×.
|
|
381
|
+
if (cached === 0) {
|
|
382
|
+
// 跟 parseOaiSseUsageField 一致, 透传 pricing + model 给 computeCost
|
|
383
|
+
// cached=0 (LLM 显式说 0 cache hit) → 走完整 4 字段路径, 跟 OAI shape 对齐
|
|
384
|
+
const breakdown = computeCost(pricing, fallbackModel, totalPrompt, completion, 0);
|
|
385
|
+
if (breakdown !== undefined) {
|
|
386
|
+
out.cache_hit_rate = breakdown.cache_hit_rate;
|
|
387
|
+
if (breakdown.cost_turn !== undefined)
|
|
388
|
+
out.cost_turn = breakdown.cost_turn;
|
|
389
|
+
if (breakdown.cost_currency !== undefined)
|
|
390
|
+
out.cost_currency = breakdown.cost_currency;
|
|
391
|
+
out.tokens_uncached = breakdown.tokens_uncached;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
else {
|
|
395
|
+
// cache_creation OR cache_read 非零: cost 字段 absent, 但 cache_hit_rate 仍算 (观测)
|
|
396
|
+
out.cache_hit_rate = totalPrompt > 0 ? cached / totalPrompt : 0;
|
|
397
|
+
}
|
|
398
|
+
return out;
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* 把单个 RawMessageStreamEvent 翻译成 ChatChunk.
|
|
402
|
+
* 返 null 表示跳过 (heartbeat / ping / 不该透出的 event).
|
|
403
|
+
*
|
|
404
|
+
* Event 类型 (来自 SDK type):
|
|
405
|
+
* - message_start: 携带 message metadata (model, id, usage.input_tokens=0) — 不透出
|
|
406
|
+
* - content_block_start: 新的 text/tool_use/thinking block 起点 — 不透出 (避免 0 token 块)
|
|
407
|
+
* - content_block_delta: text 增量 (delta.text) 或 input_json_delta (tool_use) — 透出 content
|
|
408
|
+
* - content_block_stop: block 结束 — 不透出
|
|
409
|
+
* - message_delta: 顶层 delta (stop_reason + usage 更新) — 透出 finish_reason + 最终 usage
|
|
410
|
+
* - message_stop: 流结束 — 不透出 (caller 走 stream.finalMessage 拿 final Message)
|
|
411
|
+
* - ping: heartbeat — 不透出
|
|
412
|
+
*/
|
|
413
|
+
export function parseAnthropicSseEvent(event, fallbackModel, pricing) {
|
|
414
|
+
switch (event.type) {
|
|
415
|
+
case 'content_block_delta': {
|
|
416
|
+
const delta = event.delta;
|
|
417
|
+
if (delta.type === 'text_delta') {
|
|
418
|
+
return { delta: { content: delta.text } };
|
|
419
|
+
}
|
|
420
|
+
// input_json_delta (tool_use) / thinking_delta / signature_delta: 1b.5 跳过
|
|
421
|
+
return null;
|
|
422
|
+
}
|
|
423
|
+
case 'message_delta': {
|
|
424
|
+
// 顶层 delta: stop_reason + usage. 这是流末尾的最终 usage 更新.
|
|
425
|
+
const finishReason = mapStopReason(event.delta.stop_reason);
|
|
426
|
+
const usage = parseAnthropicUsage(event.usage, fallbackModel, pricing);
|
|
427
|
+
const chunk = { delta: {} };
|
|
428
|
+
if (finishReason !== undefined)
|
|
429
|
+
chunk.finish_reason = finishReason;
|
|
430
|
+
if (usage !== undefined)
|
|
431
|
+
chunk.usage = usage;
|
|
432
|
+
// 1b.5 简化: 即使 chunk 是空 delta, 也透出 (caller 看 finish_reason 收尾)
|
|
433
|
+
return chunk;
|
|
434
|
+
}
|
|
435
|
+
// 其他 event 类型 (start/stop) 不透出. ping event 不在 union (SDK 内部 filter)
|
|
436
|
+
case 'message_start':
|
|
437
|
+
case 'content_block_start':
|
|
438
|
+
case 'content_block_stop':
|
|
439
|
+
case 'message_stop':
|
|
440
|
+
return null;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
//# sourceMappingURL=anthropic-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"anthropic-client.js","sourceRoot":"","sources":["../src/anthropic-client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical Schema (Prefix-cache 机制 4)
|
|
3
|
+
*
|
|
4
|
+
* 借鉴自 Reasonix `schema_canonicalize.go:10-67` —— OAI function-calling
|
|
5
|
+
* tool schema 在 build 前必须 key 顺序稳定, 否则 LLM 端 hash cache 抖动 →
|
|
6
|
+
* prefix-cache 命中率归零。
|
|
7
|
+
*
|
|
8
|
+
* 关键不变量:
|
|
9
|
+
* - object properties 字母序
|
|
10
|
+
* - required 数组字母序
|
|
11
|
+
* - enum 数组保持原序(enum 是有限值集合, 顺序是协议语义, 不能动)
|
|
12
|
+
* - 递归: nested object / array of object / array items
|
|
13
|
+
* - 纯函数 + 0 副作用 + 0 外部依赖
|
|
14
|
+
*
|
|
15
|
+
* Sprint 1b 范围: LLM 层, 不依赖 Tool 运行时类型
|
|
16
|
+
* (canonicalize 是 LLM wire-level 概念, 归属 @deepwhale/llm)
|
|
17
|
+
*/
|
|
18
|
+
import type { LLMToolSchema } from './types.js';
|
|
19
|
+
/**
|
|
20
|
+
* 把任意顺序的 tool schema 变成 key 顺序稳定的等价 schema。
|
|
21
|
+
*
|
|
22
|
+
* 返回**新**对象(深拷贝), 不修改入参。
|
|
23
|
+
* 同一 input 跑 N 次, output 严格相等(稳定性是 prefix-cache 的命根子)。
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* canonicalizeSchema({
|
|
27
|
+
* name: 'read',
|
|
28
|
+
* description: '...',
|
|
29
|
+
* parameters: {
|
|
30
|
+
* type: 'object',
|
|
31
|
+
* properties: { path: {...}, encoding: {...} },
|
|
32
|
+
* required: ['encoding', 'path'], // 任意顺序
|
|
33
|
+
* },
|
|
34
|
+
* })
|
|
35
|
+
* // =>
|
|
36
|
+
* // properties: { encoding: {...}, path: {...} } 字母序
|
|
37
|
+
* // required: ['encoding', 'path'] 字母序
|
|
38
|
+
*/
|
|
39
|
+
export declare function canonicalizeSchema(schema: LLMToolSchema): LLMToolSchema;
|
|
40
|
+
//# sourceMappingURL=canonicalize-schema.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"canonicalize-schema.d.ts","sourceRoot":"","sources":["../src/canonicalize-schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,aAAa,EAA+C,MAAM,YAAY,CAAC;AAE7F;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,aAAa,GAAG,aAAa,CAMvE"}
|