@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,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @deepwhale/llm — Pricing 配置 (Sprint 1b.5 Step 1)
|
|
3
|
+
*
|
|
4
|
+
* 把原本 hardcode 在 `types.ts:computeCostBreakdown` 的 V4-Flash pricing 抽到
|
|
5
|
+
* ship-in + 用户可覆盖的 TOML 配置。Per-model currency (DeepSeek CNY, Anthropic USD),
|
|
6
|
+
* 不在 UI 层做汇率换算 — UI 从 `cost_currency` 字段读 symbol (¥ / $)。
|
|
7
|
+
*
|
|
8
|
+
* ## 设计原则 (R7 / Step 0 拍板, 2026-06-03)
|
|
9
|
+
*
|
|
10
|
+
* 1. `loadPricingConfig(userPath)`:
|
|
11
|
+
* - 显式传 `userPath` 但文件不存在或非法 → **抛 PricingConfigParseError** (不静默)
|
|
12
|
+
* - 不传 → 加载 ship-in `pricing.default.toml`
|
|
13
|
+
*
|
|
14
|
+
* 2. `computeCost(pricing, model, ...)` 是**纯函数**: 无 console / 无 logger / 无 IO。
|
|
15
|
+
* 3 种返回路径 (`CostBreakdownResult` 联合类型):
|
|
16
|
+
* - 整体 `undefined`: 没 `cached_tokens` → 4 字段全 absent
|
|
17
|
+
* - `{ base, no cost }`: 有 cache 但 pricing 找不到 model → 有 cache_hit_rate / tokens_uncached, 缺 cost
|
|
18
|
+
* - 完整 4 字段: 找到
|
|
19
|
+
*
|
|
20
|
+
* 3. Warning 责任在 caller (client/mode 边界), 纯函数零副作用。
|
|
21
|
+
*
|
|
22
|
+
* 4. UI/RPC: `cost_turn` absent 时字段不显示 (absent 不是 null), 读 `cost_currency` 决 symbol。
|
|
23
|
+
*/
|
|
24
|
+
import { readFile } from 'node:fs/promises';
|
|
25
|
+
import { fileURLToPath } from 'node:url';
|
|
26
|
+
import { dirname, resolve } from 'node:path';
|
|
27
|
+
import { parse as parseToml } from 'smol-toml';
|
|
28
|
+
/**
|
|
29
|
+
* pricing TOML 解析/加载错误. `userPath` 显式传但文件不存在/非法时抛.
|
|
30
|
+
*/
|
|
31
|
+
export class PricingConfigParseError extends Error {
|
|
32
|
+
userPath;
|
|
33
|
+
name = 'PricingConfigParseError';
|
|
34
|
+
constructor(message, userPath, options) {
|
|
35
|
+
super(message);
|
|
36
|
+
this.userPath = userPath;
|
|
37
|
+
if (options?.cause !== undefined) {
|
|
38
|
+
this.cause = options.cause;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
// ---- 纯函数 (无 console, 无 IO) ----
|
|
43
|
+
/**
|
|
44
|
+
* 解析 TOML string → PricingConfig. 纯函数, 无 IO.
|
|
45
|
+
*
|
|
46
|
+
* 校验:
|
|
47
|
+
* - 必须有 `[models.xxx]` 块 (至少 1 个)
|
|
48
|
+
* - 每个 model 4 字段全必填 (cache_miss_per_m / cache_hit_per_m / completion_per_m / currency)
|
|
49
|
+
* - currency 必须在 'CNY' | 'USD' enum
|
|
50
|
+
* - 价格数字必须非负有限
|
|
51
|
+
*
|
|
52
|
+
* @throws PricingConfigParseError — malformed / 缺字段 / currency 错 / 数字非有限
|
|
53
|
+
*/
|
|
54
|
+
export function parsePricingConfig(toml) {
|
|
55
|
+
let raw;
|
|
56
|
+
try {
|
|
57
|
+
raw = parseToml(toml);
|
|
58
|
+
}
|
|
59
|
+
catch (cause) {
|
|
60
|
+
throw new PricingConfigParseError(`TOML parse failed: ${cause.message}`, '<inline>', { cause });
|
|
61
|
+
}
|
|
62
|
+
if (raw === null || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
63
|
+
throw new PricingConfigParseError('TOML root must be a table', '<inline>');
|
|
64
|
+
}
|
|
65
|
+
const root = raw;
|
|
66
|
+
const modelsRaw = root['models'];
|
|
67
|
+
if (modelsRaw === undefined || modelsRaw === null) {
|
|
68
|
+
throw new PricingConfigParseError('Missing required section: [models.*]', '<inline>');
|
|
69
|
+
}
|
|
70
|
+
if (typeof modelsRaw !== 'object' || Array.isArray(modelsRaw)) {
|
|
71
|
+
throw new PricingConfigParseError('Section [models.*] must be a table', '<inline>');
|
|
72
|
+
}
|
|
73
|
+
const models = {};
|
|
74
|
+
for (const [modelId, value] of Object.entries(modelsRaw)) {
|
|
75
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value)) {
|
|
76
|
+
throw new PricingConfigParseError(`[models.${modelId}] must be a table`, '<inline>');
|
|
77
|
+
}
|
|
78
|
+
const block = value;
|
|
79
|
+
const cacheMiss = block['cache_miss_per_m'];
|
|
80
|
+
const cacheHit = block['cache_hit_per_m'];
|
|
81
|
+
const completion = block['completion_per_m'];
|
|
82
|
+
const currency = block['currency'];
|
|
83
|
+
for (const [field, val] of [
|
|
84
|
+
['cache_miss_per_m', cacheMiss],
|
|
85
|
+
['cache_hit_per_m', cacheHit],
|
|
86
|
+
['completion_per_m', completion],
|
|
87
|
+
]) {
|
|
88
|
+
if (typeof val !== 'number' || !Number.isFinite(val) || val < 0) {
|
|
89
|
+
throw new PricingConfigParseError(`[models.${modelId}].${field} must be a non-negative finite number, got: ${JSON.stringify(val)}`, '<inline>');
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if (currency !== 'CNY' && currency !== 'USD') {
|
|
93
|
+
throw new PricingConfigParseError(`[models.${modelId}].currency must be 'CNY' or 'USD', got: ${JSON.stringify(currency)}`, '<inline>');
|
|
94
|
+
}
|
|
95
|
+
models[modelId] = {
|
|
96
|
+
cache_miss_per_m: cacheMiss,
|
|
97
|
+
cache_hit_per_m: cacheHit,
|
|
98
|
+
completion_per_m: completion,
|
|
99
|
+
currency,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
if (Object.keys(models).length === 0) {
|
|
103
|
+
throw new PricingConfigParseError('At least one model required in [models.*]', '<inline>');
|
|
104
|
+
}
|
|
105
|
+
return { models };
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* 纯函数, 无 console / 无 logger / 无 IO. 3 种返回路径见 `CostBreakdownResult`.
|
|
109
|
+
*
|
|
110
|
+
* 公式: tokens_uncached = max(0, prompt - cached)
|
|
111
|
+
* cache_hit_rate = cached / prompt (prompt=0 时 0, 避免除零)
|
|
112
|
+
* cost_turn = tokens_uncached * cache_miss_per_m/1e6
|
|
113
|
+
* + cached * cache_hit_per_m/1e6
|
|
114
|
+
* + completion * completion_per_m/1e6
|
|
115
|
+
*
|
|
116
|
+
* 把 /M 转 /token 必须除 1e6, 不要直接当 /token 用 (会大 1000×).
|
|
117
|
+
*/
|
|
118
|
+
export function computeCost(pricing, model, promptTokens, completionTokens, cachedTokens) {
|
|
119
|
+
if (cachedTokens === undefined) {
|
|
120
|
+
return undefined;
|
|
121
|
+
}
|
|
122
|
+
const tokensUncached = Math.max(0, promptTokens - cachedTokens);
|
|
123
|
+
const hitRate = promptTokens > 0 ? cachedTokens / promptTokens : 0;
|
|
124
|
+
// model undefined 或 pricing 找不到 model → 走 R7 中间路径
|
|
125
|
+
// (有 cached 但 cost 字段 absent). 不静默 fallback, 不抛错, 不写 0.
|
|
126
|
+
const modelPricing = model !== undefined ? pricing?.models[model] : undefined;
|
|
127
|
+
if (modelPricing === undefined) {
|
|
128
|
+
return {
|
|
129
|
+
cache_hit_rate: hitRate,
|
|
130
|
+
tokens_uncached: tokensUncached,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
const costTurn = tokensUncached * (modelPricing.cache_miss_per_m / 1_000_000) +
|
|
134
|
+
cachedTokens * (modelPricing.cache_hit_per_m / 1_000_000) +
|
|
135
|
+
completionTokens * (modelPricing.completion_per_m / 1_000_000);
|
|
136
|
+
return {
|
|
137
|
+
cache_hit_rate: hitRate,
|
|
138
|
+
tokens_uncached: tokensUncached,
|
|
139
|
+
cost_turn: costTurn,
|
|
140
|
+
cost_currency: modelPricing.currency,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
// ---- IO 函数 (一次启动加载, 注入到 client) ----
|
|
144
|
+
/**
|
|
145
|
+
* 加载 pricing config. 三种来源优先级 (高→低):
|
|
146
|
+
* 1. `userPath` 显式指定且存在 → 解析用户文件
|
|
147
|
+
* 2. `userPath` 显式指定但不存在或非法 → **抛 PricingConfigParseError** (不静默 fallback)
|
|
148
|
+
* 3. `userPath` 未传 → 加载 ship-in `pricing.default.toml`
|
|
149
|
+
*
|
|
150
|
+
* hot path 不应调 (走 client.pricing 字段). Step 2 的 AnthropicClient 启动时调一次.
|
|
151
|
+
*/
|
|
152
|
+
export async function loadPricingConfig(userPath) {
|
|
153
|
+
if (userPath === undefined) {
|
|
154
|
+
// 走 ship-in default: 跟 pricing-config.ts 同目录, build 后复制到 dist/.
|
|
155
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
156
|
+
// dist/pricing-config.js → dist/pricing.default.toml
|
|
157
|
+
const defaultPath = resolve(here, 'pricing.default.toml');
|
|
158
|
+
return await loadFromPath(defaultPath);
|
|
159
|
+
}
|
|
160
|
+
return await loadFromPath(userPath);
|
|
161
|
+
}
|
|
162
|
+
async function loadFromPath(path) {
|
|
163
|
+
let tomlText;
|
|
164
|
+
try {
|
|
165
|
+
tomlText = await readFile(path, 'utf-8');
|
|
166
|
+
}
|
|
167
|
+
catch (cause) {
|
|
168
|
+
const err = cause;
|
|
169
|
+
if (err.code === 'ENOENT') {
|
|
170
|
+
throw new PricingConfigParseError(`Pricing config not found at: ${path}`, path, { cause });
|
|
171
|
+
}
|
|
172
|
+
throw new PricingConfigParseError(`Failed to read pricing config at ${path}: ${err.message}`, path, { cause });
|
|
173
|
+
}
|
|
174
|
+
try {
|
|
175
|
+
return parsePricingConfig(tomlText);
|
|
176
|
+
}
|
|
177
|
+
catch (cause) {
|
|
178
|
+
if (cause instanceof PricingConfigParseError) {
|
|
179
|
+
// 重新抛, 用真实 path 替换 inner 错误的 '<inline>' 占位符.
|
|
180
|
+
// 保留原始 userPath (readonly 字段, 不用 spread 因为 class 自带字段).
|
|
181
|
+
throw new PricingConfigParseError(cause.message.replace('<inline>', path), path, { cause });
|
|
182
|
+
}
|
|
183
|
+
throw cause;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
//# sourceMappingURL=pricing-config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pricing-config.js","sourceRoot":"","sources":["../src/pricing-config.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# deepwhale 默认 pricing (2026-06)
|
|
2
|
+
# 用户可在 ~/.deepwhale/pricing.toml 覆盖 (--pricing-config <path> 后续可加, 1b.5 走 env)。
|
|
3
|
+
# **per-model currency**: DeepSeek CNY, Anthropic USD。不在 UI 层做汇率换算。
|
|
4
|
+
#
|
|
5
|
+
# **当前生产 model 2 个** (Q 拍板 2026-06-03): flash + pro.
|
|
6
|
+
# Only production DeepSeek models are supported. (Q 拍板 2026-06-04, Task 2 Gap 4 留痕)
|
|
7
|
+
# 不再加 deepseek-chat / deepseek-reasoner 老 alias — 那些是 1a/1b era 历史,
|
|
8
|
+
# 2026-07-24 后服务端 hard-fail, 老 session replay 走 R7 中间路径 (base 2 字段, cost 字段 absent).
|
|
9
|
+
#
|
|
10
|
+
# **信源唯一性**: DeepSeek pricing 永远以 https://api-docs.deepseek.com/quick_start/pricing
|
|
11
|
+
# 为唯一信源。Q 在 2026-06-03 review 时贴出官方 "模型细节" 页截屏纠正我之前 commit 用的
|
|
12
|
+
# 0.5/0.1/1.0 (来自 1b 时代印象 + 二手源, 未核对官方). 实际官方:
|
|
13
|
+
# - cache_miss_per_m = input 缓存未命中 (1M tokens)
|
|
14
|
+
# - cache_hit_per_m = input 缓存命中 (1M tokens)
|
|
15
|
+
# - completion_per_m = output (1M tokens)
|
|
16
|
+
# 注意: 1b follow-up P1 改的 0.01/0.1 fix 是 **公式 1000× off** — pricing 数字本身的 0.1
|
|
17
|
+
# 看上去像 0.1 ¥/M 但其实只是 0.5→0.1 cache hit 巧合小, 真正的官方是 0.02¥/M (5× off 真实价).
|
|
18
|
+
|
|
19
|
+
[models.deepseek-v4-flash]
|
|
20
|
+
cache_miss_per_m = 1.0 # ¥1 /M tokens (input 缓存未命中, 官方 api-docs.deepseek.com 2026-06)
|
|
21
|
+
cache_hit_per_m = 0.02 # ¥0.02 /M tokens (input 缓存命中, 官方)
|
|
22
|
+
completion_per_m = 2.0 # ¥2 /M tokens (output, 官方)
|
|
23
|
+
currency = "CNY"
|
|
24
|
+
|
|
25
|
+
[models.deepseek-v4-pro]
|
|
26
|
+
cache_miss_per_m = 3.0 # ¥3 /M tokens (input 缓存未命中, 官方)
|
|
27
|
+
cache_hit_per_m = 0.025 # ¥0.025 /M tokens (input 缓存命中, 官方)
|
|
28
|
+
completion_per_m = 6.0 # ¥6 /M tokens (output, 官方)
|
|
29
|
+
currency = "CNY"
|
|
30
|
+
|
|
31
|
+
# Anthropic Claude Sonnet 4.5 (2026-06 official first-party API pricing)
|
|
32
|
+
# https://platform.claude.com/docs/en/about-claude/pricing
|
|
33
|
+
[models.claude-sonnet-4-5]
|
|
34
|
+
cache_miss_per_m = 3.0 # $3 /M tokens (input)
|
|
35
|
+
cache_hit_per_m = 0.30 # $0.30 /M tokens (cache read, 1h TTL — 不是 5min ephemeral $0.06)
|
|
36
|
+
completion_per_m = 15.0 # $15 /M tokens
|
|
37
|
+
currency = "USD"
|
|
38
|
+
|
|
39
|
+
# Anthropic Claude Opus 4.5 (2026-06 official first-party API pricing)
|
|
40
|
+
# P1 plan correction (2026-06-03): user 核对官方 pricing 页后纠正
|
|
41
|
+
# 之前值 $24/$0.48/$120 是错的 (类似某种旧/特殊模式价格, 不适合 first-party default)
|
|
42
|
+
[models.claude-opus-4-5]
|
|
43
|
+
cache_miss_per_m = 5.0 # $5 /M
|
|
44
|
+
cache_hit_per_m = 0.50 # $0.50 /M (cache read, 1h TTL — 不是 5min ephemeral $0.48)
|
|
45
|
+
completion_per_m = 25.0 # $25 /M (不是 $120)
|
|
46
|
+
currency = "USD"
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @deepwhale/llm — 公共类型
|
|
3
|
+
*
|
|
4
|
+
* Sprint 0.3 范围:非流式 chat + 5 个 LLMError 子类。
|
|
5
|
+
* Sprint 1a 扩展:system/tool role + tool_calls + stream() + usage + retry metadata。
|
|
6
|
+
* Sprint 1b 再加:prompt cache、cost accounting、canonical schema。
|
|
7
|
+
*/
|
|
8
|
+
import type { Brand } from '@deepwhale/core';
|
|
9
|
+
/** 模型 ID 的品牌类型,避免把 'deepseek-chat' 当成任意 string 传来传去。 */
|
|
10
|
+
export type ModelId = Brand<string, 'ModelId'>;
|
|
11
|
+
/** Role 联合 — Sprint 1a 加 'system' / 'tool'(tool loop 需要)。 */
|
|
12
|
+
export type Role = 'system' | 'user' | 'assistant' | 'tool';
|
|
13
|
+
/**
|
|
14
|
+
* LLM 调用的 tool 调用描述。Sprint 1a 最小:
|
|
15
|
+
* - id: 由 LLM 返回,tool 响应里 echo 回去
|
|
16
|
+
* - name: tool 名(去 registry 查)
|
|
17
|
+
* - args: tool 参数对象(已 JSON 解析,不是 string)
|
|
18
|
+
*
|
|
19
|
+
* Sprint 1b+ 会在 tool_loop 里用这个 type guard args 是否合法(schema 校验)。
|
|
20
|
+
*/
|
|
21
|
+
export interface ToolCall {
|
|
22
|
+
readonly id: string;
|
|
23
|
+
readonly name: string;
|
|
24
|
+
readonly args: Record<string, unknown>;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* 单条 chat message。Sprint 1a:
|
|
28
|
+
* - role: 加 'system' / 'tool'
|
|
29
|
+
* - content: assistant 在有 tool_calls 时可为空字符串(OAI spec)
|
|
30
|
+
* - tool_calls: assistant 消息携带
|
|
31
|
+
* - tool_call_id: tool 消息携带,echo 上面那个 id
|
|
32
|
+
* - name: tool 消息携带,工具名(OAI 协议要求,便于审计)
|
|
33
|
+
*/
|
|
34
|
+
export interface ChatMessage {
|
|
35
|
+
role: Role;
|
|
36
|
+
content: string;
|
|
37
|
+
tool_calls?: ReadonlyArray<ToolCall>;
|
|
38
|
+
tool_call_id?: string;
|
|
39
|
+
name?: string;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Token usage 来自 LLM 响应。Sprint 1a: OAI 标准 + cached_tokens。
|
|
43
|
+
* Sprint 1b: 加 cache_hit_rate / cost_turn / tokens_uncached (可观测性)。
|
|
44
|
+
* Sprint 1b.5: 加 cost_currency (¥ / $) 让 UI 决 symbol, 不在 UI 层做汇率换算.
|
|
45
|
+
*
|
|
46
|
+
* OAI 标准字段 + DeepSeek 扩展:
|
|
47
|
+
* - prompt_tokens / completion_tokens / total_tokens
|
|
48
|
+
* - cached_tokens (DeepSeek V4 起, 命中 cache 的 token 数)
|
|
49
|
+
*
|
|
50
|
+
* Sprint 1b 扩展 (Prefix-cache 可观测性):
|
|
51
|
+
* - cache_hit_rate: 0..1, cached_tokens / prompt_tokens。LLM 不返 cached_tokens 时 undefined。
|
|
52
|
+
* - cost_turn: 本次 turn 估算费用, 按 pricing config 算 (Sprint 1b.5 抽 config.toml)。
|
|
53
|
+
* - tokens_uncached: prompt_tokens - cached_tokens, 方便人眼扫读"实际新付的 token"。
|
|
54
|
+
*
|
|
55
|
+
* Sprint 1b.5 扩展 (per-model currency, 不引入汇率换算):
|
|
56
|
+
* - cost_currency: 'CNY' | 'USD' — 跟 cost_turn 同步存在或同步 absent。
|
|
57
|
+
* UI 读这个字段决定显示 ¥ 还是 $, 不在数字层做换算。
|
|
58
|
+
* cost_turn absent 时 cost_currency 也必须 absent (不变量)。
|
|
59
|
+
*/
|
|
60
|
+
export interface Usage {
|
|
61
|
+
prompt_tokens: number;
|
|
62
|
+
completion_tokens: number;
|
|
63
|
+
total_tokens: number;
|
|
64
|
+
cached_tokens?: number;
|
|
65
|
+
/** 0..1, cached_tokens / prompt_tokens。LLM 不返 cached_tokens 时 undefined。 */
|
|
66
|
+
cache_hit_rate?: number;
|
|
67
|
+
/**
|
|
68
|
+
* 本 turn 估算费用, pricing config 算 (Sprint 1b.5 抽 config.toml)。
|
|
69
|
+
* 数字单位: currency (CNY ¥ 或 USD $). 不在 UI 层做汇率换算。
|
|
70
|
+
*/
|
|
71
|
+
cost_turn?: number;
|
|
72
|
+
/** Currency 跟 cost_turn 同步存在, UI 读这个决 symbol (¥ / $). */
|
|
73
|
+
cost_currency?: 'CNY' | 'USD';
|
|
74
|
+
/** prompt_tokens - cached_tokens, "实际新付"的 token 数, 方便人眼扫读。 */
|
|
75
|
+
tokens_uncached?: number;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* 旧的 `computeCostBreakdown` (Sprint 1b, hardcode V4-Flash pricing) 已整体迁移到
|
|
79
|
+
* `pricing-config.ts:computeCost`, 同时:
|
|
80
|
+
* - 接收 `PricingConfig` + `ModelId` 参数 (per-model currency)
|
|
81
|
+
* - 接收 `pricing: undefined` (显式表达"没 pricing 也走纯函数 2 字段路径")
|
|
82
|
+
* - 返 `CostBreakdownResult` 联合类型 (3 种路径, 显式表达无 pricing 也能算 cache 字段)
|
|
83
|
+
* - 行为兼容: 公式 (V4-Flash pricing 1.0/0.5/0.1 等数字没变) + 边界 (cached=undefined → undefined)
|
|
84
|
+
*
|
|
85
|
+
* R7 设计原则: pricing 是观测, 不让成功 LLM 响应因"缺价格表"失败; silent fallback 制造假成本更危险。
|
|
86
|
+
* 缺 pricing 时不静默 fallback 到硬编码, 也不抛错, 走"base 2 字段, cost 字段 absent"路径。
|
|
87
|
+
* 找不到 model 的 warning 责任在 caller (client/mode 边界), 纯函数零副作用。
|
|
88
|
+
*
|
|
89
|
+
* Sprint 1a: cache_hit_rate 仅做总 cost 估算, 没暴露。Sprint 1b 起可观测。
|
|
90
|
+
*/
|
|
91
|
+
/** chat() 完整调用的返回值。Sprint 1a 加 tool_calls + usage。 */
|
|
92
|
+
export interface ChatResult {
|
|
93
|
+
model: ModelId;
|
|
94
|
+
content: string;
|
|
95
|
+
tool_calls?: ReadonlyArray<ToolCall>;
|
|
96
|
+
usage?: Usage;
|
|
97
|
+
/**
|
|
98
|
+
* finish_reason:
|
|
99
|
+
* - 'stop': 自然结束
|
|
100
|
+
* - 'tool_calls': LLM 决定调工具
|
|
101
|
+
* - 'length': 触达 max_tokens
|
|
102
|
+
* - 'content_filter': 触发安全过滤
|
|
103
|
+
*/
|
|
104
|
+
finish_reason?: 'stop' | 'tool_calls' | 'length' | 'content_filter';
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* 流式 chunk。Sprint 1a 极简:
|
|
108
|
+
* - delta.content: 增量文本(可能为空,如纯 tool_calls 增量)
|
|
109
|
+
* - delta.tool_calls: 增量 tool_call(Sprint 1a 一次性返回完整,Sprint 1b+ 再支持 incremental)
|
|
110
|
+
* - usage: 只在最后一个 chunk 出现(OAI 协议)
|
|
111
|
+
* - finish_reason: 同 ChatResult
|
|
112
|
+
*/
|
|
113
|
+
export interface ChatChunk {
|
|
114
|
+
delta: {
|
|
115
|
+
content?: string;
|
|
116
|
+
tool_calls?: ReadonlyArray<ToolCall>;
|
|
117
|
+
};
|
|
118
|
+
usage?: Usage;
|
|
119
|
+
finish_reason?: ChatResult['finish_reason'];
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* LLM 客户端的抽象接口。Sprint 1a 加 stream() + tools/tool_choice 字段。
|
|
123
|
+
*
|
|
124
|
+
* - chat(): 非流式,一次性返回完整结果
|
|
125
|
+
* - stream(): 流式,逐 chunk 回调(onChunk 同步;onComplete 异步 return final)
|
|
126
|
+
*
|
|
127
|
+
* @throws APIKeyMissingError — API key 未设置
|
|
128
|
+
* @throws LLMRateLimitError — 429(retry 透传后仍未恢复)
|
|
129
|
+
* @throws LLMAuthError — 401/403
|
|
130
|
+
* @throws LLMNetworkError — 网络/DNS 失败
|
|
131
|
+
* @throws LLMUnknownError — 其它 5xx 或 JSON 解析失败
|
|
132
|
+
* @throws LLMStreamError — SSE 解析中途断流
|
|
133
|
+
*/
|
|
134
|
+
export interface LLMClient {
|
|
135
|
+
readonly model: ModelId;
|
|
136
|
+
/**
|
|
137
|
+
* 发送一组 messages,拿到助手完整回复(非流式)。
|
|
138
|
+
*
|
|
139
|
+
* Sprint 1a 新增:
|
|
140
|
+
* - tools: 工具 schema 列表(LLM 看到后可能决定调)
|
|
141
|
+
* - tool_choice: 'auto' / 'none' / 'required'(OAI 标准)
|
|
142
|
+
*
|
|
143
|
+
* retry: 内部对 429 / 5xx / network error 自动指数退避(默认 3 次),
|
|
144
|
+
* 全失败才抛 LLMError。Sprint 1a 简化为固定 3 次。
|
|
145
|
+
*/
|
|
146
|
+
chat(messages: ChatMessage[], options?: {
|
|
147
|
+
signal?: AbortSignal;
|
|
148
|
+
tools?: ReadonlyArray<LLMToolSchema>;
|
|
149
|
+
tool_choice?: 'auto' | 'none' | 'required';
|
|
150
|
+
}): Promise<ChatResult>;
|
|
151
|
+
/**
|
|
152
|
+
* 流式 chat。Sprint 1a 新增。
|
|
153
|
+
*
|
|
154
|
+
* 用法:
|
|
155
|
+
* for await (const chunk of client.stream(messages, { onChunk })) {
|
|
156
|
+
* // chunk = ChatChunk
|
|
157
|
+
* }
|
|
158
|
+
*
|
|
159
|
+
* 也支持 callback 模式(更简单,适合 REPL):
|
|
160
|
+
* await client.stream(messages, { onChunk: (c) => out.write(c.delta.content ?? '') });
|
|
161
|
+
*
|
|
162
|
+
* retry 行为: 内部 stream 重连在 Sprint 1a **不做**(流式断流语义复杂),
|
|
163
|
+
* 整个 stream 失败抛 LLMError 让 caller 决定(Sprint 1b+ 补断点续传)。
|
|
164
|
+
*/
|
|
165
|
+
stream(messages: ChatMessage[], options: {
|
|
166
|
+
signal?: AbortSignal;
|
|
167
|
+
tools?: ReadonlyArray<LLMToolSchema>;
|
|
168
|
+
tool_choice?: 'auto' | 'none' | 'required';
|
|
169
|
+
onChunk: (chunk: ChatChunk) => void;
|
|
170
|
+
}): Promise<ChatResult>;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Sprint 1a 极简:复用 OpenAI function-calling 协议 + ToolInputSchema 兼容形态。
|
|
174
|
+
*
|
|
175
|
+
* 不用 import @deepwhale/coding-agent 避免循环依赖 — Tool 那边把 schema 转成
|
|
176
|
+
* 这个形态即可(structural typing,运行时不影响)。
|
|
177
|
+
*/
|
|
178
|
+
export interface LLMToolSchema {
|
|
179
|
+
name: string;
|
|
180
|
+
description: string;
|
|
181
|
+
parameters: LLMToolParametersSchema;
|
|
182
|
+
}
|
|
183
|
+
/** OAI function-calling 参数 schema(简化形态,跟 coding-agent 的 ToolInputSchema 兼容)。 */
|
|
184
|
+
export interface LLMToolParametersSchema {
|
|
185
|
+
readonly type: 'object';
|
|
186
|
+
readonly properties: Record<string, LLMToolParamSchema>;
|
|
187
|
+
readonly required?: ReadonlyArray<string>;
|
|
188
|
+
}
|
|
189
|
+
export type LLMToolParamSchema = {
|
|
190
|
+
type: 'string';
|
|
191
|
+
description: string;
|
|
192
|
+
enum?: ReadonlyArray<string>;
|
|
193
|
+
} | {
|
|
194
|
+
type: 'number';
|
|
195
|
+
description: string;
|
|
196
|
+
minimum?: number;
|
|
197
|
+
maximum?: number;
|
|
198
|
+
} | {
|
|
199
|
+
type: 'boolean';
|
|
200
|
+
description: string;
|
|
201
|
+
} | {
|
|
202
|
+
type: 'array';
|
|
203
|
+
description: string;
|
|
204
|
+
items: LLMToolParamSchema;
|
|
205
|
+
};
|
|
206
|
+
/** Sprint 1a:SSE 流中途断流专用。Sprint 1b 再加 retry/续传。 */
|
|
207
|
+
export declare class LLMStreamError extends Error implements LLMError {
|
|
208
|
+
readonly name: "LLMStreamError";
|
|
209
|
+
readonly isLLMError: true;
|
|
210
|
+
constructor(message: string, options?: {
|
|
211
|
+
cause?: unknown;
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
export interface LLMError {
|
|
215
|
+
readonly isLLMError: true;
|
|
216
|
+
readonly name: string;
|
|
217
|
+
readonly message: string;
|
|
218
|
+
readonly cause?: unknown;
|
|
219
|
+
}
|
|
220
|
+
/** 类型守卫:通过 isLLMError 标签识别 LLM 派生的 Error。 */
|
|
221
|
+
export declare function isLLMError(err: unknown): err is LLMError;
|
|
222
|
+
export declare class APIKeyMissingError extends Error implements LLMError {
|
|
223
|
+
readonly name: "APIKeyMissingError";
|
|
224
|
+
readonly isLLMError: true;
|
|
225
|
+
constructor(message: string);
|
|
226
|
+
}
|
|
227
|
+
export declare class LLMRateLimitError extends Error implements LLMError {
|
|
228
|
+
readonly name: "LLMRateLimitError";
|
|
229
|
+
readonly isLLMError: true;
|
|
230
|
+
readonly status: 429;
|
|
231
|
+
constructor(message: string);
|
|
232
|
+
}
|
|
233
|
+
export declare class LLMAuthError extends Error implements LLMError {
|
|
234
|
+
readonly name: "LLMAuthError";
|
|
235
|
+
readonly isLLMError: true;
|
|
236
|
+
readonly status: number;
|
|
237
|
+
constructor(status: number, message: string);
|
|
238
|
+
}
|
|
239
|
+
export declare class LLMNetworkError extends Error implements LLMError {
|
|
240
|
+
readonly name: "LLMNetworkError";
|
|
241
|
+
readonly isLLMError: true;
|
|
242
|
+
constructor(message: string, options?: {
|
|
243
|
+
cause?: unknown;
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
export declare class LLMUnknownError extends Error implements LLMError {
|
|
247
|
+
readonly name: "LLMUnknownError";
|
|
248
|
+
readonly isLLMError: true;
|
|
249
|
+
readonly status?: number;
|
|
250
|
+
constructor(message: string, options?: {
|
|
251
|
+
cause?: unknown;
|
|
252
|
+
status?: number;
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAC;AAE7C,wDAAwD;AACxD,MAAM,MAAM,OAAO,GAAG,KAAK,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;AAE/C,6DAA6D;AAC7D,MAAM,MAAM,IAAI,GAAG,QAAQ,GAAG,MAAM,GAAG,WAAW,GAAG,MAAM,CAAC;AAE5D;;;;;;;GAOG;AACH,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACxC;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,IAAI,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,aAAa,CAAC,QAAQ,CAAC,CAAC;IACrC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,WAAW,KAAK;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,4EAA4E;IAC5E,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,yDAAyD;IACzD,aAAa,CAAC,EAAE,KAAK,GAAG,KAAK,CAAC;IAC9B,8DAA8D;IAC9D,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED;;;;;;;;;;;;;GAaG;AAEH,sDAAsD;AACtD,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,OAAO,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,aAAa,CAAC,QAAQ,CAAC,CAAC;IACrC,KAAK,CAAC,EAAE,KAAK,CAAC;IACd;;;;;;OAMG;IACH,aAAa,CAAC,EAAE,MAAM,GAAG,YAAY,GAAG,QAAQ,GAAG,gBAAgB,CAAC;CACrE;AAED;;;;;;GAMG;AACH,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE;QACL,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,UAAU,CAAC,EAAE,aAAa,CAAC,QAAQ,CAAC,CAAC;KACtC,CAAC;IACF,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,aAAa,CAAC,EAAE,UAAU,CAAC,eAAe,CAAC,CAAC;CAC7C;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IAExB;;;;;;;;;OASG;IACH,IAAI,CACF,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,CAAC;IAEvB;;;;;;;;;;;;;OAaG;IACH,MAAM,CACJ,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,CAAC;CACxB;AAED;;;;;GAKG;AACH,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,uBAAuB,CAAC;CACrC;AAED,gFAAgF;AAChF,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC;IACxD,QAAQ,CAAC,QAAQ,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;CAC3C;AAED,MAAM,MAAM,kBAAkB,GAC1B;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAA;CAAE,GACrE;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,GAC3E;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,GACxC;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,kBAAkB,CAAA;CAAE,CAAC;AAEtE,mDAAmD;AACnD,qBAAa,cAAe,SAAQ,KAAM,YAAW,QAAQ;IAC3D,SAAkB,IAAI,EAAG,gBAAgB,CAAU;IACnD,QAAQ,CAAC,UAAU,EAAG,IAAI,CAAU;gBACxB,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE;CAM3D;AAQD,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,UAAU,EAAE,IAAI,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,6CAA6C;AAC7C,wBAAgB,UAAU,CAAC,GAAG,EAAE,OAAO,GAAG,GAAG,IAAI,QAAQ,CAExD;AAED,qBAAa,kBAAmB,SAAQ,KAAM,YAAW,QAAQ;IAC/D,SAAkB,IAAI,EAAG,oBAAoB,CAAU;IACvD,QAAQ,CAAC,UAAU,EAAG,IAAI,CAAU;gBACxB,OAAO,EAAE,MAAM;CAG5B;AAED,qBAAa,iBAAkB,SAAQ,KAAM,YAAW,QAAQ;IAC9D,SAAkB,IAAI,EAAG,mBAAmB,CAAU;IACtD,QAAQ,CAAC,UAAU,EAAG,IAAI,CAAU;IACpC,QAAQ,CAAC,MAAM,EAAG,GAAG,CAAU;gBACnB,OAAO,EAAE,MAAM;CAG5B;AAED,qBAAa,YAAa,SAAQ,KAAM,YAAW,QAAQ;IACzD,SAAkB,IAAI,EAAG,cAAc,CAAU;IACjD,QAAQ,CAAC,UAAU,EAAG,IAAI,CAAU;IACpC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;gBACZ,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;CAI5C;AAED,qBAAa,eAAgB,SAAQ,KAAM,YAAW,QAAQ;IAC5D,SAAkB,IAAI,EAAG,iBAAiB,CAAU;IACpD,QAAQ,CAAC,UAAU,EAAG,IAAI,CAAU;gBACxB,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE;CAM3D;AAED,qBAAa,eAAgB,SAAQ,KAAM,YAAW,QAAQ;IAC5D,SAAkB,IAAI,EAAG,iBAAiB,CAAU;IACpD,QAAQ,CAAC,UAAU,EAAG,IAAI,CAAU;IACpC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;gBACb,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE;CAO5E"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @deepwhale/llm — 公共类型
|
|
3
|
+
*
|
|
4
|
+
* Sprint 0.3 范围:非流式 chat + 5 个 LLMError 子类。
|
|
5
|
+
* Sprint 1a 扩展:system/tool role + tool_calls + stream() + usage + retry metadata。
|
|
6
|
+
* Sprint 1b 再加:prompt cache、cost accounting、canonical schema。
|
|
7
|
+
*/
|
|
8
|
+
/** Sprint 1a:SSE 流中途断流专用。Sprint 1b 再加 retry/续传。 */
|
|
9
|
+
export class LLMStreamError extends Error {
|
|
10
|
+
name = 'LLMStreamError';
|
|
11
|
+
isLLMError = true;
|
|
12
|
+
constructor(message, options) {
|
|
13
|
+
super(message);
|
|
14
|
+
if (options?.cause !== undefined) {
|
|
15
|
+
this.cause = options.cause;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/** 类型守卫:通过 isLLMError 标签识别 LLM 派生的 Error。 */
|
|
20
|
+
export function isLLMError(err) {
|
|
21
|
+
return err instanceof Error && err.isLLMError === true;
|
|
22
|
+
}
|
|
23
|
+
export class APIKeyMissingError extends Error {
|
|
24
|
+
name = 'APIKeyMissingError';
|
|
25
|
+
isLLMError = true;
|
|
26
|
+
constructor(message) {
|
|
27
|
+
super(message);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
export class LLMRateLimitError extends Error {
|
|
31
|
+
name = 'LLMRateLimitError';
|
|
32
|
+
isLLMError = true;
|
|
33
|
+
status = 429;
|
|
34
|
+
constructor(message) {
|
|
35
|
+
super(message);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
export class LLMAuthError extends Error {
|
|
39
|
+
name = 'LLMAuthError';
|
|
40
|
+
isLLMError = true;
|
|
41
|
+
status;
|
|
42
|
+
constructor(status, message) {
|
|
43
|
+
super(message);
|
|
44
|
+
this.status = status;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
export class LLMNetworkError extends Error {
|
|
48
|
+
name = 'LLMNetworkError';
|
|
49
|
+
isLLMError = true;
|
|
50
|
+
constructor(message, options) {
|
|
51
|
+
super(message);
|
|
52
|
+
if (options?.cause !== undefined) {
|
|
53
|
+
this.cause = options.cause;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
export class LLMUnknownError extends Error {
|
|
58
|
+
name = 'LLMUnknownError';
|
|
59
|
+
isLLMError = true;
|
|
60
|
+
status;
|
|
61
|
+
constructor(message, options) {
|
|
62
|
+
super(message);
|
|
63
|
+
if (options?.cause !== undefined) {
|
|
64
|
+
this.cause = options.cause;
|
|
65
|
+
}
|
|
66
|
+
if (options?.status !== undefined)
|
|
67
|
+
this.status = options.status;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAiNH,mDAAmD;AACnD,MAAM,OAAO,cAAe,SAAQ,KAAK;IACrB,IAAI,GAAG,gBAAyB,CAAC;IAC1C,UAAU,GAAG,IAAa,CAAC;IACpC,YAAY,OAAe,EAAE,OAA6B;QACxD,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,OAAO,EAAE,KAAK,KAAK,SAAS,EAAE,CAAC;YAChC,IAA4B,CAAC,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC;QACtD,CAAC;IACH,CAAC;CACF;AAeD,6CAA6C;AAC7C,MAAM,UAAU,UAAU,CAAC,GAAY;IACrC,OAAO,GAAG,YAAY,KAAK,IAAK,GAAgC,CAAC,UAAU,KAAK,IAAI,CAAC;AACvF,CAAC;AAED,MAAM,OAAO,kBAAmB,SAAQ,KAAK;IACzB,IAAI,GAAG,oBAA6B,CAAC;IAC9C,UAAU,GAAG,IAAa,CAAC;IACpC,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;IACjB,CAAC;CACF;AAED,MAAM,OAAO,iBAAkB,SAAQ,KAAK;IACxB,IAAI,GAAG,mBAA4B,CAAC;IAC7C,UAAU,GAAG,IAAa,CAAC;IAC3B,MAAM,GAAG,GAAY,CAAC;IAC/B,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;IACjB,CAAC;CACF;AAED,MAAM,OAAO,YAAa,SAAQ,KAAK;IACnB,IAAI,GAAG,cAAuB,CAAC;IACxC,UAAU,GAAG,IAAa,CAAC;IAC3B,MAAM,CAAS;IACxB,YAAY,MAAc,EAAE,OAAe;QACzC,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;CACF;AAED,MAAM,OAAO,eAAgB,SAAQ,KAAK;IACtB,IAAI,GAAG,iBAA0B,CAAC;IAC3C,UAAU,GAAG,IAAa,CAAC;IACpC,YAAY,OAAe,EAAE,OAA6B;QACxD,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,OAAO,EAAE,KAAK,KAAK,SAAS,EAAE,CAAC;YAChC,IAA4B,CAAC,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC;QACtD,CAAC;IACH,CAAC;CACF;AAED,MAAM,OAAO,eAAgB,SAAQ,KAAK;IACtB,IAAI,GAAG,iBAA0B,CAAC;IAC3C,UAAU,GAAG,IAAa,CAAC;IAC3B,MAAM,CAAU;IACzB,YAAY,OAAe,EAAE,OAA8C;QACzE,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,OAAO,EAAE,KAAK,KAAK,SAAS,EAAE,CAAC;YAChC,IAA4B,CAAC,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC;QACtD,CAAC;QACD,IAAI,OAAO,EAAE,MAAM,KAAK,SAAS;YAAE,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAClE,CAAC;CACF"}
|
package/package.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@deepwhale/llm",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"types": "./dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"import": "./dist/index.js"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"dist",
|
|
15
|
+
"src/pricing.default.toml"
|
|
16
|
+
],
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@anthropic-ai/sdk": "^0.40.1",
|
|
19
|
+
"@deepwhale/core": "^1.0.0",
|
|
20
|
+
"smol-toml": "^1.6.1"
|
|
21
|
+
},
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "tsc -b && node scripts/copy-toml.mjs",
|
|
24
|
+
"copy-toml": "node scripts/copy-toml.mjs",
|
|
25
|
+
"dev": "tsc -b --watch",
|
|
26
|
+
"clean": "rm -rf dist .tsbuildinfo"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# deepwhale 默认 pricing (2026-06)
|
|
2
|
+
# 用户可在 ~/.deepwhale/pricing.toml 覆盖 (--pricing-config <path> 后续可加, 1b.5 走 env)。
|
|
3
|
+
# **per-model currency**: DeepSeek CNY, Anthropic USD。不在 UI 层做汇率换算。
|
|
4
|
+
#
|
|
5
|
+
# **当前生产 model 2 个** (Q 拍板 2026-06-03): flash + pro.
|
|
6
|
+
# Only production DeepSeek models are supported. (Q 拍板 2026-06-04, Task 2 Gap 4 留痕)
|
|
7
|
+
# 不再加 deepseek-chat / deepseek-reasoner 老 alias — 那些是 1a/1b era 历史,
|
|
8
|
+
# 2026-07-24 后服务端 hard-fail, 老 session replay 走 R7 中间路径 (base 2 字段, cost 字段 absent).
|
|
9
|
+
#
|
|
10
|
+
# **信源唯一性**: DeepSeek pricing 永远以 https://api-docs.deepseek.com/quick_start/pricing
|
|
11
|
+
# 为唯一信源。Q 在 2026-06-03 review 时贴出官方 "模型细节" 页截屏纠正我之前 commit 用的
|
|
12
|
+
# 0.5/0.1/1.0 (来自 1b 时代印象 + 二手源, 未核对官方). 实际官方:
|
|
13
|
+
# - cache_miss_per_m = input 缓存未命中 (1M tokens)
|
|
14
|
+
# - cache_hit_per_m = input 缓存命中 (1M tokens)
|
|
15
|
+
# - completion_per_m = output (1M tokens)
|
|
16
|
+
# 注意: 1b follow-up P1 改的 0.01/0.1 fix 是 **公式 1000× off** — pricing 数字本身的 0.1
|
|
17
|
+
# 看上去像 0.1 ¥/M 但其实只是 0.5→0.1 cache hit 巧合小, 真正的官方是 0.02¥/M (5× off 真实价).
|
|
18
|
+
|
|
19
|
+
[models.deepseek-v4-flash]
|
|
20
|
+
cache_miss_per_m = 1.0 # ¥1 /M tokens (input 缓存未命中, 官方 api-docs.deepseek.com 2026-06)
|
|
21
|
+
cache_hit_per_m = 0.02 # ¥0.02 /M tokens (input 缓存命中, 官方)
|
|
22
|
+
completion_per_m = 2.0 # ¥2 /M tokens (output, 官方)
|
|
23
|
+
currency = "CNY"
|
|
24
|
+
|
|
25
|
+
[models.deepseek-v4-pro]
|
|
26
|
+
cache_miss_per_m = 3.0 # ¥3 /M tokens (input 缓存未命中, 官方)
|
|
27
|
+
cache_hit_per_m = 0.025 # ¥0.025 /M tokens (input 缓存命中, 官方)
|
|
28
|
+
completion_per_m = 6.0 # ¥6 /M tokens (output, 官方)
|
|
29
|
+
currency = "CNY"
|
|
30
|
+
|
|
31
|
+
# Anthropic Claude Sonnet 4.5 (2026-06 official first-party API pricing)
|
|
32
|
+
# https://platform.claude.com/docs/en/about-claude/pricing
|
|
33
|
+
[models.claude-sonnet-4-5]
|
|
34
|
+
cache_miss_per_m = 3.0 # $3 /M tokens (input)
|
|
35
|
+
cache_hit_per_m = 0.30 # $0.30 /M tokens (cache read, 1h TTL — 不是 5min ephemeral $0.06)
|
|
36
|
+
completion_per_m = 15.0 # $15 /M tokens
|
|
37
|
+
currency = "USD"
|
|
38
|
+
|
|
39
|
+
# Anthropic Claude Opus 4.5 (2026-06 official first-party API pricing)
|
|
40
|
+
# P1 plan correction (2026-06-03): user 核对官方 pricing 页后纠正
|
|
41
|
+
# 之前值 $24/$0.48/$120 是错的 (类似某种旧/特殊模式价格, 不适合 first-party default)
|
|
42
|
+
[models.claude-opus-4-5]
|
|
43
|
+
cache_miss_per_m = 5.0 # $5 /M
|
|
44
|
+
cache_hit_per_m = 0.50 # $0.50 /M (cache read, 1h TTL — 不是 5min ephemeral $0.48)
|
|
45
|
+
completion_per_m = 25.0 # $25 /M (不是 $120)
|
|
46
|
+
currency = "USD"
|