@gaunt-sloth/core 2.0.0-alpha.3 → 2.0.0-alpha.30

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 (271) hide show
  1. package/README.md +52 -20
  2. package/dist/config/colour.d.ts +38 -0
  3. package/dist/config/colour.js +36 -0
  4. package/dist/config/colour.js.map +1 -0
  5. package/dist/config/defaults.d.ts +84 -0
  6. package/dist/config/defaults.js +99 -0
  7. package/dist/config/defaults.js.map +1 -0
  8. package/dist/config/filesystem-tools.d.ts +41 -0
  9. package/dist/config/filesystem-tools.js +56 -0
  10. package/dist/config/filesystem-tools.js.map +1 -0
  11. package/dist/config/jsonc.d.ts +12 -0
  12. package/dist/config/jsonc.js +41 -0
  13. package/dist/config/jsonc.js.map +1 -0
  14. package/dist/config/loader.d.ts +246 -0
  15. package/dist/config/loader.js +1321 -0
  16. package/dist/config/loader.js.map +1 -0
  17. package/dist/config/mouse.d.ts +50 -0
  18. package/dist/config/mouse.js +44 -0
  19. package/dist/config/mouse.js.map +1 -0
  20. package/dist/config/profiles.d.ts +68 -0
  21. package/dist/config/profiles.js +93 -0
  22. package/dist/config/profiles.js.map +1 -0
  23. package/dist/config/providerKeys.d.ts +69 -0
  24. package/dist/config/providerKeys.js +69 -0
  25. package/dist/config/providerKeys.js.map +1 -0
  26. package/dist/config/schema.d.ts +2999 -0
  27. package/dist/config/schema.js +1519 -0
  28. package/dist/config/schema.js.map +1 -0
  29. package/dist/config/shell-policy.d.ts +896 -0
  30. package/dist/config/shell-policy.js +750 -0
  31. package/dist/config/shell-policy.js.map +1 -0
  32. package/dist/config/tool-descriptions.d.ts +211 -0
  33. package/dist/config/tool-descriptions.js +272 -0
  34. package/dist/config/tool-descriptions.js.map +1 -0
  35. package/dist/config/types.d.ts +760 -0
  36. package/dist/config/types.js +13 -0
  37. package/dist/config/types.js.map +1 -0
  38. package/dist/config.d.ts +34 -827
  39. package/dist/config.js +25 -657
  40. package/dist/config.js.map +1 -1
  41. package/dist/constants.d.ts +46 -0
  42. package/dist/constants.js +46 -0
  43. package/dist/constants.js.map +1 -1
  44. package/dist/core/GthAbstractAgent.d.ts +135 -2
  45. package/dist/core/GthAbstractAgent.js +496 -30
  46. package/dist/core/GthAbstractAgent.js.map +1 -1
  47. package/dist/core/GthAgentRunner.d.ts +568 -54
  48. package/dist/core/GthAgentRunner.js +1537 -136
  49. package/dist/core/GthAgentRunner.js.map +1 -1
  50. package/dist/core/GthLangChainAgent.d.ts +115 -0
  51. package/dist/core/GthLangChainAgent.js +644 -17
  52. package/dist/core/GthLangChainAgent.js.map +1 -1
  53. package/dist/core/approvals/annotations.d.ts +122 -0
  54. package/dist/core/approvals/annotations.js +137 -0
  55. package/dist/core/approvals/annotations.js.map +1 -0
  56. package/dist/core/approvals/grants.d.ts +216 -0
  57. package/dist/core/approvals/grants.js +469 -0
  58. package/dist/core/approvals/grants.js.map +1 -0
  59. package/dist/core/approvals/matcher.d.ts +202 -0
  60. package/dist/core/approvals/matcher.js +267 -0
  61. package/dist/core/approvals/matcher.js.map +1 -0
  62. package/dist/core/approvals/mcpSubjects.d.ts +40 -0
  63. package/dist/core/approvals/mcpSubjects.js +99 -0
  64. package/dist/core/approvals/mcpSubjects.js.map +1 -0
  65. package/dist/core/approvals/toolAnnotationSources.d.ts +105 -0
  66. package/dist/core/approvals/toolAnnotationSources.js +277 -0
  67. package/dist/core/approvals/toolAnnotationSources.js.map +1 -0
  68. package/dist/core/approvals/toolHost.d.ts +46 -0
  69. package/dist/core/approvals/toolHost.js +108 -0
  70. package/dist/core/approvals/toolHost.js.map +1 -0
  71. package/dist/core/debugCapture.d.ts +75 -0
  72. package/dist/core/debugCapture.js +101 -0
  73. package/dist/core/debugCapture.js.map +1 -0
  74. package/dist/core/gthLeanAgentFactory.d.ts +9 -0
  75. package/dist/core/gthLeanAgentFactory.js +10 -0
  76. package/dist/core/gthLeanAgentFactory.js.map +1 -0
  77. package/dist/core/launchBanner.d.ts +120 -0
  78. package/dist/core/launchBanner.js +418 -0
  79. package/dist/core/launchBanner.js.map +1 -0
  80. package/dist/core/plainToolIndication.d.ts +15 -0
  81. package/dist/core/plainToolIndication.js +174 -0
  82. package/dist/core/plainToolIndication.js.map +1 -0
  83. package/dist/core/reasoningBlocks.d.ts +60 -0
  84. package/dist/core/reasoningBlocks.js +98 -0
  85. package/dist/core/reasoningBlocks.js.map +1 -0
  86. package/dist/core/refusal.d.ts +53 -0
  87. package/dist/core/refusal.js +133 -0
  88. package/dist/core/refusal.js.map +1 -0
  89. package/dist/core/runStats.d.ts +52 -0
  90. package/dist/core/runStats.js +118 -0
  91. package/dist/core/runStats.js.map +1 -0
  92. package/dist/core/shell/ShellCommandFailedError.d.ts +54 -0
  93. package/dist/core/shell/ShellCommandFailedError.js +68 -0
  94. package/dist/core/shell/ShellCommandFailedError.js.map +1 -0
  95. package/dist/core/shell/abstention.d.ts +88 -0
  96. package/dist/core/shell/abstention.js +184 -0
  97. package/dist/core/shell/abstention.js.map +1 -0
  98. package/dist/core/shell/approvalCapture.d.ts +271 -0
  99. package/dist/core/shell/approvalCapture.js +108 -0
  100. package/dist/core/shell/approvalCapture.js.map +1 -0
  101. package/dist/core/shell/approvalStop.d.ts +84 -0
  102. package/dist/core/shell/approvalStop.js +119 -0
  103. package/dist/core/shell/approvalStop.js.map +1 -0
  104. package/dist/core/shell/arity.d.ts +6 -0
  105. package/dist/core/shell/arity.js +20 -6
  106. package/dist/core/shell/arity.js.map +1 -1
  107. package/dist/core/shell/denylist.d.ts +11 -0
  108. package/dist/core/shell/denylist.js +37 -0
  109. package/dist/core/shell/denylist.js.map +1 -0
  110. package/dist/core/shell/escalationSeverity.d.ts +141 -0
  111. package/dist/core/shell/escalationSeverity.js +89 -0
  112. package/dist/core/shell/escalationSeverity.js.map +1 -0
  113. package/dist/core/shell/framing.d.ts +190 -0
  114. package/dist/core/shell/framing.js +633 -0
  115. package/dist/core/shell/framing.js.map +1 -0
  116. package/dist/core/shell/hardline.d.ts +81 -0
  117. package/dist/core/shell/hardline.js +607 -0
  118. package/dist/core/shell/hardline.js.map +1 -0
  119. package/dist/core/shell/negotiation.d.ts +249 -0
  120. package/dist/core/shell/negotiation.js +355 -0
  121. package/dist/core/shell/negotiation.js.map +1 -0
  122. package/dist/core/shell/normalize.d.ts +44 -4
  123. package/dist/core/shell/normalize.js +61 -7
  124. package/dist/core/shell/normalize.js.map +1 -1
  125. package/dist/core/shell/openWorld.d.ts +263 -0
  126. package/dist/core/shell/openWorld.js +1188 -0
  127. package/dist/core/shell/openWorld.js.map +1 -0
  128. package/dist/core/shell/rater.d.ts +935 -0
  129. package/dist/core/shell/rater.js +1473 -0
  130. package/dist/core/shell/rater.js.map +1 -0
  131. package/dist/core/shell/raterModel.d.ts +41 -0
  132. package/dist/core/shell/raterModel.js +51 -0
  133. package/dist/core/shell/raterModel.js.map +1 -0
  134. package/dist/core/shell/rejection.d.ts +69 -0
  135. package/dist/core/shell/rejection.js +38 -0
  136. package/dist/core/shell/rejection.js.map +1 -0
  137. package/dist/core/toolCallRepair/grammar.d.ts +41 -0
  138. package/dist/core/toolCallRepair/grammar.js +116 -0
  139. package/dist/core/toolCallRepair/grammar.js.map +1 -0
  140. package/dist/core/toolCallRepair/index.d.ts +2 -0
  141. package/dist/core/toolCallRepair/index.js +7 -0
  142. package/dist/core/toolCallRepair/index.js.map +1 -0
  143. package/dist/core/toolCallRepair/payload.d.ts +36 -0
  144. package/dist/core/toolCallRepair/payload.js +341 -0
  145. package/dist/core/toolCallRepair/payload.js.map +1 -0
  146. package/dist/core/toolCallRepair/promote.d.ts +45 -0
  147. package/dist/core/toolCallRepair/promote.js +90 -0
  148. package/dist/core/toolCallRepair/promote.js.map +1 -0
  149. package/dist/core/toolDisplay.d.ts +123 -0
  150. package/dist/core/toolDisplay.js +451 -0
  151. package/dist/core/toolDisplay.js.map +1 -0
  152. package/dist/core/toolOutputChannel.d.ts +95 -0
  153. package/dist/core/toolOutputChannel.js +165 -0
  154. package/dist/core/toolOutputChannel.js.map +1 -0
  155. package/dist/core/types.d.ts +294 -12
  156. package/dist/core/types.js.map +1 -1
  157. package/dist/history/historyFormat.d.ts +28 -0
  158. package/dist/history/historyFormat.js +127 -0
  159. package/dist/history/historyFormat.js.map +1 -0
  160. package/dist/history/historyStore.d.ts +198 -0
  161. package/dist/history/historyStore.js +482 -0
  162. package/dist/history/historyStore.js.map +1 -0
  163. package/dist/history/recordSession.d.ts +37 -0
  164. package/dist/history/recordSession.js +56 -0
  165. package/dist/history/recordSession.js.map +1 -0
  166. package/dist/index.d.ts +4 -0
  167. package/dist/index.js +4 -0
  168. package/dist/index.js.map +1 -1
  169. package/dist/providers/anthropic.d.ts +1 -1
  170. package/dist/providers/anthropic.js +5 -10
  171. package/dist/providers/anthropic.js.map +1 -1
  172. package/dist/providers/deepseek.d.ts +1 -1
  173. package/dist/providers/deepseek.js +5 -10
  174. package/dist/providers/deepseek.js.map +1 -1
  175. package/dist/providers/geminiSchemaSanitizer.d.ts +55 -0
  176. package/dist/providers/geminiSchemaSanitizer.js +347 -0
  177. package/dist/providers/geminiSchemaSanitizer.js.map +1 -0
  178. package/dist/providers/geminiThinking.d.ts +52 -0
  179. package/dist/providers/geminiThinking.js +72 -0
  180. package/dist/providers/geminiThinking.js.map +1 -0
  181. package/dist/providers/google-genai.d.ts +1 -1
  182. package/dist/providers/google-genai.js +12 -11
  183. package/dist/providers/google-genai.js.map +1 -1
  184. package/dist/providers/groq.d.ts +1 -1
  185. package/dist/providers/groq.js +5 -10
  186. package/dist/providers/groq.js.map +1 -1
  187. package/dist/providers/huggingface.d.ts +25 -0
  188. package/dist/providers/huggingface.js +69 -0
  189. package/dist/providers/huggingface.js.map +1 -0
  190. package/dist/providers/modelCatalog.d.ts +109 -0
  191. package/dist/providers/modelCatalog.js +245 -0
  192. package/dist/providers/modelCatalog.js.map +1 -0
  193. package/dist/providers/modelDiscovery.d.ts +153 -6
  194. package/dist/providers/modelDiscovery.js +295 -41
  195. package/dist/providers/modelDiscovery.js.map +1 -1
  196. package/dist/providers/ollama.d.ts +19 -5
  197. package/dist/providers/ollama.js +60 -52
  198. package/dist/providers/ollama.js.map +1 -1
  199. package/dist/providers/openai.d.ts +1 -1
  200. package/dist/providers/openai.js +39 -10
  201. package/dist/providers/openai.js.map +1 -1
  202. package/dist/providers/openrouter.d.ts +4 -5
  203. package/dist/providers/openrouter.js +20 -35
  204. package/dist/providers/openrouter.js.map +1 -1
  205. package/dist/providers/vertexai.d.ts +1 -1
  206. package/dist/providers/vertexai.js +12 -11
  207. package/dist/providers/vertexai.js.map +1 -1
  208. package/dist/providers/xai.d.ts +1 -1
  209. package/dist/providers/xai.js +5 -10
  210. package/dist/providers/xai.js.map +1 -1
  211. package/dist/runtime/askStructured.d.ts +105 -0
  212. package/dist/runtime/askStructured.js +120 -0
  213. package/dist/runtime/askStructured.js.map +1 -0
  214. package/dist/runtime/conversation.d.ts +60 -0
  215. package/dist/runtime/conversation.js +151 -0
  216. package/dist/runtime/conversation.js.map +1 -0
  217. package/dist/runtime/singleShot.d.ts +25 -5
  218. package/dist/runtime/singleShot.js +95 -36
  219. package/dist/runtime/singleShot.js.map +1 -1
  220. package/dist/runtime/structuredOutput.d.ts +104 -0
  221. package/dist/runtime/structuredOutput.js +393 -0
  222. package/dist/runtime/structuredOutput.js.map +1 -0
  223. package/dist/utils/ProgressIndicator.d.ts +21 -0
  224. package/dist/utils/ProgressIndicator.js +30 -3
  225. package/dist/utils/ProgressIndicator.js.map +1 -1
  226. package/dist/utils/aiignoreUtils.js.map +1 -1
  227. package/dist/utils/binaryOutputUtils.js.map +1 -1
  228. package/dist/utils/consoleUtils.d.ts +52 -0
  229. package/dist/utils/consoleUtils.js +72 -2
  230. package/dist/utils/consoleUtils.js.map +1 -1
  231. package/dist/utils/crashHandler.d.ts +87 -0
  232. package/dist/utils/crashHandler.js +128 -0
  233. package/dist/utils/crashHandler.js.map +1 -0
  234. package/dist/utils/debugDump.d.ts +134 -0
  235. package/dist/utils/debugDump.js +381 -0
  236. package/dist/utils/debugDump.js.map +1 -0
  237. package/dist/utils/debugUtils.d.ts +13 -4
  238. package/dist/utils/debugUtils.js +36 -13
  239. package/dist/utils/debugUtils.js.map +1 -1
  240. package/dist/utils/displayWidth.d.ts +53 -0
  241. package/dist/utils/displayWidth.js +195 -0
  242. package/dist/utils/displayWidth.js.map +1 -0
  243. package/dist/utils/fileUtils.d.ts +24 -2
  244. package/dist/utils/fileUtils.js +54 -12
  245. package/dist/utils/fileUtils.js.map +1 -1
  246. package/dist/utils/llmUtils.d.ts +39 -8
  247. package/dist/utils/llmUtils.js +76 -8
  248. package/dist/utils/llmUtils.js.map +1 -1
  249. package/dist/utils/redactSecrets.d.ts +63 -0
  250. package/dist/utils/redactSecrets.js +286 -0
  251. package/dist/utils/redactSecrets.js.map +1 -0
  252. package/dist/utils/systemPromptNotes.d.ts +225 -0
  253. package/dist/utils/systemPromptNotes.js +340 -0
  254. package/dist/utils/systemPromptNotes.js.map +1 -0
  255. package/dist/utils/systemUtils.d.ts +49 -1
  256. package/dist/utils/systemUtils.js +67 -3
  257. package/dist/utils/systemUtils.js.map +1 -1
  258. package/dist/utils/toolMatching.d.ts +30 -0
  259. package/dist/utils/toolMatching.js +44 -0
  260. package/dist/utils/toolMatching.js.map +1 -0
  261. package/dist/utils/untrustedText.d.ts +66 -0
  262. package/dist/utils/untrustedText.js +80 -0
  263. package/dist/utils/untrustedText.js.map +1 -0
  264. package/package.json +22 -6
  265. package/schema/gsloth-config.schema.json +3134 -0
  266. package/dist/core/shell/allowlist.d.ts +0 -75
  267. package/dist/core/shell/allowlist.js +0 -187
  268. package/dist/core/shell/allowlist.js.map +0 -1
  269. package/dist/core/shell/judge.d.ts +0 -161
  270. package/dist/core/shell/judge.js +0 -261
  271. package/dist/core/shell/judge.js.map +0 -1
