dsh-kenari-plugin 0.1.1

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.
Files changed (116) hide show
  1. package/LICENSE +21 -0
  2. package/README.en.md +281 -0
  3. package/README.md +283 -0
  4. package/assets/kenari-favicon-128.png +0 -0
  5. package/assets/kenari-favicon-256.png +0 -0
  6. package/client/index.js +1998 -0
  7. package/cordis.patch.yml +137 -0
  8. package/lib/billing.d.ts +120 -0
  9. package/lib/billing.d.ts.map +1 -0
  10. package/lib/billing.js +209 -0
  11. package/lib/billing.js.map +1 -0
  12. package/lib/catalog-view.d.ts +78 -0
  13. package/lib/catalog-view.d.ts.map +1 -0
  14. package/lib/catalog-view.js +113 -0
  15. package/lib/catalog-view.js.map +1 -0
  16. package/lib/catalog.d.ts +239 -0
  17. package/lib/catalog.d.ts.map +1 -0
  18. package/lib/catalog.js +445 -0
  19. package/lib/catalog.js.map +1 -0
  20. package/lib/default-route.d.ts +35 -0
  21. package/lib/default-route.d.ts.map +1 -0
  22. package/lib/default-route.js +111 -0
  23. package/lib/default-route.js.map +1 -0
  24. package/lib/errors.d.ts +29 -0
  25. package/lib/errors.d.ts.map +1 -0
  26. package/lib/errors.js +74 -0
  27. package/lib/errors.js.map +1 -0
  28. package/lib/favicon.d.ts +24 -0
  29. package/lib/favicon.d.ts.map +1 -0
  30. package/lib/favicon.js +68 -0
  31. package/lib/favicon.js.map +1 -0
  32. package/lib/http.d.ts +104 -0
  33. package/lib/http.d.ts.map +1 -0
  34. package/lib/http.js +247 -0
  35. package/lib/http.js.map +1 -0
  36. package/lib/index.d.ts +83 -0
  37. package/lib/index.d.ts.map +1 -0
  38. package/lib/index.js +285 -0
  39. package/lib/index.js.map +1 -0
  40. package/lib/llm/adapter.d.ts +78 -0
  41. package/lib/llm/adapter.d.ts.map +1 -0
  42. package/lib/llm/adapter.js +429 -0
  43. package/lib/llm/adapter.js.map +1 -0
  44. package/lib/llm/candidates.d.ts +57 -0
  45. package/lib/llm/candidates.d.ts.map +1 -0
  46. package/lib/llm/candidates.js +86 -0
  47. package/lib/llm/candidates.js.map +1 -0
  48. package/lib/llm/recovery.d.ts +48 -0
  49. package/lib/llm/recovery.d.ts.map +1 -0
  50. package/lib/llm/recovery.js +118 -0
  51. package/lib/llm/recovery.js.map +1 -0
  52. package/lib/llm/retry.d.ts +35 -0
  53. package/lib/llm/retry.d.ts.map +1 -0
  54. package/lib/llm/retry.js +53 -0
  55. package/lib/llm/retry.js.map +1 -0
  56. package/lib/plans.d.ts +59 -0
  57. package/lib/plans.d.ts.map +1 -0
  58. package/lib/plans.js +118 -0
  59. package/lib/plans.js.map +1 -0
  60. package/lib/session-title.d.ts +63 -0
  61. package/lib/session-title.d.ts.map +1 -0
  62. package/lib/session-title.js +280 -0
  63. package/lib/session-title.js.map +1 -0
  64. package/lib/settings.d.ts +35 -0
  65. package/lib/settings.d.ts.map +1 -0
  66. package/lib/settings.js +78 -0
  67. package/lib/settings.js.map +1 -0
  68. package/lib/tools/account.d.ts +11 -0
  69. package/lib/tools/account.d.ts.map +1 -0
  70. package/lib/tools/account.js +132 -0
  71. package/lib/tools/account.js.map +1 -0
  72. package/lib/tools/billing.d.ts +10 -0
  73. package/lib/tools/billing.d.ts.map +1 -0
  74. package/lib/tools/billing.js +100 -0
  75. package/lib/tools/billing.js.map +1 -0
  76. package/lib/tools/count-tokens.d.ts +11 -0
  77. package/lib/tools/count-tokens.d.ts.map +1 -0
  78. package/lib/tools/count-tokens.js +84 -0
  79. package/lib/tools/count-tokens.js.map +1 -0
  80. package/lib/tools/data.d.ts +12 -0
  81. package/lib/tools/data.d.ts.map +1 -0
  82. package/lib/tools/data.js +205 -0
  83. package/lib/tools/data.js.map +1 -0
  84. package/lib/tools/docs.d.ts +13 -0
  85. package/lib/tools/docs.d.ts.map +1 -0
  86. package/lib/tools/docs.js +209 -0
  87. package/lib/tools/docs.js.map +1 -0
  88. package/lib/tools/documents.d.ts +10 -0
  89. package/lib/tools/documents.d.ts.map +1 -0
  90. package/lib/tools/documents.js +118 -0
  91. package/lib/tools/documents.js.map +1 -0
  92. package/lib/tools/media.d.ts +28 -0
  93. package/lib/tools/media.d.ts.map +1 -0
  94. package/lib/tools/media.js +599 -0
  95. package/lib/tools/media.js.map +1 -0
  96. package/lib/tools/shared.d.ts +89 -0
  97. package/lib/tools/shared.d.ts.map +1 -0
  98. package/lib/tools/shared.js +128 -0
  99. package/lib/tools/shared.js.map +1 -0
  100. package/lib/tools/x-search.d.ts +11 -0
  101. package/lib/tools/x-search.d.ts.map +1 -0
  102. package/lib/tools/x-search.js +129 -0
  103. package/lib/tools/x-search.js.map +1 -0
  104. package/lib/web/fallback.d.ts +39 -0
  105. package/lib/web/fallback.d.ts.map +1 -0
  106. package/lib/web/fallback.js +79 -0
  107. package/lib/web/fallback.js.map +1 -0
  108. package/lib/web/fetch.d.ts +19 -0
  109. package/lib/web/fetch.d.ts.map +1 -0
  110. package/lib/web/fetch.js +41 -0
  111. package/lib/web/fetch.js.map +1 -0
  112. package/lib/web/search.d.ts +21 -0
  113. package/lib/web/search.d.ts.map +1 -0
  114. package/lib/web/search.js +47 -0
  115. package/lib/web/search.js.map +1 -0
  116. package/package.json +84 -0
