cortico 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 (311) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +47 -0
  3. package/package.json +43 -0
  4. package/src/boot.ts +46 -0
  5. package/src/bot.ts +1342 -0
  6. package/src/config-file.ts +41 -0
  7. package/src/core/README.md +92 -0
  8. package/src/core/billing.ts +26 -0
  9. package/src/core/blobs.ts +106 -0
  10. package/src/core/bus.ts +311 -0
  11. package/src/core/config-schema.ts +201 -0
  12. package/src/core/config.ts +262 -0
  13. package/src/core/core.ts +699 -0
  14. package/src/core/cost.ts +244 -0
  15. package/src/core/event-store.ts +304 -0
  16. package/src/core/fork.ts +113 -0
  17. package/src/core/generation.ts +118 -0
  18. package/src/core/instance-lock.ts +215 -0
  19. package/src/core/ipc-logger.ts +105 -0
  20. package/src/core/language.ts +62 -0
  21. package/src/core/log-context.ts +38 -0
  22. package/src/core/loop.ts +1858 -0
  23. package/src/core/markers.ts +48 -0
  24. package/src/core/prefix.ts +121 -0
  25. package/src/core/run.ts +95 -0
  26. package/src/core/secrets.ts +18 -0
  27. package/src/core/session.ts +56 -0
  28. package/src/core/sessions.ts +261 -0
  29. package/src/core/state.ts +104 -0
  30. package/src/core/template.ts +72 -0
  31. package/src/core/timers.ts +129 -0
  32. package/src/core/tool-log.ts +135 -0
  33. package/src/core/transcript.ts +92 -0
  34. package/src/core/truncate.ts +121 -0
  35. package/src/core/types.ts +1140 -0
  36. package/src/core/usage-log.ts +79 -0
  37. package/src/core/util.ts +386 -0
  38. package/src/deploy-listing.ts +160 -0
  39. package/src/deploy.ts +162 -0
  40. package/src/extensions/README.md +72 -0
  41. package/src/extensions/dry-mount.ts +390 -0
  42. package/src/extensions/manifest.ts +153 -0
  43. package/src/extensions/runtime.ts +42 -0
  44. package/src/extensions.ts +519 -0
  45. package/src/launcher.ts +285 -0
  46. package/src/paths.ts +155 -0
  47. package/src/protocol/open-responses/LICENSE +201 -0
  48. package/src/protocol/open-responses/README.md +14 -0
  49. package/src/protocol/open-responses/context-helpers.ts +27 -0
  50. package/src/protocol/open-responses/context-log.ts +59 -0
  51. package/src/protocol/open-responses/context.ts +89 -0
  52. package/src/protocol/open-responses/generated.ts +115 -0
  53. package/src/protocol/open-responses/index.ts +33 -0
  54. package/src/protocol/open-responses/openapi.json +4230 -0
  55. package/src/protocol/open-responses/stream.ts +146 -0
  56. package/src/protocol/open-responses/tokens.ts +31 -0
  57. package/src/providers/README.md +86 -0
  58. package/src/providers/base.ts +111 -0
  59. package/src/providers/configuration.ts +97 -0
  60. package/src/providers/console/config.ts +21 -0
  61. package/src/providers/console/settings.ts +443 -0
  62. package/src/providers/console/strings.ts +74 -0
  63. package/src/providers/console/types.ts +11 -0
  64. package/src/providers/llamacpp/archive.ts +148 -0
  65. package/src/providers/llamacpp/catalog.ts +247 -0
  66. package/src/providers/llamacpp/config.ts +36 -0
  67. package/src/providers/llamacpp/console/client.ts +6 -0
  68. package/src/providers/llamacpp/console/models-panel.ts +120 -0
  69. package/src/providers/llamacpp/console/runtime-panel.ts +173 -0
  70. package/src/providers/llamacpp/console/server.ts +153 -0
  71. package/src/providers/llamacpp/index.ts +95 -0
  72. package/src/providers/llamacpp/native.ts +51 -0
  73. package/src/providers/llamacpp/options.ts +117 -0
  74. package/src/providers/llamacpp/runtime-store.ts +139 -0
  75. package/src/providers/llamacpp/runtime.ts +214 -0
  76. package/src/providers/llamacpp/server.ts +286 -0
  77. package/src/providers/llamacpp/strings.ts +230 -0
  78. package/src/providers/openai-responses-compat/config.ts +20 -0
  79. package/src/providers/openai-responses-compat/index.ts +89 -0
  80. package/src/providers/openai-responses-compat/native.ts +127 -0
  81. package/src/providers/openai-responses-compat/strings.ts +19 -0
  82. package/src/providers/pricebook.ts +75 -0
  83. package/src/providers/registry.ts +137 -0
  84. package/src/providers/strings.ts +71 -0
  85. package/src/providers/transport/chat.ts +156 -0
  86. package/src/providers/transport/errors.ts +48 -0
  87. package/src/providers/transport/history.ts +68 -0
  88. package/src/providers/transport/native-input.ts +83 -0
  89. package/src/providers/transport/native-types.ts +13 -0
  90. package/src/providers/transport/response-assembly.ts +191 -0
  91. package/src/providers/transport/response-http.ts +196 -0
  92. package/src/providers/transport/response-meters.ts +27 -0
  93. package/src/providers/transport/responses-input.ts +56 -0
  94. package/src/web/README.md +84 -0
  95. package/src/web/client/console-pages/builtins/llm-settings/panel.ts +625 -0
  96. package/src/web/client/console-pages/builtins/llm-settings/pricing-panel.ts +168 -0
  97. package/src/web/client/console-pages/builtins/llm-settings/strings.ts +213 -0
  98. package/src/web/client/console-pages/builtins.ts +10 -0
  99. package/src/web/client/console-pages/context.ts +135 -0
  100. package/src/web/client/console-pages/host.ts +633 -0
  101. package/src/web/client/console-pages/loader.ts +132 -0
  102. package/src/web/client/console-pages/strings.ts +90 -0
  103. package/src/web/client/console-pages/tools/strings.ts +49 -0
  104. package/src/web/client/console-pages/tools/view.ts +215 -0
  105. package/src/web/client/core/api.ts +200 -0
  106. package/src/web/client/core/language.ts +56 -0
  107. package/src/web/client/core/lifecycle.ts +132 -0
  108. package/src/web/client/core/router.ts +199 -0
  109. package/src/web/client/core/stream.ts +157 -0
  110. package/src/web/client/core/websocket.ts +82 -0
  111. package/src/web/client/features/appearance/index.ts +333 -0
  112. package/src/web/client/features/appearance/strings.ts +87 -0
  113. package/src/web/client/features/config/index.ts +11 -0
  114. package/src/web/client/features/config/strings.ts +41 -0
  115. package/src/web/client/features/config/view.ts +428 -0
  116. package/src/web/client/features/core/events.ts +197 -0
  117. package/src/web/client/features/core/index.ts +254 -0
  118. package/src/web/client/features/core/run.ts +56 -0
  119. package/src/web/client/features/core/runlog.ts +214 -0
  120. package/src/web/client/features/core/sessions.ts +53 -0
  121. package/src/web/client/features/core/strings.ts +157 -0
  122. package/src/web/client/features/extensions/index.ts +422 -0
  123. package/src/web/client/features/extensions/strings.ts +164 -0
  124. package/src/web/client/features/feature.ts +93 -0
  125. package/src/web/client/features/live/context.ts +280 -0
  126. package/src/web/client/features/live/fork.ts +156 -0
  127. package/src/web/client/features/live/index.ts +392 -0
  128. package/src/web/client/features/live/onboarding.ts +113 -0
  129. package/src/web/client/features/live/protocol.ts +122 -0
  130. package/src/web/client/features/live/sessions.ts +71 -0
  131. package/src/web/client/features/live/status.ts +68 -0
  132. package/src/web/client/features/live/strings.ts +252 -0
  133. package/src/web/client/features/live/timeline.ts +590 -0
  134. package/src/web/client/features/prompts/editor.ts +292 -0
  135. package/src/web/client/features/prompts/index.ts +346 -0
  136. package/src/web/client/features/prompts/strings.ts +136 -0
  137. package/src/web/client/features/prompts/view.ts +256 -0
  138. package/src/web/client/features/providers/index.ts +100 -0
  139. package/src/web/client/features/providers/strings.ts +23 -0
  140. package/src/web/client/features/settings/general.ts +28 -0
  141. package/src/web/client/features/settings/index.ts +82 -0
  142. package/src/web/client/features/settings/strings.ts +33 -0
  143. package/src/web/client/features/storage/index.ts +1 -0
  144. package/src/web/client/features/storage/strings.ts +45 -0
  145. package/src/web/client/features/storage/view.ts +154 -0
  146. package/src/web/client/features/usage/chart.ts +486 -0
  147. package/src/web/client/features/usage/index.ts +407 -0
  148. package/src/web/client/features/usage/labels.ts +46 -0
  149. package/src/web/client/features/usage/range.ts +36 -0
  150. package/src/web/client/features/usage/state.ts +37 -0
  151. package/src/web/client/features/usage/strings.ts +191 -0
  152. package/src/web/client/features/usage/tooltip.ts +86 -0
  153. package/src/web/client/features/usage/types.ts +96 -0
  154. package/src/web/client/features/worlds/index.ts +424 -0
  155. package/src/web/client/features/worlds/strings.ts +121 -0
  156. package/src/web/client/main.ts +273 -0
  157. package/src/web/client/shell/avatar.ts +233 -0
  158. package/src/web/client/shell/index.ts +549 -0
  159. package/src/web/client/shell/strings.ts +89 -0
  160. package/src/web/client/theme/handoff.ts +43 -0
  161. package/src/web/client/theme/palette.ts +109 -0
  162. package/src/web/client/theme/registry.ts +130 -0
  163. package/src/web/client/theme/storage.ts +72 -0
  164. package/src/web/client/theme/strings.ts +123 -0
  165. package/src/web/client/theme/studio.ts +366 -0
  166. package/src/web/client/ui/actions.ts +109 -0
  167. package/src/web/client/ui/data.ts +185 -0
  168. package/src/web/client/ui/dom.ts +23 -0
  169. package/src/web/client/ui/fields.ts +191 -0
  170. package/src/web/client/ui/format.ts +75 -0
  171. package/src/web/client/ui/icons.ts +183 -0
  172. package/src/web/client/ui/images.ts +91 -0
  173. package/src/web/client/ui/index.ts +89 -0
  174. package/src/web/client/ui/lamp.ts +125 -0
  175. package/src/web/client/ui/log.ts +98 -0
  176. package/src/web/client/ui/overlay.ts +184 -0
  177. package/src/web/client/ui/page.ts +8 -0
  178. package/src/web/client/ui/prompt-input.tsx +311 -0
  179. package/src/web/client/ui/sheet.ts +131 -0
  180. package/src/web/client/ui/strings.ts +71 -0
  181. package/src/web/console-pages.ts +367 -0
  182. package/src/web/files.ts +107 -0
  183. package/src/web/path-picker.ts +296 -0
  184. package/src/web/public/index.html +17 -0
  185. package/src/web/public/styles.css +1715 -0
  186. package/src/web/server.ts +1883 -0
  187. package/src/web/shared/client-panel.ts +603 -0
  188. package/src/web/shared/console-protocol.ts +560 -0
  189. package/src/web/shared/css.d.ts +1 -0
  190. package/src/web/shared/path-picker.ts +23 -0
  191. package/src/web/shared/theme.ts +256 -0
  192. package/src/web/theme-store.ts +37 -0
  193. package/src/world.ts +449 -0
  194. package/src/worlds/bilibili/ENV_PROMPT.md +5 -0
  195. package/src/worlds/bilibili/README.md +176 -0
  196. package/src/worlds/bilibili/audience-admission.ts +995 -0
  197. package/src/worlds/bilibili/client.ts +528 -0
  198. package/src/worlds/bilibili/coalescing-buffer.ts +125 -0
  199. package/src/worlds/bilibili/config.ts +185 -0
  200. package/src/worlds/bilibili/console/client.ts +202 -0
  201. package/src/worlds/bilibili/definition.ts +35 -0
  202. package/src/worlds/bilibili/gift-frame.ts +131 -0
  203. package/src/worlds/bilibili/normalize.ts +452 -0
  204. package/src/worlds/bilibili/overlay/announcement.ts +76 -0
  205. package/src/worlds/bilibili/overlay/assets.ts +82 -0
  206. package/src/worlds/bilibili/overlay/model.ts +494 -0
  207. package/src/worlds/bilibili/overlay/project.ts +202 -0
  208. package/src/worlds/bilibili/overlay/server.ts +286 -0
  209. package/src/worlds/bilibili/overlay/types.ts +217 -0
  210. package/src/worlds/bilibili/overlay/web/app.js +690 -0
  211. package/src/worlds/bilibili/overlay/web/editor.css +297 -0
  212. package/src/worlds/bilibili/overlay/web/editor.html +102 -0
  213. package/src/worlds/bilibili/overlay/web/editor.js +2116 -0
  214. package/src/worlds/bilibili/overlay/web/overlay.html +13 -0
  215. package/src/worlds/bilibili/overlay/web/styles.css +258 -0
  216. package/src/worlds/bilibili/protobuf.ts +123 -0
  217. package/src/worlds/bilibili/wire.ts +94 -0
  218. package/src/worlds/bilibili/world.ts +1389 -0
  219. package/src/worlds/console-fixture/ENV_PROMPT.md +1 -0
  220. package/src/worlds/console-fixture/console/client.ts +72 -0
  221. package/src/worlds/console-fixture/world.ts +73 -0
  222. package/src/worlds/index.ts +15 -0
  223. package/src/worlds/minecraft/ENV_PROMPT.md +32 -0
  224. package/src/worlds/minecraft/ENV_PROMPT_CAMERA.md +2 -0
  225. package/src/worlds/minecraft/LICENSE-mineflayer-pathfinder.txt +21 -0
  226. package/src/worlds/minecraft/README.md +917 -0
  227. package/src/worlds/minecraft/blueprint-plan.ts +915 -0
  228. package/src/worlds/minecraft/blueprint-registry.ts +566 -0
  229. package/src/worlds/minecraft/blueprint-repair.ts +236 -0
  230. package/src/worlds/minecraft/blueprint-resource.ts +272 -0
  231. package/src/worlds/minecraft/blueprint.ts +492 -0
  232. package/src/worlds/minecraft/body-lease.ts +340 -0
  233. package/src/worlds/minecraft/bridge.ts +749 -0
  234. package/src/worlds/minecraft/check.ts +651 -0
  235. package/src/worlds/minecraft/chests.ts +292 -0
  236. package/src/worlds/minecraft/client-launch.ts +272 -0
  237. package/src/worlds/minecraft/client-options.ts +85 -0
  238. package/src/worlds/minecraft/client-skins.ts +164 -0
  239. package/src/worlds/minecraft/client-window.ps1 +91 -0
  240. package/src/worlds/minecraft/client.ts +423 -0
  241. package/src/worlds/minecraft/combat-context.ts +109 -0
  242. package/src/worlds/minecraft/combat.ts +1552 -0
  243. package/src/worlds/minecraft/config.ts +499 -0
  244. package/src/worlds/minecraft/console/access.ts +218 -0
  245. package/src/worlds/minecraft/console/client.ts +244 -0
  246. package/src/worlds/minecraft/console/log.ts +110 -0
  247. package/src/worlds/minecraft/console/mount.ts +420 -0
  248. package/src/worlds/minecraft/console/skin.ts +257 -0
  249. package/src/worlds/minecraft/console/style.css +154 -0
  250. package/src/worlds/minecraft/console/world.ts +449 -0
  251. package/src/worlds/minecraft/deaths.ts +135 -0
  252. package/src/worlds/minecraft/definition.ts +18 -0
  253. package/src/worlds/minecraft/engine-child.ts +274 -0
  254. package/src/worlds/minecraft/engine-ipc.ts +98 -0
  255. package/src/worlds/minecraft/entity-facts.ts +132 -0
  256. package/src/worlds/minecraft/escape.ts +407 -0
  257. package/src/worlds/minecraft/executor.ts +14494 -0
  258. package/src/worlds/minecraft/explored.ts +156 -0
  259. package/src/worlds/minecraft/geometry.ts +205 -0
  260. package/src/worlds/minecraft/goal-plan.ts +647 -0
  261. package/src/worlds/minecraft/item-break.ts +126 -0
  262. package/src/worlds/minecraft/item-facts.ts +146 -0
  263. package/src/worlds/minecraft/item-pick.ts +67 -0
  264. package/src/worlds/minecraft/level-dat.ts +176 -0
  265. package/src/worlds/minecraft/log.ts +132 -0
  266. package/src/worlds/minecraft/mineflayer-fixes.ts +1180 -0
  267. package/src/worlds/minecraft/names.ts +377 -0
  268. package/src/worlds/minecraft/pathfinder-lib.d.ts +13 -0
  269. package/src/worlds/minecraft/pathfinder-perf.ts +796 -0
  270. package/src/worlds/minecraft/piglin.ts +49 -0
  271. package/src/worlds/minecraft/policy.ts +547 -0
  272. package/src/worlds/minecraft/precheck.ts +655 -0
  273. package/src/worlds/minecraft/proxy.ts +445 -0
  274. package/src/worlds/minecraft/ranged.ts +635 -0
  275. package/src/worlds/minecraft/readouts.ts +102 -0
  276. package/src/worlds/minecraft/round.ts +26 -0
  277. package/src/worlds/minecraft/search-observation.ts +92 -0
  278. package/src/worlds/minecraft/server-config.ts +467 -0
  279. package/src/worlds/minecraft/server.ts +658 -0
  280. package/src/worlds/minecraft/show.ts +84 -0
  281. package/src/worlds/minecraft/skills.ts +1763 -0
  282. package/src/worlds/minecraft/terrain.ts +1735 -0
  283. package/src/worlds/minecraft/window.ts +96 -0
  284. package/src/worlds/minecraft/works.ts +179 -0
  285. package/src/worlds/minecraft/world.ts +6438 -0
  286. package/src/worlds/qq/ENV_PROMPT.md +5 -0
  287. package/src/worlds/qq/config.ts +108 -0
  288. package/src/worlds/qq/console/client.ts +126 -0
  289. package/src/worlds/qq/console/events.ts +155 -0
  290. package/src/worlds/qq/console/gate.ts +232 -0
  291. package/src/worlds/qq/console/roster.ts +203 -0
  292. package/src/worlds/qq/console/style.css +65 -0
  293. package/src/worlds/qq/conversation.ts +28 -0
  294. package/src/worlds/qq/definition.ts +62 -0
  295. package/src/worlds/qq/driver.ts +375 -0
  296. package/src/worlds/qq/history-tools.ts +209 -0
  297. package/src/worlds/qq/normalize.ts +323 -0
  298. package/src/worlds/qq/qface-map.ts +282 -0
  299. package/src/worlds/qq/vision-prompt.ts +27 -0
  300. package/src/worlds/qq/vision.ts +642 -0
  301. package/src/worlds/qq/vlm.ts +142 -0
  302. package/src/worlds/qq/world.ts +1503 -0
  303. package/src/worlds/terminal/ENV_PROMPT.md +11 -0
  304. package/src/worlds/terminal/config.ts +9 -0
  305. package/src/worlds/terminal/definition.ts +14 -0
  306. package/src/worlds/terminal/world.ts +700 -0
  307. package/src/worlds/websearch/ENV_PROMPT.md +1 -0
  308. package/src/worlds/websearch/brave-client.ts +261 -0
  309. package/src/worlds/websearch/config.ts +75 -0
  310. package/src/worlds/websearch/definition.ts +20 -0
  311. package/src/worlds/websearch/world.ts +171 -0