@@ -1,77 +1,85 @@
1
1
  import { displayWarning } from '#src/utils/consoleUtils.js';
2
2
  import { env } from '#src/utils/systemUtils.js';
3
- import { writeFileIfNotExistsWithMessages } from '#src/utils/fileUtils.js';
3
+ import { writeConfigFileWithMessages } from '#src/utils/fileUtils.js';
4
+ import { buildInitConfigContent, getCuratedFallbackModel } from '#src/providers/modelDiscovery.js';
4
5
  /**
5
- * Default Ollama daemon host, matching the Ollama CLI/library default. The
6
- * OpenAI-compatible surface lives under `/v1` on this host. Kept in sync with
7
- * `DEFAULT_OLLAMA_HOST` in `modelDiscovery.ts`.
6
+ * Default Ollama daemon host, matching the Ollama CLI/library default. GS2-59 — `ChatOllama` talks
7
+ * to the daemon's NATIVE endpoint (`/api/chat`), NOT the OpenAI-compatible `/v1` shim this provider
8
+ * used before. The same root host is reused by `modelDiscovery.ts`'s model picker, which still
9
+ * queries the `/v1/models` surface (discovery only); both derive from this root — they stay in sync
10
+ * at the host level even though the two use different endpoints under it.
8
11
  */
9
12
  const DEFAULT_OLLAMA_HOST = 'http://127.0.0.1:11434';
