@gaunt-sloth/core 2.0.0-alpha.9 → 2.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (275) hide show
  1. package/.gsloth.review.md +2 -0
  2. package/README.md +71 -20
  3. package/dist/config/colour.d.ts +38 -0
  4. package/dist/config/colour.js +36 -0
  5. package/dist/config/colour.js.map +1 -0
  6. package/dist/config/configDiscovery.d.ts +79 -0
  7. package/dist/config/configDiscovery.js +80 -0
  8. package/dist/config/configDiscovery.js.map +1 -0
  9. package/dist/config/defaults.d.ts +20 -20
  10. package/dist/config/defaults.js +10 -8
  11. package/dist/config/defaults.js.map +1 -1
  12. package/dist/config/filesystem-tools.d.ts +41 -0
  13. package/dist/config/filesystem-tools.js +56 -0
  14. package/dist/config/filesystem-tools.js.map +1 -0
  15. package/dist/config/loader.d.ts +171 -19
  16. package/dist/config/loader.js +1054 -144
  17. package/dist/config/loader.js.map +1 -1
  18. package/dist/config/mouse.d.ts +50 -0
  19. package/dist/config/mouse.js +44 -0
  20. package/dist/config/mouse.js.map +1 -0
  21. package/dist/config/profiles.d.ts +68 -0
  22. package/dist/config/profiles.js +93 -0
  23. package/dist/config/profiles.js.map +1 -0
  24. package/dist/config/providerKeys.d.ts +69 -0
  25. package/dist/config/providerKeys.js +69 -0
  26. package/dist/config/providerKeys.js.map +1 -0
  27. package/dist/config/schema.d.ts +2671 -138
  28. package/dist/config/schema.js +1361 -85
  29. package/dist/config/schema.js.map +1 -1
  30. package/dist/config/shell-policy.d.ts +899 -111
  31. package/dist/config/shell-policy.js +800 -70
  32. package/dist/config/shell-policy.js.map +1 -1
  33. package/dist/config/tool-descriptions.d.ts +211 -0
  34. package/dist/config/tool-descriptions.js +272 -0
  35. package/dist/config/tool-descriptions.js.map +1 -0
  36. package/dist/config/types.d.ts +352 -41
  37. package/dist/config/types.js +1 -0
  38. package/dist/config/types.js.map +1 -1
  39. package/dist/config.d.ts +35 -1
  40. package/dist/config.js +16 -1
  41. package/dist/config.js.map +1 -1
  42. package/dist/constants.d.ts +28 -1
  43. package/dist/constants.js +28 -1
  44. package/dist/constants.js.map +1 -1
  45. package/dist/core/GthAbstractAgent.d.ts +166 -11
  46. package/dist/core/GthAbstractAgent.js +484 -45
  47. package/dist/core/GthAbstractAgent.js.map +1 -1
  48. package/dist/core/GthAgentRunner.d.ts +543 -57
  49. package/dist/core/GthAgentRunner.js +1494 -140
  50. package/dist/core/GthAgentRunner.js.map +1 -1
  51. package/dist/core/GthLangChainAgent.d.ts +117 -2
  52. package/dist/core/GthLangChainAgent.js +602 -29
  53. package/dist/core/GthLangChainAgent.js.map +1 -1
  54. package/dist/core/approvals/annotations.d.ts +122 -0
  55. package/dist/core/approvals/annotations.js +137 -0
  56. package/dist/core/approvals/annotations.js.map +1 -0
  57. package/dist/core/approvals/grants.d.ts +216 -0
  58. package/dist/core/approvals/grants.js +469 -0
  59. package/dist/core/approvals/grants.js.map +1 -0
  60. package/dist/core/approvals/matcher.d.ts +202 -0
  61. package/dist/core/approvals/matcher.js +267 -0
  62. package/dist/core/approvals/matcher.js.map +1 -0
  63. package/dist/core/approvals/mcpSubjects.d.ts +40 -0
  64. package/dist/core/approvals/mcpSubjects.js +99 -0
  65. package/dist/core/approvals/mcpSubjects.js.map +1 -0
  66. package/dist/core/approvals/promptHeader.d.ts +28 -0
  67. package/dist/core/approvals/promptHeader.js +62 -0
  68. package/dist/core/approvals/promptHeader.js.map +1 -0
  69. package/dist/core/approvals/toolAnnotationSources.d.ts +105 -0
  70. package/dist/core/approvals/toolAnnotationSources.js +277 -0
  71. package/dist/core/approvals/toolAnnotationSources.js.map +1 -0
  72. package/dist/core/approvals/toolHost.d.ts +46 -0
  73. package/dist/core/approvals/toolHost.js +108 -0
  74. package/dist/core/approvals/toolHost.js.map +1 -0
  75. package/dist/core/debugCapture.d.ts +74 -0
  76. package/dist/core/debugCapture.js +100 -0
  77. package/dist/core/debugCapture.js.map +1 -0
  78. package/dist/core/gthLeanAgentFactory.d.ts +4 -4
  79. package/dist/core/gthLeanAgentFactory.js +4 -4
  80. package/dist/core/launchBanner.d.ts +127 -0
  81. package/dist/core/launchBanner.js +414 -0
  82. package/dist/core/launchBanner.js.map +1 -0
  83. package/dist/core/modelLabel.d.ts +19 -0
  84. package/dist/core/modelLabel.js +26 -0
  85. package/dist/core/modelLabel.js.map +1 -0
  86. package/dist/core/plainToolIndication.d.ts +15 -0
  87. package/dist/core/plainToolIndication.js +174 -0
  88. package/dist/core/plainToolIndication.js.map +1 -0
  89. package/dist/core/reasoningBlocks.d.ts +65 -0
  90. package/dist/core/reasoningBlocks.js +103 -0
  91. package/dist/core/reasoningBlocks.js.map +1 -0
  92. package/dist/core/refusal.d.ts +53 -0
  93. package/dist/core/refusal.js +133 -0
  94. package/dist/core/refusal.js.map +1 -0
  95. package/dist/core/runHeader.d.ts +38 -0
  96. package/dist/core/runHeader.js +42 -0
  97. package/dist/core/runHeader.js.map +1 -0
  98. package/dist/core/runStats.d.ts +14 -3
  99. package/dist/core/runStats.js +48 -3
  100. package/dist/core/runStats.js.map +1 -1
  101. package/dist/core/shell/ShellCommandFailedError.d.ts +3 -4
  102. package/dist/core/shell/ShellCommandFailedError.js +3 -4
  103. package/dist/core/shell/ShellCommandFailedError.js.map +1 -1
  104. package/dist/core/shell/abstention.d.ts +88 -0
  105. package/dist/core/shell/abstention.js +184 -0
  106. package/dist/core/shell/abstention.js.map +1 -0
  107. package/dist/core/shell/approvalCapture.d.ts +271 -0
  108. package/dist/core/shell/approvalCapture.js +108 -0
  109. package/dist/core/shell/approvalCapture.js.map +1 -0
  110. package/dist/core/shell/approvalStop.d.ts +123 -0
  111. package/dist/core/shell/approvalStop.js +269 -0
  112. package/dist/core/shell/approvalStop.js.map +1 -0
  113. package/dist/core/shell/arity.d.ts +6 -0
  114. package/dist/core/shell/arity.js +20 -6
  115. package/dist/core/shell/arity.js.map +1 -1
  116. package/dist/core/shell/denylist.d.ts +11 -0
  117. package/dist/core/shell/denylist.js +37 -0
  118. package/dist/core/shell/denylist.js.map +1 -0
  119. package/dist/core/shell/escalationSeverity.d.ts +141 -0
  120. package/dist/core/shell/escalationSeverity.js +89 -0
  121. package/dist/core/shell/escalationSeverity.js.map +1 -0
  122. package/dist/core/shell/framing.d.ts +190 -0
  123. package/dist/core/shell/framing.js +633 -0
  124. package/dist/core/shell/framing.js.map +1 -0
  125. package/dist/core/shell/hardline.d.ts +103 -0
  126. package/dist/core/shell/hardline.js +780 -0
  127. package/dist/core/shell/hardline.js.map +1 -0
  128. package/dist/core/shell/negotiation.d.ts +328 -0
  129. package/dist/core/shell/negotiation.js +488 -0
  130. package/dist/core/shell/negotiation.js.map +1 -0
  131. package/dist/core/shell/normalize.d.ts +44 -4
  132. package/dist/core/shell/normalize.js +61 -7
  133. package/dist/core/shell/normalize.js.map +1 -1
  134. package/dist/core/shell/openWorld.d.ts +263 -0
  135. package/dist/core/shell/openWorld.js +1188 -0
  136. package/dist/core/shell/openWorld.js.map +1 -0
  137. package/dist/core/shell/rater.d.ts +873 -0
  138. package/dist/core/shell/rater.js +1454 -0
  139. package/dist/core/shell/rater.js.map +1 -0
  140. package/dist/core/shell/raterModel.d.ts +41 -0
  141. package/dist/core/shell/raterModel.js +51 -0
  142. package/dist/core/shell/raterModel.js.map +1 -0
  143. package/dist/core/shell/raterVocabulary.d.ts +121 -0
  144. package/dist/core/shell/raterVocabulary.js +116 -0
  145. package/dist/core/shell/raterVocabulary.js.map +1 -0
  146. package/dist/core/shell/rejection.d.ts +69 -0
  147. package/dist/core/shell/rejection.js +38 -0
  148. package/dist/core/shell/rejection.js.map +1 -0
  149. package/dist/core/toolCallRepair/grammar.d.ts +41 -0
  150. package/dist/core/toolCallRepair/grammar.js +116 -0
  151. package/dist/core/toolCallRepair/grammar.js.map +1 -0
  152. package/dist/core/toolCallRepair/index.d.ts +2 -0
  153. package/dist/core/toolCallRepair/index.js +7 -0
  154. package/dist/core/toolCallRepair/index.js.map +1 -0
  155. package/dist/core/toolCallRepair/payload.d.ts +36 -0
  156. package/dist/core/toolCallRepair/payload.js +341 -0
  157. package/dist/core/toolCallRepair/payload.js.map +1 -0
  158. package/dist/core/toolCallRepair/promote.d.ts +45 -0
  159. package/dist/core/toolCallRepair/promote.js +90 -0
  160. package/dist/core/toolCallRepair/promote.js.map +1 -0
  161. package/dist/core/toolDisplay.d.ts +123 -0
  162. package/dist/core/toolDisplay.js +451 -0
  163. package/dist/core/toolDisplay.js.map +1 -0
  164. package/dist/core/toolOutputChannel.d.ts +95 -0
  165. package/dist/core/toolOutputChannel.js +165 -0
  166. package/dist/core/toolOutputChannel.js.map +1 -0
  167. package/dist/core/types.d.ts +349 -16
  168. package/dist/core/types.js.map +1 -1
  169. package/dist/history/historyFormat.d.ts +12 -3
  170. package/dist/history/historyFormat.js +50 -8
  171. package/dist/history/historyFormat.js.map +1 -1
  172. package/dist/history/historyStore.d.ts +77 -0
  173. package/dist/history/historyStore.js +173 -6
  174. package/dist/history/historyStore.js.map +1 -1
  175. package/dist/history/recordSession.d.ts +10 -1
  176. package/dist/history/recordSession.js +27 -0
  177. package/dist/history/recordSession.js.map +1 -1
  178. package/dist/providers/anthropic.js +12 -0
  179. package/dist/providers/anthropic.js.map +1 -1
  180. package/dist/providers/configurationPassthrough.d.ts +107 -0
  181. package/dist/providers/configurationPassthrough.js +148 -0
  182. package/dist/providers/configurationPassthrough.js.map +1 -0
  183. package/dist/providers/geminiSchemaSanitizer.d.ts +55 -0
  184. package/dist/providers/geminiSchemaSanitizer.js +347 -0
  185. package/dist/providers/geminiSchemaSanitizer.js.map +1 -0
  186. package/dist/providers/geminiThinking.d.ts +60 -0
  187. package/dist/providers/geminiThinking.js +92 -0
  188. package/dist/providers/geminiThinking.js.map +1 -0
  189. package/dist/providers/google-genai.js +18 -1
  190. package/dist/providers/google-genai.js.map +1 -1
  191. package/dist/providers/groq.js +12 -0
  192. package/dist/providers/groq.js.map +1 -1
  193. package/dist/providers/huggingface.d.ts +25 -0
  194. package/dist/providers/huggingface.js +69 -0
  195. package/dist/providers/huggingface.js.map +1 -0
  196. package/dist/providers/modelCatalog.d.ts +109 -0
  197. package/dist/providers/modelCatalog.js +245 -0
  198. package/dist/providers/modelCatalog.js.map +1 -0
  199. package/dist/providers/modelDiscovery.d.ts +99 -5
  200. package/dist/providers/modelDiscovery.js +191 -35
  201. package/dist/providers/modelDiscovery.js.map +1 -1
  202. package/dist/providers/ollama.d.ts +18 -4
  203. package/dist/providers/ollama.js +67 -37
  204. package/dist/providers/ollama.js.map +1 -1
  205. package/dist/providers/openai.js +34 -0
  206. package/dist/providers/openai.js.map +1 -1
  207. package/dist/providers/openrouter.d.ts +26 -4
  208. package/dist/providers/openrouter.js +83 -26
  209. package/dist/providers/openrouter.js.map +1 -1
  210. package/dist/providers/vertexai.js +19 -1
  211. package/dist/providers/vertexai.js.map +1 -1
  212. package/dist/providers/xai.js +20 -0
  213. package/dist/providers/xai.js.map +1 -1
  214. package/dist/runtime/askStructured.d.ts +105 -0
  215. package/dist/runtime/askStructured.js +120 -0
  216. package/dist/runtime/askStructured.js.map +1 -0
  217. package/dist/runtime/conversation.d.ts +64 -0
  218. package/dist/runtime/conversation.js +171 -0
  219. package/dist/runtime/conversation.js.map +1 -0
  220. package/dist/runtime/singleShot.d.ts +37 -6
  221. package/dist/runtime/singleShot.js +113 -67
  222. package/dist/runtime/singleShot.js.map +1 -1
  223. package/dist/runtime/structuredOutput.d.ts +104 -0
  224. package/dist/runtime/structuredOutput.js +393 -0
  225. package/dist/runtime/structuredOutput.js.map +1 -0
  226. package/dist/utils/ProgressIndicator.d.ts +21 -0
  227. package/dist/utils/ProgressIndicator.js +30 -3
  228. package/dist/utils/ProgressIndicator.js.map +1 -1
  229. package/dist/utils/aiignoreUtils.js.map +1 -1
  230. package/dist/utils/binaryOutputUtils.js.map +1 -1
  231. package/dist/utils/consoleUtils.d.ts +95 -0
  232. package/dist/utils/consoleUtils.js +112 -2
  233. package/dist/utils/consoleUtils.js.map +1 -1
  234. package/dist/utils/crashHandler.d.ts +87 -0
  235. package/dist/utils/crashHandler.js +128 -0
  236. package/dist/utils/crashHandler.js.map +1 -0
  237. package/dist/utils/debugDump.d.ts +134 -0
  238. package/dist/utils/debugDump.js +381 -0
  239. package/dist/utils/debugDump.js.map +1 -0
  240. package/dist/utils/debugUtils.d.ts +13 -4
  241. package/dist/utils/debugUtils.js +36 -13
  242. package/dist/utils/debugUtils.js.map +1 -1
  243. package/dist/utils/displayWidth.d.ts +53 -0
  244. package/dist/utils/displayWidth.js +195 -0
  245. package/dist/utils/displayWidth.js.map +1 -0
  246. package/dist/utils/fileUtils.js.map +1 -1
  247. package/dist/utils/globalConfigUtils.d.ts +14 -2
  248. package/dist/utils/globalConfigUtils.js +22 -4
  249. package/dist/utils/globalConfigUtils.js.map +1 -1
  250. package/dist/utils/llmUtils.d.ts +39 -8
  251. package/dist/utils/llmUtils.js +76 -8
  252. package/dist/utils/llmUtils.js.map +1 -1
  253. package/dist/utils/redactSecrets.d.ts +63 -0
  254. package/dist/utils/redactSecrets.js +286 -0
  255. package/dist/utils/redactSecrets.js.map +1 -0
  256. package/dist/utils/systemPromptNotes.d.ts +222 -0
  257. package/dist/utils/systemPromptNotes.js +338 -0
  258. package/dist/utils/systemPromptNotes.js.map +1 -0
  259. package/dist/utils/systemUtils.d.ts +18 -1
  260. package/dist/utils/systemUtils.js +38 -3
  261. package/dist/utils/systemUtils.js.map +1 -1
  262. package/dist/utils/toolMatching.d.ts +30 -0
  263. package/dist/utils/toolMatching.js +44 -0
  264. package/dist/utils/toolMatching.js.map +1 -0
  265. package/dist/utils/untrustedText.d.ts +86 -0
  266. package/dist/utils/untrustedText.js +101 -0
  267. package/dist/utils/untrustedText.js.map +1 -0
  268. package/package.json +21 -7
  269. package/schema/gsloth-config.schema.json +1921 -326
  270. package/dist/core/shell/allowlist.d.ts +0 -75
  271. package/dist/core/shell/allowlist.js +0 -187
  272. package/dist/core/shell/allowlist.js.map +0 -1
  273. package/dist/core/shell/judge.d.ts +0 -161
  274. package/dist/core/shell/judge.js +0 -261
  275. package/dist/core/shell/judge.js.map +0 -1
