c0de-agent 0.0.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (330) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +101 -0
  3. package/dist/cli/commands/acp.d.ts +19 -0
  4. package/dist/cli/commands/acp.js +50 -0
  5. package/dist/cli/commands/chat.d.ts +13 -0
  6. package/dist/cli/commands/chat.js +26 -0
  7. package/dist/cli/commands/config.d.ts +17 -0
  8. package/dist/cli/commands/config.js +77 -0
  9. package/dist/cli/commands/init.d.ts +9 -0
  10. package/dist/cli/commands/init.js +14 -0
  11. package/dist/cli/commands/plugin.d.ts +11 -0
  12. package/dist/cli/commands/plugin.js +31 -0
  13. package/dist/cli/commands/serve.d.ts +18 -0
  14. package/dist/cli/commands/serve.js +35 -0
  15. package/dist/cli/commands/update.d.ts +24 -0
  16. package/dist/cli/commands/update.js +40 -0
  17. package/dist/cli/deps.d.ts +19 -0
  18. package/dist/cli/deps.js +57 -0
  19. package/dist/cli/index.d.ts +8 -0
  20. package/dist/cli/index.js +149 -0
  21. package/dist/cli/modes/acp.d.ts +18 -0
  22. package/dist/cli/modes/acp.js +57 -0
  23. package/dist/cli/modes/print.d.ts +16 -0
  24. package/dist/cli/modes/print.js +30 -0
  25. package/dist/cli/parser.d.ts +19 -0
  26. package/dist/cli/parser.js +28 -0
  27. package/dist/cli/utils/output.d.ts +11 -0
  28. package/dist/cli/utils/output.js +23 -0
  29. package/dist/cli/utils/prompt.d.ts +12 -0
  30. package/dist/cli/utils/prompt.js +18 -0
  31. package/dist/core/agent.d.ts +12 -0
  32. package/dist/core/agent.js +95 -0
  33. package/dist/core/agents/builtin.d.ts +6 -0
  34. package/dist/core/agents/builtin.js +76 -0
  35. package/dist/core/agents/discovery.d.ts +6 -0
  36. package/dist/core/agents/discovery.js +96 -0
  37. package/dist/core/agents/index.d.ts +6 -0
  38. package/dist/core/agents/index.js +4 -0
  39. package/dist/core/agents/parallel.d.ts +13 -0
  40. package/dist/core/agents/parallel.js +37 -0
  41. package/dist/core/agents/registry.d.ts +4 -0
  42. package/dist/core/agents/registry.js +23 -0
  43. package/dist/core/agents/types.d.ts +37 -0
  44. package/dist/core/agents/types.js +1 -0
  45. package/dist/core/compact.d.ts +11 -0
  46. package/dist/core/compact.js +31 -0
  47. package/dist/core/config.d.ts +7 -0
  48. package/dist/core/config.js +84 -0
  49. package/dist/core/context.d.ts +23 -0
  50. package/dist/core/context.js +93 -0
  51. package/dist/core/index.d.ts +13 -0
  52. package/dist/core/index.js +11 -0
  53. package/dist/core/loop.d.ts +25 -0
  54. package/dist/core/loop.js +651 -0
  55. package/dist/core/metrics.d.ts +27 -0
  56. package/dist/core/metrics.js +94 -0
  57. package/dist/core/prompt-registry.d.ts +21 -0
  58. package/dist/core/prompt-registry.js +200 -0
  59. package/dist/core/prompt.d.ts +11 -0
  60. package/dist/core/prompt.js +13 -0
  61. package/dist/core/secret.d.ts +12 -0
  62. package/dist/core/secret.js +51 -0
  63. package/dist/core/slash.d.ts +15 -0
  64. package/dist/core/slash.js +114 -0
  65. package/dist/core/steering.d.ts +8 -0
  66. package/dist/core/steering.js +15 -0
  67. package/dist/core/title.d.ts +42 -0
  68. package/dist/core/title.js +115 -0
  69. package/dist/core/tool-exec.d.ts +20 -0
  70. package/dist/core/tool-exec.js +64 -0
  71. package/dist/core/types.d.ts +95 -0
  72. package/dist/core/types.js +1 -0
  73. package/dist/core/worktree.d.ts +24 -0
  74. package/dist/core/worktree.js +68 -0
  75. package/dist/dap/index.d.ts +6 -0
  76. package/dist/dap/index.js +4 -0
  77. package/dist/dap/protocol.d.ts +40 -0
  78. package/dist/dap/protocol.js +132 -0
  79. package/dist/dap/session.d.ts +23 -0
  80. package/dist/dap/session.js +130 -0
  81. package/dist/dap/transport.d.ts +8 -0
  82. package/dist/dap/transport.js +30 -0
  83. package/dist/dap/types.d.ts +50 -0
  84. package/dist/dap/types.js +2 -0
  85. package/dist/db/client.d.ts +22 -0
  86. package/dist/db/client.js +39 -0
  87. package/dist/db/index.d.ts +6 -0
  88. package/dist/db/index.js +4 -0
  89. package/dist/db/migrate.d.ts +9 -0
  90. package/dist/db/migrate.js +15 -0
  91. package/dist/db/schema.d.ts +965 -0
  92. package/dist/db/schema.js +112 -0
  93. package/dist/db/types.d.ts +11 -0
  94. package/dist/db/types.js +1 -0
  95. package/dist/llm/index.d.ts +13 -0
  96. package/dist/llm/index.js +8 -0
  97. package/dist/llm/protocols/openai-compat.d.ts +139 -0
  98. package/dist/llm/protocols/openai-compat.js +214 -0
  99. package/dist/llm/protocols/utils/index.d.ts +2 -0
  100. package/dist/llm/protocols/utils/index.js +2 -0
  101. package/dist/llm/protocols/utils/lifecycle.d.ts +23 -0
  102. package/dist/llm/protocols/utils/lifecycle.js +46 -0
  103. package/dist/llm/protocols/utils/tool-stream.d.ts +46 -0
  104. package/dist/llm/protocols/utils/tool-stream.js +94 -0
  105. package/dist/llm/provider-error.d.ts +5 -0
  106. package/dist/llm/provider-error.js +33 -0
  107. package/dist/llm/provider.d.ts +32 -0
  108. package/dist/llm/provider.js +191 -0
  109. package/dist/llm/registry.d.ts +44 -0
  110. package/dist/llm/registry.js +107 -0
  111. package/dist/llm/retry.d.ts +37 -0
  112. package/dist/llm/retry.js +82 -0
  113. package/dist/llm/routing.d.ts +36 -0
  114. package/dist/llm/routing.js +53 -0
  115. package/dist/llm/schema/errors.d.ts +88 -0
  116. package/dist/llm/schema/errors.js +33 -0
  117. package/dist/llm/schema/events.d.ts +135 -0
  118. package/dist/llm/schema/events.js +35 -0
  119. package/dist/llm/schema/ids.d.ts +22 -0
  120. package/dist/llm/schema/ids.js +3 -0
  121. package/dist/llm/schema/index.d.ts +5 -0
  122. package/dist/llm/schema/index.js +5 -0
  123. package/dist/llm/schema/messages.d.ts +97 -0
  124. package/dist/llm/schema/messages.js +18 -0
  125. package/dist/llm/schema/options.d.ts +54 -0
  126. package/dist/llm/schema/options.js +54 -0
  127. package/dist/llm/token.d.ts +2 -0
  128. package/dist/llm/token.js +13 -0
  129. package/dist/llm/transport.d.ts +31 -0
  130. package/dist/llm/transport.js +165 -0
  131. package/dist/mcp/index.d.ts +1 -0
  132. package/dist/mcp/index.js +3 -0
  133. package/dist/plugins/builtin.d.ts +6 -0
  134. package/dist/plugins/builtin.js +54 -0
  135. package/dist/plugins/hooks.d.ts +3 -0
  136. package/dist/plugins/hooks.js +79 -0
  137. package/dist/plugins/index.d.ts +9 -0
  138. package/dist/plugins/index.js +8 -0
  139. package/dist/plugins/init.d.ts +23 -0
  140. package/dist/plugins/init.js +30 -0
  141. package/dist/plugins/lifecycle.d.ts +6 -0
  142. package/dist/plugins/lifecycle.js +74 -0
  143. package/dist/plugins/loader.d.ts +16 -0
  144. package/dist/plugins/loader.js +68 -0
  145. package/dist/plugins/logger.d.ts +4 -0
  146. package/dist/plugins/logger.js +31 -0
  147. package/dist/plugins/registry.d.ts +7 -0
  148. package/dist/plugins/registry.js +24 -0
  149. package/dist/plugins/types.d.ts +113 -0
  150. package/dist/plugins/types.js +1 -0
  151. package/dist/project/detect.d.ts +4 -0
  152. package/dist/project/detect.js +131 -0
  153. package/dist/project/index.d.ts +4 -0
  154. package/dist/project/index.js +2 -0
  155. package/dist/project/project.d.ts +23 -0
  156. package/dist/project/project.js +87 -0
  157. package/dist/project/resolve.d.ts +8 -0
  158. package/dist/project/resolve.js +81 -0
  159. package/dist/server/agent-manager.d.ts +32 -0
  160. package/dist/server/agent-manager.js +54 -0
  161. package/dist/server/app.d.ts +5 -0
  162. package/dist/server/app.js +75 -0
  163. package/dist/server/context.d.ts +20 -0
  164. package/dist/server/context.js +47 -0
  165. package/dist/server/dev.d.ts +11 -0
  166. package/dist/server/dev.js +83 -0
  167. package/dist/server/index.d.ts +11 -0
  168. package/dist/server/index.js +7 -0
  169. package/dist/server/middleware/auth.d.ts +15 -0
  170. package/dist/server/middleware/auth.js +29 -0
  171. package/dist/server/middleware/cors.d.ts +16 -0
  172. package/dist/server/middleware/cors.js +44 -0
  173. package/dist/server/middleware/error.d.ts +15 -0
  174. package/dist/server/middleware/error.js +11 -0
  175. package/dist/server/permission/interactive.d.ts +24 -0
  176. package/dist/server/permission/interactive.js +56 -0
  177. package/dist/server/permission/store.d.ts +30 -0
  178. package/dist/server/permission/store.js +47 -0
  179. package/dist/server/routes/agent.d.ts +4 -0
  180. package/dist/server/routes/agent.js +19 -0
  181. package/dist/server/routes/catalog.d.ts +42 -0
  182. package/dist/server/routes/catalog.js +194 -0
  183. package/dist/server/routes/chat.d.ts +8 -0
  184. package/dist/server/routes/chat.js +282 -0
  185. package/dist/server/routes/commands.d.ts +5 -0
  186. package/dist/server/routes/commands.js +17 -0
  187. package/dist/server/routes/config.d.ts +4 -0
  188. package/dist/server/routes/config.js +27 -0
  189. package/dist/server/routes/files.d.ts +4 -0
  190. package/dist/server/routes/files.js +183 -0
  191. package/dist/server/routes/filesystem.d.ts +29 -0
  192. package/dist/server/routes/filesystem.js +108 -0
  193. package/dist/server/routes/health.d.ts +3 -0
  194. package/dist/server/routes/health.js +13 -0
  195. package/dist/server/routes/permissions.d.ts +5 -0
  196. package/dist/server/routes/permissions.js +23 -0
  197. package/dist/server/routes/project.d.ts +4 -0
  198. package/dist/server/routes/project.js +51 -0
  199. package/dist/server/routes/provider.d.ts +16 -0
  200. package/dist/server/routes/provider.js +66 -0
  201. package/dist/server/routes/session.d.ts +4 -0
  202. package/dist/server/routes/session.js +130 -0
  203. package/dist/server/routes/tool.d.ts +4 -0
  204. package/dist/server/routes/tool.js +28 -0
  205. package/dist/server/routes/update.d.ts +14 -0
  206. package/dist/server/routes/update.js +49 -0
  207. package/dist/server/server.d.ts +43 -0
  208. package/dist/server/server.js +197 -0
  209. package/dist/server/types.d.ts +73 -0
  210. package/dist/server/types.js +2 -0
  211. package/dist/server/util/safe-path.d.ts +7 -0
  212. package/dist/server/util/safe-path.js +7 -0
  213. package/dist/session/archive.d.ts +15 -0
  214. package/dist/session/archive.js +129 -0
  215. package/dist/session/branch.d.ts +9 -0
  216. package/dist/session/branch.js +63 -0
  217. package/dist/session/compaction.d.ts +27 -0
  218. package/dist/session/compaction.js +192 -0
  219. package/dist/session/context.d.ts +16 -0
  220. package/dist/session/context.js +173 -0
  221. package/dist/session/index.d.ts +10 -0
  222. package/dist/session/index.js +10 -0
  223. package/dist/session/message.d.ts +22 -0
  224. package/dist/session/message.js +145 -0
  225. package/dist/session/session.d.ts +37 -0
  226. package/dist/session/session.js +144 -0
  227. package/dist/session/snapshot.d.ts +11 -0
  228. package/dist/session/snapshot.js +60 -0
  229. package/dist/session/squash.d.ts +9 -0
  230. package/dist/session/squash.js +88 -0
  231. package/dist/session/token.d.ts +10 -0
  232. package/dist/session/token.js +33 -0
  233. package/dist/session/types.d.ts +122 -0
  234. package/dist/session/types.js +1 -0
  235. package/dist/shared/index.d.ts +2 -0
  236. package/dist/shared/index.js +1 -0
  237. package/dist/shared/types/agent.d.ts +202 -0
  238. package/dist/shared/types/agent.js +1 -0
  239. package/dist/shared/types/base.d.ts +32 -0
  240. package/dist/shared/types/base.js +1 -0
  241. package/dist/shared/types/config.d.ts +104 -0
  242. package/dist/shared/types/config.js +1 -0
  243. package/dist/shared/types/index.d.ts +6 -0
  244. package/dist/shared/types/index.js +1 -0
  245. package/dist/shared/types/llm.d.ts +122 -0
  246. package/dist/shared/types/llm.js +1 -0
  247. package/dist/shared/types/message.d.ts +71 -0
  248. package/dist/shared/types/message.js +1 -0
  249. package/dist/shared/types/tool.d.ts +134 -0
  250. package/dist/shared/types/tool.js +1 -0
  251. package/dist/shared/utils/path.d.ts +15 -0
  252. package/dist/shared/utils/path.js +23 -0
  253. package/dist/shared/utils.d.ts +5 -0
  254. package/dist/shared/utils.js +10 -0
  255. package/dist/tools/builtin/bash.d.ts +12 -0
  256. package/dist/tools/builtin/bash.js +121 -0
  257. package/dist/tools/builtin/dap.d.ts +23 -0
  258. package/dist/tools/builtin/dap.js +231 -0
  259. package/dist/tools/builtin/edit.d.ts +12 -0
  260. package/dist/tools/builtin/edit.js +151 -0
  261. package/dist/tools/builtin/glob.d.ts +11 -0
  262. package/dist/tools/builtin/glob.js +120 -0
  263. package/dist/tools/builtin/grep.d.ts +6 -0
  264. package/dist/tools/builtin/grep.js +141 -0
  265. package/dist/tools/builtin/read.d.ts +6 -0
  266. package/dist/tools/builtin/read.js +73 -0
  267. package/dist/tools/builtin/resolvers.d.ts +12 -0
  268. package/dist/tools/builtin/resolvers.js +79 -0
  269. package/dist/tools/builtin/task.d.ts +9 -0
  270. package/dist/tools/builtin/task.js +100 -0
  271. package/dist/tools/builtin/write.d.ts +6 -0
  272. package/dist/tools/builtin/write.js +36 -0
  273. package/dist/tools/builtin/yield.d.ts +9 -0
  274. package/dist/tools/builtin/yield.js +40 -0
  275. package/dist/tools/executor.d.ts +9 -0
  276. package/dist/tools/executor.js +54 -0
  277. package/dist/tools/hashline/index.d.ts +2 -0
  278. package/dist/tools/hashline/index.js +3 -0
  279. package/dist/tools/hashline/patch.d.ts +48 -0
  280. package/dist/tools/hashline/patch.js +201 -0
  281. package/dist/tools/index.d.ts +30 -0
  282. package/dist/tools/index.js +55 -0
  283. package/dist/tools/permission.d.ts +13 -0
  284. package/dist/tools/permission.js +39 -0
  285. package/dist/tools/registry.d.ts +15 -0
  286. package/dist/tools/registry.js +54 -0
  287. package/dist/tools/resolver.d.ts +15 -0
  288. package/dist/tools/resolver.js +47 -0
  289. package/dist/tools/truncate.d.ts +8 -0
  290. package/dist/tools/truncate.js +50 -0
  291. package/dist/tools/types.d.ts +101 -0
  292. package/dist/tools/types.js +1 -0
  293. package/dist/tools/validate.d.ts +8 -0
  294. package/dist/tools/validate.js +130 -0
  295. package/dist/tools/websearch/fetch.d.ts +11 -0
  296. package/dist/tools/websearch/fetch.js +21 -0
  297. package/dist/tools/websearch/index.d.ts +19 -0
  298. package/dist/tools/websearch/index.js +78 -0
  299. package/dist/tools/websearch/providers/brave.d.ts +5 -0
  300. package/dist/tools/websearch/providers/brave.js +53 -0
  301. package/dist/tools/websearch/providers/duckduckgo.d.ts +3 -0
  302. package/dist/tools/websearch/providers/duckduckgo.js +75 -0
  303. package/dist/tools/websearch/providers/tavily.d.ts +11 -0
  304. package/dist/tools/websearch/providers/tavily.js +89 -0
  305. package/dist/tools/websearch/types.d.ts +51 -0
  306. package/dist/tools/websearch/types.js +18 -0
  307. package/dist/tools/websearch/websearch.d.ts +10 -0
  308. package/dist/tools/websearch/websearch.js +48 -0
  309. package/dist/update/index.d.ts +10 -0
  310. package/dist/update/index.js +6 -0
  311. package/dist/update/ipc.d.ts +15 -0
  312. package/dist/update/ipc.js +41 -0
  313. package/dist/update/scheduler.d.ts +34 -0
  314. package/dist/update/scheduler.js +57 -0
  315. package/dist/update/snapshot.d.ts +39 -0
  316. package/dist/update/snapshot.js +99 -0
  317. package/dist/update/updater.d.ts +48 -0
  318. package/dist/update/updater.js +83 -0
  319. package/dist/update/version.d.ts +18 -0
  320. package/dist/update/version.js +49 -0
  321. package/drizzle/0000_magical_nightshade.sql +55 -0
  322. package/drizzle/0001_chemical_doctor_faustus.sql +13 -0
  323. package/drizzle/0002_amusing_micromacro.sql +12 -0
  324. package/drizzle/0003_regular_purifiers.sql +2 -0
  325. package/drizzle/meta/0000_snapshot.json +448 -0
  326. package/drizzle/meta/0001_snapshot.json +539 -0
  327. package/drizzle/meta/0002_snapshot.json +633 -0
  328. package/drizzle/meta/0003_snapshot.json +645 -0
  329. package/drizzle/meta/_journal.json +34 -0
  330. package/package.json +100 -8