10
13
  /**
11
- * Curated default model mirrors the first preferred model advertised for
12
- * ollama by first-run discovery (`PROVIDER_DESCRIPTORS` in modelDiscovery.ts).
13
- */
14
- const DEFAULT_OLLAMA_MODEL = 'qwen3-coder';
15
- /**
16
- * Ollama serves an unauthenticated local daemon, but `ChatOpenAI` requires a
17
- * non-empty `apiKey` string. Send a harmless placeholder so the client builds;
18
- * the local daemon ignores it.
14
+ * GS2-59 default context window (`num_ctx`) for Ollama models. Ollama's OWN default is 4096, but
15
+ * gaunt-sloth's agentic prompt (system + full lean toolset + a tool result) already lands ~4000
16
+ * tokens; at 4096 a thinking model (e.g. gemma4:31b) spends its entire remaining budget on the
17
+ * reasoning field and emits EMPTY `content` on the turn after a tool executes — the GS2-59
18
+ * blank-answer regression. The previous `ChatOpenAI`→`/v1` path could not fix this because the
19
+ * OpenAI-compat shim IGNORES `num_ctx`; the native `/api/chat` path honors it.
20
+ *
21
+ * 16384 is chosen as the largest window that is BOTH safely above the ~4000-token starvation point
22
+ * (4× headroom for reasoning + a few tool results) AND fits constrained consumer VRAM: Ollama
23
+ * preallocates the KV cache at `num_ctx`, so on a box where a large model already spills partly to
24
+ * CPU (e.g. a 19GB model on ~18GB of GPU), a 32768 cache tips the GPU allocation into an
25
+ * out-of-memory error. 16384 was verified live to run the agentic tool→synthesis turn on such a
26
+ * box; 32768 OOM'd it. Overridable per config via `llm.numCtx` — raise it if you have the VRAM and
27
+ * run long sessions, lower it on very tight hardware. NOTE: a per-request `num_ctx` overrides the
28
+ * daemon's `OLLAMA_CONTEXT_LENGTH`, so a user who tuned their server window higher should set
29
+ * `llm.numCtx` to match rather than rely on the server default.
19
30
  */
20
- const OLLAMA_PLACEHOLDER_API_KEY = 'ollama';
31
+ const DEFAULT_OLLAMA_NUM_CTX = 16384;
21
32
  /**
22
- * Resolve the OpenAI-compatible base URL for the local Ollama daemon.
23
- *
24
- * Honors the `OLLAMA_HOST` env override (the same variable the Ollama CLI uses).
25
- * `OLLAMA_HOST` is typically a full URL (`http://127.0.0.1:11434`) or a bare
26
- * `host:port`; either form is normalized to a `http(s)://host[:port]/v1` base.
33
+ * Resolve the base URL for the local Ollama daemon — the NATIVE root (no `/v1` suffix; `ChatOllama`
34
+ * appends its own `/api/chat` path). Honors the `OLLAMA_HOST` env override (the same variable the
35
+ * Ollama CLI uses): a full URL (`http://127.0.0.1:11434`) or a bare `host:port` is normalized to
36
+ * `http(s)://host[:port]` with any trailing slash stripped.
27
37
  */
28
38
  function resolveBaseUrl() {
29
39
  const host = env.OLLAMA_HOST;
30
- let base;
31
- if (!host) {
32
- base = DEFAULT_OLLAMA_HOST;
33
- }
34
- else if (/^https?:\/\//.test(host)) {
35
- base = host.replace(/\/+$/, '');
36
- }
37
- else {
38
- base = `http://${host}`.replace(/\/+$/, '');
39
- }
40
- return `${base}/v1`;
40
+ if (!host)
41
+ return DEFAULT_OLLAMA_HOST;
42
+ const base = /^https?:\/\//.test(host) ? host : `http://${host}`;
43
+ return base.replace(/\/+$/, '');
41
44
  }
42
- // Function to process JSON config and create an Ollama (OpenAI-compatible) LLM instance
45
+ /**
46
+ * GS2-59 — build a native `ChatOllama` client (`@langchain/ollama`, talking to Ollama's
47
+ * `/api/chat`). This replaces the previous `ChatOpenAI`→`/v1` client, which had two fatal
48
+ * properties for local thinking models: it DROPPED the model's separate reasoning field (the
49
+ * completions converter discards it — `__includeRawResponse` is openrouter-only), and it IGNORED
50
+ * `num_ctx` so a large agentic prompt starved the answer to empty content. `ChatOllama` fixes both:
51
+ * it honors `numCtx`, and it surfaces the model's thinking in `additional_kwargs.reasoning_content`
52
+ * — the exact field the reasoning pipeline (`pickReasoningDelta`) already reads — so the `/reasoning`
53
+ * panel populates with no downstream change.
54
+ *
55
+ * Ollama is a local, unauthenticated daemon, so the old OpenAI-client knobs are gone (2.0 migration
56
+ * note): point elsewhere with `OLLAMA_HOST`; the previous `configuration.baseURL` / placeholder
57
+ * `apiKey` fields no longer apply. If a reverse proxy in front of Ollama needs auth, set `headers`
58
+ * in the config (passed straight through to `ChatOllama`).
59
+ */
43
60
  // noinspection JSUnusedGlobalSymbols
44
61
  export async function processJsonConfig(llmConfig) {
45
- const { ChatOpenAI } = await import('@langchain/openai');
46
- // Ollama is local and unauthenticated; ChatOpenAI still needs a non-empty key.
47
- const apiKey = llmConfig.apiKey || OLLAMA_PLACEHOLDER_API_KEY;
62
+ const { ChatOllama } = await import('@langchain/ollama');
48
63
  const configFields = {
49
64
  ...llmConfig,
50
- apiKey,
51
- model: llmConfig.model || DEFAULT_OLLAMA_MODEL,
52
- configuration: {
53
- baseURL: resolveBaseUrl(),
54
- ...(llmConfig.configuration || {}),
55
- },
65
+ model: llmConfig.model || getCuratedFallbackModel('ollama'),
66
+ // Precedence: an explicit config `baseUrl` (the native ChatOllama field) wins, else the
67
+ // `OLLAMA_HOST` env, else the local default. Config is the more specific/intentional signal.
68
+ baseUrl: llmConfig.baseUrl ?? resolveBaseUrl(),
69
+ numCtx: llmConfig.numCtx ?? DEFAULT_OLLAMA_NUM_CTX,
56
70
  };
57
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
58
- delete configFields.type;
59
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
60
- delete configFields.apiKeyEnvironmentVariable;
61
- return new ChatOpenAI(configFields);
71
+ // Strip OpenAI-client / gaunt-sloth-internal keys ChatOllama neither needs nor understands.
72
+ for (const key of ['type', 'apiKeyEnvironmentVariable', 'apiKey', 'configuration']) {
73
+ delete configFields[key];
74
+ }
75
+ return new ChatOllama(configFields);
62
76
  }