@@ -2,59 +2,89 @@ import { displayWarning } from '#src/utils/consoleUtils.js';
2
2
  import { env } from '#src/utils/systemUtils.js';
3
3
  import { writeConfigFileWithMessages } from '#src/utils/fileUtils.js';
4
4
  import { buildInitConfigContent, getCuratedFallbackModel } from '#src/providers/modelDiscovery.js';
5
+ import { NATIVE_CLIENT_REASON, warnUnusedConfiguration, } from '#src/providers/configurationPassthrough.js';
5
6
  /**
6
- * Default Ollama daemon host, matching the Ollama CLI/library default. The
7
- * OpenAI-compatible surface lives under `/v1` on this host. Kept in sync with
8
- * `DEFAULT_OLLAMA_HOST` in `modelDiscovery.ts`.
7
+ * Default Ollama daemon host, matching the Ollama CLI/library default. GS2-59 — `ChatOllama` talks
8
+ * to the daemon's NATIVE endpoint (`/api/chat`), NOT the OpenAI-compatible `/v1` shim this provider
9
+ * used before. The same root host is reused by `modelDiscovery.ts`'s model picker, which still
10
+ * queries the `/v1/models` surface (discovery only); both derive from this root — they stay in sync
11
+ * at the host level even though the two use different endpoints under it.
9
12
  */
