@gaunt-sloth/core 2.0.0-alpha.8 → 2.0.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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
@@ -0,0 +1,148 @@
1
+ import { displayWarning } from '#src/utils/consoleUtils.js';
2
+ /**
3
+ * `configuration` is `ChatOpenAI`'s own constructor field, which LangChain forwards to the OpenAI
4
+ * Node SDK's `ClientOptions`. It therefore only means anything for a provider that ends up handing
5
+ * the user's block to such a client: `openai` and `huggingface` build one directly, and `deepseek`
6
+ * spreads the user's block over its own defaults. For those, a block is a supported, working
7
+ * passthrough and MUST be left alone.
8
+ *
9
+ * **Descending from `ChatOpenAI` is NOT evidence of a passthrough — read the constructor.** `xai`
10
+ * descends from it and still consumes nothing: `ChatXAI` REPLACES `configuration` with a block of
11
+ * its own before calling `super`, so a timeout, headers, and even a base URL set there all reach
12
+ * nothing. Reading ancestry as consumption is exactly how a provider keeps its silence here, so
13
+ * classify a provider by what its constructor does with the block, never by what it extends.
14
+ *
15
+ * A provider on a NATIVE (non-OpenAI-SDK) client has nothing to hand the block to, so anything the
16
+ * factory does not read itself goes nowhere. `llmConfigSchema` is a `z.looseObject`, so an orphaned
17
+ * block also passes validation without a word — which is the 2.0 config policy's exact failure mode:
18
+ * a removed key is fine, a SILENTLY IGNORED one is not, because the config then behaves differently
19
+ * than it reads. This is the one place that turns that silence into a message, so a second native
20
+ * provider does not have to reinvent it.
21
+ *
22
+ * **Scope: paths INSIDE a `configuration` block, and nothing else.** A top-level key of the `llm`
23
+ * block that the chosen provider does not read — `llm.defaultHeaders` on `xai`, say — is the same
24
+ * silently-ignored-key defect, and this module does not cover it: the loose schema accepts it, no
25
+ * factory reads it, and nothing is printed. Guidance that sends a setting "to the top level" is
26
+ * therefore only safe for a field that provider actually has, which is why every `guidance` here
27
+ * names the replacement rather than a direction.
28
+ *
29
+ * Use it from a native-client factory only, and pass the paths that factory genuinely consumes.
30
+ */
31
+ /** A `configuration` sub-object is a plain record; anything else cannot carry consumable settings. */
32
+ function isPlainRecord(value) {
33
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
34
+ }
35
+ /**
36
+ * List the paths inside a `configuration` block that the calling factory will NOT read.
37
+ *
38
+ * `consumedPaths` entries are either a whole key (`baseURL`) or a dotted one-level path
39
+ * (`defaultHeaders.X-Title`). The dotted form matters because consumption is genuinely PARTIAL:
40
+ * openrouter reads two named attribution headers out of `defaultHeaders` and drops every other
41
+ * header in it. Reporting `defaultHeaders` as wholly consumed would silence the warning for
42
+ * exactly the case the user most needs it for — an auth header for a self-hosted gateway that is
43
+ * quietly discarded.
44
+ *
45
+ * A `configuration` that is not a record at all (`configuration: "https://x/v1"`, a plausible typo
46
+ * for the block) has no paths and is reported as nothing. That is NOT a silent drop: `llm.configuration`
47
+ * is `z.record` in `schema.ts`, and every config layer goes through the loader's `validateRawConfigLayer`,
48
+ * so a non-record value is a hard, path-scoped validation error that ends the run before any provider
49
+ * factory is reached. The schema is the gate for the shape; this function is the gate for the
50
+ * contents. `configurationPassthrough.spec.ts` pins that boundary — if the schema ever stops
51
+ * requiring a record, the case has to be handled here instead.
52
+ */
53
+ export function findUnusedConfigurationPaths(configuration, consumedPaths) {
54
+ if (!isPlainRecord(configuration))
55
+ return [];
56
+ const unused = [];
57
+ for (const [key, value] of Object.entries(configuration)) {
58
+ if (consumedPaths.includes(key))
59
+ continue;
60
+ const consumedSubKeys = consumedPaths
61
+ .filter((path) => path.startsWith(`${key}.`))
62
+ .map((path) => path.slice(key.length + 1));
63
+ if (consumedSubKeys.length > 0 && isPlainRecord(value)) {
64
+ for (const subKey of Object.keys(value)) {
65
+ if (!consumedSubKeys.includes(subKey))
66
+ unused.push(`${key}.${subKey}`);
67
+ }
68
+ continue;
69
+ }
70
+ unused.push(key);
71
+ }
72
+ return unused;
73
+ }
74
+ /**
75
+ * The `reason` for a provider built on a NATIVE client: there is no OpenAI client anywhere in the
76
+ * chain, so the block has nothing to be handed to.
77
+ *
78
+ * A shared clause is only safe while it is true of every caller that passes it, and nothing in the
79
+ * language can check that: the field takes any string, so a caller can pass this sentence — or
80
+ * another provider's — for a provider it is false of, and a warning that states a false reason is
81
+ * the same defect as the silence it replaces. Requiring the field stops one narrow version of that
82
+ * (a default nobody re-read against the new caller) and stops nothing else; copying the neighbouring
83
+ * call site is the likelier move and is exactly how a false clause would spread.
84
+ *
85
+ * What actually holds it is `configurationPassthrough.spec.ts`, which pins every warned provider's
86
+ * printed clause IN ITS SLOT beside that provider's own name. A clause that migrates to a provider
87
+ * it is false of reddens a cell, and editing THIS sentence reddens every caller that passes it —
88
+ * which is the point: a shared clause has to be re-checked against each of them.
89
+ */
90
+ export const NATIVE_CLIENT_REASON = 'does not build an OpenAI client, so a "configuration" block is not passed through to one';
91
+ export function warnUnusedConfiguration({ provider, configuration, consumedPaths, reason, guidance, }) {
92
+ const unused = findUnusedConfigurationPaths(configuration, consumedPaths);
93
+ if (unused.length === 0)
94
+ return;
95
+ displayWarning(`Ignoring ${unused.map((path) => `llm.configuration.${path}`).join(', ')} — ` +
96
+ `the "${provider}" provider ${reason}. ${guidance}`);
97
+ }
98
+ /**
99
+ * Warn when a `configuration` path the factory DOES consume silently beats a top-level field of the
100
+ * `llm` block that sets the same thing.
101
+ *
102
+ * Neither value is unusable here and neither location is wrong, so this is not a case for
103
+ * {@link warnUnusedConfiguration}: both are honoured surfaces, and the only defect is that one wins
104
+ * without saying so. The user's config then reads as two settings and behaves as one.
105
+ *
106
+ * Values are never printed — a base URL can carry credentials — so the message names the two paths
107
+ * and which of them takes effect.
108
+ *
109
+ * @param appliedValue The value the factory actually applied. Pass the result of the SAME expression
110
+ * that decides it, never a second copy of the test, for the reason given on
111
+ * {@link warnUnappliedConfigurationPath}.
112
+ */
113
+ export function warnConfigurationOverridesTopLevelField(provider, field, topLevelValue, appliedValue) {
114
+ // Nothing to lose: no top-level field set, or the block's value was not applied over it.
115
+ if (topLevelValue === undefined || topLevelValue === null || topLevelValue === '')
116
+ return;
117
+ if (appliedValue === undefined || appliedValue === null)
118
+ return;
119
+ // The same endpoint written twice is redundant, not a conflict, and warning on it would train
120
+ // users to ignore the message.
121
+ if (topLevelValue === appliedValue)
122
+ return;
123
+ displayWarning(`Ignoring llm.${field} — the "${provider}" provider also has llm.configuration.${field} set, ` +
124
+ `and that one takes precedence. Set only one of the two so the "llm" block reads the way it ` +
125
+ `behaves.`);
126
+ }
127
+ /**
128
+ * Warn when a path the factory DECLARES it consumes is present in the user's block but was not in
129
+ * fact applied — the empty string or `null` a factory's own guard skips.
130
+ *
131
+ * {@link findUnusedConfigurationPaths} cannot see this and must not: the path genuinely IS consumed,
132
+ * so it is correctly absent from the unused list — and the user's setting still goes nowhere.
133
+ * "Declared supported, in fact dropped" is the same silence this module exists to end, so it gets
134
+ * its own message: the "no OpenAI client" reason above would be the wrong reason for it.
135
+ *
136
+ * @param applied Whether the factory actually used the value. Pass the result of the SAME expression
137
+ * that decides it (`'baseURL' in baseURLOverride`), never a second copy of the test — a re-test is
138
+ * exactly how the guard and what the user is told drift apart.
139
+ */
140
+ export function warnUnappliedConfigurationPath(provider, configuration, path, applied, guidance) {
141
+ if (applied)
142
+ return;
143
+ if (!isPlainRecord(configuration) || !(path in configuration))
144
+ return;
145
+ displayWarning(`Ignoring llm.configuration.${path} — it is set for the "${provider}" provider but carries no ` +
146
+ `usable value, so it is not applied. ${guidance}`);
147
+ }
148
+ //# sourceMappingURL=configurationPassthrough.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"configurationPassthrough.js","sourceRoot":"","sources":["../../src/providers/configurationPassthrough.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,55 @@
1
+ /**
2
+ * @packageDocumentation
3
+ * GS2-58 — systemic Gemini tool-schema sanitizer at the `@langchain/google` provider boundary.
4
+ *
5
+ * Google Gemini's function-declaration schema is a SELECT SUBSET of OpenAPI 3.0. `@langchain/google`'s
6
+ * own `removeAdditionalProperties` strips only `additionalProperties`, so every other JSON-Schema-draft
7
+ * keyword the subset does not declare (`exclusiveMinimum`/`exclusiveMaximum`/`multipleOf`, `$defs`,
8
+ * `patternProperties`, `const`, `$ref`, `allOf`/`oneOf`/`not`, …) is passed straight to the wire, and
9
+ * Gemini 400s at tool-declaration send time — before any tool runs.
10
+ *
11
+ * This is the DURABLE fix (GS2-58, fix-cycle 1): rather than a denylist that is always one unknown
12
+ * keyword behind, {@link sanitizeGeminiToolSchema} is an ALLOWLIST — it keeps ONLY the fields the
13
+ * installed Gemini `Schema` type declares and drops everything else, so a future keyword in ANY tool
14
+ * (built-in, custom, or MCP — including schemas gaunt-sloth does not author) cannot re-break Gemini.
15
+ *
16
+ * The allowlist ({@link GEMINI_SUPPORTED_SCHEMA_KEYWORDS}) is derived DIRECTLY from the authoritative
17
+ * in-repo type — the `Gemini.Tools.Schema` interface in
18
+ * `node_modules/@langchain/google/dist/chat_models/api-types.d.{ts,cts}` (a `FunctionDeclaration`'s
19
+ * `parameters?: Schema`), "a select subset of an OpenAPI 3.0 schema object". Keeping it aligned with
20
+ * that type (not a remembered list) is what prevents a stale allowlist silently over-stripping.
21
+ *
22
+ * Scope is the google/gemini provider path ONLY: {@link applyGeminiToolSchemaSanitizer} is wired into
23
+ * the `google-genai` and `vertexai` presets' `processJsonConfig`. It leaves OpenAI/Anthropic/Ollama
24
+ * wiring untouched, and does not weaken the GS2-56/57 build-time denylist guard (that test still runs;
25
+ * this transform runs ahead of the wire send, so a sanitized tool is what Gemini sees).
26
+ */
27
+ import type { BaseChatModel } from '@langchain/core/language_models/chat_models';
28
+ /**
29
+ * The EXACT set of schema keywords Gemini's function-declaration schema accepts, transcribed field-
30
+ * for-field from the `Gemini.Tools.Schema` interface in
31
+ * `@langchain/google/dist/chat_models/api-types.d.ts`. Anything not in this set is dropped (allowlist).
32
+ *
33
+ * Note `anyOf` IS supported (unions / nullable) and MUST survive — only `allOf`/`oneOf`/`not` are
34
+ * absent from the type and therefore dropped. `exclusiveMinimum`/`exclusiveMaximum` are NOT in the type
35
+ * either; they are handled specially by rewriting them to `minimum`/`maximum` (see {@link sanitizeNode})
36
+ * before the allowlist filter runs.
37
+ */
38
+ export declare const GEMINI_SUPPORTED_SCHEMA_KEYWORDS: ReadonlySet<string>;
39
+ /**
40
+ * Pure, recursive normaliser: returns a cleaned DEEP COPY of a JSON-Schema containing only keywords
41
+ * Gemini's function-declaration `Schema` accepts, so its OpenAPI-3.0 subset accepts the tool. At each
42
+ * node the SAFE composition keywords are first RESOLVED into supported equivalents
43
+ * ({@link resolveComposition}: `const` → `enum`, clean `allOf` → shallow merge) and only then does the
44
+ * allowlist drop the rest. Does not mutate the input. `anyOf` unions survive; `$ref`/`oneOf`/`not` are
45
+ * dropped (see {@link resolveComposition} for why the last three are deferred, not resolved).
46
+ */
47
+ export declare function sanitizeGeminiToolSchema<T = unknown>(schema: T): T;
48
+ /**
49
+ * Wire the sanitizer into a ChatGoogle model at the tool-binding boundary. Overrides the instance's
50
+ * `bindTools` so every tool passed to it — built-in, custom, or MCP, via `createAgent`/`createDeepAgent`
51
+ * which both call `model.bindTools(tools)` — is sanitized before it reaches `@langchain/google`'s
52
+ * Gemini converter. Provider-scoped: only the google-genai/vertexai presets call this, so no other
53
+ * provider's tools are affected. Returns the same model instance for convenient chaining.
54
+ */
55
+ export declare function applyGeminiToolSchemaSanitizer<T extends BaseChatModel>(model: T): T;
@@ -0,0 +1,347 @@
1
+ import { toJsonSchema } from '@langchain/core/utils/json_schema';
2
+ import { isInteropZodSchema } from '@langchain/core/utils/types';
3
+ import { isSerializableSchema } from '@langchain/core/utils/standard_schema';
4
+ /**
5
+ * The EXACT set of schema keywords Gemini's function-declaration schema accepts, transcribed field-
6
+ * for-field from the `Gemini.Tools.Schema` interface in
7
+ * `@langchain/google/dist/chat_models/api-types.d.ts`. Anything not in this set is dropped (allowlist).
8
+ *
9
+ * Note `anyOf` IS supported (unions / nullable) and MUST survive — only `allOf`/`oneOf`/`not` are
10
+ * absent from the type and therefore dropped. `exclusiveMinimum`/`exclusiveMaximum` are NOT in the type
11
+ * either; they are handled specially by rewriting them to `minimum`/`maximum` (see {@link sanitizeNode})
12
+ * before the allowlist filter runs.
13
+ */
14
+ export const GEMINI_SUPPORTED_SCHEMA_KEYWORDS = new Set([
15
+ 'anyOf',
16
+ 'default',
17
+ 'description',
18
+ 'enum',
19
+ 'example',
20
+ 'format',
21
+ 'items',
22
+ 'maxItems',
23
+ 'maxLength',
24
+ 'maxProperties',
25
+ 'maximum',
26
+ 'minItems',
27
+ 'minLength',
28
+ 'minProperties',
29
+ 'minimum',
30
+ 'nullable',
31
+ 'pattern',
32
+ 'properties',
33
+ 'propertyOrdering',
34
+ 'required',
35
+ 'title',
36
+ 'type',
37
+ ]);
38
+ function isPlainObject(value) {
39
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
40
+ }
41
+ /** Deep-clone a literal (JSON-schema data) value so the returned schema never aliases the input. */
42
+ function cloneLiteral(value) {
43
+ if (value === null || typeof value !== 'object')
44
+ return value;
45
+ try {
46
+ return structuredClone(value);
47
+ }
48
+ catch {
49
+ // Non-cloneable (functions/symbols) never appear in JSON-schema data; keep the reference.
50
+ return value;
51
+ }
52
+ }
53
+ /** Structural equality by JSON serialisation. Used only for CONSERVATIVE conflict detection during
54
+ * an `allOf` merge: a false "not equal" (e.g. key-order differences) merely makes the merge abort to
55
+ * the safe drop, never produces an unsound merge — so a best-effort compare is sufficient here. */
56
+ function jsonEqual(a, b) {
57
+ return JSON.stringify(a) === JSON.stringify(b);
58
+ }
59
+ /**
60
+ * A `const` is resolved to `enum` ONLY when its value is a SCALAR (string / number / boolean / null).
61
+ * An object- or array-valued `const` is deliberately NOT resolved: synthesising an object/array-valued
62
+ * `enum` is a plausible-but-unverified Gemini 400 path, so we leave it to fall through to the safe
63
+ * allowlist drop (typeless `{}`) — exactly the prior GS2-58 behaviour, never widened.
64
+ */
65
+ function isScalarConst(value) {
66
+ return (value === null ||
67
+ typeof value === 'string' ||
68
+ typeof value === 'number' ||
69
+ typeof value === 'boolean');
70
+ }
71
+ /** Infer the Gemini `type` for a SCALAR `const` value when the schema declares none. Numbers map to
72
+ * the general `number` (integer vs number is not "obvious" from a literal); `null` yields no type. */
73
+ function inferTypeFromConst(value) {
74
+ switch (typeof value) {
75
+ case 'string':
76
+ return 'string';
77
+ case 'boolean':
78
+ return 'boolean';
79
+ case 'number':
80
+ return 'number';
81
+ default:
82
+ return undefined; // null (the only other scalar reaching here) → no type
83
+ }
84
+ }
85
+ /**
86
+ * Attempt a shallow merge of `node.allOf` (a list of subschemas) into the parent node. Gemini has no
87
+ * `allOf`, so GS2-58 simply drops it — losing any content carried only by the branches. When every
88
+ * branch is a plain-object subschema that merges CLEANLY, we fold it in instead:
89
+ * - `properties` are unioned (a property name appearing in the parent or two branches with DIFFERENT
90
+ * schemas is a conflict → abort);
91
+ * - `required` arrays are unioned;
92
+ * - any other (scalar) keyword is copied, but a key already set — on the parent or an earlier branch —
93
+ * to a DIFFERENT value is a conflict → abort.
94
+ * On ANY conflict, or if a branch is not a plain object (a boolean subschema `true`/`false`), returns
95
+ * `null` so the caller leaves `allOf` in place for the allowlist to drop — the safe GS2-58 behaviour,
96
+ * never a guessed merge. Note a branch carrying an UNRESOLVED keyword (e.g. `{ $ref: '#/…' }`) is still
97
+ * a plain object, so the merge PROCEEDS: the other branches merge and the `$ref` is copied onto the
98
+ * parent, where the allowlist then drops it — still safe, and higher-fidelity than dropping every
99
+ * branch. Returns a fresh object (never mutates `node`) with `allOf` removed on success.
100
+ */
101
+ function mergeAllOf(node) {
102
+ const branches = node.allOf;
103
+ if (!Array.isArray(branches) || branches.length === 0)
104
+ return null;
105
+ if (!branches.every(isPlainObject))
106
+ return null;
107
+ const acc = { ...node };
108
+ delete acc.allOf;
109
+ const props = isPlainObject(acc.properties) ? { ...acc.properties } : {};
110
+ let sawProps = isPlainObject(acc.properties);
111
+ const required = new Set(Array.isArray(acc.required) ? acc.required : []);
112
+ let sawRequired = Array.isArray(acc.required);
113
+ for (const branch of branches) {
114
+ for (const [key, value] of Object.entries(branch)) {
115
+ if (key === 'properties') {
116
+ if (!isPlainObject(value))
117
+ return null;
118
+ for (const [name, sub] of Object.entries(value)) {
119
+ if (name in props && !jsonEqual(props[name], sub))
120
+ return null; // conflicting property
121
+ props[name] = sub;
122
+ }
123
+ sawProps = true;
124
+ }
125
+ else if (key === 'required') {
126
+ if (Array.isArray(value))
127
+ for (const r of value)
128
+ required.add(r);
129
+ sawRequired = true;
130
+ }
131
+ else {
132
+ // scalar / other keyword: parent and every branch must agree on it.
133
+ if (key in acc && !jsonEqual(acc[key], value))
134
+ return null;
135
+ acc[key] = value;
136
+ }
137
+ }
138
+ }
139
+ if (sawProps)
140
+ acc.properties = props;
141
+ if (sawRequired)
142
+ acc.required = [...required];
143
+ return acc;
144
+ }
145
+ /**
146
+ * Resolve the SAFE structural-composition keywords Gemini rejects into supported equivalents, BEFORE
147
+ * {@link sanitizeNode}'s allowlist drops the rest. Its output feeds the same allowlist + `exclusive*`
148
+ * rewrite pass, so merged-in `properties`/`items`/`anyOf` are still recursed and a merged-in
149
+ * `exclusiveMinimum` is still rewritten. A strict NO-OP (returns the input node) when the node carries
150
+ * none of the handled keywords, so clean schemas pass through byte-identical.
151
+ *
152
+ * Implemented (high-fidelity, no external context needed):
153
+ * - SCALAR `const` → `enum: [value]` (+ infer `type` from the value when the node declares none).
154
+ * Gemini has no `const` but supports `enum`; a single-value `enum` is an exact model of `const`.
155
+ * Presence (`'const' in node`), not truthiness, so `const: 0 / false / '' / null` resolve too.
156
+ * An OBJECT/ARRAY-valued `const` is NOT resolved (see {@link isScalarConst}) — it falls through to
157
+ * the safe allowlist drop (typeless `{}`), never widening into an unverified object-`enum` 400 path.
158
+ * - `allOf` of plain-object branches → shallow-merged when clean (see {@link mergeAllOf}); otherwise
159
+ * left for the allowlist to drop.
160
+ *
161
+ * Deliberately NOT resolved — kept as the safe GS2-58 drop (see the GS2-68 characterization tests):
162
+ * - `oneOf` / `not`: `oneOf` is XOR, semantically distinct from `anyOf`'s OR, so remapping it would
163
+ * silently change a tool's contract; `not` has no Gemini equivalent. Both are dropped.
164
+ * - `$ref` / `$defs` / `definitions`: inlining a same-document `$ref` is only sound when fully self-
165
+ * contained AND cycle-guarded, and is NEAR-ZERO in practice — `@langchain/mcp-adapters` dereferences
166
+ * `$ref` and merges `allOf` UPSTREAM before tools reach this boundary, and gaunt-sloth's own zod
167
+ * tools inline+type their schemas. No live path authors a bare `$ref` here, so this is DEFERRED
168
+ * (GS2-68): a `$ref`-only property still sanitizes to a typeless `{}` — non-400 and callable, just
169
+ * without type fidelity. A raw non-adapter tool that needs it should dereference upstream, not here.
170
+ */
171
+ function resolveComposition(node) {
172
+ let out = node;
173
+ if ('const' in node && !('enum' in node) && isScalarConst(node.const)) {
174
+ if (out === node)
175
+ out = { ...node };
176
+ out.enum = [cloneLiteral(node.const)];
177
+ if (!('type' in out)) {
178
+ const inferred = inferTypeFromConst(node.const);
179
+ if (inferred)
180
+ out.type = inferred;
181
+ }
182
+ }
183
+ if (Array.isArray(out.allOf)) {
184
+ const merged = mergeAllOf(out);
185
+ if (merged)
186
+ out = merged;
187
+ // else: leave `allOf` in place → the allowlist drops it (safe GS2-58 behaviour).
188
+ }
189
+ return out;
190
+ }
191
+ /**
192
+ * Recursively normalise one schema node to Gemini's supported subset:
193
+ * - rewrite `exclusiveMinimum`→`minimum` / `exclusiveMaximum`→`maximum` (Gemini has no exclusive
194
+ * bound), keeping the TIGHTER bound when an inclusive one is also present;
195
+ * - keep ONLY {@link GEMINI_SUPPORTED_SCHEMA_KEYWORDS}; drop everything else;
196
+ * - recurse ONLY through real subschema positions — `properties` (map), `items` (schema or tuple),
197
+ * and `anyOf` (array of schemas). Literal-data positions (`enum`, `default`, `example`, `required`,
198
+ * `propertyOrdering`) are copied verbatim, so a keyword that merely appears as DATA is untouched.
199
+ */
200
+ function sanitizeNode(node) {
201
+ if (Array.isArray(node)) {
202
+ return node.map(sanitizeNode);
203
+ }
204
+ if (!isPlainObject(node)) {
205
+ return node;
206
+ }
207
+ // Resolve the safe composition keywords (const → enum, clean allOf → shallow merge) BEFORE the
208
+ // allowlist drop, so their content survives; the allowlist below then still guarantees no
209
+ // unsupported keyword escapes. `resolved` is `node` itself when nothing needed resolving.
210
+ const resolved = resolveComposition(node);
211
+ const out = {};
212
+ for (const [key, value] of Object.entries(resolved)) {
213
+ // Allowlist: silently drop any keyword Gemini's Schema type does not declare (this is where
214
+ // $defs / definitions / patternProperties / const / multipleOf / $ref / allOf / oneOf / not /
215
+ // additionalProperties / $schema / exclusive* are removed).
216
+ if (!GEMINI_SUPPORTED_SCHEMA_KEYWORDS.has(key)) {
217
+ continue;
218
+ }
219
+ if (key === 'properties' && isPlainObject(value)) {
220
+ const mapped = {};
221
+ for (const [name, sub] of Object.entries(value)) {
222
+ mapped[name] = sanitizeNode(sub);
223
+ }
224
+ out[key] = mapped;
225
+ }
226
+ else if (key === 'items') {
227
+ out[key] = Array.isArray(value) ? value.map(sanitizeNode) : sanitizeNode(value);
228
+ }
229
+ else if (key === 'anyOf' && Array.isArray(value)) {
230
+ out[key] = value.map(sanitizeNode);
231
+ }
232
+ else {
233
+ // Supported scalar / literal-data keyword (type, enum, required, description, default, …).
234
+ out[key] = cloneLiteral(value);
235
+ }
236
+ }
237
+ // exclusive* → inclusive of the SAME value. Gemini has no exclusive bound; args are hints, so the
238
+ // loosening is accepted. When both an exclusive and an inclusive bound are present, keep the TIGHTER
239
+ // one (higher lower-bound / lower upper-bound) rather than letting the exclusive value clobber it.
240
+ // Read from `resolved` so a bound merged in from an `allOf` branch is rewritten too.
241
+ const exclusiveMinimum = resolved.exclusiveMinimum;
242
+ if (typeof exclusiveMinimum === 'number') {
243
+ out.minimum =
244
+ typeof out.minimum === 'number' ? Math.max(out.minimum, exclusiveMinimum) : exclusiveMinimum;
245
+ }
246
+ const exclusiveMaximum = resolved.exclusiveMaximum;
247
+ if (typeof exclusiveMaximum === 'number') {
248
+ out.maximum =
249
+ typeof out.maximum === 'number' ? Math.min(out.maximum, exclusiveMaximum) : exclusiveMaximum;
250
+ }
251
+ return out;
252
+ }
253
+ /**
254
+ * Pure, recursive normaliser: returns a cleaned DEEP COPY of a JSON-Schema containing only keywords
255
+ * Gemini's function-declaration `Schema` accepts, so its OpenAPI-3.0 subset accepts the tool. At each
256
+ * node the SAFE composition keywords are first RESOLVED into supported equivalents
257
+ * ({@link resolveComposition}: `const` → `enum`, clean `allOf` → shallow merge) and only then does the
258
+ * allowlist drop the rest. Does not mutate the input. `anyOf` unions survive; `$ref`/`oneOf`/`not` are
259
+ * dropped (see {@link resolveComposition} for why the last three are deferred, not resolved).
260
+ */
261
+ export function sanitizeGeminiToolSchema(schema) {
262
+ return sanitizeNode(schema);
263
+ }
264
+ /**
265
+ * The SINGLE normalization pass. Converts a schema to JSON (zod → JSON via the same `@langchain/core`
266
+ * converter `@langchain/google` uses internally) and runs the allowlist. Every tool-shape branch in
267
+ * {@link sanitizeToolForGemini} routes its schema(s) through here — so no branch can filter
268
+ * inconsistently, and a future tool format is covered the moment it calls this.
269
+ */
270
+ function normalizeSchema(rawSchema) {
271
+ const jsonSchema = isInteropZodSchema(rawSchema) || isSerializableSchema(rawSchema)
272
+ ? toJsonSchema(rawSchema)
273
+ : rawSchema;
274
+ return sanitizeGeminiToolSchema(jsonSchema);
275
+ }
276
+ /** Build a shallow copy of a tool that preserves its prototype (so it stays a recognisable
277
+ * LangChain tool) while overriding one own property (its schema) with the normalized value. */
278
+ function cloneWithOverride(source, key, value) {
279
+ const clone = Object.assign(Object.create(Object.getPrototypeOf(source)), source);
280
+ Object.defineProperty(clone, key, {
281
+ value,
282
+ enumerable: true,
283
+ writable: true,
284
+ configurable: true,
285
+ });
286
+ return clone;
287
+ }
288
+ /**
289
+ * Normalise one tool's argument schema for Gemini. Handles the three shapes that reach a ChatGoogle
290
+ * model's `bindTools`: a LangChain structured tool (`.schema`, zod or JSON), an OpenAI-format tool
291
+ * (`.function.parameters`), and a Gemini-native `functionDeclarations` tool. Every schema position in
292
+ * every branch is passed through the SINGLE {@link normalizeSchema} pass; anything else is untouched.
293
+ */
294
+ function sanitizeToolForGemini(tool) {
295
+ if (!tool || typeof tool !== 'object') {
296
+ return tool;
297
+ }
298
+ const record = tool;
299
+ // LangChain structured tool / StructuredToolParams — its arg schema is `.schema` (zod or JSON).
300
+ if ('schema' in record && record.schema != null) {
301
+ return cloneWithOverride(tool, 'schema', normalizeSchema(record.schema));
302
+ }
303
+ // OpenAI-format tool: { type: 'function', function: { parameters } }.
304
+ const fn = record.function;
305
+ if (fn && typeof fn === 'object' && 'parameters' in fn) {
306
+ const fnRecord = fn;
307
+ return {
308
+ ...record,
309
+ function: { ...fnRecord, parameters: normalizeSchema(fnRecord.parameters) },
310
+ };
311
+ }
312
+ // Gemini-native tool: { functionDeclarations: [{ parameters }, …] }.
313
+ if (Array.isArray(record.functionDeclarations)) {
314
+ return {
315
+ ...record,
316
+ functionDeclarations: record.functionDeclarations.map((decl) => {
317
+ if (decl && typeof decl === 'object' && 'parameters' in decl) {
318
+ const declRecord = decl;
319
+ return { ...declRecord, parameters: normalizeSchema(declRecord.parameters) };
320
+ }
321
+ return decl;
322
+ }),
323
+ };
324
+ }
325
+ return tool;
326
+ }
327
+ /**
328
+ * Wire the sanitizer into a ChatGoogle model at the tool-binding boundary. Overrides the instance's
329
+ * `bindTools` so every tool passed to it — built-in, custom, or MCP, via `createAgent`/`createDeepAgent`
330
+ * which both call `model.bindTools(tools)` — is sanitized before it reaches `@langchain/google`'s
331
+ * Gemini converter. Provider-scoped: only the google-genai/vertexai presets call this, so no other
332
+ * provider's tools are affected. Returns the same model instance for convenient chaining.
333
+ */
334
+ export function applyGeminiToolSchemaSanitizer(model) {
335
+ const holder = model;
336
+ const original = holder.bindTools;
337
+ if (typeof original !== 'function') {
338
+ return model;
339
+ }
340
+ const bound = original.bind(model);
341
+ holder.bindTools = function sanitizedBindTools(tools, kwargs) {
342
+ const nextTools = Array.isArray(tools) ? tools.map(sanitizeToolForGemini) : tools;
343
+ return bound(nextTools, kwargs);
344
+ };
345
+ return model;
346
+ }
347
+ //# sourceMappingURL=geminiSchemaSanitizer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"geminiSchemaSanitizer.js","sourceRoot":"","sources":["../../src/providers/geminiSchemaSanitizer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * @packageDocumentation
3
+ * CFG-33 — ask Gemini to RETURN the thinking it is already doing.
4
+ *
5
+ * Two knobs are easy to conflate. The thinking BUDGET is what costs money, and Gemini thinks by
6
+ * default — those tokens are billed whether or not anyone sees them. Thought SUMMARIES
7
+ * (`thinkingConfig.includeThoughts`, default off) decide only whether any of that is returned. So
8
+ * without this, gaunt-sloth pays for reasoning on every Gemini turn and throws it away unseen, and
9
+ * the `/reasoning` panel is empty.
10
+ *
11
+ * Hence the rule here: wherever thinking is enabled, show it. This never changes the budget, and it
12
+ * is not opt-in — a knob belongs on the thing that actually costs (`thinkingBudget` /
13
+ * `thinkingLevel` / `reasoningEffort` in the `llm` config), never on whether the user may see what
14
+ * they have already paid for. Setting the budget to zero or minimal turns thinking off, and this
15
+ * respects that: there are then no thoughts to show.
16
+ *
17
+ * Why an `invocationParams` override rather than a constructor field: `@langchain/google` derives
18
+ * `generationConfig.thinkingConfig` from the budget/level fields on every call and does not read a
19
+ * `thinkingConfig` passed to the constructor, so the ONLY way to add `includeThoughts` without also
20
+ * pinning a budget is at the built params. Overriding the instance method (the shape
21
+ * {@link applyGeminiToolSchemaSanitizer} already uses for `bindTools`) keeps working through
22
+ * `bindTools`, which returns a `RunnableBinding` around this same instance.
23
+ *
24
+ * Gemini returns a thought summary as a content BLOCK marked `thought: true`, not in
25
+ * `additional_kwargs`; `#src/core/reasoningBlocks.js` is the half of this fix that reads it. That
26
+ * block is typed `text`, exactly like an answer block, so a surface that does NOT route content
27
+ * through gsloth's own reasoning bridge cannot tell thinking from answer — which is what
28
+ * {@link disableGeminiThoughtSummaries} is for.
29
+ */
30
+ import type { BaseChatModel } from '@langchain/core/language_models/chat_models';
31
+ /**
32
+ * Wire thought summaries into a `ChatGoogle` model. Overrides the instance's `invocationParams` so
33
+ * that `generationConfig.thinkingConfig.includeThoughts` is set — but ONLY when the library derived
34
+ * no thinking config at all, which is the "user configured nothing, the API default budget applies"
35
+ * case. When the user DID configure a budget or level, `@langchain/google` has already decided
36
+ * `includeThoughts` from it (true when thinking is on, false when they asked for none/minimal) and
37
+ * that decision is left exactly as it stands. Returns the same model instance for chaining.
38
+ */
39
+ export declare function applyGeminiThoughtSummaries<T extends BaseChatModel>(model: T): T;
40
+ /**
41
+ * The inverse, for a surface that must not receive thought summaries at all: keep whatever thinking
42
+ * budget or level applies and force `includeThoughts: false`, so the model still thinks and only the
43
+ * summary is withheld. Never express this as a zero/minimal budget — that turns THINKING off, which
44
+ * is a different and much larger change, and the coercion differs between the 2.5 and 3.x presets.
45
+ *
46
+ * This exists because a consumer outside {@link GthAbstractAgent} routes content blocks by `type`,
47
+ * and Gemini's thought summary is typed `text` exactly like an answer block — so a third party has
48
+ * no way to tell them apart and prints the thinking as the assistant's answer. Not asking for the
49
+ * summary is the only thing that reliably stops that; nothing is stripped, so the message kept in
50
+ * graph state (and any `thoughtSignature` riding on it) is untouched. Returns the same instance.
51
+ *
52
+ * It applies to EVERY model family, including the image/tts ones the enable path skips: those are
53
+ * precisely where `@langchain/google` sets `includeThoughts: true` itself once a budget or level is
54
+ * configured, so a shared "does this model produce summaries?" guard would let exactly those
55
+ * summaries through. What it will not do is INTRODUCE a thinking config where the library built
56
+ * none — a request that carries no `thinkingConfig` gets no summary anyway (that is the whole
57
+ * premise of {@link applyGeminiThoughtSummaries}), and adding the field to a model family the
58
+ * library withholds it from would send something it deliberately did not.
59
+ */
60
+ export declare function disableGeminiThoughtSummaries<T extends BaseChatModel>(model: T): T;