63
- const jsonContent = `{
64
- "llm": {
65
- "type": "ollama",
66
- "model": "qwen3-coder"
67
- }
68
- }`;
69
- export function init(configFileName) {
77
+ export function init(configFileName, force = false, model) {
70
78
  // Determine which content to use based on file extension
71
79
  if (!configFileName.endsWith('.json')) {
72
80
  throw new Error('Only JSON config is supported.');
73
81
  }
74
- writeFileIfNotExistsWithMessages(configFileName, jsonContent);
82
+ writeConfigFileWithMessages(configFileName, buildInitConfigContent('ollama', model), force);
75
83
  displayWarning(`You need to edit your ${configFileName} to configure the model. ` +
76
84
  'Ollama runs locally and needs no API key; set OLLAMA_HOST if your daemon ' +
77
85
  'is not on the default http://127.0.0.1:11434.');
@@ -1 +1 @@
1
- {"version":3,"file":"ollama.js","sourceRoot":"","sources":["../../src/providers/ollama.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAC5D,OAAO,EAAE,GAAG,EAAE,MAAM,2BAA2B,CAAC;AAQhD,OAAO,EAAE,gCAAgC,EAAE,MAAM,yBAAyB,CAAC;AAE3E;;;;GAIG;AACH,MAAM,mBAAmB,GAAG,wBAAwB,CAAC;AAErD;;;GAGG;AACH,MAAM,oBAAoB,GAAG,aAAa,CAAC;AAE3C;;;;GAIG;AACH,MAAM,0BAA0B,GAAG,QAAQ,CAAC;AAE5C;;;;;;GAMG;AACH,SAAS,cAAc;IACrB,MAAM,IAAI,GAAG,GAAG,CAAC,WAAW,CAAC;IAC7B,IAAI,IAAY,CAAC;IACjB,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,IAAI,GAAG,mBAAmB,CAAC;IAC7B,CAAC;SAAM,IAAI,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACrC,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAClC,CAAC;SAAM,CAAC;QACN,IAAI,GAAG,UAAU,IAAI,EAAE,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAC9C,CAAC;IACD,OAAO,GAAG,IAAI,KAAK,CAAC;AACtB,CAAC;AAED,wFAAwF;AACxF,qCAAqC;AACrC,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,SAAmE;IAEnE,MAAM,EAAE,UAAU,EAAE,GAAG,MAAM,MAAM,CAAC,mBAAmB,CAAC,CAAC;IACzD,+EAA+E;IAC/E,MAAM,MAAM,GAAG,SAAS,CAAC,MAAM,IAAI,0BAA0B,CAAC;IAC9D,MAAM,YAAY,GAAG;QACnB,GAAG,SAAS;QACZ,MAAM;QACN,KAAK,EAAE,SAAS,CAAC,KAAK,IAAI,oBAAoB;QAC9C,aAAa,EAAE;YACb,OAAO,EAAE,cAAc,EAAE;YACzB,GAAG,CAAC,SAAS,CAAC,aAAa,IAAI,EAAE,CAAC;SACnC;KACF,CAAC;IACF,8DAA8D;IAC9D,OAAQ,YAAoB,CAAC,IAAI,CAAC;IAClC,8DAA8D;IAC9D,OAAQ,YAAoB,CAAC,yBAAyB,CAAC;IAEvD,OAAO,IAAI,UAAU,CAAC,YAAY,CAAC,CAAC;AACtC,CAAC;AAED,MAAM,WAAW,GAAG;;;;;EAKlB,CAAC;AAEH,MAAM,UAAU,IAAI,CAAC,cAAsB;IACzC,yDAAyD;IACzD,IAAI,CAAC,cAAc,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;QACtC,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAC;IACpD,CAAC;IAED,gCAAgC,CAAC,cAAc,EAAE,WAAW,CAAC,CAAC;IAC9D,cAAc,CACZ,yBAAyB,cAAc,2BAA2B;QAChE,2EAA2E;QAC3E,+CAA+C,CAClD,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"ollama.js","sourceRoot":"","sources":["../../src/providers/ollama.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAC5D,OAAO,EAAE,GAAG,EAAE,MAAM,2BAA2B,CAAC;AAOhD,OAAO,EAAE,2BAA2B,EAAE,MAAM,yBAAyB,CAAC;AACtE,OAAO,EAAE,sBAAsB,EAAE,uBAAuB,EAAE,MAAM,kCAAkC,CAAC;AAEnG;;;;;;GAMG;AACH,MAAM,mBAAmB,GAAG,wBAAwB,CAAC;AAErD;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,sBAAsB,GAAG,KAAK,CAAC;AAErC;;;;;GAKG;AACH,SAAS,cAAc;IACrB,MAAM,IAAI,GAAG,GAAG,CAAC,WAAW,CAAC;IAC7B,IAAI,CAAC,IAAI;QAAE,OAAO,mBAAmB,CAAC;IACtC,MAAM,IAAI,GAAG,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,UAAU,IAAI,EAAE,CAAC;IACjE,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;AAClC,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,qCAAqC;AACrC,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,SAAgD;IAEhD,MAAM,EAAE,UAAU,EAAE,GAAG,MAAM,MAAM,CAAC,mBAAmB,CAAC,CAAC;IACzD,MAAM,YAAY,GAA4B;QAC5C,GAAG,SAAS;QACZ,KAAK,EAAE,SAAS,CAAC,KAAK,IAAI,uBAAuB,CAAC,QAAQ,CAAC;QAC3D,wFAAwF;QACxF,6FAA6F;QAC7F,OAAO,EAAE,SAAS,CAAC,OAAO,IAAI,cAAc,EAAE;QAC9C,MAAM,EAAE,SAAS,CAAC,MAAM,IAAI,sBAAsB;KACnD,CAAC;IACF,4FAA4F;IAC5F,KAAK,MAAM,GAAG,IAAI,CAAC,MAAM,EAAE,2BAA2B,EAAE,QAAQ,EAAE,eAAe,CAAC,EAAE,CAAC;QACnF,OAAO,YAAY,CAAC,GAAG,CAAC,CAAC;IAC3B,CAAC;IACD,OAAO,IAAI,UAAU,CAAC,YAA+B,CAAC,CAAC;AACzD,CAAC;AAED,MAAM,UAAU,IAAI,CAAC,cAAsB,EAAE,KAAK,GAAG,KAAK,EAAE,KAAc;IACxE,yDAAyD;IACzD,IAAI,CAAC,cAAc,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;QACtC,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAC;IACpD,CAAC;IAED,2BAA2B,CAAC,cAAc,EAAE,sBAAsB,CAAC,QAAQ,EAAE,KAAK,CAAC,EAAE,KAAK,CAAC,CAAC;IAC5F,cAAc,CACZ,yBAAyB,cAAc,2BAA2B;QAChE,2EAA2E;QAC3E,+CAA+C,CAClD,CAAC;AACJ,CAAC"}
@@ -2,4 +2,4 @@ import { BaseChatModel, type BaseChatModelParams } from '@langchain/core/languag
2
2
  import { OpenAIChatInput } from '@langchain/openai';
3
3
  import { ChatOpenAIFields } from '@langchain/openai';
4
4
  export declare function processJsonConfig(llmConfig: OpenAIChatInput & ChatOpenAIFields & BaseChatModelParams): Promise<BaseChatModel>;
5
- export declare function init(configFileName: string): void;
5
+ export declare function init(configFileName: string, force?: boolean, model?: string): void;
@@ -1,6 +1,7 @@
1
1
  import { displayWarning } from '#src/utils/consoleUtils.js';
2
2
  import { env } from '#src/utils/systemUtils.js';
3
- import { writeFileIfNotExistsWithMessages } from '#src/utils/fileUtils.js';
3
+ import { writeConfigFileWithMessages } from '#src/utils/fileUtils.js';
4
+ import { buildInitConfigContent, getCuratedFallbackModel } from '#src/providers/modelDiscovery.js';
4
5
  /**
5
6
  * OpenAI reasoning-model families that reject any non-default `temperature`: the API 400s with
6
7
  * "Unsupported value: 'temperature' does not support 0 ... Only the default (1) value is
@@ -15,6 +16,35 @@ const SUPPORTED_DEFAULT_TEMPERATURE = 1;
15
16
  function isTemperatureRestrictedModel(model) {
16
17
  return !!model && TEMPERATURE_RESTRICTED_MODEL.test(model);
17
18
  }
19
+ /**
20
+ * Decide whether to force the OpenAI Responses API (`/v1/responses`) for this request.
21
+ *
22
+ * OpenAI's `/v1/chat/completions` endpoint rejects reasoning-capable models (the gpt-5.x / o-series
23
+ * families) when `reasoning_effort` is combined with function tools:
24
+ * "Function tools with reasoning_effort are not supported for gpt-5.6-luna in
25
+ * /v1/chat/completions. To use function tools, use /v1/responses or set reasoning_effort to
26
+ * 'none'."
27
+ * LangChain's auto-router only flips to the Responses API for OpenAI built-in tools,
28
+ * `reasoning.summary` (not `.effort`), custom tools, or a hardcoded model allowlist — none of which
29
+ * covers ordinary function tools on a reasoning model that isn't yet on that allowlist (e.g.
30
+ * gpt-5.6-luna). Crucially, these models **default `reasoning_effort` to a non-`none` value**, so the
31
+ * collision fires even when the gth config sets NO reasoning at all (the failing case: a bare
32
+ * `{ type: openai, model: gpt-5.6-luna }` still 400s on `chat/completions` + tools). So for any
33
+ * reasoning-capable model, when the user has not made an explicit choice, default `useResponsesApi`
34
+ * to true — do NOT gate on reasoning being explicitly configured (GS2-74's original bug: it required
35
+ * `reasoningEffort`/`reasoning` to be set, missing the far more common bare-config path).
36
+ *
37
+ * Guards: (1) only reasoning-capable models — leave gpt-4o etc. on completions; (2) never override an
38
+ * explicit `useResponsesApi` the user set (true OR false). The model-name guard also scopes this away
39
+ * from OpenAI-compatible providers that reuse this file (Inception/DeepSeek/xAI ids don't match the
40
+ * regex). Routing a no-tools/plain call to Responses is still correct FOR GTH because gth never
41
+ * injects reasoning params itself — so even a non-reasoning `gpt-5`-prefixed id (e.g.
42
+ * `gpt-5-chat-latest`, which this regex also sweeps) is accepted there. (Responses isn't a universal
43
+ * superset — it drops some chat/completions-only params like `n>1` — but gth uses none of those.)
44
+ */
45
+ function shouldUseResponsesApi(fields) {
46
+ return fields.useResponsesApi === undefined && isTemperatureRestrictedModel(fields.model);
47
+ }
18
48
  // Function to process JSON config and create OpenAI LLM instance
19
49
  // noinspection JSUnusedGlobalSymbols
20
50
  export async function processJsonConfig(llmConfig) {
@@ -24,7 +54,7 @@ export async function processJsonConfig(llmConfig) {
24
54
  const configFields = {
25
55
  ...llmConfig,
26
56
  apiKey: openaiApiKey,
27
- model: llmConfig.model || 'gpt-5.5',
57
+ model: llmConfig.model || getCuratedFallbackModel('openai'),
28
58
  };
29
59
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
30
60
  delete configFields.type;
@@ -42,6 +72,11 @@ export async function processJsonConfig(llmConfig) {
42
72
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
43
73
  delete configFields.temperature;
44
74
  }
75
+ // Reasoning-capable OpenAI models 400 on `/v1/chat/completions` when `reasoning_effort` meets
76
+ // function tools; route them to the Responses API unless the user pinned `useResponsesApi`.
77
+ if (shouldUseResponsesApi(configFields)) {
78
+ configFields.useResponsesApi = true;
79
+ }
45
80
  return new ChatOpenAI(configFields);
46
81
  }
47
82
  function getApiKey(llmConfig) {
@@ -54,18 +89,12 @@ function getApiKey(llmConfig) {
54
89
  return llmConfig.apiKey || env.OPENAI_API_KEY;
55
90
  }
56
91
  }
57
- const jsonContent = `{
58
- "llm": {
59
- "type": "openai",
60
- "model": "gpt-5.5"
61
- }
62
- }`;
63
- export function init(configFileName) {
92
+ export function init(configFileName, force = false, model) {
64
93
  // Determine which content to use based on file extension
65
94
  if (!configFileName.endsWith('.json')) {
66
95
  throw new Error('Only JSON config is supported.');
67
96
  }
68
- writeFileIfNotExistsWithMessages(configFileName, jsonContent);
97
+ writeConfigFileWithMessages(configFileName, buildInitConfigContent('openai', model), force);
69
98
  displayWarning(`You need to edit your ${configFileName} to configure model, ` +
70
99
  'or define OPENAI_API_KEY environment variable.');
71
100
  }
@@ -1 +1 @@
1
- {"version":3,"file":"openai.js","sourceRoot":"","sources":["../../src/providers/openai.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAC5D,OAAO,EAAE,GAAG,EAAE,MAAM,2BAA2B,CAAC;AAQhD,OAAO,EAAE,gCAAgC,EAAE,MAAM,yBAAyB,CAAC;AAE3E;;;;;;;GAOG;AACH,MAAM,4BAA4B,GAAG,eAAe,CAAC;AAErD,yFAAyF;AACzF,MAAM,6BAA6B,GAAG,CAAC,CAAC;AAExC,SAAS,4BAA4B,CAAC,KAAyB;IAC7D,OAAO,CAAC,CAAC,KAAK,IAAI,4BAA4B,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AAC7D,CAAC;AAED,iEAAiE;AACjE,qCAAqC;AACrC,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,SAAmE;IAEnE,MAAM,EAAE,UAAU,EAAE,GAAG,MAAM,MAAM,CAAC,mBAAmB,CAAC,CAAC;IACzD,wEAAwE;IACxE,MAAM,YAAY,GAAG,SAAS,CAAC,SAAS,CAAC,CAAC;IAC1C,MAAM,YAAY,GAAG;QACnB,GAAG,SAAS;QACZ,MAAM,EAAE,YAAY;QACpB,KAAK,EAAE,SAAS,CAAC,KAAK,IAAI,SAAS;KACpC,CAAC;IACF,8DAA8D;IAC9D,OAAQ,YAAoB,CAAC,IAAI,CAAC;IAClC,8DAA8D;IAC9D,OAAQ,YAAoB,CAAC,yBAAyB,CAAC;IAEvD,6FAA6F;IAC7F,8FAA8F;IAC9F,6FAA6F;IAC7F,IACE,4BAA4B,CAAC,YAAY,CAAC,KAAK,CAAC;QAChD,YAAY,CAAC,WAAW,KAAK,SAAS;QACtC,YAAY,CAAC,WAAW,KAAK,6BAA6B,EAC1D,CAAC;QACD,cAAc,CACZ,UAAU,YAAY,CAAC,KAAK,0CAA0C;YACpE,qBAAqB,6BAA6B,gBAAgB;YAClE,0CAA0C,YAAY,CAAC,WAAW,GAAG,CACxE,CAAC;QACF,8DAA8D;QAC9D,OAAQ,YAAoB,CAAC,WAAW,CAAC;IAC3C,CAAC;IAED,OAAO,IAAI,UAAU,CAAC,YAAY,CAAC,CAAC;AACtC,CAAC;AAED,SAAS,SAAS,CAAC,SAAmE;IACpF,8DAA8D;IAC9D,MAAM,IAAI,GAAG,SAA0C,CAAC;IACxD,IAAI,IAAI,CAAC,yBAAyB,IAAI,GAAG,CAAC,IAAI,CAAC,yBAAyB,CAAC,EAAE,CAAC;QAC1E,OAAO,GAAG,CAAC,IAAI,CAAC,yBAAyB,CAAC,CAAC;IAC7C,CAAC;SAAM,CAAC;QACN,OAAO,SAAS,CAAC,MAAM,IAAI,GAAG,CAAC,cAAc,CAAC;IAChD,CAAC;AACH,CAAC;AAED,MAAM,WAAW,GAAG;;;;;EAKlB,CAAC;AAEH,MAAM,UAAU,IAAI,CAAC,cAAsB;IACzC,yDAAyD;IACzD,IAAI,CAAC,cAAc,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;QACtC,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAC;IACpD,CAAC;IAED,gCAAgC,CAAC,cAAc,EAAE,WAAW,CAAC,CAAC;IAC9D,cAAc,CACZ,yBAAyB,cAAc,uBAAuB;QAC5D,gDAAgD,CACnD,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"openai.js","sourceRoot":"","sources":["../../src/providers/openai.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAC5D,OAAO,EAAE,GAAG,EAAE,MAAM,2BAA2B,CAAC;AAQhD,OAAO,EAAE,2BAA2B,EAAE,MAAM,yBAAyB,CAAC;AACtE,OAAO,EAAE,sBAAsB,EAAE,uBAAuB,EAAE,MAAM,kCAAkC,CAAC;AAEnG;;;;;;;GAOG;AACH,MAAM,4BAA4B,GAAG,eAAe,CAAC;AAErD,yFAAyF;AACzF,MAAM,6BAA6B,GAAG,CAAC,CAAC;AAExC,SAAS,4BAA4B,CAAC,KAAyB;IAC7D,OAAO,CAAC,CAAC,KAAK,IAAI,4BAA4B,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AAC7D,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,SAAS,qBAAqB,CAAC,MAAqD;IAClF,OAAO,MAAM,CAAC,eAAe,KAAK,SAAS,IAAI,4BAA4B,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAC5F,CAAC;AAED,iEAAiE;AACjE,qCAAqC;AACrC,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,SAAmE;IAEnE,MAAM,EAAE,UAAU,EAAE,GAAG,MAAM,MAAM,CAAC,mBAAmB,CAAC,CAAC;IACzD,wEAAwE;IACxE,MAAM,YAAY,GAAG,SAAS,CAAC,SAAS,CAAC,CAAC;IAC1C,MAAM,YAAY,GAAG;QACnB,GAAG,SAAS;QACZ,MAAM,EAAE,YAAY;QACpB,KAAK,EAAE,SAAS,CAAC,KAAK,IAAI,uBAAuB,CAAC,QAAQ,CAAC;KAC5D,CAAC;IACF,8DAA8D;IAC9D,OAAQ,YAAoB,CAAC,IAAI,CAAC;IAClC,8DAA8D;IAC9D,OAAQ,YAAoB,CAAC,yBAAyB,CAAC;IAEvD,6FAA6F;IAC7F,8FAA8F;IAC9F,6FAA6F;IAC7F,IACE,4BAA4B,CAAC,YAAY,CAAC,KAAK,CAAC;QAChD,YAAY,CAAC,WAAW,KAAK,SAAS;QACtC,YAAY,CAAC,WAAW,KAAK,6BAA6B,EAC1D,CAAC;QACD,cAAc,CACZ,UAAU,YAAY,CAAC,KAAK,0CAA0C;YACpE,qBAAqB,6BAA6B,gBAAgB;YAClE,0CAA0C,YAAY,CAAC,WAAW,GAAG,CACxE,CAAC;QACF,8DAA8D;QAC9D,OAAQ,YAAoB,CAAC,WAAW,CAAC;IAC3C,CAAC;IAED,8FAA8F;IAC9F,4FAA4F;IAC5F,IAAI,qBAAqB,CAAC,YAAY,CAAC,EAAE,CAAC;QACxC,YAAY,CAAC,eAAe,GAAG,IAAI,CAAC;IACtC,CAAC;IAED,OAAO,IAAI,UAAU,CAAC,YAAY,CAAC,CAAC;AACtC,CAAC;AAED,SAAS,SAAS,CAAC,SAAmE;IACpF,8DAA8D;IAC9D,MAAM,IAAI,GAAG,SAA0C,CAAC;IACxD,IAAI,IAAI,CAAC,yBAAyB,IAAI,GAAG,CAAC,IAAI,CAAC,yBAAyB,CAAC,EAAE,CAAC;QAC1E,OAAO,GAAG,CAAC,IAAI,CAAC,yBAAyB,CAAC,CAAC;IAC7C,CAAC;SAAM,CAAC;QACN,OAAO,SAAS,CAAC,MAAM,IAAI,GAAG,CAAC,cAAc,CAAC;IAChD,CAAC;AACH,CAAC;AAED,MAAM,UAAU,IAAI,CAAC,cAAsB,EAAE,KAAK,GAAG,KAAK,EAAE,KAAc;IACxE,yDAAyD;IACzD,IAAI,CAAC,cAAc,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;QACtC,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAC;IACpD,CAAC;IAED,2BAA2B,CAAC,cAAc,EAAE,sBAAsB,CAAC,QAAQ,EAAE,KAAK,CAAC,EAAE,KAAK,CAAC,CAAC;IAC5F,cAAc,CACZ,yBAAyB,cAAc,uBAAuB;QAC5D,gDAAgD,CACnD,CAAC;AACJ,CAAC"}
@@ -1,5 +1,4 @@
1
- import { BaseChatModel, type BaseChatModelParams } from '@langchain/core/language_models/chat_models';
2
- import { OpenAIChatInput } from '@langchain/openai';
3
- import { ChatOpenAIFields } from '@langchain/openai';
4
- export declare function processJsonConfig(llmConfig: OpenAIChatInput & ChatOpenAIFields & BaseChatModelParams): Promise<BaseChatModel>;
5
- export declare function init(configFileName: string): void;
1
+ import type { BaseChatModel, BaseChatModelParams } from '@langchain/core/language_models/chat_models';
2
+ import type { ChatOpenRouterInput } from '@langchain/openrouter';
3
+ export declare function processJsonConfig(llmConfig: ChatOpenRouterInput & BaseChatModelParams & Record<string, unknown>): Promise<BaseChatModel>;
4
+ export declare function init(configFileName: string, force?: boolean, model?: string): void;
@@ -1,56 +1,41 @@
1
1
  import { displayWarning } from '#src/utils/consoleUtils.js';
2
2
  import { env } from '#src/utils/systemUtils.js';
3
- import { writeFileIfNotExistsWithMessages } from '#src/utils/fileUtils.js';
3
+ import { writeConfigFileWithMessages } from '#src/utils/fileUtils.js';
4
+ import { buildInitConfigContent, getCuratedFallbackModel } from '#src/providers/modelDiscovery.js';
4
5
  // Function to process JSON config and create OpenRouter LLM instance
5
6
  // noinspection JSUnusedGlobalSymbols
6
7
  export async function processJsonConfig(llmConfig) {
7
- const { ChatOpenAI } = await import('@langchain/openai');
8
- // Use environment variable if available, otherwise use the config value
8
+ const { ChatOpenRouter } = await import('@langchain/openrouter');
9
9
  const openRouterApiKey = getApiKey(llmConfig);
10
10
  if (!openRouterApiKey) {
11
11
  throw new Error('You need to define OPEN_ROUTER_API_KEY environment variable, or set apiKey in your config file.');
12
12
  }
13
- const configFields = {
14
- ...llmConfig,
13
+ const { type: _type, apiKeyEnvironmentVariable: _envVar, configuration, siteUrl, siteName, ...restConfig } = llmConfig;
14
+ const configObj = configuration;
15
+ const resolvedSiteUrl = siteUrl ?? configObj?.defaultHeaders?.['HTTP-Referer'] ?? 'https://gauntsloth.app/';
16
+ const resolvedSiteName = siteName ?? configObj?.defaultHeaders?.['X-Title'] ?? 'Gaunt Sloth';
17
+ return new ChatOpenRouter({
18
+ ...restConfig,
15
19
  apiKey: openRouterApiKey,
16
- model: llmConfig.model || 'qwen/qwen3-coder',
17
- configuration: {
18
- baseURL: 'https://openrouter.ai/api/v1',
19
- ...(llmConfig.configuration || {}),
20
- defaultHeaders: {
21
- 'HTTP-Referer': 'https://gauntsloth.app/',
22
- 'X-Title': 'Gaunt Sloth',
23
- },
24
- },
25
- };
26
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
27
- delete configFields.type;
28
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
29
- delete configFields.apiKeyEnvironmentVariable;
30
- return new ChatOpenAI(configFields);
20
+ model: llmConfig.model || getCuratedFallbackModel('openrouter'),
21
+ siteUrl: resolvedSiteUrl,
22
+ siteName: resolvedSiteName,
23
+ ...(configObj?.baseURL ? { baseURL: configObj.baseURL } : {}),
24
+ });
31
25
  }
32
26
  function getApiKey(llmConfig) {
33
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
34
- const conf = llmConfig;
35
- if (conf.apiKeyEnvironmentVariable && env[conf.apiKeyEnvironmentVariable]) {
36
- return env[conf.apiKeyEnvironmentVariable];
37
- }
38
- else {
39
- return llmConfig.apiKey || env.OPEN_ROUTER_API_KEY || env.OPENROUTER_API_KEY;
27
+ const envVarName = llmConfig.apiKeyEnvironmentVariable;
28
+ if (envVarName && env[envVarName]) {
29
+ return env[envVarName];
40
30
  }
31
+ return (llmConfig.apiKey || env.OPEN_ROUTER_API_KEY || env.OPENROUTER_API_KEY);
41
32
  }
42
- const jsonContent = `{
43
- "llm": {
44
- "type": "openrouter",
45
- "model": "qwen/qwen3-coder"
46
- }
47
- }`;
48
- export function init(configFileName) {
33
+ export function init(configFileName, force = false, model) {
49
34
  // Determine which content to use based on file extension
50
35
  if (!configFileName.endsWith('.json')) {
51
36
  throw new Error('Only JSON config is supported.');
52
37
  }
53
- writeFileIfNotExistsWithMessages(configFileName, jsonContent);
38
+ writeConfigFileWithMessages(configFileName, buildInitConfigContent('openrouter', model), force);
54
39
  displayWarning(`You need to edit your ${configFileName} to configure model, ` +
55
40
  'or define OPEN_ROUTER_API_KEY environment variable.');
56
41
  }
@@ -1 +1 @@
1
- {"version":3,"file":"openrouter.js","sourceRoot":"","sources":["../../src/providers/openrouter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAC5D,OAAO,EAAE,GAAG,EAAE,MAAM,2BAA2B,CAAC;AAQhD,OAAO,EAAE,gCAAgC,EAAE,MAAM,yBAAyB,CAAC;AAE3E,qEAAqE;AACrE,qCAAqC;AACrC,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,SAAmE;IAEnE,MAAM,EAAE,UAAU,EAAE,GAAG,MAAM,MAAM,CAAC,mBAAmB,CAAC,CAAC;IACzD,wEAAwE;IACxE,MAAM,gBAAgB,GAAG,SAAS,CAAC,SAAS,CAAC,CAAC;IAC9C,IAAI,CAAC,gBAAgB,EAAE,CAAC;QACtB,MAAM,IAAI,KAAK,CACb,iGAAiG,CAClG,CAAC;IACJ,CAAC;IACD,MAAM,YAAY,GAAG;QACnB,GAAG,SAAS;QACZ,MAAM,EAAE,gBAAgB;QACxB,KAAK,EAAE,SAAS,CAAC,KAAK,IAAI,kBAAkB;QAC5C,aAAa,EAAE;YACb,OAAO,EAAE,8BAA8B;YACvC,GAAG,CAAC,SAAS,CAAC,aAAa,IAAI,EAAE,CAAC;YAClC,cAAc,EAAE;gBACd,cAAc,EAAE,yBAAyB;gBACzC,SAAS,EAAE,aAAa;aACzB;SACF;KACF,CAAC;IACF,8DAA8D;IAC9D,OAAQ,YAAoB,CAAC,IAAI,CAAC;IAClC,8DAA8D;IAC9D,OAAQ,YAAoB,CAAC,yBAAyB,CAAC;IACvD,OAAO,IAAI,UAAU,CAAC,YAAY,CAAC,CAAC;AACtC,CAAC;AAED,SAAS,SAAS,CAAC,SAAmE;IACpF,8DAA8D;IAC9D,MAAM,IAAI,GAAG,SAA0C,CAAC;IACxD,IAAI,IAAI,CAAC,yBAAyB,IAAI,GAAG,CAAC,IAAI,CAAC,yBAAyB,CAAC,EAAE,CAAC;QAC1E,OAAO,GAAG,CAAC,IAAI,CAAC,yBAAyB,CAAC,CAAC;IAC7C,CAAC;SAAM,CAAC;QACN,OAAO,SAAS,CAAC,MAAM,IAAI,GAAG,CAAC,mBAAmB,IAAI,GAAG,CAAC,kBAAkB,CAAC;IAC/E,CAAC;AACH,CAAC;AAED,MAAM,WAAW,GAAG;;;;;EAKlB,CAAC;AAEH,MAAM,UAAU,IAAI,CAAC,cAAsB;IACzC,yDAAyD;IACzD,IAAI,CAAC,cAAc,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;QACtC,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAC;IACpD,CAAC;IAED,gCAAgC,CAAC,cAAc,EAAE,WAAW,CAAC,CAAC;IAC9D,cAAc,CACZ,yBAAyB,cAAc,uBAAuB;QAC5D,qDAAqD,CACxD,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"openrouter.js","sourceRoot":"","sources":["../../src/providers/openrouter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAC5D,OAAO,EAAE,GAAG,EAAE,MAAM,2BAA2B,CAAC;AAOhD,OAAO,EAAE,2BAA2B,EAAE,MAAM,yBAAyB,CAAC;AACtE,OAAO,EAAE,sBAAsB,EAAE,uBAAuB,EAAE,MAAM,kCAAkC,CAAC;AAEnG,qEAAqE;AACrE,qCAAqC;AACrC,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,SAA8E;IAE9E,MAAM,EAAE,cAAc,EAAE,GAAG,MAAM,MAAM,CAAC,uBAAuB,CAAC,CAAC;IAEjE,MAAM,gBAAgB,GAAG,SAAS,CAAC,SAAS,CAAC,CAAC;IAC9C,IAAI,CAAC,gBAAgB,EAAE,CAAC;QACtB,MAAM,IAAI,KAAK,CACb,iGAAiG,CAClG,CAAC;IACJ,CAAC;IAED,MAAM,EACJ,IAAI,EAAE,KAAK,EACX,yBAAyB,EAAE,OAAO,EAClC,aAAa,EACb,OAAO,EACP,QAAQ,EACR,GAAG,UAAU,EACd,GAAG,SAAS,CAAC;IAEd,MAAM,SAAS,GAAG,aACyD,CAAC;IAE5E,MAAM,eAAe,GACnB,OAAO,IAAI,SAAS,EAAE,cAAc,EAAE,CAAC,cAAc,CAAC,IAAI,yBAAyB,CAAC;IACtF,MAAM,gBAAgB,GAAG,QAAQ,IAAI,SAAS,EAAE,cAAc,EAAE,CAAC,SAAS,CAAC,IAAI,aAAa,CAAC;IAE7F,OAAO,IAAI,cAAc,CAAC;QACxB,GAAG,UAAU;QACb,MAAM,EAAE,gBAAgB;QACxB,KAAK,EAAE,SAAS,CAAC,KAAK,IAAI,uBAAuB,CAAC,YAAY,CAAC;QAC/D,OAAO,EAAE,eAAe;QACxB,QAAQ,EAAE,gBAAgB;QAC1B,GAAG,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,SAAS,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC9D,CAAC,CAAC;AACL,CAAC;AAED,SAAS,SAAS,CAAC,SAAkC;IACnD,MAAM,UAAU,GAAG,SAAS,CAAC,yBAA+C,CAAC;IAC7E,IAAI,UAAU,IAAI,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC;QAClC,OAAO,GAAG,CAAC,UAAU,CAAC,CAAC;IACzB,CAAC;IACD,OAAO,CACJ,SAAS,CAAC,MAA6B,IAAI,GAAG,CAAC,mBAAmB,IAAI,GAAG,CAAC,kBAAkB,CAC9F,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,IAAI,CAAC,cAAsB,EAAE,KAAK,GAAG,KAAK,EAAE,KAAc;IACxE,yDAAyD;IACzD,IAAI,CAAC,cAAc,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;QACtC,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAC;IACpD,CAAC;IAED,2BAA2B,CAAC,cAAc,EAAE,sBAAsB,CAAC,YAAY,EAAE,KAAK,CAAC,EAAE,KAAK,CAAC,CAAC;IAChG,cAAc,CACZ,yBAAyB,cAAc,uBAAuB;QAC5D,qDAAqD,CACxD,CAAC;AACJ,CAAC"}
@@ -1,6 +1,6 @@
1
1
  import type { BaseChatModel } from '@langchain/core/language_models/chat_models';
2
2
  import type { ChatGoogleParams } from '@langchain/google/node';
3
- export declare function init(configFileName: string): void;
3
+ export declare function init(configFileName: string, force?: boolean, model?: string): void;
4
4
  export declare function processJsonConfig(llmConfig: ChatGoogleParams & {
5
5
  type?: string;
6
6
  apiKeyEnvironmentVariable?: string;
@@ -11,19 +11,16 @@
11
11
  * Hopefully this issue will go away when LangChain switches to the new GenAI dependency.
12
12
  */
13
13
  import { displayWarning } from '#src/utils/consoleUtils.js';
14
- import { writeFileIfNotExistsWithMessages } from '#src/utils/fileUtils.js';
15
- const jsonContent = `{
16
- "llm": {
17
- "type": "vertexai",
18
- "model": "gemini-3.5-flash"
19
- }
20
- }`;
21
- export function init(configFileName) {
14
+ import { writeConfigFileWithMessages } from '#src/utils/fileUtils.js';
15
+ import { buildInitConfigContent, getCuratedFallbackModel } from '#src/providers/modelDiscovery.js';
16
+ import { applyGeminiToolSchemaSanitizer } from '#src/providers/geminiSchemaSanitizer.js';
17
+ import { applyGeminiThoughtSummaries } from '#src/providers/geminiThinking.js';
18
+ export function init(configFileName, force = false, model) {
22
19
  // Determine which content to use based on file extension
23
20
  if (!configFileName.endsWith('.json')) {
24
21
  throw new Error('Only JSON config is supported.');
25
22
  }
26
- writeFileIfNotExistsWithMessages(configFileName, jsonContent);
23
+ writeConfigFileWithMessages(configFileName, buildInitConfigContent('vertexai', model), force);
27
24
  displayWarning('For Google VertexAI you likely to need to do `gcloud auth login` and `gcloud auth application-default login`.');
28
25
  }
29
26
  // Function to process JSON config and create VertexAI LLM instance
@@ -31,11 +28,15 @@ export async function processJsonConfig(llmConfig) {
31
28
  const { ChatGoogle } = await import('@langchain/google/node');
32
29
  const configFields = {
33
30
  ...llmConfig,
34
- model: llmConfig.model || 'gemini-3.5-flash',
31
+ model: llmConfig.model || getCuratedFallbackModel('vertexai'),
35
32
  vertexai: true,
36
33
  };
37
34
  delete configFields.type;
38
35
  delete configFields.apiKeyEnvironmentVariable;
39
- return new ChatGoogle(configFields);
36
+ // GS2-58: normalise every tool's JSON-Schema at the ChatGoogle boundary so Gemini's OpenAPI-3.0
37
+ // subset accepts built-in, custom, and MCP tools alike (see geminiSchemaSanitizer).
38
+ // CFG-33: Vertex serves the same Gemini models through the same ChatGoogle class, so it has the
39
+ // same silently-discarded thinking; ask for the summaries here too (see geminiThinking).
40
+ return applyGeminiThoughtSummaries(applyGeminiToolSchemaSanitizer(new ChatGoogle(configFields)));
40
41
  }
41
42
  //# sourceMappingURL=vertexai.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"vertexai.js","sourceRoot":"","sources":["../../src/providers/vertexai.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAI5D,OAAO,EAAE,gCAAgC,EAAE,MAAM,yBAAyB,CAAC;AAE3E,MAAM,WAAW,GAAG;;;;;EAKlB,CAAC;AAEH,MAAM,UAAU,IAAI,CAAC,cAAsB;IACzC,yDAAyD;IACzD,IAAI,CAAC,cAAc,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;QACtC,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAC;IACpD,CAAC;IAED,gCAAgC,CAAC,cAAc,EAAE,WAAW,CAAC,CAAC;IAC9D,cAAc,CACZ,+GAA+G,CAChH,CAAC;AACJ,CAAC;AAED,mEAAmE;AACnE,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,SAAmF;IAEnF,MAAM,EAAE,UAAU,EAAE,GAAG,MAAM,MAAM,CAAC,wBAAwB,CAAC,CAAC;IAC9D,MAAM,YAAY,GAAG;QACnB,GAAG,SAAS;QACZ,KAAK,EAAE,SAAS,CAAC,KAAK,IAAI,kBAAkB;QAC5C,QAAQ,EAAE,IAAI;KACf,CAAC;IACF,OAAO,YAAY,CAAC,IAAI,CAAC;IACzB,OAAO,YAAY,CAAC,yBAAyB,CAAC;IAC9C,OAAO,IAAI,UAAU,CAAC,YAAY,CAAC,CAAC;AACtC,CAAC"}
1
+ {"version":3,"file":"vertexai.js","sourceRoot":"","sources":["../../src/providers/vertexai.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAI5D,OAAO,EAAE,2BAA2B,EAAE,MAAM,yBAAyB,CAAC;AACtE,OAAO,EAAE,sBAAsB,EAAE,uBAAuB,EAAE,MAAM,kCAAkC,CAAC;AACnG,OAAO,EAAE,8BAA8B,EAAE,MAAM,yCAAyC,CAAC;AACzF,OAAO,EAAE,2BAA2B,EAAE,MAAM,kCAAkC,CAAC;AAE/E,MAAM,UAAU,IAAI,CAAC,cAAsB,EAAE,KAAK,GAAG,KAAK,EAAE,KAAc;IACxE,yDAAyD;IACzD,IAAI,CAAC,cAAc,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;QACtC,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAC;IACpD,CAAC;IAED,2BAA2B,CAAC,cAAc,EAAE,sBAAsB,CAAC,UAAU,EAAE,KAAK,CAAC,EAAE,KAAK,CAAC,CAAC;IAC9F,cAAc,CACZ,+GAA+G,CAChH,CAAC;AACJ,CAAC;AAED,mEAAmE;AACnE,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,SAAmF;IAEnF,MAAM,EAAE,UAAU,EAAE,GAAG,MAAM,MAAM,CAAC,wBAAwB,CAAC,CAAC;IAC9D,MAAM,YAAY,GAAG;QACnB,GAAG,SAAS;QACZ,KAAK,EAAE,SAAS,CAAC,KAAK,IAAI,uBAAuB,CAAC,UAAU,CAAC;QAC7D,QAAQ,EAAE,IAAI;KACf,CAAC;IACF,OAAO,YAAY,CAAC,IAAI,CAAC;IACzB,OAAO,YAAY,CAAC,yBAAyB,CAAC;IAC9C,gGAAgG;IAChG,oFAAoF;IACpF,gGAAgG;IAChG,yFAAyF;IACzF,OAAO,2BAA2B,CAAC,8BAA8B,CAAC,IAAI,UAAU,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;AACnG,CAAC"}
@@ -1,4 +1,4 @@
1
1
  import type { BaseChatModel, BaseChatModelParams } from '@langchain/core/language_models/chat_models';
2
2
  import type { ChatXAIInput } from '@langchain/xai';
3
3
  export declare function processJsonConfig(llmConfig: ChatXAIInput & BaseChatModelParams): Promise<BaseChatModel>;
4
- export declare function init(configFileName: string): void;
4
+ export declare function init(configFileName: string, force?: boolean, model?: string): void;
@@ -1,6 +1,7 @@
1
1
  import { displayWarning } from '#src/utils/consoleUtils.js';
2
2
  import { env } from '#src/utils/systemUtils.js';
3
- import { writeFileIfNotExistsWithMessages } from '#src/utils/fileUtils.js';
3
+ import { writeConfigFileWithMessages } from '#src/utils/fileUtils.js';
4
+ import { buildInitConfigContent, getCuratedFallbackModel } from '#src/providers/modelDiscovery.js';
4
5
  // Function to process JSON config and create XAI LLM instance
5
6
  export async function processJsonConfig(llmConfig) {
6
7
  const { ChatXAI } = await import('@langchain/xai');
@@ -9,21 +10,15 @@ export async function processJsonConfig(llmConfig) {
9
10
  return new ChatXAI({
10
11
  ...llmConfig,
11
12
  apiKey,
12
- model: llmConfig.model || 'grok-4.3',
13
+ model: llmConfig.model || getCuratedFallbackModel('xai'),
13
14
  });
14
15
  }
15
- const jsonContent = `{
16
- "llm": {
17
- "type": "xai",
18
- "model": "grok-4.3"
19
- }
20
- }`;
21
- export function init(configFileName) {
16
+ export function init(configFileName, force = false, model) {
22
17
  // Determine which content to use based on file extension
23
18
  if (!configFileName.endsWith('.json')) {
24
19
  throw new Error('Only JSON config is supported.');
25
20
  }
26
- writeFileIfNotExistsWithMessages(configFileName, jsonContent);
21
+ writeConfigFileWithMessages(configFileName, buildInitConfigContent('xai', model), force);
27
22
  displayWarning(`You need to update your ${configFileName} to add your xAI API key, ` +
28
23
  'or define XAI_API_KEY environment variable.');
29
24
  }
@@ -1 +1 @@
1
- {"version":3,"file":"xai.js","sourceRoot":"","sources":["../../src/providers/xai.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAC5D,OAAO,EAAE,GAAG,EAAE,MAAM,2BAA2B,CAAC;AAOhD,OAAO,EAAE,gCAAgC,EAAE,MAAM,yBAAyB,CAAC;AAE3E,8DAA8D;AAC9D,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,SAA6C;IAE7C,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,MAAM,CAAC,gBAAgB,CAAC,CAAC;IACnD,wEAAwE;IACxE,MAAM,MAAM,GAAG,SAAS,CAAC,MAAM,IAAI,GAAG,CAAC,WAAW,CAAC;IACnD,OAAO,IAAI,OAAO,CAAC;QACjB,GAAG,SAAS;QACZ,MAAM;QACN,KAAK,EAAE,SAAS,CAAC,KAAK,IAAI,UAAU;KACrC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,WAAW,GAAG;;;;;EAKlB,CAAC;AAEH,MAAM,UAAU,IAAI,CAAC,cAAsB;IACzC,yDAAyD;IACzD,IAAI,CAAC,cAAc,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;QACtC,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAC;IACpD,CAAC;IAED,gCAAgC,CAAC,cAAc,EAAE,WAAW,CAAC,CAAC;IAC9D,cAAc,CACZ,2BAA2B,cAAc,4BAA4B;QACnE,6CAA6C,CAChD,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"xai.js","sourceRoot":"","sources":["../../src/providers/xai.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAC5D,OAAO,EAAE,GAAG,EAAE,MAAM,2BAA2B,CAAC;AAOhD,OAAO,EAAE,2BAA2B,EAAE,MAAM,yBAAyB,CAAC;AACtE,OAAO,EAAE,sBAAsB,EAAE,uBAAuB,EAAE,MAAM,kCAAkC,CAAC;AAEnG,8DAA8D;AAC9D,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,SAA6C;IAE7C,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,MAAM,CAAC,gBAAgB,CAAC,CAAC;IACnD,wEAAwE;IACxE,MAAM,MAAM,GAAG,SAAS,CAAC,MAAM,IAAI,GAAG,CAAC,WAAW,CAAC;IACnD,OAAO,IAAI,OAAO,CAAC;QACjB,GAAG,SAAS;QACZ,MAAM;QACN,KAAK,EAAE,SAAS,CAAC,KAAK,IAAI,uBAAuB,CAAC,KAAK,CAAC;KACzD,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,IAAI,CAAC,cAAsB,EAAE,KAAK,GAAG,KAAK,EAAE,KAAc;IACxE,yDAAyD;IACzD,IAAI,CAAC,cAAc,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;QACtC,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAC;IACpD,CAAC;IAED,2BAA2B,CAAC,cAAc,EAAE,sBAAsB,CAAC,KAAK,EAAE,KAAK,CAAC,EAAE,KAAK,CAAC,CAAC;IACzF,cAAc,CACZ,2BAA2B,cAAc,4BAA4B;QACnE,6CAA6C,CAChD,CAAC;AACJ,CAAC"}
@@ -0,0 +1,105 @@
1
+ /**
2
+ * @module runtime/askStructured
3
+ *
4
+ * A reusable, non-agentic "ask the LLM and get a schema-validated object back" primitive — the
5
+ * structured-output half the (later) `gth workflow` host calls. Deliberately mirrors the
6
+ * *mechanism* of the structured-evaluation calls ({@link judgeEvalCase} — the EVAL GRADER in
7
+ * `@gaunt-sloth/batch`'s `judge.ts` — and its in-core sibling {@link rateShellCommand}, the
8
+ * approvals AI rater in `core/shell/rater.ts`):
9
+ * `model.withStructuredOutput(schema)` for a single structured call, `.invoke([SystemMessage,
10
+ * HumanMessage])` raced against a wall-clock timeout via `Promise.race`, a defensive `safeParse`
11
+ * re-validation, `clearTimeout` in `finally`, and — crucially — it **never throws**, returning a
12
+ * failure object instead.
13
+ *
14
+ * Differences from those two: this one is **generic** over the Zod schema and takes the
15
+ * system/user strings from the caller (they hard-code a schema and build a rubric/safety
16
+ * prompt), and it reads the model from `config.llm` (like `runSingleShot`/`rateShellCommand`),
17
+ * so the workflow host can hand it the resolved {@link GthConfig} directly. `judgeEvalCase` could
18
+ * later be refactored to delegate to this primitive — out of scope here.
19
+ */
20
+ import * as z from 'zod';
21
+ import type { GthConfig } from '#src/config.js';
22
+ /**
23
+ * Default wall-clock budget (ms) for the structured LLM call — same value as their
24
+ * `EVAL_JUDGE_DEFAULT_TIMEOUT_MS` / `JUDGE_DEFAULT_TIMEOUT_MS`, kept as this module's own constant
25
+ * since the primitive is conceptually independent of them.
26
+ */
27
+ export declare const ASK_STRUCTURED_DEFAULT_TIMEOUT_MS = 30000;
28
+ /**
29
+ * The exact `error` text {@link askStructured} returns when its wall-clock budget fires — i.e. the
30
+ * provider produced **no answer at all** within the budget.
31
+ *
32
+ * Exported as a builder rather than left as an inline literal because the three failure classes here
33
+ * (no response · an answer that does not fit the schema · a call that failed outright) are the
34
+ * question a reader of a red run has to answer first, and they are only distinguishable by this text.
35
+ * A caller that re-types the sentence to recognise it stops recognising it the day the wording moves,
36
+ * and silently re-files a stall as a rejection — which is precisely the confusion this exists to end.
37
+ */
38
+ export declare function structuredCallTimedOutError(timeoutMs: number): string;
39
+ /**
40
+ * The exact `error` text {@link askStructured} returns when the provider **did** answer and the
41
+ * answer failed the caller's own schema. The counterpart of {@link structuredCallTimedOutError};
42
+ * see that doc for why both are exported rather than written inline.
43
+ */
44
+ export declare const STRUCTURED_CALL_UNPARSEABLE_ERROR = "Model returned unparseable output.";
45
+ /**
46
+ * Which of {@link askStructured}'s three failure paths a result came from.
47
+ *
48
+ * - `timeout` — the budget fired and the provider had returned nothing at all.
49
+ * - `unparseable` — the provider answered, and the answer failed the caller's schema.
50
+ * - `call-failed` — the call threw before any answer arrived: a provider rejection (OpenAI's 400 on
51
+ * a strict `json_schema` is the canonical one), an auth failure, a transport failure.
52
+ */
53
+ export type StructuredFailureKind = 'timeout' | 'unparseable' | 'call-failed';
54
+ /**
55
+ * Name which failure happened, given the `error` string and the budget the call was made with.
56
+ *
57
+ * The single place the three classes are told apart. A stall and a rejection are the two failures a
58
+ * reader has to distinguish first and the two that look most alike from the outside — a red that
59
+ * cannot say which happened sends the reader to re-run a real defect, or to investigate a provider
60
+ * hiccup. Every caller that wants to say which one it was calls this rather than matching the prose
61
+ * itself, so the wording can only be got wrong in one place, and that place has a test.
62
+ *
63
+ * @param error The `error` from a `{ ok: false }` {@link AskStructuredResult}.
64
+ * @param timeoutMs The budget passed to that same {@link askStructured} call — the timeout text
65
+ * carries it, so a different value here reports a genuine timeout as `call-failed`.
66
+ */
67
+ export declare function classifyStructuredFailure(error: string, timeoutMs: number): StructuredFailureKind;
68
+ /** Inputs to {@link askStructured}. The caller supplies the model (via config), the two message
69
+ * texts, and an optional timeout — the Zod schema is a separate positional argument so `<T>` can
70
+ * be inferred from it. */
71
+ export interface AskStructuredOptions {
72
+ config: GthConfig;
73
+ /** System-message text (instructions). May be empty. */
74
+ system: string;
75
+ /** Human-message text (the actual content/question). */
76
+ user: string;
77
+ /** Wall-clock budget in ms. Default {@link ASK_STRUCTURED_DEFAULT_TIMEOUT_MS} (30_000). */
78
+ timeoutMs?: number;
79
+ }
80
+ /** Discriminated result of {@link askStructured}: the parsed value on success, an error string on
81
+ * any failure (unusable model, timeout, unparseable output, or a thrown error). Never throws. */
82
+ export type AskStructuredResult<T> = {
83
+ ok: true;
84
+ value: T;
85
+ } | {
86
+ ok: false;
87
+ error: string;
88
+ };
89
+ /**
90
+ * Ask the configured model for a single schema-validated object — a non-agentic structured-output
91
+ * call that mirrors their mechanism (see the module doc) and never throws.
92
+ *
93
+ * - No usable model (`config.llm` missing or lacking `withStructuredOutput`) →
94
+ * `{ ok: false, error: 'No usable model configured.' }`.
95
+ * - Timeout → `ok: false` with the error {@link structuredCallTimedOutError} builds for that budget.
96
+ * - Output that fails `schema.safeParse` → `{ ok: false, error: {@link STRUCTURED_CALL_UNPARSEABLE_ERROR} }`.
97
+ * - Any thrown error → `{ ok: false, error: <message> }` — a provider rejection (e.g. OpenAI's 400 on
98
+ * a strict `json_schema`), an auth or transport failure. Distinct from the two above BY CODE PATH,
99
+ * so a caller can classify a failure by comparing against those two exported texts.
100
+ * - Success → `{ ok: true, value }` with the parsed data.
101
+ *
102
+ * @param schema The Zod schema the model output must satisfy; `<T>` is inferred from it.
103
+ * @param opts The model (via `config.llm`), the system/user message texts, and an optional timeout.
104
+ */
105
+ export declare function askStructured<T>(schema: z.ZodType<T>, opts: AskStructuredOptions): Promise<AskStructuredResult<T>>;