10
13
  const DEFAULT_OLLAMA_HOST = 'http://127.0.0.1:11434';
11
14
  /**
12
- * Ollama serves an unauthenticated local daemon, but `ChatOpenAI` requires a
13
- * non-empty `apiKey` string. Send a harmless placeholder so the client builds;
14
- * the local daemon ignores it.
15
+ * GS2-59 — default context window (`num_ctx`) for Ollama models. Ollama's OWN default is 4096, but
16
+ * gaunt-sloth's agentic prompt (system + full lean toolset + a tool result) already lands ~4000
17
+ * tokens; at 4096 a thinking model (e.g. gemma4:31b) spends its entire remaining budget on the
18
+ * reasoning field and emits EMPTY `content` on the turn after a tool executes — the GS2-59
19
+ * blank-answer regression. The previous `ChatOpenAI`→`/v1` path could not fix this because the
20
+ * OpenAI-compat shim IGNORES `num_ctx`; the native `/api/chat` path honors it.
21
+ *
22
+ * 16384 is chosen as the largest window that is BOTH safely above the ~4000-token starvation point
23
+ * (4× headroom for reasoning + a few tool results) AND fits constrained consumer VRAM: Ollama
24
+ * preallocates the KV cache at `num_ctx`, so on a box where a large model already spills partly to
25
+ * CPU (e.g. a 19GB model on ~18GB of GPU), a 32768 cache tips the GPU allocation into an
26
+ * out-of-memory error. 16384 was verified live to run the agentic tool→synthesis turn on such a
27
+ * box; 32768 OOM'd it. Overridable per config via `llm.numCtx` — raise it if you have the VRAM and
28
+ * run long sessions, lower it on very tight hardware. NOTE: a per-request `num_ctx` overrides the
29
+ * daemon's `OLLAMA_CONTEXT_LENGTH`, so a user who tuned their server window higher should set
30
+ * `llm.numCtx` to match rather than rely on the server default.
15
31
  */