@@ -0,0 +1,429 @@
1
+ /**
2
+ * Kenari 原生 LlmAdapter(第 5 期,**可选**,默认关闭)。
3
+ *
4
+ * 为什么有它:`llm-pi-ai` 预设(第 3 期)已经能把 Kenari 当会话模型用,但它按通用
5
+ * OpenAI 兼容语义处理线上字段,丢掉了几样 Kenari 特有的东西:
6
+ *
7
+ * 1. **真实价格进模型选择器**:目录里的 `pricing` 换算成 IDR/1M 写进 `description`
8
+ * 2. **token 与费用进账本**:pi-ai 的 usage 到不了本插件,`kenari_billing` 的
9
+ * `cached_tokens` 命中率因此一直没有数据;本适配器逐次把 usage 记进同一本账
10
+ * 3. **推理档位按目录原样暴露**:`reasoning_options` 的值就是线上 `reasoning_effort`
11
+ * 的写法(含 `none`),不再需要 off/none 的键位翻译
12
+ * 4. **reasoning 通道**:`reasoning_content` → `reasoning-delta`,与可见文本分开
13
+ *
14
+ * 不做的事:不回放思考块、不注入 `file-parser`(文件块投影成说明文本,读文档请用
15
+ * `kenari_ocr` 工具)。这些在 README 里写明。
16
+ *
17
+ * 与预设路由**不同名**(默认 `kenari-direct`),所以两者可以并存、可以回退。
18
+ * @module dsh-kenari-plugin/llm/adapter
19
+ */
20
+ import { LlmAdapter, LlmError, attributionHeaders, isContextWindowExceededError } from '@deepseek-ai/dsh-llm';
21
+ import { inputModalitiesOf, isFreeModel, microToIdrPerMillion, toResolvedModelInfo } from '../catalog.js';
22
+ import { GLOBAL_SCOPE } from '../billing.js';
23
+ import { extractKenariErrorMessage } from '../errors.js';
24
+ /** 会话模型的价格摘要(进选择器 description;不确定的量不写)。 */
25
+ function describePricing(model) {
26
+ const bits = [];
27
+ if (isFreeModel(model)) {
28
+ bits.push('免费');
29
+ }
30
+ else {
31
+ const input = model.pricing?.input;
32
+ const output = model.pricing?.output;
33
+ if (typeof input === 'number' && typeof output === 'number') {
34
+ bits.push(`入 ${microToIdrPerMillion(input)} / 出 ${microToIdrPerMillion(output)} IDR per 1M tokens`);
35
+ }
36
+ else if (model.pricing?.varies === true) {
37
+ bits.push('价格浮动');
38
+ }
39
+ }
40
+ if (typeof model.context_length === 'number')
41
+ bits.push(`ctx ${model.context_length}`);
42
+ if (inputModalitiesOf(model).includes('image'))
43
+ bits.push('视觉');
44
+ if ((model.reasoning_options ?? []).length > 0)
45
+ bits.push(`推理 ${(model.reasoning_options ?? []).join('/')}`);
46
+ return bits.join(' · ');
47
+ }
48
+ /** HTTP 状态 → dsh 的 provider-neutral 失败码(路由靠 code,不靠 message)。 */
49
+ function failureCodeFor(status, detail) {
50
+ if (status === 401 || status === 403)
51
+ return 'AUTH';
52
+ if (status === 402)
53
+ return 'QUOTA';
54
+ if (status === 429)
55
+ return 'RATE_LIMIT';
56
+ if (isContextWindowExceededError(detail))
57
+ return 'CONTEXT_WINDOW_EXCEEDED';
58
+ return status >= 500 ? 'TRANSPORT' : 'INVALID_REQUEST';
59
+ }
60
+ /** 嵌套块 → 纯文本(工具结果、文件引用等只能以文本回传的情形)。 */
61
+ function textOfBlocks(blocks) {
62
+ const parts = [];
63
+ for (const block of blocks) {
64
+ if (block.type === 'text')
65
+ parts.push(block.text);
66
+ else if (block.type === 'reasoning')
67
+ continue;
68
+ else if (block.type === 'tool-result')
69
+ parts.push(textOfBlocks(block.content));
70
+ else if (block.type === 'image')
71
+ parts.push(`[图像 ${block.attachment.width}×${block.attachment.height}]`);
72
+ else if (block.type === 'file')
73
+ parts.push(`[文件 ${block.attachment.name}]`);
74
+ }
75
+ return parts.filter((part) => part.length > 0).join('\n');
76
+ }
77
+ /** 图像引用 → data URI;没有 attachment 存储时返回 undefined(调用方降级为文本)。 */
78
+ async function imageDataUri(imageRef, read) {
79
+ if (read === undefined)
80
+ return undefined;
81
+ try {
82
+ const stored = await read(imageRef);
83
+ return `data:${stored.ref.mediaType};base64,${Buffer.from(stored.data).toString('base64')}`;
84
+ }
85
+ catch {
86
+ return undefined;
87
+ }
88
+ }
89
+ /** 一条 dsh 消息 → 一条或多条 [OI] 消息。 */
90
+ async function toWireMessages(message, read) {
91
+ // 工具结果:dsh 把它挂在一条 user 角色的消息上,线上要用 role=tool + tool_call_id
92
+ if (message.source.kind === 'tool') {
93
+ return [{ role: 'tool', tool_call_id: String(message.source.callId), content: textOfBlocks(message.content) }];
94
+ }
95
+ if (message.role === 'assistant') {
96
+ const text = textOfBlocks(message.content);
97
+ const calls = message.content
98
+ .filter((block) => block.type === 'tool-call')
99
+ .map((block) => ({ id: String(block.id), type: 'function', function: { name: block.name, arguments: block.arguments } }));
100
+ return [{
101
+ role: 'assistant',
102
+ // content 为 null 表示「只有工具调用」,[OI] 接受这种组合
103
+ content: text.length > 0 ? text : null,
104
+ ...(calls.length > 0 ? { tool_calls: calls } : {}),
105
+ }];
106
+ }
107
+ // user / plugin / system:文本 + 图像走 parts,纯文本走字符串(线上两种都收)
108
+ const parts = [];
109
+ const texts = [];
110
+ for (const block of message.content) {
111
+ if (block.type === 'text') {
112
+ texts.push(block.text);
113
+ parts.push({ type: 'text', text: block.text });
114
+ }
115
+ else if (block.type === 'reasoning') {
116
+ // 思考块不属于对话内容,不回放
117
+ }
118
+ else if (block.type === 'image') {
119
+ const uri = await imageDataUri(block.attachment, read);
120
+ if (uri === undefined) {
121
+ texts.push('[图像:本部署没有可读的 attachment 存储,无法回传]');
122
+ parts.push({ type: 'text', text: '[图像:本部署没有可读的 attachment 存储,无法回传]' });
123
+ }
124
+ else {
125
+ parts.push({ type: 'image_url', image_url: { url: uri } });
126
+ }
127
+ }
128
+ else if (block.type === 'file') {
129
+ const note = `[文件 ${block.attachment.name}:本适配器不回传文件字节,读文档请用 kenari_ocr 工具]`;
130
+ texts.push(note);
131
+ parts.push({ type: 'text', text: note });
132
+ }
133
+ else if (block.type === 'tool-result') {
134
+ const nested = textOfBlocks(block.content);
135
+ texts.push(nested);
136
+ parts.push({ type: 'text', text: nested });
137
+ }
138
+ }
139
+ const hasImage = parts.some((part) => part.type === 'image_url');
140
+ return [{
141
+ role: message.role === 'system' ? 'system' : 'user',
142
+ content: hasImage ? parts : texts.join('\n'),
143
+ }];
144
+ }
145
+ /** dsh ToolSchema → [OI] tools 条目。 */
146
+ function toWireTool(tool) {
147
+ return { type: 'function', function: { name: tool.name, description: tool.description, parameters: tool.parameters } };
148
+ }
149
+ /** [OI] usage → dsh TokenUsage(dsh 要求各项**互斥**:inputTokens 是不含缓存的那部分)。 */
150
+ export function mapUsage(usage) {
151
+ const cached = usage.prompt_tokens_details?.cached_tokens ?? 0;
152
+ const prompt = usage.prompt_tokens ?? 0;
153
+ const mapped = {
154
+ inputTokens: Math.max(0, prompt - cached),
155
+ outputTokens: usage.completion_tokens ?? 0,
156
+ };
157
+ if (cached > 0)
158
+ mapped.cacheReadTokens = cached;
159
+ if (typeof usage.total_tokens === 'number')
160
+ mapped.totalTokens = usage.total_tokens;
161
+ return mapped;
162
+ }
163
+ /** 逐行切 SSE:`data: <payload>`,空行与注释行跳过。 */
164
+ async function* ssePayloads(body) {
165
+ const decoder = new TextDecoder();
166
+ let buffer = '';
167
+ for await (const chunk of body) {
168
+ buffer += decoder.decode(chunk, { stream: true });
169
+ let newline = buffer.indexOf('\n');
170
+ while (newline !== -1) {
171
+ const line = buffer.slice(0, newline).trim();
172
+ buffer = buffer.slice(newline + 1);
173
+ if (line.startsWith('data:'))
174
+ yield line.slice(5).trim();
175
+ newline = buffer.indexOf('\n');
176
+ }
177
+ }
178
+ const tail = buffer.trim();
179
+ if (tail.startsWith('data:'))
180
+ yield tail.slice(5).trim();
181
+ }
182
+ /** Kenari [OI] 线的原生适配器。 */
183
+ export class KenariLlmAdapter extends LlmAdapter {
184
+ deps;
185
+ readImage;
186
+ constructor(deps,
187
+ /** 目录里读图像字节的入口(`ctx.attachments.readImage`);缺失则图像降级为文本。 */
188
+ readImage) {
189
+ super();
190
+ this.deps = deps;
191
+ this.readImage = readImage;
192
+ }
193
+ providerInfo(provider) {
194
+ return { id: provider, name: 'Kenari(原生适配器)' };
195
+ }
196
+ /**
197
+ * 把插件配置拼出的重试策略交给 dsh 的重试机制。基类默认返回 undefined,
198
+ * 也就是走 dsh 默认策略(指数退避 500ms→10s、5 次)。
199
+ * 注意 dsh 只在 `registerAdapter()` 时读取一次并冻结,所以改配置要重启才生效。
200
+ */
201
+ providerRetryPolicy(_provider) {
202
+ return this.deps.retryPolicy;
203
+ }
204
+ /** 目录即模型列表:价格与上下文随目录刷新,不再静态写死在配置里。 */
205
+ async listModels(provider) {
206
+ const models = await this.deps.catalog.chatModels();
207
+ return models.map((model) => ({
208
+ provider,
209
+ id: model.id,
210
+ name: model.name ?? model.id,
211
+ description: describePricing(model),
212
+ inputModalities: inputModalitiesOf(model),
213
+ }));
214
+ }
215
+ /** 逐模型元数据:上下文窗口、推理档位、输出上限都来自目录。 */
216
+ async resolveModel(provider, model) {
217
+ const resolved = await this.deps.catalog.resolve(model);
218
+ // 目录解析失败不拒绝请求(目录是建议性的),只给出最小元数据
219
+ if (resolved === undefined)
220
+ return { provider, id: model, name: model, inputModalities: ['text'] };
221
+ return toResolvedModelInfo(resolved.model, provider);
222
+ }
223
+ stream(options) {
224
+ return this.run(options);
225
+ }
226
+ async *run(options) {
227
+ const { deps } = this;
228
+ const config = deps.http.config;
229
+ const apiKey = await deps.http.resolveApiKey();
230
+ if (apiKey === undefined || apiKey.length === 0) {
231
+ throw new LlmError(`Kenari 凭据 ${config.apiKeyEnv ?? 'KENARI_API_KEY'} 未配置,无法发起模型请求`, 'MISSING_CREDENTIAL');
232
+ }
233
+ const wireMessages = [];
234
+ if (options.system !== undefined && options.system.length > 0) {
235
+ wireMessages.push({ role: 'system', content: options.system });
236
+ }
237
+ for (const message of options.messages) {
238
+ wireMessages.push(...await toWireMessages(message, this.readImage));
239
+ }
240
+ const base = (config.baseURL ?? 'https://kenari.id').replace(/\/+$/, '');
241
+ const endpoint = `${base.endsWith('/v1') ? base : `${base}/v1`}/chat/completions`;
242
+ const body = {
243
+ model: options.model,
244
+ messages: wireMessages,
245
+ stream: true,
246
+ // 最后一帧带 usage:cached_tokens 计量的唯一来源
247
+ stream_options: { include_usage: true },
248
+ };
249
+ if (options.maxTokens !== undefined)
250
+ body.max_tokens = options.maxTokens;
251
+ if (options.temperature !== undefined)
252
+ body.temperature = options.temperature;
253
+ if (options.stop !== undefined && options.stop.length > 0)
254
+ body.stop = options.stop;
255
+ if (options.reasoningEffort !== undefined)
256
+ body.reasoning_effort = String(options.reasoningEffort);
257
+ if (options.tools !== undefined && options.tools.length > 0)
258
+ body.tools = options.tools.map(toWireTool);
259
+ const response = await fetch(endpoint, {
260
+ method: 'POST',
261
+ // 契约:每个 provider 请求都必须带 attribution 头
262
+ headers: {
263
+ ...attributionHeaders(),
264
+ authorization: `Bearer ${apiKey}`,
265
+ 'content-type': 'application/json',
266
+ accept: 'text/event-stream',
267
+ },
268
+ body: JSON.stringify(body),
269
+ ...(options.signal === undefined ? {} : { signal: options.signal }),
270
+ });
271
+ if (!response.ok) {
272
+ const raw = await response.text().catch(() => '');
273
+ const detail = extractKenariErrorMessage(raw, response.status);
274
+ deps.logger?.warn(`kenari: ${endpoint} → HTTP ${response.status}`);
275
+ throw new LlmError(`kenari: ${detail}`, failureCodeFor(response.status, detail), { status: response.status });
276
+ }
277
+ if (response.body === null)
278
+ throw new LlmError('kenari: 响应没有 body', 'TRANSPORT');
279
+ let textIndex;
280
+ let reasoningIndex;
281
+ let nextIndex = 0;
282
+ let textBuffer = '';
283
+ let reasoningBuffer = '';
284
+ const toolStates = new Map();
285
+ let usage;
286
+ let finishKind = { kind: 'stop' };
287
+ let emitted = 0;
288
+ const closeText = function* () {
289
+ if (textIndex === undefined)
290
+ return;
291
+ yield { type: 'block-end', index: textIndex, block: { type: 'text', text: textBuffer } };
292
+ textIndex = undefined;
293
+ textBuffer = '';
294
+ };
295
+ const closeReasoning = function* () {
296
+ if (reasoningIndex === undefined)
297
+ return;
298
+ yield { type: 'block-end', index: reasoningIndex, block: { type: 'reasoning', text: reasoningBuffer } };
299
+ reasoningIndex = undefined;
300
+ reasoningBuffer = '';
301
+ };
302
+ for await (const payload of ssePayloads(response.body)) {
303
+ if (payload === '[DONE]')
304
+ break;
305
+ let parsed;
306
+ try {
307
+ parsed = JSON.parse(payload);
308
+ }
309
+ catch {
310
+ continue;
311
+ }
312
+ if (parsed.usage !== undefined)
313
+ usage = mapUsage(parsed.usage);
314
+ const choice = parsed.choices?.[0];
315
+ const delta = choice?.delta;
316
+ if (delta !== undefined) {
317
+ const reasoning = delta.reasoning_content ?? delta.reasoning;
318
+ if (typeof reasoning === 'string' && reasoning.length > 0) {
319
+ yield* closeText();
320
+ if (reasoningIndex === undefined) {
321
+ reasoningIndex = nextIndex++;
322
+ yield { type: 'block-start', index: reasoningIndex, blockType: 'reasoning' };
323
+ }
324
+ reasoningBuffer += reasoning;
325
+ emitted++;
326
+ yield { type: 'reasoning-delta', index: reasoningIndex, text: reasoning };
327
+ }
328
+ if (typeof delta.content === 'string' && delta.content.length > 0) {
329
+ yield* closeReasoning();
330
+ if (textIndex === undefined) {
331
+ textIndex = nextIndex++;
332
+ yield { type: 'block-start', index: textIndex, blockType: 'text' };
333
+ }
334
+ textBuffer += delta.content;
335
+ emitted++;
336
+ yield { type: 'text-delta', index: textIndex, text: delta.content };
337
+ }
338
+ for (const call of delta.tool_calls ?? []) {
339
+ yield* closeText();
340
+ yield* closeReasoning();
341
+ const wireIndex = call.index ?? 0;
342
+ let state = toolStates.get(wireIndex);
343
+ if (state === undefined) {
344
+ state = { index: nextIndex++, id: call.id ?? `call_kenari_${wireIndex}_${Date.now()}`, name: '', args: '' };
345
+ toolStates.set(wireIndex, state);
346
+ yield { type: 'block-start', index: state.index, blockType: 'tool-call' };
347
+ }
348
+ if (call.id !== undefined)
349
+ state.id = call.id;
350
+ const namePart = call.function?.name;
351
+ if (namePart !== undefined && namePart.length > 0)
352
+ state.name += namePart;
353
+ const argsPart = call.function?.arguments ?? '';
354
+ state.args += argsPart;
355
+ emitted++;
356
+ yield {
357
+ type: 'tool-call-delta',
358
+ index: state.index,
359
+ id: state.id,
360
+ ...(namePart !== undefined && namePart.length > 0 ? { name: namePart } : {}),
361
+ argumentsDelta: argsPart,
362
+ };
363
+ }
364
+ }
365
+ if (choice?.finish_reason != null) {
366
+ finishKind = choice.finish_reason === 'tool_calls' || choice.finish_reason === 'tool_call'
367
+ ? { kind: 'tool-calls' }
368
+ : choice.finish_reason === 'length'
369
+ ? { kind: 'max-tokens' }
370
+ : { kind: 'stop' };
371
+ }
372
+ }
373
+ yield* closeText();
374
+ yield* closeReasoning();
375
+ for (const state of toolStates.values()) {
376
+ yield {
377
+ type: 'block-end',
378
+ index: state.index,
379
+ block: { type: 'tool-call', id: state.id, name: state.name, arguments: state.args },
380
+ };
381
+ }
382
+ if (usage !== undefined) {
383
+ yield { type: 'usage', usage };
384
+ this.recordUsage(options, usage);
385
+ }
386
+ // 退化响应:一次空完成会让 turn 静默结束,按契约显式归类
387
+ if (emitted === 0) {
388
+ throw new LlmError('kenari: 模型返回了空响应(没有文本、思考或工具调用)', 'EMPTY_RESPONSE');
389
+ }
390
+ yield { type: 'finish', reason: finishKind };
391
+ }
392
+ /** usage 记进共享账本:命中率直接来自 `cached_tokens`,费用按目录单价预估。 */
393
+ recordUsage(options, usage) {
394
+ const scope = options.sessionId === undefined ? GLOBAL_SCOPE : String(options.sessionId);
395
+ this.deps.billing.recordUsage(scope, {
396
+ model: options.model,
397
+ inputTokens: usage.inputTokens,
398
+ outputTokens: usage.outputTokens,
399
+ ...(usage.cacheReadTokens === undefined ? {} : { cacheReadTokens: usage.cacheReadTokens }),
400
+ ...(usage.cacheWriteTokens === undefined ? {} : { cacheWriteTokens: usage.cacheWriteTokens }),
401
+ ...(usage.reasoningTokens === undefined ? {} : { reasoningTokens: usage.reasoningTokens }),
402
+ });
403
+ void this.recordSpendEstimate(options, usage, scope);
404
+ }
405
+ async recordSpendEstimate(options, usage, scope) {
406
+ try {
407
+ const resolved = await this.deps.catalog.resolve(options.model);
408
+ if (resolved === undefined || isFreeModel(resolved.model))
409
+ return;
410
+ const inputPrice = resolved.model.pricing?.input;
411
+ const outputPrice = resolved.model.pricing?.output;
412
+ if (typeof inputPrice !== 'number' || typeof outputPrice !== 'number')
413
+ return;
414
+ // 缓存读有独立单价就按它算,否则退回输入价(宁可高估也不谎报)
415
+ const cacheReadPrice = typeof resolved.model.pricing?.cache_read === 'number'
416
+ ? resolved.model.pricing.cache_read
417
+ : inputPrice;
418
+ // token_1m 单价 × 百万 token 数:token 类保留小数,不按整单位上取
419
+ const microIdr = Math.round((inputPrice * usage.inputTokens
420
+ + cacheReadPrice * (usage.cacheReadTokens ?? 0)
421
+ + outputPrice * usage.outputTokens) / 1_000_000);
422
+ this.deps.billing.recordSpend(scope, { tool: `chat:${options.model}`, model: options.model, microIdr, estimated: true });
423
+ }
424
+ catch {
425
+ // 目录不可用时不计费,模型调用本身已完成
426
+ }
427
+ }
428
+ }
429
+ //# sourceMappingURL=adapter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adapter.js","sourceRoot":"","sources":["../../src/llm/adapter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,57 @@
1
+ /**
2
+ * 「换一个上下文 >= 当前的模型」的候选挑选。
3
+ *
4
+ * 挑最小够用者而不是最大者:一次失败不值得把会话切到又贵又慢的最大上下文模型。
5
+ * 阈值取**当前**模型的窗口,所以一次会话里窗口只会持平或变大,不会来回横跳。
6
+ * @module dsh-kenari-plugin/llm/candidates
7
+ */
8
+ /** 候选挑选用到的 `ctx.llm` 最小面。注入是为了单测能替身,也避免把整个 LlmRuntime 拖进依赖。 */
9
+ export interface ModelDirectory {
10
+ listProviders(): readonly {
11
+ id: string;
12
+ }[];
13
+ listModels(provider: string): Promise<readonly {
14
+ id: string;
15
+ }[]>;
16
+ resolveModelInfo(provider: string, model: string, signal?: AbortSignal): Promise<{
17
+ context?: {
18
+ contextWindow: number;
19
+ };
20
+ }>;
21
+ }
22
+ /** 一个候选模型及其已解析的上下文窗口。 */
23
+ export interface CandidateModel {
24
+ provider: string;
25
+ model: string;
26
+ contextWindow: number;
27
+ }
28
+ /** {@link ModelCandidates} 的依赖。 */
29
+ export interface ModelCandidatesDeps {
30
+ llm: ModelDirectory;
31
+ /** 目录缓存时长;复用设置节的 catalogCacheTtlMs,语义同为「模型元数据可信多久」。 */
32
+ cacheTtlMs: number;
33
+ /** 注入时钟,便于测试 TTL。 */
34
+ now?: () => number;
35
+ }
36
+ /** `pick` 的输入。 */
37
+ export interface PickInput {
38
+ provider: string;
39
+ model: string;
40
+ contextWindow?: number;
41
+ signal?: AbortSignal;
42
+ }
43
+ /**
44
+ * 找一个上下文窗口 >= 阈值的候选:先在同一 provider 内,再跨 provider。
45
+ * 每次失败都去解析「所有 provider × 所有模型」太贵,所以按 provider 缓存目录。
46
+ */
47
+ export declare class ModelCandidates {
48
+ private readonly deps;
49
+ private readonly cache;
50
+ private readonly now;
51
+ constructor(deps: ModelCandidatesDeps);
52
+ /** 一个 provider 下所有「上下文窗口已知」的模型。未知窗口的模型无法证明 >= 阈值,直接排除。 */
53
+ private entriesOf;
54
+ /** 找最小够用的候选;找不到返回 undefined。 */
55
+ pick(input: PickInput): Promise<CandidateModel | undefined>;
56
+ }
57
+ //# sourceMappingURL=candidates.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"candidates.d.ts","sourceRoot":"","sources":["../../src/llm/candidates.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,+DAA+D;AAC/D,MAAM,WAAW,cAAc;IAC7B,aAAa,IAAI,SAAS;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;IAC1C,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC,CAAA;IAChE,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC;QAAE,OAAO,CAAC,EAAE;YAAE,aAAa,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE,CAAC,CAAA;CAC1H;AAED,yBAAyB;AACzB,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,EAAE,MAAM,CAAA;IACb,aAAa,EAAE,MAAM,CAAA;CACtB;AAED,mCAAmC;AACnC,MAAM,WAAW,mBAAmB;IAClC,GAAG,EAAE,cAAc,CAAA;IACnB,uDAAuD;IACvD,UAAU,EAAE,MAAM,CAAA;IAClB,qBAAqB;IACrB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;CACnB;AAED,kBAAkB;AAClB,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,EAAE,MAAM,CAAA;IACb,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,MAAM,CAAC,EAAE,WAAW,CAAA;CACrB;AAED;;;GAGG;AACH,qBAAa,eAAe;IAId,OAAO,CAAC,QAAQ,CAAC,IAAI;IAHjC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA+D;IACrF,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAc;gBAEL,IAAI,EAAE,mBAAmB;IAItD,0DAA0D;YAC5C,SAAS;IA4BvB,gCAAgC;IAC1B,IAAI,CAAC,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC;CAalE"}
@@ -0,0 +1,86 @@
1
+ /**
2
+ * 「换一个上下文 >= 当前的模型」的候选挑选。
3
+ *
4
+ * 挑最小够用者而不是最大者:一次失败不值得把会话切到又贵又慢的最大上下文模型。
5
+ * 阈值取**当前**模型的窗口,所以一次会话里窗口只会持平或变大,不会来回横跳。
6
+ * @module dsh-kenari-plugin/llm/candidates
7
+ */
8
+ /**
9
+ * 找一个上下文窗口 >= 阈值的候选:先在同一 provider 内,再跨 provider。
10
+ * 每次失败都去解析「所有 provider × 所有模型」太贵,所以按 provider 缓存目录。
11
+ */
12
+ export class ModelCandidates {
13
+ deps;
14
+ cache = new Map();
15
+ now;
16
+ constructor(deps) {
17
+ this.deps = deps;
18
+ this.now = deps.now ?? (() => Date.now());
19
+ }
20
+ /** 一个 provider 下所有「上下文窗口已知」的模型。未知窗口的模型无法证明 >= 阈值,直接排除。 */
21
+ async entriesOf(provider, signal) {
22
+ const at = this.now();
23
+ const cached = this.cache.get(provider);
24
+ if (cached !== undefined && at - cached.at < this.deps.cacheTtlMs)
25
+ return cached.entries;
26
+ let entries = [];
27
+ try {
28
+ const models = await this.deps.llm.listModels(provider);
29
+ const resolved = await Promise.all(models.map(async (model) => {
30
+ try {
31
+ const info = await this.deps.llm.resolveModelInfo(provider, model.id, signal);
32
+ const contextWindow = info.context?.contextWindow;
33
+ if (contextWindow === undefined || !Number.isFinite(contextWindow) || contextWindow <= 0)
34
+ return undefined;
35
+ return { provider, model: model.id, contextWindow };
36
+ }
37
+ catch {
38
+ // 单个模型元数据解析失败不该毁掉整个候选列表
39
+ return undefined;
40
+ }
41
+ }));
42
+ entries = resolved.filter((entry) => entry !== undefined);
43
+ }
44
+ catch {
45
+ // 目录整体不可用时当作「没有候选」,让调用方走到下一阶段
46
+ entries = [];
47
+ }
48
+ this.cache.set(provider, { at, entries });
49
+ return entries;
50
+ }
51
+ /** 找最小够用的候选;找不到返回 undefined。 */
52
+ async pick(input) {
53
+ const threshold = input.contextWindow ?? 0;
54
+ const local = smallestAtLeast(await this.entriesOf(input.provider, input.signal), input.model, threshold);
55
+ if (local !== undefined)
56
+ return local;
57
+ for (const entry of this.deps.llm.listProviders()) {
58
+ if (entry.id === input.provider)
59
+ continue;
60
+ const found = smallestAtLeast(await this.entriesOf(entry.id, input.signal), undefined, threshold);
61
+ if (found !== undefined)
62
+ return found;
63
+ }
64
+ return undefined;
65
+ }
66
+ }
67
+ /**
68
+ * 排除 `exclude`、取 `contextWindow >= threshold` 的最小者。
69
+ * 同窗口时按 model id 字典序取小 —— 确定性很重要,否则同一份配置在不同机器上会换到不同模型。
70
+ */
71
+ function smallestAtLeast(entries, exclude, threshold) {
72
+ let best;
73
+ for (const entry of entries) {
74
+ if (exclude !== undefined && entry.model === exclude)
75
+ continue;
76
+ if (entry.contextWindow < threshold)
77
+ continue;
78
+ if (best === undefined
79
+ || entry.contextWindow < best.contextWindow
80
+ || (entry.contextWindow === best.contextWindow && entry.model < best.model)) {
81
+ best = entry;
82
+ }
83
+ }
84
+ return best;
85
+ }
86
+ //# sourceMappingURL=candidates.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"candidates.js","sourceRoot":"","sources":["../../src/llm/candidates.ts"],"names":[],"mappings":"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"}