@@ -0,0 +1,137 @@
1
+ import { existsSync, readdirSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { URL, fileURLToPath, pathToFileURL } from 'node:url';
4
+ import type { LLMProviderEntry } from '../core/types.ts';
5
+ import type { ProviderHostBase, ProviderInstance, ProviderModule } from './base.ts';
6
+ import { quotePrices } from './pricebook.ts';
7
+ import { createHash } from 'node:crypto';
8
+ import { secretReader } from '../core/secrets.ts';
9
+ import type { ResponseClient } from '../core/generation.ts';
10
+
11
+ /** Modules are discovered from providers/<module>/index.ts. */
12
+ export async function discoverProviderModules(
13
+ root = fileURLToPath(new URL('.', import.meta.url)),
14
+ ): Promise<ProviderModule[]> {
15
+ const modules: ProviderModule[] = [];
16
+ for (const dir of readdirSync(root, { withFileTypes: true }).sort((a, b) =>
17
+ a.name.localeCompare(b.name),
18
+ )) {
19
+ if (!dir.isDirectory()) continue;
20
+ const entry = join(root, dir.name, 'index.ts');
21
+ if (!existsSync(entry)) continue;
22
+ const module = (await import(pathToFileURL(entry).href)).default as ProviderModule;
23
+ if (module.id !== dir.name)
24
+ throw new Error(`Provider module ID must match directory: ${dir.name}`);
25
+ modules.push(module);
26
+ }
27
+ return modules;
28
+ }
29
+
30
+ export const providerModules = await discoverProviderModules();
31
+ const byId = new Map(providerModules.map((module) => [module.id, module]));
32
+
33
+ /**
34
+ * 追加扩展带来的 provider 模块。就地改这一个数组,不换引用:`ProviderRegistry` 与
35
+ * `ProviderSettings` 的默认参数引用的就是它,而两者都在启动器注册完之后才构造。
36
+ * id 撞车直接抛:一个 kind 对应哪份实现不能有歧义。
37
+ */
38
+ export function registerProviderModules(extra: readonly ProviderModule[]): void {
39
+ for (const module of extra) {
40
+ if (byId.has(module.id)) throw new Error(`Provider module id 已被占用: ${module.id}`);
41
+ byId.set(module.id, module);
42
+ providerModules.push(module);
43
+ }
44
+ }
45
+
46
+ export function providerModule(kind: string): ProviderModule {
47
+ const module = byId.get(kind);
48
+ if (!module) throw new Error(`Unknown provider module: ${kind}`);
49
+ return module;
50
+ }
51
+
52
+ export class ProviderRegistry {
53
+ private readonly instances = new Map<string, { key: string; value: ProviderInstance }>();
54
+ private readonly resources = new Map<string, unknown>();
55
+ constructor(
56
+ private readonly entries: () => Record<string, LLMProviderEntry>,
57
+ private readonly host: ProviderHostBase,
58
+ private readonly modules: readonly ProviderModule[] = providerModules,
59
+ ) {}
60
+
61
+ private module(kind: string): ProviderModule {
62
+ const module = this.modules.find((module) => module.id === kind);
63
+ if (!module) throw new Error(`Unknown provider module: ${kind}`);
64
+ return module;
65
+ }
66
+
67
+ resolve(name: string): ProviderInstance {
68
+ const raw = this.entries()[name];
69
+ if (!raw) throw new Error(`没有这个 LLM provider: ${name}`);
70
+ const module = this.module(raw.kind);
71
+ const entry = module.normalize?.(structuredClone(raw)) ?? structuredClone(raw);
72
+ const { pricing: _pricing, spec: _spec, ...transportEntry } = entry;
73
+ const key = JSON.stringify(transportEntry);
74
+ const previous = this.instances.get(name);
75
+ if (previous?.key === key) return previous.value;
76
+ // 按端点名分岔的两样在这里填:模块自己的目录,与只读那个目录的密钥链。
77
+ const { stateRoot, ...base } = this.host;
78
+ const stateDir = join(stateRoot, name);
79
+ const value = module.create(name, entry, {
80
+ ...base,
81
+ stateDir,
82
+ secret: secretReader(join(stateDir, '.env')),
83
+ currentEntry: () =>
84
+ module.normalize?.(structuredClone(this.entries()[name])) ?? this.entries()[name],
85
+ resource: <T>(resource: string, create: () => T): T => {
86
+ const id = JSON.stringify([module.id, name, resource]);
87
+ if (!this.resources.has(id)) this.resources.set(id, create());
88
+ return this.resources.get(id) as T;
89
+ },
90
+ });
91
+ this.instances.set(name, { key, value });
92
+ return value;
93
+ }
94
+
95
+ bind(name: string): ResponseClient {
96
+ const raw = this.entries()[name];
97
+ if (!raw) throw new Error(`没有这个 LLM provider: ${name}`);
98
+ const module = this.module(raw.kind);
99
+ const entry = module.normalize?.(structuredClone(raw)) ?? structuredClone(raw);
100
+ const instance = this.resolve(name);
101
+ const domain = () =>
102
+ createHash('sha256')
103
+ .update(JSON.stringify([entry.kind, entry.baseUrl, instance.compatibilityKey?.() ?? null]))
104
+ .digest('hex');
105
+ const client: ResponseClient = {
106
+ bind: () => client,
107
+ respond: (request, options) =>
108
+ instance.client.respond(request, {
109
+ ...options,
110
+ quote: (at) => quotePrices(entry, request, at, module.prices?.(entry, request, at) ?? []),
111
+ origin: {
112
+ instance: name,
113
+ module: entry.kind,
114
+ model: request.model ?? '',
115
+ compatibilityDomain: domain(),
116
+ },
117
+ }),
118
+ };
119
+ return client;
120
+ }
121
+
122
+ /**
123
+ * Drop the cached instance so the next resolve rebuilds it; `host.resource` objects
124
+ * survive. Secrets are read once per instance, so a key written to the endpoint's `.env`
125
+ * takes effect only through this.
126
+ */
127
+ invalidate(name: string): void {
128
+ this.instances.delete(name);
129
+ }
130
+
131
+ async start(name: string): Promise<unknown> {
132
+ return this.resolve(name).start?.();
133
+ }
134
+ async stopAll(): Promise<void> {
135
+ await Promise.all([...this.instances.values()].map((instance) => instance.value.stop?.()));
136
+ }
137
+ }
@@ -0,0 +1,71 @@
1
+ import { pick, type Language } from '../core/language.ts';
2
+
3
+ /** Validation errors that reach the console as `{ error }` from the provider settings. */
4
+ const zh = {
5
+ profileObject: '模型配置必须是对象',
6
+ modelRequired: '模型名不能为空',
7
+ thinkingBoolean: '推理开关必须是布尔值',
8
+ tierUnsupported: (title: string) => `${title} 不支持该推理档位`,
9
+ effortString: '推理强度必须是非空字符串',
10
+ effortWithoutThinking: '思维链关闭时不能带推理强度',
11
+ temperatureRange: 'temperature 必须在 0–2 之间',
12
+ positiveInteger: (field: string) => `${field} 必须为正整数`,
13
+ optionsObject: '原生参数必须是对象',
14
+ kindChange: '实例不能改变 Provider 模块类型',
15
+ baseUrlFormat: '供应地址需要不含凭据的 HTTP(S) URL',
16
+ secretName: '密钥引用必须是环境变量名',
17
+ multimodalBoolean: '多模态开关必须是布尔值',
18
+ serviceTierString: '服务档必须是字符串',
19
+ serviceTierUnsupported: (title: string, tier: string) => `${title} 不支持服务档 ${tier}`,
20
+ pricingArray: '报价必须是数组',
21
+ rulesArray: '报价规则必须是数组',
22
+ ruleShape: '报价规则需要计量维度和非负单价',
23
+ unitRequired: '计量单位不能为空',
24
+ tokenUnit: '标准 token 计量必须使用 token 单位',
25
+ bandsArray: '输入阶梯必须是数组',
26
+ bandsIncreasing: '输入阶梯阈值必须严格递增且大于 0',
27
+ modelsRequired: '报价需要明确模型名或 *',
28
+ currencyRequired: '报价币种不能为空',
29
+ basisValue: '报价口径必须为 marginal 或 equivalent',
30
+ sourceRequired: '报价需要来源',
31
+ tiersObject: '服务档报价必须是对象',
32
+ tierNameRequired: '服务档名不能为空',
33
+ tierRules: '服务档报价需要规则对象',
34
+ noModel: '未选模型',
35
+ noSecret: (name: string) => `缺少密钥 ${name}`,
36
+ };
37
+ const en: typeof zh = {
38
+ profileObject: 'Model configuration must be an object',
39
+ modelRequired: 'Model name cannot be empty',
40
+ thinkingBoolean: 'Reasoning switch must be a boolean',
41
+ tierUnsupported: (title: string) => `${title} does not support this reasoning tier`,
42
+ effortString: 'Reasoning effort must be a non-empty string',
43
+ effortWithoutThinking: 'Reasoning effort cannot be set while reasoning is off',
44
+ temperatureRange: 'temperature must be between 0 and 2',
45
+ positiveInteger: (field: string) => `${field} must be a positive integer`,
46
+ optionsObject: 'Native options must be an object',
47
+ kindChange: 'An instance cannot change its provider module type',
48
+ baseUrlFormat: 'Provider URL must be an HTTP(S) URL without credentials',
49
+ secretName: 'Secret reference must be an environment variable name',
50
+ multimodalBoolean: 'Multimodal switch must be a boolean',
51
+ serviceTierString: 'Service tier must be a string',
52
+ serviceTierUnsupported: (title: string, tier: string) =>
53
+ `${title} does not support service tier ${tier}`,
54
+ pricingArray: 'Pricing must be an array',
55
+ rulesArray: 'Pricing rules must be an array',
56
+ ruleShape: 'A pricing rule needs a meter and a non-negative rate',
57
+ unitRequired: 'Unit cannot be empty',
58
+ tokenUnit: 'Standard token meters must use the token unit',
59
+ bandsArray: 'Input bands must be an array',
60
+ bandsIncreasing: 'Input band thresholds must be strictly increasing and greater than 0',
61
+ modelsRequired: 'A quote needs explicit model names or *',
62
+ currencyRequired: 'Quote currency cannot be empty',
63
+ basisValue: 'Quote basis must be marginal or equivalent',
64
+ sourceRequired: 'A quote needs a source',
65
+ tiersObject: 'Service tier pricing must be an object',
66
+ tierNameRequired: 'Service tier name cannot be empty',
67
+ tierRules: 'Service tier pricing needs a rules object',
68
+ noModel: 'No model selected',
69
+ noSecret: (name: string) => `Missing secret ${name}`,
70
+ };
71
+ export const text = (language: Language) => pick(language, { zh, en });
@@ -0,0 +1,156 @@
1
+ import type { ModelSpec, ToolSchema, Logger } from '../../core/types.ts';
2
+ import type { NativeChatMessage } from './native-types.ts';
3
+ import { BaseProvider } from '../base.ts';
4
+ import type { Request } from '../../protocol/open-responses/index.ts';
5
+ import { GenerationError, type GenerateOptions, type Generation, type ResponseClient } from '../../core/generation.ts';
6
+ import { generate } from './response-http.ts';
7
+ import { ChatResponseAssembly, parseChatResponse, type ResponseAssembly } from './response-assembly.ts';
8
+ import { nativeChatInput, requestSpec, requestTools } from './native-input.ts';
9
+
10
+ const INFLIGHT_ALERT_WINDOW_MS = 60_000;
11
+ const INFLIGHT_ALERT_MIN = 2;
12
+
13
+ export interface StreamFailureInfo {
14
+ model: string;
15
+ /** 调用方的 session 声明 id;调用方没报就缺席。 */
16
+ role?: string;
17
+ /** 同一 (model, role) 上连续第几次在途生成失败(成功即清零)。 */
18
+ streak: number;
19
+ /** Elapsed time from stream start to failure, in milliseconds. */
20
+ elapsedMs: number;
21
+ /** LLMError.status;0 = 流内失败,不是 HTTP 码。 */
22
+ status: number;
23
+ requestId: string | null;
24
+ /** 完整的上游失败事件,用于诊断。 */
25
+ body: string;
26
+ message: string;
27
+ diagnose: ResponseClient['respond'];
28
+ }
29
+
30
+ /** 传输适配器对流内失败的处理决定。 */
31
+ export interface StreamFailureVerdict {
32
+ /** false 表示停止重试并抛出本次错误。 */
33
+ retry: boolean;
34
+ /** 可选的诊断说明,追加到通用断流日志。 */
35
+ note?: string;
36
+ }
37
+ export abstract class OpenAIHttpClient extends BaseProvider {
38
+ protected buildResponseBody(request: Request, options: GenerateOptions): Record<string, unknown> {
39
+ const body = this.buildBody(requestSpec(request, options), nativeChatInput(request, options), requestTools(request), options.sessionId);
40
+ for (const key of ['top_p', 'presence_penalty', 'frequency_penalty', 'parallel_tool_calls'] as const) if (request[key] !== undefined) body[key] = request[key];
41
+ if (request.tool_choice != null) {
42
+ const choice = request.tool_choice;
43
+ if (typeof choice === 'string') body.tool_choice = choice;
44
+ else if (choice.type === 'function') body.tool_choice = { type: 'function', function: { name: choice.name } };
45
+ else throw new Error('Native Chat provider cannot map this tool_choice');
46
+ }
47
+ if (request.text?.format) {
48
+ const format = request.text.format;
49
+ body.response_format = format.type === 'json_schema' ? { type: 'json_schema', json_schema: { name: format.name, schema: format.schema, description: format.description, strict: format.strict } } : format;
50
+ }
51
+ if (options.onEvent) this.applyStreamFlags(body);
52
+ return body;
53
+ }
54
+
55
+ protected responseAssembly(request: Request): ResponseAssembly { return new ChatResponseAssembly(request); }
56
+
57
+ protected parseResponse(raw: unknown, request: Request): ReturnType<typeof parseChatResponse> { return parseChatResponse(raw, request); }
58
+
59
+ async respond(request: Request, options: GenerateOptions = {}): Promise<Generation> {
60
+ const origin = options.origin ?? { instance: this.constructor.name, module: this.constructor.name,
61
+ model: request.model ?? '', compatibilityDomain: this.baseUrl };
62
+ const key = OpenAIHttpClient.streakKey(request.model ?? '', options.role);
63
+ return generate(request, options, origin, {
64
+ url: `${this.baseUrl}${this.chatPath}`, body: this.buildResponseBody(request, options),
65
+ headers: () => this.headers(), refresh: () => this.onAuthError(),
66
+ assembly: () => this.responseAssembly(request), parse: raw => this.parseResponse(raw, request), log: this.log,
67
+ succeeded: () => { if (!options.diagnostic) this.inflightStreak.delete(key); },
68
+ failure: async (info, record) => {
69
+ const streak = (this.inflightStreak.get(key) ?? 0) + 1;
70
+ this.inflightStreak.set(key, streak);
71
+ this.noteInflightFailure({ ...info, streak });
72
+ const verdict = await this.judgeStreamFailure({ ...info, streak, diagnose: async (probe, probeOptions) => {
73
+ try {
74
+ const signals = [options.signal, probeOptions?.signal].filter((signal): signal is AbortSignal => Boolean(signal));
75
+ const result = await this.respond(probe, { ...options, onEvent: undefined, context: undefined, ...probeOptions,
76
+ signal: signals.length ? AbortSignal.any(signals) : undefined, diagnostic: true });
77
+ record(result.attempts);
78
+ return result;
79
+ } catch (error) {
80
+ if (error instanceof GenerationError) record(error.attempts);
81
+ throw error;
82
+ }
83
+ } });
84
+ this.log.warn('LLM 生成阶段中断:连续失败 ' + streak + ' 次,开流后 ' + (info.elapsedMs / 1000).toFixed(1) + ' 秒' + (verdict.note ? ';' + verdict.note : ''), info);
85
+ return verdict.retry;
86
+ },
87
+ });
88
+ }
89
+ protected baseUrl: string;
90
+ protected log: Logger;
91
+ /** 相对 baseUrl 的端点路径,可由适配器覆盖。 */
92
+ protected chatPath = '/chat/completions';
93
+ /** 同一 (model, role) 上的连续在途生成失败数;成功即清零。 */
94
+ private inflightStreak = new Map<string, number>();
95
+ /** 在途生成失败的时刻表,只用于频次告警(见 noteInflightFailure)。 */
96
+ private inflightFailAt: number[] = [];
97
+
98
+ constructor(baseUrl: string, log: Logger) {
99
+ super();
100
+ this.baseUrl = baseUrl.replace(/\/+$/, '');
101
+ this.log = log;
102
+ }
103
+
104
+ private static streakKey(model: string, role?: string): string {
105
+ return `${model}::${role ?? ''}`;
106
+ }
107
+
108
+ /** 流内失败后调用;适配器可禁用该次重试,默认允许。 */
109
+ protected async judgeStreamFailure(_info: StreamFailureInfo): Promise<StreamFailureVerdict> {
110
+ return { retry: true };
111
+ }
112
+
113
+ /**
114
+ * 记录在途生成失败,窗口内达到密度门槛时发送操作员告警;仅观察,不自动处置上游或会话。
115
+ */
116
+ private noteInflightFailure(info: Omit<StreamFailureInfo, 'diagnose'>): void {
117
+ const now = Date.now();
118
+ this.inflightFailAt.push(now);
119
+ const cutoff = now - INFLIGHT_ALERT_WINDOW_MS;
120
+ while (this.inflightFailAt.length > 0 && this.inflightFailAt[0] < cutoff) this.inflightFailAt.shift();
121
+ if (this.inflightFailAt.length < INFLIGHT_ALERT_MIN) return;
122
+ this.log.warn(
123
+ `[告警] ${INFLIGHT_ALERT_WINDOW_MS / 1000} 秒内第 ${this.inflightFailAt.length} 次在途生成失败` +
124
+ `;请检查请求错误与上游状态`,
125
+ {
126
+ model: info.model,
127
+ ...(info.role ? { role: info.role } : {}),
128
+ status: info.status,
129
+ ...(info.requestId ? { requestId: info.requestId } : {}),
130
+ },
131
+ );
132
+ }
133
+
134
+ /** 方言请求体(不含 stream 字段;流式路径自行追加)。 */
135
+ protected abstract buildBody(
136
+ spec: ModelSpec,
137
+ messages: NativeChatMessage[],
138
+ tools?: ToolSchema[],
139
+ sessionId?: string,
140
+ ): Record<string, unknown>;
141
+
142
+ /** 每次请求的 HTTP 头(含鉴权,若有)。可异步:OAuth 方言在这里等一次临期刷新。 */
143
+ protected abstract headers(): Record<string, string> | Promise<Record<string, string>>;
144
+
145
+ /** 401/403 后调用,返回 true 时刷新后重试一次;默认返回 false。 */
146
+ protected async onAuthError(): Promise<boolean> {
147
+ return false;
148
+ }
149
+
150
+ /** 请求体上开流的字段。缺省是 OpenAI chat/completions 的写法。 */
151
+ protected applyStreamFlags(body: Record<string, unknown>): void {
152
+ body.stream = true;
153
+ body.stream_options = { include_usage: true };
154
+ }
155
+
156
+ }
@@ -0,0 +1,48 @@
1
+ export class LLMError extends Error {
2
+ status: number;
3
+ body: string;
4
+ constructor(message: string, status: number, body: string) {
5
+ super(message);
6
+ this.name = 'LLMError';
7
+ this.status = status;
8
+ this.body = body;
9
+ }
10
+ }
11
+
12
+ /**
13
+ * Whether an upstream rejection says the input exceeded the model's context. Status 0 is an
14
+ * in-stream failure event; 400/413 are the request-rejection codes. The body patterns cover
15
+ * the OpenAI `context_length_exceeded` code, DeepSeek's "maximum context length" message
16
+ * and llama-server's "exceeds the available context size".
17
+ */
18
+ export function isContextOverflow(error: { status: number; body: string }): boolean {
19
+ if (error.status !== 0 && error.status !== 400 && error.status !== 413) return false;
20
+ return /context_length_exceeded|context length|context size|context window|maximum context|prompt is too long|input is too long|too many tokens/i.test(error.body);
21
+ }
22
+
23
+ /** 上游请求 id 拼进错误消息的固定形状(没有就什么都不拼)。 */
24
+ export function reqIdSuffix(requestId: string | null | undefined): string {
25
+ return requestId ? ` [req=${requestId}]` : '';
26
+ }
27
+
28
+ export function abortError(signal: AbortSignal): LLMError {
29
+ const reason = signal.reason;
30
+ const detail = reason instanceof Error ? reason.message : String(reason ?? '调用方取消');
31
+ return new LLMError(`LLM请求已取消: ${detail}`, 0, '');
32
+ }
33
+
34
+ export function retryDelay(ms: number, signal?: AbortSignal): Promise<void> {
35
+ if (!signal) return new Promise((resolve) => setTimeout(resolve, ms));
36
+ if (signal.aborted) return Promise.reject(abortError(signal));
37
+ return new Promise<void>((resolve, reject) => {
38
+ const timer = setTimeout(() => {
39
+ signal.removeEventListener('abort', onAbort);
40
+ resolve();
41
+ }, ms);
42
+ const onAbort = (): void => {
43
+ clearTimeout(timer);
44
+ reject(abortError(signal));
45
+ };
46
+ signal.addEventListener('abort', onAbort, { once: true });
47
+ });
48
+ }
@@ -0,0 +1,68 @@
1
+ import type { NativeChatMessage } from './native-types.ts';
2
+ import type { ToolSchema } from '../../core/types.ts';
3
+ export function dropPastThinking(messages: NativeChatMessage[]): NativeChatMessage[] {
4
+ return messages.map((m) =>
5
+ m.role === 'assistant' && m.reasoning_content && !m.head
6
+ ? { ...m, reasoning_content: '' }
7
+ : m,
8
+ );
9
+ }
10
+
11
+ /** 线上不带 blobs 字段:句柄是 core 内部形态,分片渲染(若有)另行处理。 */
12
+ export function dropBlobsField(m: NativeChatMessage): NativeChatMessage {
13
+ if (m.blobs === undefined) return m;
14
+ const { blobs: _drop, ...rest } = m;
15
+ return rest as NativeChatMessage;
16
+ }
17
+
18
+ export function dropHeadMark(m: NativeChatMessage): NativeChatMessage {
19
+ return { role: m.role, content: m.content,
20
+ ...(m.reasoning_content !== undefined ? { reasoning_content: m.reasoning_content } : {}),
21
+ ...(m.tool_calls ? { tool_calls: m.tool_calls } : {}),
22
+ ...(m.tool_call_id ? { tool_call_id: m.tool_call_id } : {}), ...(m.parts ? { parts: m.parts } : {}) };
23
+ }
24
+
25
+ export interface CompatMediaOptions {
26
+ enabled: () => boolean;
27
+ read: (ref: string) => Buffer | null;
28
+ }
29
+
30
+ /** OpenAI function 格式的 tools 段(各 chat 方言共用)。 */
31
+ export function mapTools(tools?: ToolSchema[]): Array<Record<string, unknown>> | undefined {
32
+ if (!tools || tools.length === 0) return undefined;
33
+ return tools.map((t) => ({
34
+ type: 'function',
35
+ function: { name: t.name, description: t.description, parameters: t.parameters },
36
+ }));
37
+ }
38
+
39
+ /**
40
+ * 渲染 Chat 请求消息,移除内部 blobs 字段。仅在 keepReasoning 时保留非空 reasoning_content。
41
+ * 启用媒体时将可读取附件转换为 data URL 内容块;无法读取的附件跳过,原文本引用仍保留。
42
+ */
43
+ export function renderMessagesWithMedia(
44
+ messages: NativeChatMessage[],
45
+ media?: CompatMediaOptions,
46
+ opts?: { keepReasoning?: boolean },
47
+ ): Array<Record<string, unknown>> {
48
+ const renderMedia = media?.enabled() === true ? media : undefined;
49
+ return messages.map((m) => {
50
+ const refs = m.blobs;
51
+ const { reasoning_content: _r, parts: _parts, ...rest } = dropHeadMark(m);
52
+ const base = { ...rest, content: m.parts ?? m.content } as Record<string, unknown>;
53
+ if (opts?.keepReasoning && m.reasoning_content) base.reasoning_content = m.reasoning_content;
54
+ if (!renderMedia || !refs?.length) return base;
55
+ const parts: Array<Record<string, unknown>> = m.parts ? [...m.parts] : [{ type: 'text', text: m.content }];
56
+ let attached = false;
57
+ for (const r of refs) {
58
+ const bytes = renderMedia.read(r.handle);
59
+ if (!bytes) continue;
60
+ attached = true;
61
+ parts.push({
62
+ type: 'image_url',
63
+ image_url: { url: `data:${r.mime};base64,${bytes.toString('base64')}` },
64
+ });
65
+ }
66
+ return attached ? { ...base, content: parts } : base;
67
+ });
68
+ }
@@ -0,0 +1,83 @@
1
+ import type { NativeChatMessage } from './native-types.ts';
2
+ import type { ModelSpec, ToolSchema } from '../../core/types.ts';
3
+ import type { Request } from '../../protocol/open-responses/index.ts';
4
+ import { ResponseProtocolError } from '../../protocol/open-responses/stream.ts';
5
+ import { itemText, record, type ContextRecord } from '../../protocol/open-responses/context.ts';
6
+ import type { GenerateOptions } from '../../core/generation.ts';
7
+
8
+ export function requestSpec(request: Request, options: GenerateOptions): ModelSpec {
9
+ return { ...options.nativeSpec, model: request.model ?? options.nativeSpec?.model ?? '',
10
+ thinking: options.nativeSpec?.thinking ?? request.reasoning?.effort !== 'none',
11
+ ...(request.reasoning?.effort && request.reasoning.effort !== 'none' ? { reasoningEffort: request.reasoning.effort } : {}),
12
+ ...(options.nativeSpec?.reasoningEffort === 'max' ? { reasoningEffort: 'max' } : {}),
13
+ ...(request.temperature != null ? { temperature: request.temperature } : {}),
14
+ ...(request.max_output_tokens != null ? { maxTokens: request.max_output_tokens } : {}),
15
+ };
16
+ }
17
+
18
+ export function requestTools(request: Request): ToolSchema[] | undefined {
19
+ return request.tools?.map(tool => ({ name: tool.name, description: tool.description ?? '', parameters: tool.parameters ?? {} }));
20
+ }
21
+
22
+ export function requestContext(request: Request, options: GenerateOptions): readonly ContextRecord[] {
23
+ if (options.context) return options.context;
24
+ if (typeof request.input === 'string') return [record({ type: 'message', role: 'user', content: request.input })];
25
+ return (request.input ?? []).map(item => record(item));
26
+ }
27
+
28
+ function nativeParts(entry: ContextRecord): Array<Record<string, unknown>> | undefined {
29
+ const item = entry.item;
30
+ const content = item.type === 'message' ? item.content : item.type === 'function_call_output' ? item.output : undefined;
31
+ if (!Array.isArray(content) || content.every(part => 'text' in part || part.type === 'refusal')) return undefined;
32
+ return content.map(part => {
33
+ if ('text' in part) return { type: 'text', text: part.text };
34
+ if (part.type === 'input_image') {
35
+ if (!part.image_url) throw new ResponseProtocolError('Native Chat images require image_url');
36
+ return { type: 'image_url', image_url: { url: part.image_url, ...(part.detail ? { detail: part.detail } : {}) } };
37
+ }
38
+ if (part.type === 'input_file') {
39
+ if (!('file_data' in part) || !part.file_data) throw new ResponseProtocolError('Native Chat files require inline file_data');
40
+ return { type: 'file', file: { ...(part.filename ? { filename: part.filename } : {}), file_data: part.file_data } };
41
+ }
42
+ throw new ResponseProtocolError('Unsupported native Chat content part: ' + part.type);
43
+ });
44
+ }
45
+
46
+ /** Chat Completions has one assistant envelope for adjacent reasoning, text and function calls. */
47
+ export function nativeChatInput(request: Request, options: GenerateOptions): NativeChatMessage[] {
48
+ const messages: NativeChatMessage[] = [];
49
+ if (request.instructions) messages.push({ role: 'system', content: request.instructions });
50
+ let assistant: NativeChatMessage | null = null;
51
+ const ensureAssistant = (entry: ContextRecord): NativeChatMessage => {
52
+ if (!assistant) {
53
+ assistant = { role: 'assistant', content: '', ...(entry.context.head ? { head: true } : {}) };
54
+ messages.push(assistant);
55
+ }
56
+ return assistant;
57
+ };
58
+ let group: string | number | undefined;
59
+ for (const entry of requestContext(request, options)) {
60
+ const nextGroup = entry.context.responseId;
61
+ if (nextGroup !== undefined && nextGroup !== group) assistant = null;
62
+ group = nextGroup;
63
+ const item = entry.item;
64
+ if (item.type === 'reasoning') {
65
+ const target = ensureAssistant(entry);
66
+ target.reasoning_content = (target.reasoning_content ?? '') + itemText(item);
67
+ } else if (item.type === 'function_call') {
68
+ const target = ensureAssistant(entry);
69
+ (target.tool_calls ??= []).push({ id: item.call_id, type: 'function', function: { name: item.name, arguments: item.arguments } });
70
+ } else if (item.type === 'function_call_output') {
71
+ assistant = null;
72
+ messages.push({ role: 'tool', content: itemText(item), parts: nativeParts(entry), tool_call_id: item.call_id, blobs: entry.context.blobs });
73
+ } else if (item.type === 'message') {
74
+ if (item.role === 'assistant') {
75
+ ensureAssistant(entry).content += itemText({ ...item, type: 'message' });
76
+ } else {
77
+ assistant = null;
78
+ messages.push({ role: item.role === 'developer' ? 'system' : item.role, content: itemText({ ...item, type: 'message' }), parts: nativeParts(entry), blobs: entry.context.blobs });
79
+ }
80
+ } else throw new Error(`Native Chat provider cannot replay ${item.type}`);
81
+ }
82
+ return messages;
83
+ }
@@ -0,0 +1,13 @@
1
+ import type { BlobRef } from '../../core/types.ts';
2
+
3
+ /** Chat Completions request envelope, used only inside native provider adapters. */
4
+ export interface NativeChatMessage {
5
+ role: 'system' | 'user' | 'assistant' | 'tool';
6
+ content: string;
7
+ parts?: Array<Record<string, unknown>>;
8
+ reasoning_content?: string;
9
+ tool_calls?: { id: string; type: 'function'; function: { name: string; arguments: string } }[];
10
+ tool_call_id?: string;
11
+ blobs?: BlobRef[];
12
+ head?: true;
13
+ }