unified-llm-provider 0.1.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.
Files changed (158) hide show
  1. package/README.md +463 -0
  2. package/dist/bootstrap/extensions.d.ts +12 -0
  3. package/dist/bootstrap/extensions.d.ts.map +1 -0
  4. package/dist/bootstrap/extensions.js +13 -0
  5. package/dist/bootstrap/extensions.js.map +1 -0
  6. package/dist/config/llm.d.ts +8 -0
  7. package/dist/config/llm.d.ts.map +1 -0
  8. package/dist/config/llm.js +108 -0
  9. package/dist/config/llm.js.map +1 -0
  10. package/dist/config/types.d.ts +69 -0
  11. package/dist/config/types.d.ts.map +1 -0
  12. package/dist/config/types.js +2 -0
  13. package/dist/config/types.js.map +1 -0
  14. package/dist/index.d.ts +27 -0
  15. package/dist/index.d.ts.map +1 -0
  16. package/dist/index.js +27 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/llm/convert.d.ts +67 -0
  19. package/dist/llm/convert.d.ts.map +1 -0
  20. package/dist/llm/convert.js +982 -0
  21. package/dist/llm/convert.js.map +1 -0
  22. package/dist/llm/factory.d.ts +18 -0
  23. package/dist/llm/factory.d.ts.map +1 -0
  24. package/dist/llm/factory.js +34 -0
  25. package/dist/llm/factory.js.map +1 -0
  26. package/dist/llm/formats/claude.d.ts +27 -0
  27. package/dist/llm/formats/claude.d.ts.map +1 -0
  28. package/dist/llm/formats/claude.js +386 -0
  29. package/dist/llm/formats/claude.js.map +1 -0
  30. package/dist/llm/formats/gemini.d.ts +19 -0
  31. package/dist/llm/formats/gemini.d.ts.map +1 -0
  32. package/dist/llm/formats/gemini.js +270 -0
  33. package/dist/llm/formats/gemini.js.map +1 -0
  34. package/dist/llm/formats/openai-compatible.d.ts +19 -0
  35. package/dist/llm/formats/openai-compatible.d.ts.map +1 -0
  36. package/dist/llm/formats/openai-compatible.js +340 -0
  37. package/dist/llm/formats/openai-compatible.js.map +1 -0
  38. package/dist/llm/formats/openai-responses.d.ts +18 -0
  39. package/dist/llm/formats/openai-responses.d.ts.map +1 -0
  40. package/dist/llm/formats/openai-responses.js +474 -0
  41. package/dist/llm/formats/openai-responses.js.map +1 -0
  42. package/dist/llm/formats/schema-sanitizer.d.ts +52 -0
  43. package/dist/llm/formats/schema-sanitizer.d.ts.map +1 -0
  44. package/dist/llm/formats/schema-sanitizer.js +160 -0
  45. package/dist/llm/formats/schema-sanitizer.js.map +1 -0
  46. package/dist/llm/formats/tool-call-ids.d.ts +17 -0
  47. package/dist/llm/formats/tool-call-ids.d.ts.map +1 -0
  48. package/dist/llm/formats/tool-call-ids.js +33 -0
  49. package/dist/llm/formats/tool-call-ids.js.map +1 -0
  50. package/dist/llm/formats/types.d.ts +22 -0
  51. package/dist/llm/formats/types.d.ts.map +1 -0
  52. package/dist/llm/formats/types.js +8 -0
  53. package/dist/llm/formats/types.js.map +1 -0
  54. package/dist/llm/model-catalog.d.ts +15 -0
  55. package/dist/llm/model-catalog.d.ts.map +1 -0
  56. package/dist/llm/model-catalog.js +148 -0
  57. package/dist/llm/model-catalog.js.map +1 -0
  58. package/dist/llm/providers/base.d.ts +48 -0
  59. package/dist/llm/providers/base.d.ts.map +1 -0
  60. package/dist/llm/providers/base.js +171 -0
  61. package/dist/llm/providers/base.js.map +1 -0
  62. package/dist/llm/providers/claude.d.ts +7 -0
  63. package/dist/llm/providers/claude.d.ts.map +1 -0
  64. package/dist/llm/providers/claude.js +26 -0
  65. package/dist/llm/providers/claude.js.map +1 -0
  66. package/dist/llm/providers/deepseek.d.ts +10 -0
  67. package/dist/llm/providers/deepseek.d.ts.map +1 -0
  68. package/dist/llm/providers/deepseek.js +28 -0
  69. package/dist/llm/providers/deepseek.js.map +1 -0
  70. package/dist/llm/providers/gemini.d.ts +7 -0
  71. package/dist/llm/providers/gemini.d.ts.map +1 -0
  72. package/dist/llm/providers/gemini.js +25 -0
  73. package/dist/llm/providers/gemini.js.map +1 -0
  74. package/dist/llm/providers/openai-compatible.d.ts +7 -0
  75. package/dist/llm/providers/openai-compatible.d.ts.map +1 -0
  76. package/dist/llm/providers/openai-compatible.js +25 -0
  77. package/dist/llm/providers/openai-compatible.js.map +1 -0
  78. package/dist/llm/providers/openai-responses.d.ts +7 -0
  79. package/dist/llm/providers/openai-responses.d.ts.map +1 -0
  80. package/dist/llm/providers/openai-responses.js +25 -0
  81. package/dist/llm/providers/openai-responses.js.map +1 -0
  82. package/dist/llm/response.d.ts +17 -0
  83. package/dist/llm/response.d.ts.map +1 -0
  84. package/dist/llm/response.js +94 -0
  85. package/dist/llm/response.js.map +1 -0
  86. package/dist/llm/router.d.ts +61 -0
  87. package/dist/llm/router.d.ts.map +1 -0
  88. package/dist/llm/router.js +127 -0
  89. package/dist/llm/router.js.map +1 -0
  90. package/dist/llm/transport.d.ts +27 -0
  91. package/dist/llm/transport.d.ts.map +1 -0
  92. package/dist/llm/transport.js +166 -0
  93. package/dist/llm/transport.js.map +1 -0
  94. package/dist/llm/vision.d.ts +12 -0
  95. package/dist/llm/vision.d.ts.map +1 -0
  96. package/dist/llm/vision.js +50 -0
  97. package/dist/llm/vision.js.map +1 -0
  98. package/dist/logger/request-logger.d.ts +15 -0
  99. package/dist/logger/request-logger.d.ts.map +1 -0
  100. package/dist/logger/request-logger.js +13 -0
  101. package/dist/logger/request-logger.js.map +1 -0
  102. package/dist/plugin/tool-preview.d.ts +15 -0
  103. package/dist/plugin/tool-preview.d.ts.map +1 -0
  104. package/dist/plugin/tool-preview.js +2 -0
  105. package/dist/plugin/tool-preview.js.map +1 -0
  106. package/dist/registry/formats.d.ts +12 -0
  107. package/dist/registry/formats.d.ts.map +1 -0
  108. package/dist/registry/formats.js +17 -0
  109. package/dist/registry/formats.js.map +1 -0
  110. package/dist/registry/index.d.ts +4 -0
  111. package/dist/registry/index.d.ts.map +1 -0
  112. package/dist/registry/index.js +4 -0
  113. package/dist/registry/index.js.map +1 -0
  114. package/dist/registry/named-registry.d.ts +9 -0
  115. package/dist/registry/named-registry.d.ts.map +1 -0
  116. package/dist/registry/named-registry.js +19 -0
  117. package/dist/registry/named-registry.js.map +1 -0
  118. package/dist/registry/providers.d.ts +8 -0
  119. package/dist/registry/providers.d.ts.map +1 -0
  120. package/dist/registry/providers.js +18 -0
  121. package/dist/registry/providers.js.map +1 -0
  122. package/dist/signatures/index.d.ts +4 -0
  123. package/dist/signatures/index.d.ts.map +1 -0
  124. package/dist/signatures/index.js +4 -0
  125. package/dist/signatures/index.js.map +1 -0
  126. package/dist/signatures/normalize.d.ts +16 -0
  127. package/dist/signatures/normalize.d.ts.map +1 -0
  128. package/dist/signatures/normalize.js +154 -0
  129. package/dist/signatures/normalize.js.map +1 -0
  130. package/dist/signatures/serialize.d.ts +15 -0
  131. package/dist/signatures/serialize.d.ts.map +1 -0
  132. package/dist/signatures/serialize.js +150 -0
  133. package/dist/signatures/serialize.js.map +1 -0
  134. package/dist/signatures/types.d.ts +15 -0
  135. package/dist/signatures/types.d.ts.map +1 -0
  136. package/dist/signatures/types.js +2 -0
  137. package/dist/signatures/types.js.map +1 -0
  138. package/dist/types/index.d.ts +4 -0
  139. package/dist/types/index.d.ts.map +1 -0
  140. package/dist/types/index.js +4 -0
  141. package/dist/types/index.js.map +1 -0
  142. package/dist/types/llm.d.ts +61 -0
  143. package/dist/types/llm.d.ts.map +1 -0
  144. package/dist/types/llm.js +7 -0
  145. package/dist/types/llm.js.map +1 -0
  146. package/dist/types/message.d.ts +104 -0
  147. package/dist/types/message.d.ts.map +1 -0
  148. package/dist/types/message.js +30 -0
  149. package/dist/types/message.js.map +1 -0
  150. package/dist/types/tool.d.ts +160 -0
  151. package/dist/types/tool.d.ts.map +1 -0
  152. package/dist/types/tool.js +11 -0
  153. package/dist/types/tool.js.map +1 -0
  154. package/dist/types.d.ts +2 -0
  155. package/dist/types.d.ts.map +1 -0
  156. package/dist/types.js +2 -0
  157. package/dist/types.js.map +1 -0
  158. package/package.json +54 -0