16
- const OLLAMA_PLACEHOLDER_API_KEY = 'ollama';
32
+ const DEFAULT_OLLAMA_NUM_CTX = 16384;
17
33
  /**
18
- * Resolve the OpenAI-compatible base URL for the local Ollama daemon.
19
- *
20
- * Honors the `OLLAMA_HOST` env override (the same variable the Ollama CLI uses).
21
- * `OLLAMA_HOST` is typically a full URL (`http://127.0.0.1:11434`) or a bare
22
- * `host:port`; either form is normalized to a `http(s)://host[:port]/v1` base.
34
+ * Resolve the base URL for the local Ollama daemon — the NATIVE root (no `/v1` suffix; `ChatOllama`
35
+ * appends its own `/api/chat` path). Honors the `OLLAMA_HOST` env override (the same variable the
36
+ * Ollama CLI uses): a full URL (`http://127.0.0.1:11434`) or a bare `host:port` is normalized to
37
+ * `http(s)://host[:port]` with any trailing slash stripped.
23
38
  */
24
39
  function resolveBaseUrl() {
25
40
  const host = env.OLLAMA_HOST;
26
- let base;
27
- if (!host) {
28
- base = DEFAULT_OLLAMA_HOST;
29
- }
30
- else if (/^https?:\/\//.test(host)) {
31
- base = host.replace(/\/+$/, '');
32
- }
33
- else {
34
- base = `http://${host}`.replace(/\/+$/, '');
35
- }
36
- return `${base}/v1`;
41
+ if (!host)
42
+ return DEFAULT_OLLAMA_HOST;
43
+ const base = /^https?:\/\//.test(host) ? host : `http://${host}`;
44
+ return base.replace(/\/+$/, '');
37
45
  }