@@ -0,0 +1,94 @@
1
+ import { isLLMError, llmError, reasonMessage } from '../../schema/errors.js';
2
+ const empty = () => ({});
3
+ /**
4
+ * Append a tool-call delta. If the index is new, emit a `tool-input-start`
5
+ * (using the delta's id/name if present, else synthesized). Then emit a
6
+ * `tool-input-delta` when an argument fragment is present.
7
+ */
8
+ const appendOrStart = (state, delta) => {
9
+ const current = state[delta.index];
10
+ const events = [];
11
+ let next;
12
+ if (current === undefined) {
13
+ const id = delta.id;
14
+ const name = delta.name;
15
+ // 无效 id/name(缺失或空字符串):丢弃该 delta 而非抛错。部分兼容
16
+ // provider 把 tool call 的 arguments 流式片段拆成多个独立 delta,每片
17
+ // id/name 为空。接受空值会产生空 id 的 tool call,导致下一轮
18
+ // invalid tool_call_id;抛错则让整个流崩溃。跳过最稳妥。
19
+ if (!id || !name) {
20
+ return { state, events };
21
+ }
22
+ next = { id, name, input: '', started: false };
23
+ const start = { type: 'tool-input-start', id, name };
24
+ events.push(start);
25
+ next.started = true;
26
+ }
27
+ else {
28
+ next = { ...current };
29
+ }
30
+ if (delta.argumentsDelta !== undefined && delta.argumentsDelta.length > 0) {
31
+ next.input += delta.argumentsDelta;
32
+ const d = {
33
+ type: 'tool-input-delta',
34
+ id: next.id,
35
+ name: next.name,
36
+ text: delta.argumentsDelta,
37
+ };
38
+ events.push(d);
39
+ }
40
+ return { state: { ...state, [delta.index]: next }, events };
41
+ };
42
+ /** Parse a raw JSON arguments string; empty string becomes `{}`. */
43
+ const parseToolInput = (raw) => {
44
+ const source = raw.length === 0 ? '{}' : raw;
45
+ try {
46
+ return JSON.parse(source);
47
+ }
48
+ catch {
49
+ throw llmError('ProviderShared', 'stream', {
50
+ _tag: 'InvalidProviderOutput',
51
+ message: `Invalid JSON tool arguments: ${source}`,
52
+ raw: source,
53
+ });
54
+ }
55
+ };
56
+ /**
57
+ * Finalize all pending tool calls (OpenAI Chat style — no per-tool stop event).
58
+ * Emits `tool-input-end` + parsed `tool-call` for each, and clears state.
59
+ */
60
+ const finishAll = (state) => {
61
+ const events = [];
62
+ const tools = [];
63
+ for (const key of Object.keys(state)) {
64
+ const tool = state[Number(key)];
65
+ if (tool === undefined)
66
+ continue;
67
+ const end = { type: 'tool-input-end', id: tool.id, name: tool.name };
68
+ events.push(end);
69
+ // 解析失败不抛错:模型可能输出不完整 JSON(流被截断/提前结束)。
70
+ // 标记 _parseError + _raw 让流完整结束,由 agent loop 把错误反馈给模型重试,
71
+ // 而非让整个会话崩溃。对齐 oh-my-pi 的 __parseError 容错(agent-loop.ts:1741)。
72
+ let input;
73
+ try {
74
+ input = parseToolInput(tool.input);
75
+ }
76
+ catch (e) {
77
+ // parseToolInput 抛的是 llmError(普通对象,非 Error 实例),直接 String(e)
78
+ // 会退化成 "[object Object]"。按错误类型提取可读消息。
79
+ input = {
80
+ _parseError: isLLMError(e)
81
+ ? reasonMessage(e.reason)
82
+ : e instanceof Error
83
+ ? e.message
84
+ : String(e),
85
+ _raw: tool.input,
86
+ };
87
+ }
88
+ tools.push({ id: tool.id, name: tool.name, input });
89
+ const call = { type: 'tool-call', id: tool.id, name: tool.name, input };
90
+ events.push(call);
91
+ }
92
+ return { state: {}, events, tools };
93
+ };
94
+ export { appendOrStart, empty, finishAll, parseToolInput };
@@ -0,0 +1,5 @@
1
+ /** True when a provider error message indicates the input exceeded the context window. */
2
+ declare const isContextOverflow: (message: string) => boolean;
3
+ /** True when a thrown/produced value represents a context-overflow failure. */
4
+ declare const isContextOverflowFailure: (failure: unknown) => boolean;
5
+ export { isContextOverflow, isContextOverflowFailure };
@@ -0,0 +1,33 @@
1
+ import { isLLMError } from './schema/errors.js';
2
+ const patterns = [
3
+ /prompt is too long/i,
4
+ /input is too long for requested model/i,
5
+ /exceeds the context window/i,
6
+ /input token count.*exceeds the maximum/i,
7
+ /maximum prompt length is \d+/i,
8
+ /reduce the length of the messages/i,
9
+ /maximum context length is \d+ tokens/i,
10
+ /exceeds the limit of \d+/i,
11
+ /exceeds the available context size/i,
12
+ /greater than the context length/i,
13
+ /context window exceeds limit/i,
14
+ /exceeded model token limit/i,
15
+ /context[_ ]length[_ ]exceeded/i,
16
+ /request entity too large/i,
17
+ /context length is only \d+ tokens/i,
18
+ /input length.*exceeds.*context length/i,
19
+ /prompt too long; exceeded (?:max )?context length/i,
20
+ /too large for model with \d+ maximum context length/i,
21
+ /model_context_window_exceeded/i,
22
+ ];
23
+ /** True when a provider error message indicates the input exceeded the context window. */
24
+ const isContextOverflow = (message) => patterns.some((pattern) => pattern.test(message)) ||
25
+ /^4(00|13)\s*(status code)?\s*\(no body\)/i.test(message);
26
+ /** True when a thrown/produced value represents a context-overflow failure. */
27
+ const isContextOverflowFailure = (failure) => {
28
+ if (!isLLMError(failure))
29
+ return false;
30
+ const reason = failure.reason;
31
+ return reason._tag === 'InvalidRequest' && reason.classification === 'context-overflow';
32
+ };
33
+ export { isContextOverflow, isContextOverflowFailure };
@@ -0,0 +1,32 @@
1
+ import type { ChatMessage, ChatRequest, StreamChunk } from '../shared/types/llm.js';
2
+ import type { Registry } from './registry.js';
3
+ import type { FallbackChain } from './routing.js';
4
+ import type { StreamEvent } from './schema/events.js';
5
+ import type { InternalRequest, Message } from './schema/messages.js';
6
+ type ProviderContext = {
7
+ registry: Registry;
8
+ fetchImpl?: typeof fetch;
9
+ signal?: AbortSignal;
10
+ };
11
+ /** Map a shared ChatMessage to the internal Message shape. */
12
+ declare const toInternalMessage: (msg: ChatMessage) => Message;
13
+ /** Map a rich StreamEvent to the shared, agent-facing StreamChunk. */
14
+ declare const toStreamChunk: (event: StreamEvent) => StreamChunk | null;
15
+ type ChatOptions = {
16
+ provider: string;
17
+ model: string;
18
+ fallback?: FallbackChain;
19
+ /** Override sleep for retry testing. */
20
+ sleep?: (ms: number) => Promise<void>;
21
+ };
22
+ declare const buildInternalRequest: (request: ChatRequest, provider: string, modelId: string) => InternalRequest;
23
+ /**
24
+ * Stream a chat request incrementally as agent-facing StreamChunk values.
25
+ * Retries/falls over only BEFORE the first chunk is yielded; once streaming
26
+ * begins, errors propagate to the caller (per spec §7.6 fall-over policy).
27
+ */
28
+ declare const chatStream: (ctx: ProviderContext, request: ChatRequest, options: ChatOptions) => AsyncGenerator<StreamChunk, void, unknown>;
29
+ /** Non-streaming chat: consume the stream and return the final text. */
30
+ declare const chat: (ctx: ProviderContext, request: ChatRequest, options: ChatOptions) => Promise<string>;
31
+ export type { ChatOptions, ProviderContext };
32
+ export { buildInternalRequest, chat, chatStream, toInternalMessage, toStreamChunk };
@@ -0,0 +1,191 @@
1
+ import { bodyFrom, finalizeStream, initialStepState, parseChunk, step, } from './protocols/openai-compat.js';
2
+ import { resolveRoute } from './registry.js';
3
+ import { withRetry } from './retry.js';
4
+ import { shouldFallOver } from './routing.js';
5
+ import { model as makeModel } from './schema/options.js';
6
+ import { httpPost, sseFraming } from './transport.js';
7
+ const safeParseArgs = (raw) => {
8
+ try {
9
+ return JSON.parse(raw);
10
+ }
11
+ catch {
12
+ return raw;
13
+ }
14
+ };
15
+ /** Map a shared ChatMessage to the internal Message shape. */
16
+ const toInternalMessage = (msg) => {
17
+ // tool role messages carry tool result text — MUST handle before the
18
+ // string-content early return below, because context.ts serialises tool
19
+ // result output to a JSON string (msg.content is a string for tool msgs).
20
+ // If this runs after the early return, toolCallId is lost and the provider
21
+ // receives a tool message without tool_call_id → "invalid tool_call_id".
22
+ if (msg.role === 'tool' && msg.toolCallId !== undefined) {
23
+ const text = typeof msg.content === 'string'
24
+ ? msg.content
25
+ : msg.content.map((p) => (p.type === 'text' ? p.text : '')).join('');
26
+ return {
27
+ role: 'tool',
28
+ content: [
29
+ {
30
+ type: 'tool-result',
31
+ id: msg.toolCallId,
32
+ name: 'tool',
33
+ result: { type: 'text', value: text },
34
+ },
35
+ ],
36
+ };
37
+ }
38
+ if (typeof msg.content === 'string') {
39
+ return { role: msg.role, content: [{ type: 'text', text: msg.content }] };
40
+ }
41
+ const parts = msg.content.map((p) => {
42
+ if (p.type === 'text')
43
+ return { type: 'text', text: p.text };
44
+ return { type: 'text', text: `[image: ${p.mediaType}]` };
45
+ });
46
+ // assistant messages may carry tool_calls
47
+ if (msg.role === 'assistant' && msg.toolCalls !== undefined) {
48
+ const toolParts = msg.toolCalls.map((tc) => ({
49
+ type: 'tool-call',
50
+ id: tc.id,
51
+ name: tc.name,
52
+ input: safeParseArgs(tc.arguments),
53
+ }));
54
+ return { role: 'assistant', content: [...parts, ...toolParts] };
55
+ }
56
+ return { role: msg.role, content: parts };
57
+ };
58
+ const toInternalTool = (tool) => ({
59
+ name: tool.name,
60
+ description: tool.description,
61
+ inputSchema: tool.parameters,
62
+ });
63
+ /** Map a rich StreamEvent to the shared, agent-facing StreamChunk. */
64
+ const toStreamChunk = (event) => {
65
+ switch (event.type) {
66
+ case 'text-delta':
67
+ return { _tag: 'text', text: event.text };
68
+ case 'tool-input-start':
69
+ return { _tag: 'tool_call_start', id: event.id, name: event.name };
70
+ case 'tool-input-delta':
71
+ return { _tag: 'tool_call_delta', id: event.id, argumentsDelta: event.text };
72
+ case 'tool-input-end':
73
+ // Redundant: the finalized `tool-call` event below carries the complete
74
+ // parsed input and maps to tool_call_end. Emitting both would duplicate
75
+ // the end signal per tool call.
76
+ return null;
77
+ case 'tool-call':
78
+ return { _tag: 'tool_call_end', id: event.id, argumentsFinal: JSON.stringify(event.input) };
79
+ case 'reasoning-delta':
80
+ return { _tag: 'thinking', text: event.text };
81
+ case 'step-finish':
82
+ if (event.usage !== undefined) {
83
+ return {
84
+ _tag: 'usage',
85
+ inputTokens: event.usage.inputTokens ?? 0,
86
+ outputTokens: event.usage.outputTokens ?? 0,
87
+ cacheRead: event.usage.cacheReadInputTokens,
88
+ };
89
+ }
90
+ return null;
91
+ case 'finish':
92
+ return { _tag: 'done', finishReason: event.reason };
93
+ case 'provider-error':
94
+ return {
95
+ _tag: 'error',
96
+ error: { message: event.message, retryable: event.retryable ?? false },
97
+ };
98
+ default:
99
+ return null;
100
+ }
101
+ };
102
+ const buildInternalRequest = (request, provider, modelId) => ({
103
+ model: makeModel(modelId, provider),
104
+ system: request.system !== undefined ? [{ type: 'text', text: request.system }] : [],
105
+ messages: request.messages.map(toInternalMessage),
106
+ tools: (request.tools ?? []).map(toInternalTool),
107
+ generation: {
108
+ ...(request.maxTokens !== undefined ? { maxTokens: request.maxTokens } : {}),
109
+ ...(request.temperature !== undefined ? { temperature: request.temperature } : {}),
110
+ },
111
+ toolChoice: request.tools !== undefined && request.tools.length > 0 ? { type: 'auto' } : undefined,
112
+ });
113
+ const resolveChain = (options) => options.fallback ?? {
114
+ primary: { provider: options.provider, model: options.model },
115
+ fallbacks: [],
116
+ maxRetries: 3,
117
+ retryDelay: 2000,
118
+ sleep: options.sleep,
119
+ };
120
+ /** Open a (retryable) connection to one target route and stream its StreamChunks. */
121
+ const streamTarget = async function* (ctx, request, target, chain) {
122
+ const resolved = resolveRoute(ctx.registry, target.provider, target.model);
123
+ const internal = buildInternalRequest(request, target.provider, target.model);
124
+ const body = bodyFrom(internal);
125
+ const url = `${resolved.route.baseURL}${resolved.route.path}`;
126
+ const authHeader = resolved.route.auth.type === 'bearer' ? resolved.route.auth.apiKey : '';
127
+ // Retry only the connection (the fetch). Once streaming begins, errors propagate.
128
+ const stream = await withRetry(() => httpPost({
129
+ url,
130
+ body,
131
+ headers: { authorization: `Bearer ${authHeader}`, ...resolved.route.headers() },
132
+ signal: ctx.signal,
133
+ fetchImpl: ctx.fetchImpl,
134
+ }), { maxRetries: chain.maxRetries, sleep: chain.sleep });
135
+ let state = initialStepState();
136
+ for await (const frame of sseFraming(stream)) {
137
+ const chunk = parseChunk(resolved.route.id, frame);
138
+ const result = step(state, chunk);
139
+ state = result.state;
140
+ for (const event of result.events) {
141
+ const sc = toStreamChunk(event);
142
+ if (sc !== null)
143
+ yield sc;
144
+ }
145
+ }
146
+ // Stream ended without a trailing usage chunk — finalize (no-op if already finished).
147
+ for (const event of finalizeStream(state).events) {
148
+ const sc = toStreamChunk(event);
149
+ if (sc !== null)
150
+ yield sc;
151
+ }
152
+ };
153
+ /**
154
+ * Stream a chat request incrementally as agent-facing StreamChunk values.
155
+ * Retries/falls over only BEFORE the first chunk is yielded; once streaming
156
+ * begins, errors propagate to the caller (per spec §7.6 fall-over policy).
157
+ */
158
+ const chatStream = async function* (ctx, request, options) {
159
+ const chain = resolveChain(options);
160
+ const targets = [chain.primary, ...chain.fallbacks];
161
+ let started = false;
162
+ let lastError;
163
+ for (const target of targets) {
164
+ if (target === undefined)
165
+ continue;
166
+ resolveRoute(ctx.registry, target.provider, target.model); // fail-fast NoRoute
167
+ try {
168
+ for await (const sc of streamTarget(ctx, request, target, chain)) {
169
+ started = true;
170
+ yield sc;
171
+ }
172
+ return;
173
+ }
174
+ catch (error) {
175
+ lastError = error;
176
+ if (started || !shouldFallOver(error))
177
+ throw error;
178
+ }
179
+ }
180
+ throw lastError;
181
+ };
182
+ /** Non-streaming chat: consume the stream and return the final text. */
183
+ const chat = async (ctx, request, options) => {
184
+ let text = '';
185
+ for await (const chunk of chatStream(ctx, request, options)) {
186
+ if (chunk._tag === 'text')
187
+ text += chunk.text;
188
+ }
189
+ return text;
190
+ };
191
+ export { buildInternalRequest, chat, chatStream, toInternalMessage, toStreamChunk };
@@ -0,0 +1,44 @@
1
+ import type { ModelCapabilities, ModelRole } from '../shared/types/llm.js';
2
+ import { openAICompatRoute } from './protocols/openai-compat.js';
3
+ import type { Model } from './schema/options.js';
4
+ type RouteEntry = ReturnType<typeof openAICompatRoute> & {
5
+ models: Record<string, ModelCapabilities>;
6
+ };
7
+ type Registry = {
8
+ routes: Map<string, RouteEntry>;
9
+ roles: Map<string, {
10
+ provider: string;
11
+ model: string;
12
+ }>;
13
+ };
14
+ declare const createRegistry: () => Registry;
15
+ type ProviderInput = {
16
+ name: string;
17
+ baseURL: string;
18
+ apiKey: string;
19
+ headers?: () => Record<string, string>;
20
+ path?: string;
21
+ models?: Record<string, ModelCapabilities>;
22
+ };
23
+ declare const registerProvider: (registry: Registry, input: ProviderInput) => void;
24
+ type ResolveResult = {
25
+ route: RouteEntry;
26
+ model: Model;
27
+ capabilities: ModelCapabilities;
28
+ };
29
+ /**
30
+ * Resolve a (provider, modelId) pair into a route + typed model.
31
+ * Throws NoRoute when the provider is unknown.
32
+ */
33
+ declare const resolveRoute: (registry: Registry, provider: string, modelId: string) => ResolveResult;
34
+ /** Resolve the (provider, model) configured for a given role. */
35
+ declare const resolveModelByRole: (registry: Registry, role: ModelRole) => {
36
+ provider: string;
37
+ model: string;
38
+ };
39
+ /** Bind a role to a (provider, model) pair. */
40
+ declare const setRole: (registry: Registry, role: ModelRole, provider: string, model: string) => void;
41
+ /** Default role + a starter catalog of well-known models. */
42
+ declare const builtinCapabilities: Record<string, Record<string, ModelCapabilities>>;
43
+ export type { ProviderInput, Registry, ResolveResult, RouteEntry };
44
+ export { builtinCapabilities, createRegistry, registerProvider, resolveModelByRole, resolveRoute, setRole, };
@@ -0,0 +1,107 @@
1
+ import { openAICompatRoute } from './protocols/openai-compat.js';
2
+ import { llmError } from './schema/errors.js';
3
+ import { model as makeModel } from './schema/options.js';
4
+ const createRegistry = () => ({ routes: new Map(), roles: new Map() });
5
+ const registerProvider = (registry, input) => {
6
+ registry.routes.set(input.name, {
7
+ ...openAICompatRoute({
8
+ id: input.name,
9
+ provider: input.name,
10
+ baseURL: input.baseURL,
11
+ apiKey: input.apiKey,
12
+ headers: input.headers,
13
+ path: input.path,
14
+ }),
15
+ models: input.models ?? {},
16
+ });
17
+ };
18
+ /**
19
+ * Resolve a (provider, modelId) pair into a route + typed model.
20
+ * Throws NoRoute when the provider is unknown.
21
+ */
22
+ const resolveRoute = (registry, provider, modelId) => {
23
+ const route = registry.routes.get(provider);
24
+ if (route === undefined) {
25
+ throw llmError('LLM', 'resolve', {
26
+ _tag: 'NoRoute',
27
+ route: `${provider}`,
28
+ provider,
29
+ model: modelId,
30
+ });
31
+ }
32
+ const capabilities = route.models[modelId] ??
33
+ {
34
+ contextWindow: 8192,
35
+ maxOutput: 4096,
36
+ supportsTools: true,
37
+ supportsVision: false,
38
+ supportsThinking: false,
39
+ costPer1kInput: 0,
40
+ costPer1kOutput: 0,
41
+ };
42
+ return {
43
+ route,
44
+ model: makeModel(modelId, provider, {
45
+ context: capabilities.contextWindow,
46
+ output: capabilities.maxOutput,
47
+ }),
48
+ capabilities,
49
+ };
50
+ };
51
+ /** Resolve the (provider, model) configured for a given role. */
52
+ const resolveModelByRole = (registry, role) => {
53
+ const key = role._tag;
54
+ const entry = registry.roles.get(key);
55
+ if (entry === undefined) {
56
+ const fallback = registry.roles.get('default');
57
+ if (fallback === undefined) {
58
+ throw llmError('LLM', 'resolve', {
59
+ _tag: 'NoRoute',
60
+ route: `role:${key}`,
61
+ provider: 'unknown',
62
+ model: 'unknown',
63
+ });
64
+ }
65
+ return fallback;
66
+ }
67
+ return entry;
68
+ };
69
+ /** Bind a role to a (provider, model) pair. */
70
+ const setRole = (registry, role, provider, model) => {
71
+ registry.roles.set(role._tag, { provider, model });
72
+ };
73
+ /** Default role + a starter catalog of well-known models. */
74
+ const builtinCapabilities = {
75
+ openai: {
76
+ 'gpt-4o': {
77
+ contextWindow: 128000,
78
+ maxOutput: 16384,
79
+ supportsTools: true,
80
+ supportsVision: true,
81
+ supportsThinking: false,
82
+ costPer1kInput: 0.0025,
83
+ costPer1kOutput: 0.01,
84
+ },
85
+ },
86
+ deepseek: {
87
+ 'deepseek-chat': {
88
+ contextWindow: 64000,
89
+ maxOutput: 8192,
90
+ supportsTools: true,
91
+ supportsVision: false,
92
+ supportsThinking: false,
93
+ costPer1kInput: 0.00014,
94
+ costPer1kOutput: 0.00028,
95
+ },
96
+ 'deepseek-reasoner': {
97
+ contextWindow: 64000,
98
+ maxOutput: 8192,
99
+ supportsTools: false,
100
+ supportsVision: false,
101
+ supportsThinking: true,
102
+ costPer1kInput: 0.00055,
103
+ costPer1kOutput: 0.00219,
104
+ },
105
+ },
106
+ };
107
+ export { builtinCapabilities, createRegistry, registerProvider, resolveModelByRole, resolveRoute, setRole, };
@@ -0,0 +1,37 @@
1
+ import type { LLMErrorReason } from './schema/errors.js';
2
+ declare const RETRY_INITIAL_DELAY = 2000;
3
+ declare const RETRY_MAX_DELAY_NO_HEADERS = 30000;
4
+ declare const RETRY_MAX_DELAY = 2147483647;
5
+ /**
6
+ * Compute the delay before the next retry attempt (ms).
7
+ * Honors retry-after / retry-after-ms headers when present, else exponential backoff.
8
+ */
9
+ declare const delay: (attempt: number, error?: unknown) => number;
10
+ /** A normalized, retryable error descriptor for the session layer. */
11
+ type Retryable = {
12
+ message: string;
13
+ reason: LLMErrorReason;
14
+ };
15
+ /**
16
+ * Decide whether a thrown error is retryable. Returns undefined when not retryable
17
+ * (e.g. context overflow, auth, invalid request).
18
+ */
19
+ declare const retryable: (error: unknown) => Retryable | undefined;
20
+ type RetryOptions = {
21
+ maxRetries: number;
22
+ /** Override sleep for testing. Defaults to setTimeout-based sleep. */
23
+ sleep?: (ms: number) => Promise<void>;
24
+ /** Called with each retry attempt metadata. */
25
+ onRetry?: (info: {
26
+ attempt: number;
27
+ delayMs: number;
28
+ error: unknown;
29
+ }) => void;
30
+ };
31
+ /**
32
+ * Run an async operation with retry. Retries only on retryable LLM errors.
33
+ * Non-retryable errors (including context overflow) are re-thrown immediately.
34
+ */
35
+ declare const withRetry: <T>(fn: () => Promise<T>, options: RetryOptions) => Promise<T>;
36
+ export type { Retryable, RetryOptions };
37
+ export { delay, RETRY_INITIAL_DELAY, RETRY_MAX_DELAY, RETRY_MAX_DELAY_NO_HEADERS, retryable, withRetry, };
@@ -0,0 +1,82 @@
1
+ import { isLLMError, reasonRetryAfterMs, reasonRetryable } from './schema/errors.js';
2
+ const RETRY_INITIAL_DELAY = 2_000;
3
+ const RETRY_BACKOFF_FACTOR = 2;
4
+ const RETRY_MAX_DELAY_NO_HEADERS = 30_000;
5
+ const RETRY_MAX_DELAY = 2_147_483_647;
6
+ /** Extract response headers from an LLMError's http context (if any). */
7
+ const errorHeaders = (error) => {
8
+ if (!isLLMError(error))
9
+ return undefined;
10
+ const reason = error.reason;
11
+ if ('http' in reason && reason.http?.response) {
12
+ return reason.http.response.headers;
13
+ }
14
+ return undefined;
15
+ };
16
+ /** Cap a delay to the 32-bit safe ceiling. */
17
+ const capDelay = (ms) => Math.min(ms, RETRY_MAX_DELAY);
18
+ /**
19
+ * Compute the delay before the next retry attempt (ms).
20
+ * Honors retry-after / retry-after-ms headers when present, else exponential backoff.
21
+ */
22
+ const delay = (attempt, error) => {
23
+ const headers = errorHeaders(error);
24
+ if (headers) {
25
+ const retryAfterMs = headers['retry-after-ms'];
26
+ if (retryAfterMs !== undefined) {
27
+ const parsedMs = Number.parseFloat(retryAfterMs);
28
+ if (!Number.isNaN(parsedMs))
29
+ return capDelay(parsedMs);
30
+ }
31
+ const retryAfter = headers['retry-after'];
32
+ if (retryAfter !== undefined) {
33
+ const parsedSeconds = Number.parseFloat(retryAfter);
34
+ if (!Number.isNaN(parsedSeconds))
35
+ return capDelay(Math.ceil(parsedSeconds * 1000));
36
+ const parsed = Date.parse(retryAfter) - Date.now();
37
+ if (!Number.isNaN(parsed) && parsed > 0)
38
+ return capDelay(Math.ceil(parsed));
39
+ }
40
+ return capDelay(RETRY_INITIAL_DELAY * RETRY_BACKOFF_FACTOR ** (attempt - 1));
41
+ }
42
+ return capDelay(Math.min(RETRY_INITIAL_DELAY * RETRY_BACKOFF_FACTOR ** (attempt - 1), RETRY_MAX_DELAY_NO_HEADERS));
43
+ };
44
+ /**
45
+ * Decide whether a thrown error is retryable. Returns undefined when not retryable
46
+ * (e.g. context overflow, auth, invalid request).
47
+ */
48
+ const retryable = (error) => {
49
+ if (!isLLMError(error))
50
+ return undefined;
51
+ const reason = error.reason;
52
+ if (!reasonRetryable(reason))
53
+ return undefined;
54
+ return { message: error.message, reason };
55
+ };
56
+ const defaultSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
57
+ /**
58
+ * Run an async operation with retry. Retries only on retryable LLM errors.
59
+ * Non-retryable errors (including context overflow) are re-thrown immediately.
60
+ */
61
+ const withRetry = async (fn, options) => {
62
+ const sleep = options.sleep ?? defaultSleep;
63
+ let attempt = 0;
64
+ for (;;) {
65
+ try {
66
+ return await fn();
67
+ }
68
+ catch (error) {
69
+ const canRetry = retryable(error);
70
+ if (!canRetry || attempt >= options.maxRetries)
71
+ throw error;
72
+ attempt += 1;
73
+ const fallbackReason = isLLMError(error)
74
+ ? error.reason
75
+ : { _tag: 'InvalidRequest', message: '' };
76
+ const delayMs = reasonRetryAfterMs(fallbackReason) ?? delay(attempt, error);
77
+ options.onRetry?.({ attempt, delayMs, error });
78
+ await sleep(delayMs);
79
+ }
80
+ }
81
+ };
82
+ export { delay, RETRY_INITIAL_DELAY, RETRY_MAX_DELAY, RETRY_MAX_DELAY_NO_HEADERS, retryable, withRetry, };
@@ -0,0 +1,36 @@
1
+ import type { Registry } from './registry.js';
2
+ type FallbackChain = {
3
+ primary: {
4
+ provider: string;
5
+ model: string;
6
+ };
7
+ fallbacks: {
8
+ provider: string;
9
+ model: string;
10
+ }[];
11
+ maxRetries: number;
12
+ retryDelay: number;
13
+ /** Override sleep for testing. */
14
+ sleep?: (ms: number) => Promise<void>;
15
+ };
16
+ type RunFn<T> = (provider: string, model: string) => Promise<T>;
17
+ /**
18
+ * Run `run` against the primary route first. If it throws a retryable error
19
+ * that exhausts retries, try each fallback in order. Non-retryable errors
20
+ * (context overflow, auth, invalid request) propagate immediately without
21
+ * trying fallbacks.
22
+ */
23
+ declare const runWithFallback: <T>(registry: Registry, chain: FallbackChain, run: RunFn<T>) => Promise<{
24
+ result: T;
25
+ provider: string;
26
+ model: string;
27
+ }>;
28
+ /**
29
+ * Whether an error should trigger a fallback after retries are exhausted.
30
+ * Per spec §7.6: RateLimit (after retries), ProviderInternal, and Authentication
31
+ * fall over to the next route. Context-overflow, invalid request, and transport
32
+ * errors propagate without trying fallbacks.
33
+ */
34
+ declare const shouldFallOver: (error: unknown) => boolean;
35
+ export type { FallbackChain };
36
+ export { runWithFallback, shouldFallOver };