package/README.md ADDED
@@ -0,0 +1,463 @@
1
+ # unified-llm-provider
2
+
3
+ 统一 LLM 接口 npm 包。
4
+
5
+ 它的目标不是让用户自己处理各家格式差异,而是:
6
+
7
+ - 你可以只维护一种输入/输出格式
8
+ - 你可以显式指定要把请求体**转成什么格式**
9
+ - `thoughtSignature` / `thoughtSignatures` 会跟着请求体格式一起处理
10
+ - 不需要单独再指定“签名格式类型”
11
+
12
+ ---
13
+
14
+ ## 当前内置支持
15
+
16
+ - `unified`:包的统一格式(推荐用户长期维护这个)
17
+ - `gemini`
18
+ - `claude`
19
+ - `openai-compatible`
20
+ - `openai-responses`
21
+ - `deepseek`(wire format 视为 `openai-compatible`)
22
+
23
+ ---
24
+
25
+ ## 设计原则
26
+
27
+ ### 1. 用户主要关心的是“格式 from/to”
28
+
29
+ 不是:
30
+ - 我该把签名声明成 Gemini 还是 Claude?
31
+
32
+ 而是:
33
+ - 我现在手里的请求体是什么格式?
34
+ - 我要把它转成什么格式?
35
+
36
+ 签名会随格式转换一起处理。
37
+
38
+ ### 1.1 一个最重要的例子
39
+
40
+ 假设:
41
+
42
+ - 用户维护的是 `claude` 格式历史
43
+ - 历史里已有 `claude` 原生签名
44
+ - 这次实际要调用的是 `gemini`
45
+
46
+ 那么规则是:
47
+
48
+ 1. `from = claude`
49
+ 2. 实际 provider = `gemini`
50
+ 3. 组装 Gemini 请求时,只找 **Gemini 可用签名**
51
+ 4. 如果当前历史里没有 `gemini:` 对应签名,就等价于 **这轮不传签名**
52
+ 5. 模型返回后,如果你要继续返回 `claude` 格式给用户,那么 `signature` 字段会写成 `gemini:xxxx`,因为这条签名实际属于 Gemini
53
+
54
+ ### 2. 不伪造跨 provider 原生签名
55
+
56
+ ### 2.1 OpenAI 系前缀现在明确拆分
57
+
58
+ 为了避免歧义,这个包不再把 OpenAI 系签名统称成 `openai:`。
59
+
60
+ 现在公开前缀建议是:
61
+
62
+ - `openai-compatible:`
63
+ - `openai-responses:`
64
+
65
+ 旧的 `openai:` 输入已删除。
66
+ 如果你之前使用过 `openai:`,现在必须明确改成 `openai-compatible:` 或 `openai-responses:`。
67
+
68
+
69
+ 这个包会:
70
+ - 自动识别签名
71
+ - 自动归一化
72
+ - 自动带上统一前缀(在 `unified` 输出里)
73
+ - 自动放回目标格式对应的位置
74
+
75
+ 但**不会**做这种错误事情:
76
+ - 把 Gemini 原生签名“变成”Claude 原生签名
77
+ - 把 Claude 原生签名“变成”OpenAI 原生签名
78
+
79
+ 也就是说:
80
+ - Claude 签名仍然是 Claude 签名
81
+ - OpenAI Compatible 扩展签名仍然是 `openai-compatible` 签名
82
+ - OpenAI Responses `encrypted_content` 仍然是 `openai-responses` 签名
83
+ - Gemini thought signature 仍然是 Gemini 签名
84
+
85
+ ---
86
+
87
+ ## `unified` 格式是什么
88
+
89
+ `unified` 是这个包对外推荐的统一格式。
90
+
91
+ 它基于 Gemini-like message 结构,但额外兼容:
92
+
93
+ - `thoughtSignature: string`
94
+ - `thoughtSignatures: { [provider]: string }`
95
+
96
+ 其中 `provider` 推荐使用完整命名空间:`gemini | claude | openai-compatible | openai-responses`
97
+
98
+ 典型例子:
99
+
100
+ ```ts
101
+ const request = {
102
+ contents: [
103
+ { role: 'user', parts: [{ text: 'hello' }] },
104
+ {
105
+ role: 'model',
106
+ parts: [
107
+ {
108
+ text: 'thinking...',
109
+ thought: true,
110
+ thoughtSignature: 'claude:sig_xxx',
111
+ },
112
+ { text: 'final answer' },
113
+ ],
114
+ },
115
+ ],
116
+ };
117
+ ```
118
+
119
+ ---
120
+
121
+ ## 安装
122
+
123
+ ```bash
124
+ npm install unified-llm-provider
125
+ ```
126
+
127
+ ---
128
+
129
+ ## 最常用的两种方式
130
+
131
+ ## 方式 1:直接做格式转换
132
+
133
+ ### `convertRequest`
134
+
135
+ 把一个请求体从 A 格式转成 B 格式。
136
+
137
+ ```ts
138
+ import { convertRequest } from 'unified-llm-provider';
139
+
140
+ const claudeRequest = convertRequest({
141
+ contents: [
142
+ { role: 'user', parts: [{ text: 'hello' }] },
143
+ {
144
+ role: 'model',
145
+ parts: [
146
+ {
147
+ text: 'deep thought',
148
+ thought: true,
149
+ thoughtSignature: 'claude:sig_req_1',
150
+ },
151
+ ],
152
+ },
153
+ ],
154
+ }, {
155
+ from: 'unified',
156
+ to: 'claude',
157
+ model: 'claude-sonnet-4',
158
+ });
159
+ ```
160
+
161
+ 这里你只指定:
162
+ - `from: 'unified'`
163
+ - `to: 'claude'`
164
+
165
+ 签名会自动跟着请求体转换到 Claude 的 `thinking.signature` 位置。
166
+
167
+ ---
168
+
169
+ ### `convertResponse`
170
+
171
+ 把一个响应体从 A 格式转成 B 格式。
172
+
173
+ ```ts
174
+ import { convertResponse } from 'unified-llm-provider';
175
+
176
+ const unifiedResponse = convertResponse({
177
+ content: [
178
+ { type: 'thinking', thinking: 'let me think', signature: 'sig_resp_1' },
179
+ { type: 'text', text: 'done' },
180
+ ],
181
+ stop_reason: 'end_turn',
182
+ }, {
183
+ from: 'claude',
184
+ to: 'unified',
185
+ });
186
+ ```
187
+
188
+ 输出里会自动变成:
189
+
190
+ ```ts
191
+ thoughtSignature: 'claude:sig_resp_1'
192
+ ```
193
+
194
+ ---
195
+
196
+ ### `createStreamConverter`
197
+
198
+ 把流式 chunk / event 从一种格式转成另一种格式。
199
+
200
+ ```ts
201
+ import { createStreamConverter } from 'unified-llm-provider';
202
+
203
+ const converter = createStreamConverter({
204
+ from: 'claude',
205
+ to: 'unified',
206
+ });
207
+
208
+ const chunk = converter.convert({
209
+ type: 'content_block_delta',
210
+ delta: { type: 'signature_delta', signature: 'sig_stream_1' },
211
+ });
212
+ ```
213
+
214
+ ---
215
+
216
+ ## 方式 2:直接调用 provider,但输入输出格式可自选
217
+
218
+ ### `provider.chat()`
219
+
220
+ 你可以调用一个实际 provider,但输入和输出不一定要等于它自己的原生格式。
221
+
222
+ 比如:
223
+ - 实际调用的是 Claude
224
+ - 输入给包的是 `openai-compatible`
225
+ - 输出要回 `unified`
226
+
227
+ ```ts
228
+ import { createClaudeProvider } from 'unified-llm-provider';
229
+
230
+ const provider = createClaudeProvider({
231
+ provider: 'claude',
232
+ model: 'claude-sonnet-4',
233
+ apiKey: process.env.ANTHROPIC_API_KEY,
234
+ });
235
+
236
+ const response = await provider.chat({
237
+ model: 'gpt-4o',
238
+ messages: [
239
+ { role: 'system', content: 'You are helpful' },
240
+ { role: 'user', content: 'hello' },
241
+ ],
242
+ }, {
243
+ inputFormat: 'openai-compatible',
244
+ outputFormat: 'unified',
245
+ });
246
+ ```
247
+
248
+ 流程会是:
249
+
250
+ ```text
251
+ openai-compatible request
252
+ -> decode to unified canonical
253
+ -> encode to claude request
254
+ -> call Claude API
255
+ -> decode Claude response
256
+ -> encode back to unified response
257
+ ```
258
+
259
+ 签名也跟着这条链一起走,不用你单独声明“签名转成什么格式”。
260
+
261
+ ---
262
+
263
+ ## Router / Factory
264
+
265
+ ### 创建内置注册表
266
+
267
+ ```ts
268
+ import { createBootstrapExtensionRegistry } from 'unified-llm-provider';
269
+
270
+ const registry = createBootstrapExtensionRegistry();
271
+ ```
272
+
273
+ ### 通过 factory 创建 provider
274
+
275
+ ```ts
276
+ import { createBootstrapExtensionRegistry, createLLMFromConfig } from 'unified-llm-provider';
277
+
278
+ const registry = createBootstrapExtensionRegistry();
279
+ const provider = createLLMFromConfig({
280
+ provider: 'claude',
281
+ model: 'claude-sonnet-4',
282
+ apiKey: process.env.ANTHROPIC_API_KEY,
283
+ endpoint: {
284
+ url: 'https://example.com/custom/messages',
285
+ headers: {
286
+ 'x-endpoint-header': 'demo',
287
+ },
288
+ },
289
+ headers: {
290
+ 'x-top-header': 'top',
291
+ },
292
+ requestBody: {
293
+ metadata: { source: 'my-app' },
294
+ },
295
+ }, registry.llmProviders);
296
+ ```
297
+
298
+ 支持:
299
+ - 自定义 `url`
300
+ - 自定义 `streamUrl`
301
+ - 自定义 `headers`
302
+ - 自定义 `requestBody`
303
+ - 自定义 `fetch`
304
+ - 自定义超时
305
+
306
+ ---
307
+
308
+ ### 创建 router
309
+
310
+ ```ts
311
+ import { createBootstrapExtensionRegistry, createLLMRouter } from 'unified-llm-provider';
312
+
313
+ const registry = createBootstrapExtensionRegistry();
314
+
315
+ const router = createLLMRouter({
316
+ defaultModelName: 'main',
317
+ models: [
318
+ {
319
+ modelName: 'main',
320
+ provider: 'gemini',
321
+ model: 'gemini-2.0-flash',
322
+ apiKey: process.env.GEMINI_API_KEY,
323
+ },
324
+ {
325
+ modelName: 'backup',
326
+ provider: 'claude',
327
+ model: 'claude-sonnet-4',
328
+ apiKey: process.env.ANTHROPIC_API_KEY,
329
+ },
330
+ ],
331
+ }, undefined, registry.llmProviders);
332
+ ```
333
+
334
+ ---
335
+
336
+ ## thought signature 规则
337
+
338
+ ## 输入兼容两种写法
339
+
340
+ ### 字符串写法
341
+
342
+ ```ts
343
+ thoughtSignature: 'claude:sig_xxx'
344
+ ```
345
+
346
+ ### 对象写法
347
+
348
+ ```ts
349
+ thoughtSignatures: {
350
+ claude: 'sig_xxx'
351
+ }
352
+ ```
353
+
354
+ ---
355
+
356
+ ## 你现在不需要再单独指定“签名格式类型”
357
+
358
+ 你主要只需要指定:
359
+
360
+ - `from`
361
+ - `to`
362
+
363
+ 或者:
364
+
365
+ - `inputFormat`
366
+ - `outputFormat`
367
+
368
+ 签名会跟着格式走。
369
+
370
+ ### 具体来说是什么意思?
371
+
372
+ 比如你传入的是 `claude` 格式:
373
+
374
+ ```ts
375
+ await provider.chat(claudeLikeHistory, {
376
+ inputFormat: 'claude',
377
+ // 实际 provider 是 gemini
378
+ // outputFormat 不写时,默认回到 inputFormat,也就是 claude
379
+ });
380
+ ```
381
+
382
+ 此时:
383
+
384
+ - 请求发给 Gemini 之前,会先检查历史里有没有 `gemini` 可用签名
385
+ - 如果只有 `claude` 原生签名,没有 `gemini:` 前缀签名,那么就**不把 Claude 签名错误地发给 Gemini**
386
+ - Gemini 返回后,再编码回 `claude` 格式
387
+ - 但返回给你的 `claude` thinking block 里的 `signature` 会是:
388
+
389
+ ```ts
390
+ signature: 'gemini:xxxx'
391
+ ```
392
+
393
+ 同理,如果实际调用的是 `openai-responses`,那么回到其他结构时会写成 `openai-responses:xxxx`,不会再写成模糊的 `openai:xxxx`。
394
+
395
+ 这样你下次继续把这份 `claude` 格式历史传回来时,包就能知道:这不是 Claude 原生签名,而是 Gemini 原生签名,只是被保存在 Claude 风格结构里。
396
+
397
+ ---
398
+
399
+ ## `unified` 输出时的默认策略
400
+
401
+ ### 默认优先字符串
402
+
403
+ 如果结果里只有一个可明确归属的签名,`unified` 输出默认会是:
404
+
405
+ ```ts
406
+ thoughtSignature: 'claude:sig_xxx'
407
+ ```
408
+
409
+ ### 如果你的 `unified` 输入本来就是对象签名
410
+
411
+ 在 `provider.chat(..., { inputFormat: 'unified', outputFormat: 'unified' })` 这类场景里,
412
+ 如果你原本传的是:
413
+
414
+ ```ts
415
+ thoughtSignatures: { claude: 'sig_xxx' }
416
+ ```
417
+
418
+ 包会尽量延续对象形式。
419
+
420
+ ### 高级场景仍可手动控制
421
+
422
+ 底层 `encode* / convert*` API 仍然保留 `signatureMode` 这样的高级参数,
423
+ 但正常使用时,你通常不需要碰它。
424
+
425
+ ---
426
+
427
+ ## 推荐实践
428
+
429
+ ### 最推荐
430
+
431
+ - 长期维护 `unified`
432
+ - 真正发请求时按需转成目标 provider 格式
433
+ - 返回时再转回 `unified`
434
+
435
+ 这样用户业务代码最稳定。
436
+
437
+ ### 适合中转网关 / 兼容层
438
+
439
+ - 上游输入 `openai-compatible`
440
+ - 下游实际调用 `claude`
441
+ - 输出再转回 `openai-compatible` 或 `unified`
442
+
443
+ ---
444
+
445
+ ## 当前状态
446
+
447
+ 当前 `新项目/` 里的独立包已经完成:
448
+
449
+ - 独立 `package.json`
450
+ - TypeScript 构建
451
+ - Vitest 测试
452
+ - 内置 provider / format 注册表
453
+ - request / response / stream from-to 转换
454
+ - provider 调用桥接
455
+ - thought signature 跟随格式自动处理
456
+
457
+ 已验证通过:
458
+
459
+ ```bash
460
+ cd 新项目
461
+ npm run test
462
+ npm run build
463
+ ```
@@ -0,0 +1,12 @@
1
+ import { FormatRegistry } from '../registry/formats.js';
2
+ import { LLMProviderFactoryRegistry } from '../registry/providers.js';
3
+ export { FormatRegistry, LLMProviderFactoryRegistry };
4
+ export type { FormatAdapterFactory } from '../registry/formats.js';
5
+ export type { LLMProviderFactory } from '../registry/providers.js';
6
+ export interface BootstrapExtensionRegistry {
7
+ formats: FormatRegistry;
8
+ llmProviders: LLMProviderFactoryRegistry;
9
+ }
10
+ export declare function createBootstrapExtensionRegistry(): BootstrapExtensionRegistry;
11
+ export declare function createBuiltinRegistry(): BootstrapExtensionRegistry;
12
+ //# sourceMappingURL=extensions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extensions.d.ts","sourceRoot":"","sources":["../../src/bootstrap/extensions.ts"],"names":[],"mappings":"AAAA,OAAO,EAA+B,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACrF,OAAO,EAAiC,0BAA0B,EAAE,MAAM,0BAA0B,CAAC;AAErG,OAAO,EAAE,cAAc,EAAE,0BAA0B,EAAE,CAAC;AACtD,YAAY,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AACnE,YAAY,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAEnE,MAAM,WAAW,0BAA0B;IACzC,OAAO,EAAE,cAAc,CAAC;IACxB,YAAY,EAAE,0BAA0B,CAAC;CAC1C;AAED,wBAAgB,gCAAgC,IAAI,0BAA0B,CAK7E;AAED,wBAAgB,qBAAqB,IAAI,0BAA0B,CAElE"}
@@ -0,0 +1,13 @@
1
+ import { createBuiltinFormatRegistry, FormatRegistry } from '../registry/formats.js';
2
+ import { createBuiltinProviderRegistry, LLMProviderFactoryRegistry } from '../registry/providers.js';
3
+ export { FormatRegistry, LLMProviderFactoryRegistry };
4
+ export function createBootstrapExtensionRegistry() {
5
+ return {
6
+ formats: createBuiltinFormatRegistry(),
7
+ llmProviders: createBuiltinProviderRegistry(),
8
+ };
9
+ }
10
+ export function createBuiltinRegistry() {
11
+ return createBootstrapExtensionRegistry();
12
+ }
13
+ //# sourceMappingURL=extensions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extensions.js","sourceRoot":"","sources":["../../src/bootstrap/extensions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,2BAA2B,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACrF,OAAO,EAAE,6BAA6B,EAAE,0BAA0B,EAAE,MAAM,0BAA0B,CAAC;AAErG,OAAO,EAAE,cAAc,EAAE,0BAA0B,EAAE,CAAC;AAStD,MAAM,UAAU,gCAAgC;IAC9C,OAAO;QACL,OAAO,EAAE,2BAA2B,EAAE;QACtC,YAAY,EAAE,6BAA6B,EAAE;KAC9C,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,qBAAqB;IACnC,OAAO,gCAAgC,EAAE,CAAC;AAC5C,CAAC"}
@@ -0,0 +1,8 @@
1
+ import type { LLMConfig, LLMRegistryConfig } from './types.js';
2
+ export declare const DEFAULT_MODEL_NAME = "default";
3
+ export declare const DEFAULTS: Record<string, Partial<LLMConfig> & {
4
+ contextWindow?: number;
5
+ }>;
6
+ export declare function parseSingleLLMConfig(raw?: unknown): LLMConfig;
7
+ export declare function parseLLMConfig(raw?: unknown): LLMRegistryConfig;
8
+ //# sourceMappingURL=llm.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"llm.d.ts","sourceRoot":"","sources":["../../src/config/llm.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAe,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAE5E,eAAO,MAAM,kBAAkB,YAAY,CAAC;AAE5C,eAAO,MAAM,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,SAAS,CAAC,GAAG;IAAE,aAAa,CAAC,EAAE,MAAM,CAAA;CAAE,CA+BpF,CAAC;AAEF,wBAAgB,oBAAoB,CAAC,GAAG,GAAE,OAAY,GAAG,SAAS,CAqCjE;AAmBD,wBAAgB,cAAc,CAAC,GAAG,GAAE,OAAY,GAAG,iBAAiB,CAuBnE"}
@@ -0,0 +1,108 @@
1
+ export const DEFAULT_MODEL_NAME = 'default';
2
+ export const DEFAULTS = {
3
+ deepseek: {
4
+ format: 'openai-compatible',
5
+ model: 'deepseek-v4-flash',
6
+ baseUrl: 'https://api.deepseek.com/v1',
7
+ contextWindow: 1_000_000,
8
+ },
9
+ gemini: {
10
+ format: 'gemini',
11
+ model: 'gemini-2.0-flash',
12
+ baseUrl: 'https://generativelanguage.googleapis.com/v1beta',
13
+ contextWindow: 1_048_576,
14
+ },
15
+ 'openai-compatible': {
16
+ format: 'openai-compatible',
17
+ model: 'gpt-4o',
18
+ baseUrl: 'https://api.openai.com/v1',
19
+ contextWindow: 128_000,
20
+ },
21
+ claude: {
22
+ format: 'claude',
23
+ model: 'claude-sonnet-4-6',
24
+ baseUrl: 'https://api.anthropic.com/v1',
25
+ contextWindow: 200_000,
26
+ },
27
+ 'openai-responses': {
28
+ format: 'openai-responses',
29
+ model: 'gpt-4o',
30
+ baseUrl: 'https://api.openai.com/v1',
31
+ contextWindow: 128_000,
32
+ },
33
+ };
34
+ export function parseSingleLLMConfig(raw = {}) {
35
+ const source = raw && typeof raw === 'object' && !Array.isArray(raw)
36
+ ? raw
37
+ : {};
38
+ const provider = String(source.provider ?? 'gemini');
39
+ const defaults = DEFAULTS[provider] ?? {};
40
+ return {
41
+ ...source,
42
+ provider,
43
+ format: typeof source.format === 'string' ? source.format : defaults.format,
44
+ apiKey: typeof source.apiKey === 'string' ? source.apiKey : undefined,
45
+ model: typeof source.model === 'string' && source.model.trim() ? source.model : String(defaults.model ?? ''),
46
+ baseUrl: typeof source.baseUrl === 'string' && source.baseUrl.trim()
47
+ ? source.baseUrl
48
+ : defaults.baseUrl,
49
+ contextWindow: typeof source.contextWindow === 'number' ? source.contextWindow : defaults.contextWindow,
50
+ supportsVision: typeof source.supportsVision === 'boolean' ? source.supportsVision : undefined,
51
+ headers: source.headers && typeof source.headers === 'object' && !Array.isArray(source.headers)
52
+ ? source.headers
53
+ : undefined,
54
+ requestBody: source.requestBody && typeof source.requestBody === 'object' && !Array.isArray(source.requestBody)
55
+ ? source.requestBody
56
+ : undefined,
57
+ endpoint: source.endpoint && typeof source.endpoint === 'object' && !Array.isArray(source.endpoint)
58
+ ? source.endpoint
59
+ : undefined,
60
+ promptCaching: source.promptCaching === true ? true : undefined,
61
+ autoCaching: source.autoCaching === true ? true : undefined,
62
+ timeoutMs: typeof source.timeoutMs === 'number' ? source.timeoutMs : undefined,
63
+ streamTimeoutMs: typeof source.streamTimeoutMs === 'number' ? source.streamTimeoutMs : undefined,
64
+ fetch: typeof source.fetch === 'function' ? source.fetch : undefined,
65
+ debug: source.debug && typeof source.debug === 'object' && !Array.isArray(source.debug)
66
+ ? source.debug
67
+ : undefined,
68
+ name: typeof source.name === 'string' ? source.name : undefined,
69
+ };
70
+ }
71
+ function normalizeModelName(value) {
72
+ if (typeof value !== 'string')
73
+ return undefined;
74
+ const trimmed = value.trim();
75
+ return trimmed || undefined;
76
+ }
77
+ function toModelDef(modelName, raw) {
78
+ return {
79
+ modelName,
80
+ ...parseSingleLLMConfig(raw),
81
+ };
82
+ }
83
+ function hasObjectModels(raw) {
84
+ return !!raw && typeof raw === 'object' && !Array.isArray(raw) && !!raw.models && typeof raw.models === 'object' && !Array.isArray(raw.models);
85
+ }
86
+ export function parseLLMConfig(raw = {}) {
87
+ if (hasObjectModels(raw)) {
88
+ const source = raw;
89
+ const models = Object.entries(source.models)
90
+ .map(([modelName, value]) => ({ modelName: normalizeModelName(modelName), value }))
91
+ .filter(({ modelName, value }) => !!modelName && value && typeof value === 'object' && !Array.isArray(value))
92
+ .map(({ modelName, value }) => toModelDef(modelName, value));
93
+ if (models.length > 0) {
94
+ const defaultModel = normalizeModelName(source.defaultModelName ?? source.defaultModel);
95
+ return {
96
+ defaultModelName: defaultModel && models.some(model => model.modelName === defaultModel)
97
+ ? defaultModel
98
+ : models[0].modelName,
99
+ models,
100
+ };
101
+ }
102
+ }
103
+ return {
104
+ defaultModelName: DEFAULT_MODEL_NAME,
105
+ models: [toModelDef(DEFAULT_MODEL_NAME, raw)],
106
+ };
107
+ }
108
+ //# sourceMappingURL=llm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"llm.js","sourceRoot":"","sources":["../../src/config/llm.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,69 @@
1
+ export type FetchLike = typeof fetch;
2
+ export interface LLMRequestDebugEvent {
3
+ url: string;
4
+ stream: boolean;
5
+ headers: Record<string, string>;
6
+ body: unknown;
7
+ }
8
+ export interface LLMResponseDebugEvent {
9
+ url: string;
10
+ stream: boolean;
11
+ status?: number;
12
+ headers?: Record<string, string>;
13
+ bodyText?: string;
14
+ error?: string;
15
+ }
16
+ export interface LLMDebugHooks {
17
+ onRequest?(event: LLMRequestDebugEvent): void | Promise<void>;
18
+ onResponse?(event: LLMResponseDebugEvent): void | Promise<void>;
19
+ }
20
+ export interface LLMEndpointOverride {
21
+ url?: string;
22
+ streamUrl?: string;
23
+ headers?: Record<string, string>;
24
+ }
25
+ export interface LLMConfig {
26
+ provider: string;
27
+ /** 默认使用哪个 wire format;未填则由 provider 自身决定 */
28
+ format?: string;
29
+ apiKey?: string;
30
+ /** 提供商真实模型 id */
31
+ model: string;
32
+ /** 默认 baseUrl。若 endpoint.url 已给定,则可不依赖 baseUrl */
33
+ baseUrl?: string;
34
+ /** 直接覆盖最终 endpoint(优先级高于 provider 默认拼接规则) */
35
+ endpoint?: LLMEndpointOverride;
36
+ /** 模型上下文窗口大小(token 数) */
37
+ contextWindow?: number;
38
+ /** 显式声明当前模型是否支持图片输入 */
39
+ supportsVision?: boolean;
40
+ /** 自定义请求头,会覆盖 provider 内置同名 header */
41
+ headers?: Record<string, string>;
42
+ /** 预留给上层 UI 或客户端的思考控制标记 */
43
+ thinkingControl?: boolean;
44
+ /** 自定义请求体,会深合并到 provider 编码后的最终请求体 */
45
+ requestBody?: Record<string, unknown>;
46
+ /** [Claude] 手动 Prompt Caching */
47
+ promptCaching?: boolean;
48
+ /** [Claude] 顶层自动缓存 */
49
+ autoCaching?: boolean;
50
+ /** 非流式请求超时(毫秒) */
51
+ timeoutMs?: number;
52
+ /** 流式请求超时(毫秒) */
53
+ streamTimeoutMs?: number;
54
+ /** 自定义 fetch 实现 */
55
+ fetch?: FetchLike;
56
+ /** 调试钩子 */
57
+ debug?: LLMDebugHooks;
58
+ /** 友好名称,可选 */
59
+ name?: string;
60
+ [key: string]: unknown;
61
+ }
62
+ export interface LLMModelDef extends LLMConfig {
63
+ modelName: string;
64
+ }
65
+ export interface LLMRegistryConfig {
66
+ defaultModelName: string;
67
+ models: LLMModelDef[];
68
+ }
69
+ //# sourceMappingURL=types.d.ts.map