38
- // Function to process JSON config and create an Ollama (OpenAI-compatible) LLM instance
46
+ /**
47
+ * GS2-59 — build a native `ChatOllama` client (`@langchain/ollama`, talking to Ollama's
48
+ * `/api/chat`). This replaces the previous `ChatOpenAI`→`/v1` client, which had two fatal
49
+ * properties for local thinking models: it DROPPED the model's separate reasoning field (the
50
+ * completions converter discards it — `__includeRawResponse` is openrouter-only), and it IGNORED
51
+ * `num_ctx` so a large agentic prompt starved the answer to empty content. `ChatOllama` fixes both:
52
+ * it honors `numCtx`, and it surfaces the model's thinking in `additional_kwargs.reasoning_content`
53
+ * — the exact field the reasoning pipeline (`pickReasoningDelta`) already reads — so the `/reasoning`
54
+ * panel populates with no downstream change.
55
+ *
56
+ * Ollama is a local, unauthenticated daemon, so the old OpenAI-client knobs are gone (2.0 migration
57
+ * note): point elsewhere with `OLLAMA_HOST`; the previous `configuration.baseURL` / placeholder
58
+ * `apiKey` fields no longer apply. If a reverse proxy in front of Ollama needs auth, set `headers`
59
+ * in the config (passed straight through to `ChatOllama`).
60
+ */
39
61
  // noinspection JSUnusedGlobalSymbols
40
62
  export async function processJsonConfig(llmConfig) {
41
- const { ChatOpenAI } = await import('@langchain/openai');
42
- // Ollama is local and unauthenticated; ChatOpenAI still needs a non-empty key.
43
- const apiKey = llmConfig.apiKey || OLLAMA_PLACEHOLDER_API_KEY;
63
+ const { ChatOllama } = await import('@langchain/ollama');
44
64
  const configFields = {
45
65
  ...llmConfig,
46
- apiKey,
47
66
  model: llmConfig.model || getCuratedFallbackModel('ollama'),
48
- configuration: {
49
- baseURL: resolveBaseUrl(),
50
- ...(llmConfig.configuration || {}),
51
- },
67
+ // Precedence: an explicit config `baseUrl` (the native ChatOllama field) wins, else the
68
+ // `OLLAMA_HOST` env, else the local default. Config is the more specific/intentional signal.
69
+ baseUrl: llmConfig.baseUrl ?? resolveBaseUrl(),
70
+ numCtx: llmConfig.numCtx ?? DEFAULT_OLLAMA_NUM_CTX,
52
71
  };
53
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
54
- delete configFields.type;
55
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
56
- delete configFields.apiKeyEnvironmentVariable;
57
- return new ChatOpenAI(configFields);
72
+ // `ChatOllama` is a native client, so NOTHING in a `configuration` block reaches it — say so
73
+ // before dropping it, rather than letting a stale config behave differently than it reads.
74
+ warnUnusedConfiguration({
75
+ provider: 'ollama',
76
+ configuration: configFields.configuration,
77
+ consumedPaths: [],
78
+ reason: NATIVE_CLIENT_REASON,
79
+ guidance: 'Ollama runs as a local, unauthenticated daemon: point elsewhere with the OLLAMA_HOST ' +
80
+ 'environment variable or the "baseUrl" field of the "llm" block, and set "headers" there ' +
81
+ 'if a reverse proxy in front of it needs auth.',
82
+ });
83
+ // Strip OpenAI-client / gaunt-sloth-internal keys ChatOllama neither needs nor understands.
84
+ for (const key of ['type', 'apiKeyEnvironmentVariable', 'apiKey', 'configuration']) {
85
+ delete configFields[key];
86
+ }
87
+ return new ChatOllama(configFields);
58
88
  }
59
89
  export function init(configFileName, force = false, model) {
60
90
  // Determine which content to use based on file extension
@@ -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,2BAA2B,EAAE,MAAM,yBAAyB,CAAC;AACtE,OAAO,EAAE,sBAAsB,EAAE,uBAAuB,EAAE,MAAM,kCAAkC,CAAC;AAEnG;;;;GAIG;AACH,MAAM,mBAAmB,GAAG,wBAAwB,CAAC;AAErD;;;;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,uBAAuB,CAAC,QAAQ,CAAC;QAC3D,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,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"}
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;AACnG,OAAO,EACL,oBAAoB,EACpB,uBAAuB,GACxB,MAAM,4CAA4C,CAAC;AAEpD;;;;;;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,6FAA6F;IAC7F,2FAA2F;IAC3F,uBAAuB,CAAC;QACtB,QAAQ,EAAE,QAAQ;QAClB,aAAa,EAAE,YAAY,CAAC,aAAa;QACzC,aAAa,EAAE,EAAE;QACjB,MAAM,EAAE,oBAAoB;QAC5B,QAAQ,EACN,uFAAuF;YACvF,0FAA0F;YAC1F,+CAA+C;KAClD,CAAC,CAAC;IACH,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"}
@@ -16,6 +16,35 @@ const SUPPORTED_DEFAULT_TEMPERATURE = 1;
16
16
  function isTemperatureRestrictedModel(model) {
17
17
  return !!model && TEMPERATURE_RESTRICTED_MODEL.test(model);
18
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
+ }
19
48
  // Function to process JSON config and create OpenAI LLM instance
20
49
  // noinspection JSUnusedGlobalSymbols
21
50
  export async function processJsonConfig(llmConfig) {
@@ -43,6 +72,11 @@ export async function processJsonConfig(llmConfig) {
43
72
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
44
73
  delete configFields.temperature;
45
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
+ }
46
80
  return new ChatOpenAI(configFields);
47
81
  }
48
82
  function getApiKey(llmConfig) {
@@ -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,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,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,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
+ {"version":3,"file":"openai.js","sourceRoot":"","sources":["../../src/providers/openai.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,27 @@
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>;
1
+ import type { BaseChatModel, BaseChatModelParams } from '@langchain/core/language_models/chat_models';
2
+ import type { ChatOpenRouterInput } from '@langchain/openrouter';
3
+ /**
4
+ * Build a native `ChatOpenRouter` (`@langchain/openrouter`). It is NOT a `ChatOpenAI` subclass: it
5
+ * talks to the OpenRouter REST API through the global `fetch` with no SDK client in between, and
6
+ * `_llmType()` reports `openrouter`.
7
+ *
8
+ * OpenRouter's own options are therefore native TOP-LEVEL fields of the `llm` block, forwarded by
9
+ * the `...restConfig` spread below: `provider` (routing preferences), `models` (the fallback list),
10
+ * `route`, `plugins`, `transforms`, `trace`, `minP`, `topA`, `repetitionPenalty`, `seed`,
11
+ * `logitBias`, `topLogprobs`, `sessionId`. Write them beside `model`, not under `configuration`.
12
+ *
13
+ * Migration note — the OpenAI-client knobs a `configuration` block used to carry are gone, because
14
+ * there is no client to give them to. `configuration.baseURL` still works (mapped to the native
15
+ * `baseURL`), and the two attribution headers are still honoured as a fallback for
16
+ * `siteUrl`/`siteName`. What has no injection point at all is transport: `fetch`, `fetchOptions`,
17
+ * `timeout`, `maxRetries`, `defaultQuery`, `dangerouslyAllowBrowser`, and any other
18
+ * `defaultHeaders` entry. That is an upstream surface gap, not something this module can restore —
19
+ * per-provider transport would need a `fetch` option added to `@langchain/openrouter`.
20
+ *
21
+ * The replacement is PROCESS-WIDE rather than per-provider, and it is verified working: run node
22
+ * with `--use-env-proxy` (or set `NODE_USE_ENV_PROXY=1`) together with `HTTP_PROXY`/`HTTPS_PROXY`,
23
+ * and the global `fetch` this model calls routes through the proxy — measured end to end against a
24
+ * local proxy, with a no-proxy control that stayed direct.
25
+ */
26
+ export declare function processJsonConfig(llmConfig: ChatOpenRouterInput & BaseChatModelParams & Record<string, unknown>): Promise<BaseChatModel>;
5
27
  export declare function init(configFileName: string, force?: boolean, model?: string): void;
@@ -2,43 +2,100 @@ import { displayWarning } from '#src/utils/consoleUtils.js';
2
2
  import { env } from '#src/utils/systemUtils.js';
3
3
  import { writeConfigFileWithMessages } from '#src/utils/fileUtils.js';
4
4
  import { buildInitConfigContent, getCuratedFallbackModel } from '#src/providers/modelDiscovery.js';
5
- // Function to process JSON config and create OpenRouter LLM instance
5
+ import { NATIVE_CLIENT_REASON, warnConfigurationOverridesTopLevelField, warnUnappliedConfigurationPath, warnUnusedConfiguration, } from '#src/providers/configurationPassthrough.js';
6
+ /**
7
+ * The OpenRouter attribution headers, and the native `ChatOpenRouter` field each one is set from.
8
+ * Declared once because they are read in two places that MUST agree: the `siteUrl`/`siteName`
9
+ * fallback below, and the list of `configuration` paths this factory consumes — if those drift, the
10
+ * orphaned-config warning starts firing on a header that is in fact honoured.
11
+ */
12
+ const ATTRIBUTION_HEADERS = { siteUrl: 'HTTP-Referer', siteName: 'X-Title' };
13
+ /**
14
+ * The only paths inside a user's `configuration` block that this factory reads. Everything else in
15
+ * it reaches nothing — see the migration note on {@link processJsonConfig}.
16
+ */
17
+ const CONSUMED_CONFIGURATION_PATHS = [
18
+ 'baseURL',
19
+ `defaultHeaders.${ATTRIBUTION_HEADERS.siteUrl}`,
20
+ `defaultHeaders.${ATTRIBUTION_HEADERS.siteName}`,
21
+ ];
22
+ /**
23
+ * Build a native `ChatOpenRouter` (`@langchain/openrouter`). It is NOT a `ChatOpenAI` subclass: it
24
+ * talks to the OpenRouter REST API through the global `fetch` with no SDK client in between, and
25
+ * `_llmType()` reports `openrouter`.
26
+ *
27
+ * OpenRouter's own options are therefore native TOP-LEVEL fields of the `llm` block, forwarded by
28
+ * the `...restConfig` spread below: `provider` (routing preferences), `models` (the fallback list),
29
+ * `route`, `plugins`, `transforms`, `trace`, `minP`, `topA`, `repetitionPenalty`, `seed`,
30
+ * `logitBias`, `topLogprobs`, `sessionId`. Write them beside `model`, not under `configuration`.
31
+ *
32
+ * Migration note — the OpenAI-client knobs a `configuration` block used to carry are gone, because
33
+ * there is no client to give them to. `configuration.baseURL` still works (mapped to the native
34
+ * `baseURL`), and the two attribution headers are still honoured as a fallback for
35
+ * `siteUrl`/`siteName`. What has no injection point at all is transport: `fetch`, `fetchOptions`,
36
+ * `timeout`, `maxRetries`, `defaultQuery`, `dangerouslyAllowBrowser`, and any other
37
+ * `defaultHeaders` entry. That is an upstream surface gap, not something this module can restore —
38
+ * per-provider transport would need a `fetch` option added to `@langchain/openrouter`.
39
+ *
40
+ * The replacement is PROCESS-WIDE rather than per-provider, and it is verified working: run node
41
+ * with `--use-env-proxy` (or set `NODE_USE_ENV_PROXY=1`) together with `HTTP_PROXY`/`HTTPS_PROXY`,
42
+ * and the global `fetch` this model calls routes through the proxy — measured end to end against a
43
+ * local proxy, with a no-proxy control that stayed direct.
44
+ */
6
45
  // noinspection JSUnusedGlobalSymbols
7
46
  export async function processJsonConfig(llmConfig) {
8
- const { ChatOpenAI } = await import('@langchain/openai');
9
- // Use environment variable if available, otherwise use the config value
47
+ const { ChatOpenRouter } = await import('@langchain/openrouter');
10
48
  const openRouterApiKey = getApiKey(llmConfig);
11
49
  if (!openRouterApiKey) {
12
50
  throw new Error('You need to define OPEN_ROUTER_API_KEY environment variable, or set apiKey in your config file.');
13
51
  }
14
- const configFields = {
15
- ...llmConfig,
52
+ const { type: _type, apiKeyEnvironmentVariable: _envVar, configuration, siteUrl, siteName, ...restConfig } = llmConfig;
53
+ const configObj = configuration;
54
+ // The one place that decides whether the user's `configuration.baseURL` is applied; the warning
55
+ // below reads THIS result instead of re-testing the value, so the guard and what the user is told
56
+ // cannot drift apart.
57
+ const baseURLOverride = configObj?.baseURL ? { baseURL: configObj.baseURL } : {};
58
+ warnUnusedConfiguration({
59
+ provider: 'openrouter',
60
+ configuration,
61
+ consumedPaths: CONSUMED_CONFIGURATION_PATHS,
62
+ reason: NATIVE_CLIENT_REASON,
63
+ // Scope the advice to the options that HAVE a top-level home: told plainly to "put it at the
64
+ // top level", a user with a transport key would move it from a warned location to an unwarned
65
+ // one, which is worse than where they started.
66
+ guidance: 'OpenRouter\'s own options are native top-level fields of the "llm" block — set "provider" ' +
67
+ '(routing preferences), "models", "route", "plugins" or "transforms" beside "model" rather ' +
68
+ 'than under "configuration". Transport settings (proxy, custom fetch, timeout, other ' +
69
+ 'headers) have no top-level field and no per-provider hook at all: run node with ' +
70
+ '--use-env-proxy (or set NODE_USE_ENV_PROXY=1) plus HTTP_PROXY/HTTPS_PROXY to route this ' +
71
+ 'provider through a proxy process-wide.',
72
+ });
73
+ warnUnappliedConfigurationPath('openrouter', configuration, 'baseURL', 'baseURL' in baseURLOverride, 'Give it the full base URL of the OpenRouter-compatible endpoint, or remove it to use the ' +
74
+ 'default https://openrouter.ai/api/v1.');
75
+ // `baseURL` is ALSO a native top-level field of `ChatOpenRouter`, forwarded by `...restConfig`
76
+ // below — and the block's value is spread after it, so it wins. Both are honoured surfaces, so
77
+ // the defect is only the silence: say which one takes effect rather than dropping the other
78
+ // without a word.
79
+ warnConfigurationOverridesTopLevelField('openrouter', 'baseURL', restConfig.baseURL, baseURLOverride.baseURL);
80
+ const resolvedSiteUrl = siteUrl ??
81
+ configObj?.defaultHeaders?.[ATTRIBUTION_HEADERS.siteUrl] ??
82
+ 'https://gauntsloth.app/';
83
+ const resolvedSiteName = siteName ?? configObj?.defaultHeaders?.[ATTRIBUTION_HEADERS.siteName] ?? 'Gaunt Sloth';
84
+ return new ChatOpenRouter({
85
+ ...restConfig,
16
86
  apiKey: openRouterApiKey,
17
87
  model: llmConfig.model || getCuratedFallbackModel('openrouter'),
18
- configuration: {
19
- baseURL: 'https://openrouter.ai/api/v1',
20
- ...(llmConfig.configuration || {}),
21
- defaultHeaders: {
22
- 'HTTP-Referer': 'https://gauntsloth.app/',
23
- 'X-Title': 'Gaunt Sloth',
24
- },
25
- },
26
- };
27
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
28
- delete configFields.type;
29
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
30
- delete configFields.apiKeyEnvironmentVariable;
31
- return new ChatOpenAI(configFields);
88
+ siteUrl: resolvedSiteUrl,
89
+ siteName: resolvedSiteName,
90
+ ...baseURLOverride,
91
+ });
32
92
  }
33
93
  function getApiKey(llmConfig) {
34
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
35
- const conf = llmConfig;
36
- if (conf.apiKeyEnvironmentVariable && env[conf.apiKeyEnvironmentVariable]) {
37
- return env[conf.apiKeyEnvironmentVariable];
38
- }
39
- else {
40
- return llmConfig.apiKey || env.OPEN_ROUTER_API_KEY || env.OPENROUTER_API_KEY;
94
+ const envVarName = llmConfig.apiKeyEnvironmentVariable;
95
+ if (envVarName && env[envVarName]) {
96
+ return env[envVarName];
41
97
  }
98
+ return (llmConfig.apiKey || env.OPEN_ROUTER_API_KEY || env.OPENROUTER_API_KEY);
42
99
  }
43
100
  export function init(configFileName, force = false, model) {
44
101
  // Determine which content to use based on file extension
@@ -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,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,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,uBAAuB,CAAC,YAAY,CAAC;QAC/D,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,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
+ {"version":3,"file":"openrouter.js","sourceRoot":"","sources":["../../src/providers/openrouter.ts"],"names":[],"mappings":"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"}
@@ -13,6 +13,9 @@
13
13
  import { displayWarning } from '#src/utils/consoleUtils.js';
14
14
  import { writeConfigFileWithMessages } from '#src/utils/fileUtils.js';
15
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
+ import { NATIVE_CLIENT_REASON, warnUnusedConfiguration, } from '#src/providers/configurationPassthrough.js';
16
19
  export function init(configFileName, force = false, model) {
17
20
  // Determine which content to use based on file extension
18
21
  if (!configFileName.endsWith('.json')) {
@@ -31,6 +34,21 @@ export async function processJsonConfig(llmConfig) {
31
34
  };
32
35
  delete configFields.type;
33
36
  delete configFields.apiKeyEnvironmentVariable;
34
- return new ChatGoogle(configFields);
37
+ // `ChatGoogle` is a native client for the Gemini API, so nothing in a `configuration` block
38
+ // reaches it — say so before dropping it.
39
+ warnUnusedConfiguration({
40
+ provider: 'vertexai',
41
+ configuration: llmConfig.configuration,
42
+ consumedPaths: [],
43
+ reason: NATIVE_CLIENT_REASON,
44
+ guidance: 'ChatGoogle talks to Vertex AI through its own client instead: set "customHeaders", ' +
45
+ '"endpoint", "apiVersion" or "location" as top-level fields of the "llm" block beside ' +
46
+ '"model".',
47
+ });
48
+ // GS2-58: normalise every tool's JSON-Schema at the ChatGoogle boundary so Gemini's OpenAPI-3.0
49
+ // subset accepts built-in, custom, and MCP tools alike (see geminiSchemaSanitizer).
50
+ // CFG-33: Vertex serves the same Gemini models through the same ChatGoogle class, so it has the
51
+ // same silently-discarded thinking; ask for the summaries here too (see geminiThinking).
52
+ return applyGeminiThoughtSummaries(applyGeminiToolSchemaSanitizer(new ChatGoogle(configFields)));
35
53
  }
36
54
  //# 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,2BAA2B,EAAE,MAAM,yBAAyB,CAAC;AACtE,OAAO,EAAE,sBAAsB,EAAE,uBAAuB,EAAE,MAAM,kCAAkC,CAAC;AAEnG,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,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;AAC/E,OAAO,EACL,oBAAoB,EACpB,uBAAuB,GACxB,MAAM,4CAA4C,CAAC;AAEpD,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,4FAA4F;IAC5F,0CAA0C;IAC1C,uBAAuB,CAAC;QACtB,QAAQ,EAAE,UAAU;QACpB,aAAa,EAAG,SAAyC,CAAC,aAAa;QACvE,aAAa,EAAE,EAAE;QACjB,MAAM,EAAE,oBAAoB;QAC5B,QAAQ,EACN,qFAAqF;YACrF,uFAAuF;YACvF,UAAU;KACb,CAAC,CAAC;IACH,gGAAgG;IAChG,oFAAoF;IACpF,gGAAgG;IAChG,yFAAyF;IACzF,OAAO,2BAA2B,CAAC,8BAA8B,CAAC,IAAI,UAAU,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;AACnG,CAAC"}
@@ -2,11 +2,31 @@ import { displayWarning } from '#src/utils/consoleUtils.js';
2
2
  import { env } from '#src/utils/systemUtils.js';
3
3
  import { writeConfigFileWithMessages } from '#src/utils/fileUtils.js';
4
4
  import { buildInitConfigContent, getCuratedFallbackModel } from '#src/providers/modelDiscovery.js';
5
+ import { warnUnusedConfiguration } from '#src/providers/configurationPassthrough.js';
5
6
  // Function to process JSON config and create XAI LLM instance
6
7
  export async function processJsonConfig(llmConfig) {
7
8
  const { ChatXAI } = await import('@langchain/xai');
8
9
  // Use config value if available, otherwise use the environment variable
9
10
  const apiKey = llmConfig.apiKey || env.XAI_API_KEY;
11
+ // `ChatXAI` DOES build an OpenAI client, and still consumes none of the user's block: its
12
+ // constructor passes `configuration: { baseURL: fields.baseURL ?? <xAI default> }` to `super`,
13
+ // replacing whatever was set here. So the reason clause is xai's own — the shared native-client
14
+ // sentence would be a false explanation — and the replacements are the top-level fields that were
15
+ // measured to reach the client: `baseURL` (a declared `ChatXAIInput` field) and `timeout` (read
16
+ // by the OpenAI base class from the same spread). Headers reach it by no route at all.
17
+ warnUnusedConfiguration({
18
+ provider: 'xai',
19
+ configuration: llmConfig.configuration,
20
+ consumedPaths: [],
21
+ reason: 'builds its OpenAI client with a "configuration" block of its own, replacing anything set here',
22
+ // The escape hatch is only followable with the key hint: the "openai" provider reads
23
+ // OPENAI_API_KEY, so a user who moves to it for the headers alone hits a missing-key error.
24
+ guidance: 'Set "baseURL" or "timeout" as top-level fields of the "llm" block instead, beside "model". ' +
25
+ 'Extra headers have no home on this provider at all — to send them, use the "openai" ' +
26
+ 'provider with "configuration.baseURL" pointed at the xAI endpoint, and set ' +
27
+ '"apiKeyEnvironmentVariable" to XAI_API_KEY there, since that provider otherwise reads ' +
28
+ 'OPENAI_API_KEY.',
29
+ });
10
30
  return new ChatXAI({
11
31
  ...llmConfig,
12
32
  apiKey,
@@ -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,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"}
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;AACnG,OAAO,EAAE,uBAAuB,EAAE,MAAM,4CAA4C,CAAC;AAErF,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,0FAA0F;IAC1F,+FAA+F;IAC/F,gGAAgG;IAChG,kGAAkG;IAClG,gGAAgG;IAChG,uFAAuF;IACvF,uBAAuB,CAAC;QACtB,QAAQ,EAAE,KAAK;QACf,aAAa,EAAG,SAAyC,CAAC,aAAa;QACvE,aAAa,EAAE,EAAE;QACjB,MAAM,EACJ,+FAA+F;QACjG,qFAAqF;QACrF,4FAA4F;QAC5F,QAAQ,EACN,6FAA6F;YAC7F,sFAAsF;YACtF,6EAA6E;YAC7E,wFAAwF;YACxF,iBAAiB;KACpB,CAAC,CAAC;IACH,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>>;