@gaunt-sloth/core 2.0.0-alpha.35 → 2.0.0-alpha.36

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 (92) hide show
  1. package/dist/config/configDiscovery.d.ts +79 -0
  2. package/dist/config/configDiscovery.js +80 -0
  3. package/dist/config/configDiscovery.js.map +1 -0
  4. package/dist/config/loader.d.ts +6 -5
  5. package/dist/config/loader.js +229 -92
  6. package/dist/config/loader.js.map +1 -1
  7. package/dist/config/schema.d.ts +3 -2
  8. package/dist/config/schema.js +57 -9
  9. package/dist/config/schema.js.map +1 -1
  10. package/dist/config/shell-policy.d.ts +58 -26
  11. package/dist/config/shell-policy.js +60 -27
  12. package/dist/config/shell-policy.js.map +1 -1
  13. package/dist/config/tool-descriptions.d.ts +6 -6
  14. package/dist/config/tool-descriptions.js +7 -7
  15. package/dist/config/tool-descriptions.js.map +1 -1
  16. package/dist/config/types.d.ts +34 -31
  17. package/dist/config/types.js.map +1 -1
  18. package/dist/config.d.ts +3 -0
  19. package/dist/config.js +3 -0
  20. package/dist/config.js.map +1 -1
  21. package/dist/core/GthAbstractAgent.d.ts +11 -13
  22. package/dist/core/GthAbstractAgent.js +9 -11
  23. package/dist/core/GthAbstractAgent.js.map +1 -1
  24. package/dist/core/GthAgentRunner.d.ts +17 -33
  25. package/dist/core/GthAgentRunner.js +31 -41
  26. package/dist/core/GthAgentRunner.js.map +1 -1
  27. package/dist/core/GthLangChainAgent.js +25 -34
  28. package/dist/core/GthLangChainAgent.js.map +1 -1
  29. package/dist/core/approvals/toolAnnotationSources.d.ts +4 -4
  30. package/dist/core/approvals/toolAnnotationSources.js +8 -8
  31. package/dist/core/debugCapture.d.ts +2 -3
  32. package/dist/core/debugCapture.js +1 -2
  33. package/dist/core/debugCapture.js.map +1 -1
  34. package/dist/core/gthLeanAgentFactory.d.ts +4 -4
  35. package/dist/core/gthLeanAgentFactory.js +4 -4
  36. package/dist/core/reasoningBlocks.d.ts +7 -2
  37. package/dist/core/reasoningBlocks.js +7 -2
  38. package/dist/core/reasoningBlocks.js.map +1 -1
  39. package/dist/core/shell/ShellCommandFailedError.d.ts +3 -4
  40. package/dist/core/shell/ShellCommandFailedError.js +3 -4
  41. package/dist/core/shell/ShellCommandFailedError.js.map +1 -1
  42. package/dist/core/shell/approvalStop.d.ts +68 -29
  43. package/dist/core/shell/approvalStop.js +178 -28
  44. package/dist/core/shell/approvalStop.js.map +1 -1
  45. package/dist/core/shell/hardline.d.ts +22 -0
  46. package/dist/core/shell/hardline.js +81 -0
  47. package/dist/core/shell/hardline.js.map +1 -1
  48. package/dist/core/shell/negotiation.d.ts +81 -2
  49. package/dist/core/shell/negotiation.js +144 -11
  50. package/dist/core/shell/negotiation.js.map +1 -1
  51. package/dist/core/shell/rater.d.ts +7 -69
  52. package/dist/core/shell/rater.js +5 -24
  53. package/dist/core/shell/rater.js.map +1 -1
  54. package/dist/core/shell/raterVocabulary.d.ts +119 -0
  55. package/dist/core/shell/raterVocabulary.js +114 -0
  56. package/dist/core/shell/raterVocabulary.js.map +1 -0
  57. package/dist/core/types.d.ts +17 -3
  58. package/dist/providers/anthropic.js +12 -0
  59. package/dist/providers/anthropic.js.map +1 -1
  60. package/dist/providers/configurationPassthrough.d.ts +107 -0
  61. package/dist/providers/configurationPassthrough.js +148 -0
  62. package/dist/providers/configurationPassthrough.js.map +1 -0
  63. package/dist/providers/geminiThinking.d.ts +8 -0
  64. package/dist/providers/geminiThinking.js +33 -13
  65. package/dist/providers/geminiThinking.js.map +1 -1
  66. package/dist/providers/google-genai.js +11 -0
  67. package/dist/providers/google-genai.js.map +1 -1
  68. package/dist/providers/groq.js +12 -0
  69. package/dist/providers/groq.js.map +1 -1
  70. package/dist/providers/ollama.js +12 -0
  71. package/dist/providers/ollama.js.map +1 -1
  72. package/dist/providers/openrouter.d.ts +23 -0
  73. package/dist/providers/openrouter.js +71 -4
  74. package/dist/providers/openrouter.js.map +1 -1
  75. package/dist/providers/vertexai.js +12 -0
  76. package/dist/providers/vertexai.js.map +1 -1
  77. package/dist/providers/xai.js +20 -0
  78. package/dist/providers/xai.js.map +1 -1
  79. package/dist/runtime/conversation.d.ts +2 -2
  80. package/dist/runtime/conversation.js +19 -4
  81. package/dist/runtime/conversation.js.map +1 -1
  82. package/dist/runtime/singleShot.d.ts +2 -3
  83. package/dist/runtime/singleShot.js +22 -7
  84. package/dist/runtime/singleShot.js.map +1 -1
  85. package/dist/utils/systemPromptNotes.d.ts +17 -20
  86. package/dist/utils/systemPromptNotes.js +22 -24
  87. package/dist/utils/systemPromptNotes.js.map +1 -1
  88. package/dist/utils/untrustedText.d.ts +24 -4
  89. package/dist/utils/untrustedText.js +25 -4
  90. package/dist/utils/untrustedText.js.map +1 -1
  91. package/package.json +4 -4
  92. package/schema/gsloth-config.schema.json +0 -1
@@ -0,0 +1,114 @@
1
+ /**
2
+ * @module core/shell/raterVocabulary
3
+ *
4
+ * **The approvals gate's closed vocabularies, and nothing else.** Three lists: the outcomes a rater
5
+ * may return, the actions the gate may resolve to, and the deterministic preflights that can floor a
6
+ * command. Each is the SINGLE place its members are written down.
7
+ *
8
+ * ## Why they live apart from {@link ./rater.js}, which owns their meaning
9
+ *
10
+ * A vocabulary has two kinds of consumer. The gate itself needs the words *and* the machinery around
11
+ * them. A **checker** — a schema, a report axis, a compile-time guard in another package — needs only
12
+ * the words, and must be able to read them without loading a rating prompt, a structured-output
13
+ * boundary and a chat-model client. `rater.js` imports LangChain and Zod; a suite parser that had to
14
+ * import it to learn which actions exist would pull the whole model layer into a parse.
15
+ *
16
+ * So this module has **no imports at all**, and it must stay that way. `rater.js` re-exports every
17
+ * symbol here, so the gate's own callers are unaffected and there is exactly one spelling of each
18
+ * word in the codebase.
19
+ *
20
+ * **A checker that RESTATES one of these lists is the failure this module exists to prevent.** A
21
+ * restated list does not grow when the gate grows: the new member is simply never recognised, and
22
+ * the checker keeps reporting success over a vocabulary it no longer covers. Derive from these
23
+ * arrays — ideally as a total `Record` keyed on the type, so a new member is a compile error rather
24
+ * than a silence.
25
+ */
26
+ /**
27
+ * (Spec §4.1) — the **four** outcomes the rater may return. There is no ordering knob and no
28
+ * threshold: each outcome's consequence is fixed by the rung
29
+ * ({@link import('./rater.js').mapVerdictToAction}).
30
+ *
31
+ * - `safe` — no harmful effect.
32
+ * - `destructive` — **the catch-all**: harmful, but recoverable from inside the session, and not
33
+ * an attack — **and anything the rater cannot assess**. The rating prompt defines it *by
34
+ * exclusion* ("not safe, not catastrophic and not an attack") precisely so no command can fall
35
+ * outside the four.
36
+ * - `catastrophic` — *can this be undone from inside the session?* Irreversible without something
37
+ * OUTSIDE the session: rescue media, a backup, a re-provision, a restore from a third party.
38
+ * Escalates at both rated rungs; never negotiable and never sticky (§4.2).
39
+ * - `attack` — *is something hostile acting here?* The command's own **structure** evidences
40
+ * compromise (§4.1.1: credential targeting, privilege escalation / permission weakening,
41
+ * persistence, deception, obfuscation). It is the only outcome that HALTS the run.
42
+ *
43
+ * **`catastrophic` and `attack` are not ranked against each other** — they ask different
44
+ * questions, and the spec says so explicitly. A command can be both; `attack` wins the
45
+ * *consequence* (a manipulated session cannot be trusted to continue) but MUST NOT swallow the
46
+ * finding — see the §6.1 clause in {@link import('./rater.js').buildRaterSystemPrompt}. Nothing
47
+ * here may be written as a severity comparison between the two.
48
+ */
49
+ export const RATER_OUTCOMES = ['safe', 'destructive', 'catastrophic', 'attack'];
50
+ /**
51
+ * The actions the approvals gate can resolve to for a single gated call, BEFORE the human prompt.
52
+ * {@link import('./rater.js').mapVerdictToAction} is the whole mapping from an outcome and a rung
53
+ * onto one of these, and its docblock carries the table.
54
+ *
55
+ * - `approve` — approve ONCE; do not touch the human or the allow-list.
56
+ * - `escalate` — fall through to the human approval callback, carrying the verdict when one
57
+ * exists. Where there is no human, §6.2 turns this into an immediate non-zero exit — that
58
+ * translation belongs to the runner, not to the mapping.
59
+ * - `halt` — **end the agent loop** (§4.2). Reserved for `attack`. It is not a rejection the
60
+ * model can respond to and offers it no moves; no rung except `bypass` can turn it into
61
+ * anything else.
62
+ * - `reject` — (§5): hand the rater's explanation back to the **agent** as the refused call's tool
63
+ * result (§7), opening a round of the negotiation. Returned for `destructive` at `auto` and
64
+ * nowhere else.
65
+ *
66
+ * **`reject` says the outcome is negotiable, NOT that the negotiation may continue.** The mapping is
67
+ * keyed on the rung and knows nothing about how many rounds have been spent; §5.3's consecutive cap
68
+ * and the reachability bound live with the state they count, in the runner, which turns a `reject`
69
+ * into an escalation once either is spent. Putting the counters into the mapping would make a pure
70
+ * rung-keyed table depend on session history, and would give the eval target
71
+ * (`@gaunt-sloth/batch`'s `raterTarget`) an action that varies with something it does not model.
72
+ *
73
+ * There is deliberately **no `abstain` arm.** A command whose target the gate cannot statically
74
+ * resolve is rated like any other, under §6.1's rule — *deterministic checks fire only where we are
75
+ * confident something is a threat; where we cannot tell, the model decides*. A parser reporting that
76
+ * it could not resolve a string is not a detection, so it earns no action of its own; what it earns
77
+ * is a neutral note in the rating prompt
78
+ * ({@link import('./abstention.js').buildParserPreflightNote}) and a real rating. That is also what
79
+ * keeps the ceiling reachable: an action of its own would make `catastrophic` and `attack`
80
+ * unreachable for every composed, substituting or redirecting command, so `pwd && rm -rf ~` could
81
+ * only ever be floored at `destructive`.
82
+ *
83
+ * There is deliberately **no `refuse` arm for `catastrophic`** (§4.2). The deterministic members of
84
+ * that class — fork bomb, `mkfs`, `rm -rf /`, `dd` to a block device — are already refused
85
+ * unappealably by the §8 hardline floor under every rung including `bypass`, so a refusing
86
+ * `catastrophic` would add nothing for the commands that motivate the idea. What it would newly
87
+ * refuse is the remainder the floor cannot reach, every member of which has routine legitimate use
88
+ * (a staging database, an ephemeral `terraform destroy`, a preview namespace). An unmeasured
89
+ * classifier belongs behind a human who can correct it; a refusal has no correction path.
90
+ *
91
+ * **This list is exhaustive by construction, and downstream must treat it that way.** Anything that
92
+ * enumerates actions — a report axis, an eval suite's declared enum, a rendering table — has to
93
+ * derive from here. The failure mode of a restatement is silent rather than loud: an action the
94
+ * enumerator does not know is not an error it can raise, it is a row it quietly files under
95
+ * "unrecognized", and the thing that was being measured simply stops being measured.
96
+ */
97
+ export const RATER_ACTIONS = ['approve', 'escalate', 'halt', 'reject'];
98
+ /**
99
+ * The deterministic preflights that can floor a command at `destructive`, in the FIXED order
100
+ * {@link import('./rater.js').preflightFloorFinding} evaluates them.
101
+ *
102
+ * - `script-env-leak` — an interpreter invocation expanding an ALL_CAPS environment variable into
103
+ * its arguments. §11.1b's narrowing of the `attack` clause rests on this arm firing.
104
+ * - `open-world` — (§4.6) a host literal in a fetch/transfer position. Its reason NAMES THE HOST,
105
+ * so, unlike the other arm's, its text varies per command: identify this arm by its `kind`, never
106
+ * by matching its prose.
107
+ *
108
+ * They are arms of a single decision rather than independent checks, and the outcome is identical
109
+ * whichever fires — the order is the order of the *explanation* a human reads. Anything that
110
+ * attributes a floored decision to a mechanism must key on this list, so that a preflight added to
111
+ * the gate cannot go unattributed.
112
+ */
113
+ export const PREFLIGHT_FLOOR_KINDS = ['script-env-leak', 'open-world'];
114
+ //# sourceMappingURL=raterVocabulary.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"raterVocabulary.js","sourceRoot":"","sources":["../../../src/core/shell/raterVocabulary.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,MAAM,EAAE,aAAa,EAAE,cAAc,EAAE,QAAQ,CAAU,CAAC;AAKzF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,SAAS,EAAE,UAAU,EAAE,MAAM,EAAE,QAAQ,CAAU,CAAC;AAKhF;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,iBAAiB,EAAE,YAAY,CAAU,CAAC"}
@@ -243,6 +243,21 @@ export interface PendingToolInterrupt {
243
243
  * at all), a declared `approvals.escalate` entry, an unrated rung, a tool subject.
244
244
  */
245
245
  negotiationRounds?: readonly RaterNegotiationRound[];
246
+ /**
247
+ * [[EXT-29]] §5.3 — **how many attempts the agent actually made**, counted since a human was last
248
+ * involved rather than since the last approved call.
249
+ *
250
+ * It is a separate number from `negotiationRounds.length` because an approved call clears the
251
+ * transcript, and a surface reading the array's length reports only the attempts that happened
252
+ * after the last one. On the escalation this was measured from, the agent proposed the same
253
+ * command five times, was refused every time, and the human was shown three — the two calls it
254
+ * made in between were `git stash` and `git status`, both approved, both erasing the rounds
255
+ * before them. Persistence is the single most decision-relevant fact this block carries, so
256
+ * under-reporting it by nearly half is not a cosmetic count.
257
+ *
258
+ * Present exactly when {@link negotiationRounds} is, and never smaller than its length.
259
+ */
260
+ negotiationAttempts?: number;
246
261
  }
247
262
  /**
248
263
  * Persistence scope for an `approve` decision (spec §6):
@@ -393,9 +408,8 @@ export interface GthAgentInterface {
393
408
  }
394
409
  /**
395
410
  * Factory that produces a {@link GthAgentInterface} implementation. Injected into
396
- * {@link GthAgentRunner} so embedders can swap the lean `GthLangChainAgent` (default,
397
- * in core) for a deep `GthDeepAgent` (in `@gaunt-sloth/agent`) without core ever
398
- * importing deepagents.
411
+ * {@link GthAgentRunner} so embedders can swap the lean `GthLangChainAgent` (the default, in
412
+ * core) for another graph builder without core ever importing it.
399
413
  */
400
414
  export type GthAgentFactory = (statusUpdate: StatusUpdateCallback, resolvers?: AgentResolvers) => GthAgentInterface;
401
415
  export type ToolsResolver = (config: GthConfig, command?: GthCommand) => Promise<StructuredToolInterface[]>;
@@ -1,6 +1,7 @@
1
1
  import { displayWarning } from '#src/utils/consoleUtils.js';
2
2
  import { writeConfigFileWithMessages } from '#src/utils/fileUtils.js';
3
3
  import { buildInitConfigContent, getCuratedFallbackModel } from '#src/providers/modelDiscovery.js';
4
+ import { NATIVE_CLIENT_REASON, warnUnusedConfiguration, } from '#src/providers/configurationPassthrough.js';
4
5
  import { env } from '#src/utils/systemUtils.js';
5
6
  /**
6
7
  * Function to process JSON config and create Anthropic LLM instance
@@ -10,6 +11,17 @@ export async function processJsonConfig(llmConfig) {
10
11
  const anthropic = await import('@langchain/anthropic');
11
12
  // Use config value if available, otherwise use the environment variable
12
13
  const anthropicApiKey = llmConfig.apiKey || env.ANTHROPIC_API_KEY;
14
+ // `ChatAnthropic` builds an Anthropic SDK client from its own `clientOptions`, so nothing in a
15
+ // `configuration` block reaches it — say so before dropping it.
16
+ warnUnusedConfiguration({
17
+ provider: 'anthropic',
18
+ configuration: llmConfig.configuration,
19
+ consumedPaths: [],
20
+ reason: NATIVE_CLIENT_REASON,
21
+ guidance: 'ChatAnthropic builds an Anthropic SDK client instead: put client options such as a custom ' +
22
+ 'base URL, a timeout or extra headers under "clientOptions" in the "llm" block, or set ' +
23
+ '"anthropicApiUrl" there for the base URL alone.',
24
+ });
13
25
  return new anthropic.ChatAnthropic({
14
26
  ...llmConfig,
15
27
  apiKey: anthropicApiKey,
@@ -1 +1 @@
1
- {"version":3,"file":"anthropic.js","sourceRoot":"","sources":["../../src/providers/anthropic.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAC5D,OAAO,EAAE,2BAA2B,EAAE,MAAM,yBAAyB,CAAC;AACtE,OAAO,EAAE,sBAAsB,EAAE,uBAAuB,EAAE,MAAM,kCAAkC,CAAC;AACnG,OAAO,EAAE,GAAG,EAAE,MAAM,2BAA2B,CAAC;AAOhD;;GAEG;AACH,qCAAqC;AACrC,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,SAA+C;IAE/C,MAAM,SAAS,GAAG,MAAM,MAAM,CAAC,sBAAsB,CAAC,CAAC;IACvD,wEAAwE;IACxE,MAAM,eAAe,GAAG,SAAS,CAAC,MAAM,IAAI,GAAG,CAAC,iBAAiB,CAAC;IAClE,OAAO,IAAI,SAAS,CAAC,aAAa,CAAC;QACjC,GAAG,SAAS;QACZ,MAAM,EAAE,eAAe;QACvB,KAAK,EAAE,SAAS,CAAC,KAAK,IAAI,uBAAuB,CAAC,WAAW,CAAC;KAC/D,CAAC,CAAC;AACL,CAAC;AAED,qCAAqC;AACrC,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,WAAW,EAAE,KAAK,CAAC,EAAE,KAAK,CAAC,CAAC;IAC/F,cAAc,CACZ,2BAA2B,cAAc,kCAAkC;QACzE,mDAAmD,CACtD,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"anthropic.js","sourceRoot":"","sources":["../../src/providers/anthropic.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAC5D,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;AACpD,OAAO,EAAE,GAAG,EAAE,MAAM,2BAA2B,CAAC;AAOhD;;GAEG;AACH,qCAAqC;AACrC,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,SAA+C;IAE/C,MAAM,SAAS,GAAG,MAAM,MAAM,CAAC,sBAAsB,CAAC,CAAC;IACvD,wEAAwE;IACxE,MAAM,eAAe,GAAG,SAAS,CAAC,MAAM,IAAI,GAAG,CAAC,iBAAiB,CAAC;IAClE,+FAA+F;IAC/F,gEAAgE;IAChE,uBAAuB,CAAC;QACtB,QAAQ,EAAE,WAAW;QACrB,aAAa,EAAG,SAAyC,CAAC,aAAa;QACvE,aAAa,EAAE,EAAE;QACjB,MAAM,EAAE,oBAAoB;QAC5B,QAAQ,EACN,4FAA4F;YAC5F,wFAAwF;YACxF,iDAAiD;KACpD,CAAC,CAAC;IACH,OAAO,IAAI,SAAS,CAAC,aAAa,CAAC;QACjC,GAAG,SAAS;QACZ,MAAM,EAAE,eAAe;QACvB,KAAK,EAAE,SAAS,CAAC,KAAK,IAAI,uBAAuB,CAAC,WAAW,CAAC;KAC/D,CAAC,CAAC;AACL,CAAC;AAED,qCAAqC;AACrC,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,WAAW,EAAE,KAAK,CAAC,EAAE,KAAK,CAAC,CAAC;IAC/F,cAAc,CACZ,2BAA2B,cAAc,kCAAkC;QACzE,mDAAmD,CACtD,CAAC;AACJ,CAAC"}
@@ -0,0 +1,107 @@
1
+ /**
2
+ * List the paths inside a `configuration` block that the calling factory will NOT read.
3
+ *
4
+ * `consumedPaths` entries are either a whole key (`baseURL`) or a dotted one-level path
5
+ * (`defaultHeaders.X-Title`). The dotted form matters because consumption is genuinely PARTIAL:
6
+ * openrouter reads two named attribution headers out of `defaultHeaders` and drops every other
7
+ * header in it. Reporting `defaultHeaders` as wholly consumed would silence the warning for
8
+ * exactly the case the user most needs it for — an auth header for a self-hosted gateway that is
9
+ * quietly discarded.
10
+ *
11
+ * A `configuration` that is not a record at all (`configuration: "https://x/v1"`, a plausible typo
12
+ * for the block) has no paths and is reported as nothing. That is NOT a silent drop: `llm.configuration`
13
+ * is `z.record` in `schema.ts`, and every config layer goes through the loader's `validateRawConfigLayer`,
14
+ * so a non-record value is a hard, path-scoped validation error that ends the run before any provider
15
+ * factory is reached. The schema is the gate for the shape; this function is the gate for the
16
+ * contents. `configurationPassthrough.spec.ts` pins that boundary — if the schema ever stops
17
+ * requiring a record, the case has to be handled here instead.
18
+ */
19
+ export declare function findUnusedConfigurationPaths(configuration: unknown, consumedPaths: readonly string[]): string[];
20
+ /**
21
+ * The `reason` for a provider built on a NATIVE client: there is no OpenAI client anywhere in the
22
+ * chain, so the block has nothing to be handed to.
23
+ *
24
+ * A shared clause is only safe while it is true of every caller that passes it, and nothing in the
25
+ * language can check that: the field takes any string, so a caller can pass this sentence — or
26
+ * another provider's — for a provider it is false of, and a warning that states a false reason is
27
+ * the same defect as the silence it replaces. Requiring the field stops one narrow version of that
28
+ * (a default nobody re-read against the new caller) and stops nothing else; copying the neighbouring
29
+ * call site is the likelier move and is exactly how a false clause would spread.
30
+ *
31
+ * What actually holds it is `configurationPassthrough.spec.ts`, which pins every warned provider's
32
+ * printed clause IN ITS SLOT beside that provider's own name. A clause that migrates to a provider
33
+ * it is false of reddens a cell, and editing THIS sentence reddens every caller that passes it —
34
+ * which is the point: a shared clause has to be re-checked against each of them.
35
+ */
36
+ export declare const NATIVE_CLIENT_REASON = "does not build an OpenAI client, so a \"configuration\" block is not passed through to one";
37
+ /**
38
+ * Warn — naming the provider, saying why the block is dropped, naming every dropped path, and
39
+ * pointing at the replacement — when a user's `configuration` block carries settings this provider
40
+ * cannot consume.
41
+ *
42
+ * Warn rather than throw: a provider that reads SOME of the block (openrouter still honours
43
+ * `configuration.baseURL`) would otherwise refuse a config that is partly valid, and an upgrading
44
+ * user whose only config sets one dead transport key would be left with no way to start at all.
45
+ *
46
+ * Takes ONE named-field object rather than a positional list. `reason` and `guidance` are both free
47
+ * text, so as adjacent positionals they could be transposed with nothing to catch it: `tsc` sees two
48
+ * strings, and the rendered message still contains both sentences — only their order is wrong, which
49
+ * no `toContain` check on either one can see. Named fields make that mistake visible where it is
50
+ * written; the slot-anchored pins in `configurationPassthrough.spec.ts` are what catch it if it is
51
+ * written anyway.
52
+ */
53
+ export interface UnusedConfigurationWarning {
54
+ /** The gth provider namespace, printed to the user (`anthropic`, `xai`, …). */
55
+ provider: string;
56
+ /** The user's `llm.configuration` block, exactly as it arrived. */
57
+ configuration: unknown;
58
+ /**
59
+ * The paths inside the block this factory genuinely reads — whole keys or dotted one-level paths.
60
+ * Required rather than defaulted to `[]`, so a new caller has to state what its factory consumes
61
+ * instead of inheriting an answer.
62
+ */
63
+ consumedPaths: readonly string[];
64
+ /**
65
+ * Why THIS provider drops the block, as a clause completing `the "<provider>" provider …`. Pass
66
+ * {@link NATIVE_CLIENT_REASON} for a native-client provider; a provider that builds an OpenAI
67
+ * client and then overrides the block needs its own, because that sentence would be false for it.
68
+ */
69
+ reason: string;
70
+ /**
71
+ * What to do instead, naming the replacement on THIS provider's own client — never a generic
72
+ * "move it up a level", which for some keys moves a setting from a warned location to an unwarned
73
+ * one.
74
+ */
75
+ guidance: string;
76
+ }
77
+ export declare function warnUnusedConfiguration({ provider, configuration, consumedPaths, reason, guidance, }: UnusedConfigurationWarning): void;
78
+ /**
79
+ * Warn when a `configuration` path the factory DOES consume silently beats a top-level field of the
80
+ * `llm` block that sets the same thing.
81
+ *
82
+ * Neither value is unusable here and neither location is wrong, so this is not a case for
83
+ * {@link warnUnusedConfiguration}: both are honoured surfaces, and the only defect is that one wins
84
+ * without saying so. The user's config then reads as two settings and behaves as one.
85
+ *
86
+ * Values are never printed — a base URL can carry credentials — so the message names the two paths
87
+ * and which of them takes effect.
88
+ *
89
+ * @param appliedValue The value the factory actually applied. Pass the result of the SAME expression
90
+ * that decides it, never a second copy of the test, for the reason given on
91
+ * {@link warnUnappliedConfigurationPath}.
92
+ */
93
+ export declare function warnConfigurationOverridesTopLevelField(provider: string, field: string, topLevelValue: unknown, appliedValue: unknown): void;
94
+ /**
95
+ * Warn when a path the factory DECLARES it consumes is present in the user's block but was not in
96
+ * fact applied — the empty string or `null` a factory's own guard skips.
97
+ *
98
+ * {@link findUnusedConfigurationPaths} cannot see this and must not: the path genuinely IS consumed,
99
+ * so it is correctly absent from the unused list — and the user's setting still goes nowhere.
100
+ * "Declared supported, in fact dropped" is the same silence this module exists to end, so it gets
101
+ * its own message: the "no OpenAI client" reason above would be the wrong reason for it.
102
+ *
103
+ * @param applied Whether the factory actually used the value. Pass the result of the SAME expression
104
+ * that decides it (`'baseURL' in baseURLOverride`), never a second copy of the test — a re-test is
105
+ * exactly how the guard and what the user is told drift apart.
106
+ */
107
+ export declare function warnUnappliedConfigurationPath(provider: string, configuration: unknown, path: string, applied: boolean, guidance: string): void;
@@ -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":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAE5D;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,sGAAsG;AACtG,SAAS,aAAa,CAAC,KAAc;IACnC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAC9E,CAAC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,4BAA4B,CAC1C,aAAsB,EACtB,aAAgC;IAEhC,IAAI,CAAC,aAAa,CAAC,aAAa,CAAC;QAAE,OAAO,EAAE,CAAC;IAC7C,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,aAAa,CAAC,EAAE,CAAC;QACzD,IAAI,aAAa,CAAC,QAAQ,CAAC,GAAG,CAAC;YAAE,SAAS;QAC1C,MAAM,eAAe,GAAG,aAAa;aAClC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,GAAG,GAAG,CAAC,CAAC;aAC5C,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC;QAC7C,IAAI,eAAe,CAAC,MAAM,GAAG,CAAC,IAAI,aAAa,CAAC,KAAK,CAAC,EAAE,CAAC;YACvD,KAAK,MAAM,MAAM,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;gBACxC,IAAI,CAAC,eAAe,CAAC,QAAQ,CAAC,MAAM,CAAC;oBAAE,MAAM,CAAC,IAAI,CAAC,GAAG,GAAG,IAAI,MAAM,EAAE,CAAC,CAAC;YACzE,CAAC;YACD,SAAS;QACX,CAAC;QACD,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACnB,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAC/B,0FAA0F,CAAC;AA2C7F,MAAM,UAAU,uBAAuB,CAAC,EACtC,QAAQ,EACR,aAAa,EACb,aAAa,EACb,MAAM,EACN,QAAQ,GACmB;IAC3B,MAAM,MAAM,GAAG,4BAA4B,CAAC,aAAa,EAAE,aAAa,CAAC,CAAC;IAC1E,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO;IAChC,cAAc,CACZ,YAAY,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,qBAAqB,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK;QAC3E,QAAQ,QAAQ,cAAc,MAAM,KAAK,QAAQ,EAAE,CACtD,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,uCAAuC,CACrD,QAAgB,EAChB,KAAa,EACb,aAAsB,EACtB,YAAqB;IAErB,yFAAyF;IACzF,IAAI,aAAa,KAAK,SAAS,IAAI,aAAa,KAAK,IAAI,IAAI,aAAa,KAAK,EAAE;QAAE,OAAO;IAC1F,IAAI,YAAY,KAAK,SAAS,IAAI,YAAY,KAAK,IAAI;QAAE,OAAO;IAChE,8FAA8F;IAC9F,+BAA+B;IAC/B,IAAI,aAAa,KAAK,YAAY;QAAE,OAAO;IAC3C,cAAc,CACZ,gBAAgB,KAAK,WAAW,QAAQ,yCAAyC,KAAK,QAAQ;QAC5F,6FAA6F;QAC7F,UAAU,CACb,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,8BAA8B,CAC5C,QAAgB,EAChB,aAAsB,EACtB,IAAY,EACZ,OAAgB,EAChB,QAAgB;IAEhB,IAAI,OAAO;QAAE,OAAO;IACpB,IAAI,CAAC,aAAa,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,IAAI,IAAI,aAAa,CAAC;QAAE,OAAO;IACtE,cAAc,CACZ,8BAA8B,IAAI,yBAAyB,QAAQ,4BAA4B;QAC7F,uCAAuC,QAAQ,EAAE,CACpD,CAAC;AACJ,CAAC"}
@@ -48,5 +48,13 @@ export declare function applyGeminiThoughtSummaries<T extends BaseChatModel>(mod
48
48
  * no way to tell them apart and prints the thinking as the assistant's answer. Not asking for the
49
49
  * summary is the only thing that reliably stops that; nothing is stripped, so the message kept in
50
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.
51
59
  */
52
60
  export declare function disableGeminiThoughtSummaries<T extends BaseChatModel>(model: T): T;
@@ -1,8 +1,14 @@
1
1
  /**
2
- * Model families that produce no thought summary to show. `@langchain/google` itself declines to
3
- * send any thinking config for a 2.5 image model, so injecting one there would send a field the
4
- * library deliberately withheld; image/tts generations have no reasoning panel to fill either way.
5
- * Skipping them keeps this change to the models it is about.
2
+ * Model families to leave alone when ADDING a request for thought summaries. `@langchain/google`
3
+ * itself declines to send any thinking config for a 2.5 image model, so injecting one there would
4
+ * send a field the library deliberately withheld; image/tts generations have no reasoning panel to
5
+ * fill either way. Skipping them keeps the enable path to the models it is about.
6
+ *
7
+ * This gates the ENABLE direction only, and it must never gate the disable one. The sentence it
8
+ * encodes — "we are unsure this model produces a summary, so do not ask for one" — inverts into
9
+ * "…so let one through" the moment the same test is applied to a leak-prevention override, and
10
+ * these families are exactly where `@langchain/google` sets `includeThoughts: true` on its own
11
+ * once a thinking budget is configured.
6
12
  */
7
13
  function producesThoughtSummaries(model) {
8
14
  if (typeof model !== 'string')
@@ -18,6 +24,9 @@ function producesThoughtSummaries(model) {
18
24
  * that decision is left exactly as it stands. Returns the same model instance for chaining.
19
25
  */
20
26
  export function applyGeminiThoughtSummaries(model) {
27
+ if (!producesThoughtSummaries(model.model)) {
28
+ return model;
29
+ }
21
30
  return overrideThinkingConfig(model, (thinkingConfig) =>
22
31
  // `thinkingConfig` is always PRESENT as a key and may hold `undefined`; an explicit value means
23
32
  // the user's budget/level was honoured and must win.
@@ -34,24 +43,35 @@ export function applyGeminiThoughtSummaries(model) {
34
43
  * no way to tell them apart and prints the thinking as the assistant's answer. Not asking for the
35
44
  * summary is the only thing that reliably stops that; nothing is stripped, so the message kept in
36
45
  * graph state (and any `thoughtSignature` riding on it) is untouched. Returns the same instance.
46
+ *
47
+ * It applies to EVERY model family, including the image/tts ones the enable path skips: those are
48
+ * precisely where `@langchain/google` sets `includeThoughts: true` itself once a budget or level is
49
+ * configured, so a shared "does this model produce summaries?" guard would let exactly those
50
+ * summaries through. What it will not do is INTRODUCE a thinking config where the library built
51
+ * none — a request that carries no `thinkingConfig` gets no summary anyway (that is the whole
52
+ * premise of {@link applyGeminiThoughtSummaries}), and adding the field to a model family the
53
+ * library withholds it from would send something it deliberately did not.
37
54
  */
38
55
  export function disableGeminiThoughtSummaries(model) {
39
- return overrideThinkingConfig(model, (thinkingConfig) => ({
40
- ...(typeof thinkingConfig === 'object' && thinkingConfig !== null ? thinkingConfig : {}),
41
- includeThoughts: false,
42
- }));
56
+ return overrideThinkingConfig(model, (thinkingConfig) => thinkingConfig === undefined
57
+ ? thinkingConfig
58
+ : {
59
+ ...(typeof thinkingConfig === 'object' && thinkingConfig !== null ? thinkingConfig : {}),
60
+ includeThoughts: false,
61
+ });
43
62
  }
44
63
  /**
45
64
  * Shared plumbing: re-derive `generationConfig.thinkingConfig` on every built request. Models that
46
- * build no `generationConfig` (every non-Google provider) and model families that produce no thought
47
- * summary are left completely alone, so this is a no-op wherever it does not apply. Overrides stack:
48
- * the outermost one sees what the inner ones produced, which is what lets a surface-level decision
49
- * override the construction-time default.
65
+ * build no `generationConfig` (every non-Google provider) are left completely alone, so this is a
66
+ * no-op wherever it does not apply. Which model families to skip is the CALLER's decision, because
67
+ * it differs by direction see {@link producesThoughtSummaries}. Overrides stack: the outermost one
68
+ * sees what the inner ones produced, which is what lets a surface-level decision override the
69
+ * construction-time default.
50
70
  */
51
71
  function overrideThinkingConfig(model, next) {
52
72
  const holder = model;
53
73
  const original = holder.invocationParams;
54
- if (typeof original !== 'function' || !producesThoughtSummaries(holder.model)) {
74
+ if (typeof original !== 'function') {
55
75
  return model;
56
76
  }
57
77
  const bound = original.bind(model);
@@ -1 +1 @@
1
- {"version":3,"file":"geminiThinking.js","sourceRoot":"","sources":["../../src/providers/geminiThinking.ts"],"names":[],"mappings":"AAmCA;;;;;GAKG;AACH,SAAS,wBAAwB,CAAC,KAAc;IAC9C,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IAC3C,OAAO,CAAC,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AACnC,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,2BAA2B,CAA0B,KAAQ;IAC3E,OAAO,sBAAsB,CAAC,KAAK,EAAE,CAAC,cAAc,EAAE,EAAE;IACtD,gGAAgG;IAChG,qDAAqD;IACrD,cAAc,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,eAAe,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,cAAc,CAC1E,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,6BAA6B,CAA0B,KAAQ;IAC7E,OAAO,sBAAsB,CAAC,KAAK,EAAE,CAAC,cAAc,EAAE,EAAE,CAAC,CAAC;QACxD,GAAG,CAAC,OAAO,cAAc,KAAK,QAAQ,IAAI,cAAc,KAAK,IAAI,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,EAAE,CAAC;QACxF,eAAe,EAAE,KAAK;KACvB,CAAC,CAAC,CAAC;AACN,CAAC;AAED;;;;;;GAMG;AACH,SAAS,sBAAsB,CAC7B,KAAQ,EACR,IAA2C;IAE3C,MAAM,MAAM,GAAG,KAA8E,CAAC;IAC9F,MAAM,QAAQ,GAAG,MAAM,CAAC,gBAAgB,CAAC;IACzC,IAAI,OAAO,QAAQ,KAAK,UAAU,IAAI,CAAC,wBAAwB,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;QAC9E,OAAO,KAAK,CAAC;IACf,CAAC;IACD,MAAM,KAAK,GAAG,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAuB,CAAC;IACzD,MAAM,CAAC,gBAAgB,GAAG,SAAS,kCAAkC,CACnE,OAAiB;QAEjB,MAAM,MAAM,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC;QAC9B,MAAM,gBAAgB,GAAG,MAAM,EAAE,gBAAgB,CAAC;QAClD,IAAI,CAAC,gBAAgB,EAAE,CAAC;YACtB,OAAO,MAAM,CAAC;QAChB,CAAC;QACD,MAAM,cAAc,GAAG,IAAI,CAAC,gBAAgB,CAAC,cAAc,CAAC,CAAC;QAC7D,IAAI,cAAc,KAAK,gBAAgB,CAAC,cAAc,EAAE,CAAC;YACvD,OAAO,MAAM,CAAC;QAChB,CAAC;QACD,OAAO,EAAE,GAAG,MAAM,EAAE,gBAAgB,EAAE,EAAE,GAAG,gBAAgB,EAAE,cAAc,EAAE,EAAE,CAAC;IAClF,CAAC,CAAC;IACF,OAAO,KAAK,CAAC;AACf,CAAC"}
1
+ {"version":3,"file":"geminiThinking.js","sourceRoot":"","sources":["../../src/providers/geminiThinking.ts"],"names":[],"mappings":"AAmCA;;;;;;;;;;;GAWG;AACH,SAAS,wBAAwB,CAAC,KAAc;IAC9C,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IAC3C,OAAO,CAAC,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AACnC,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,2BAA2B,CAA0B,KAAQ;IAC3E,IAAI,CAAC,wBAAwB,CAAE,KAAwC,CAAC,KAAK,CAAC,EAAE,CAAC;QAC/E,OAAO,KAAK,CAAC;IACf,CAAC;IACD,OAAO,sBAAsB,CAAC,KAAK,EAAE,CAAC,cAAc,EAAE,EAAE;IACtD,gGAAgG;IAChG,qDAAqD;IACrD,cAAc,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,eAAe,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,cAAc,CAC1E,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,6BAA6B,CAA0B,KAAQ;IAC7E,OAAO,sBAAsB,CAAC,KAAK,EAAE,CAAC,cAAc,EAAE,EAAE,CACtD,cAAc,KAAK,SAAS;QAC1B,CAAC,CAAC,cAAc;QAChB,CAAC,CAAC;YACE,GAAG,CAAC,OAAO,cAAc,KAAK,QAAQ,IAAI,cAAc,KAAK,IAAI,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,EAAE,CAAC;YACxF,eAAe,EAAE,KAAK;SACvB,CACN,CAAC;AACJ,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,sBAAsB,CAC7B,KAAQ,EACR,IAA2C;IAE3C,MAAM,MAAM,GAAG,KAA8E,CAAC;IAC9F,MAAM,QAAQ,GAAG,MAAM,CAAC,gBAAgB,CAAC;IACzC,IAAI,OAAO,QAAQ,KAAK,UAAU,EAAE,CAAC;QACnC,OAAO,KAAK,CAAC;IACf,CAAC;IACD,MAAM,KAAK,GAAG,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAuB,CAAC;IACzD,MAAM,CAAC,gBAAgB,GAAG,SAAS,kCAAkC,CACnE,OAAiB;QAEjB,MAAM,MAAM,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC;QAC9B,MAAM,gBAAgB,GAAG,MAAM,EAAE,gBAAgB,CAAC;QAClD,IAAI,CAAC,gBAAgB,EAAE,CAAC;YACtB,OAAO,MAAM,CAAC;QAChB,CAAC;QACD,MAAM,cAAc,GAAG,IAAI,CAAC,gBAAgB,CAAC,cAAc,CAAC,CAAC;QAC7D,IAAI,cAAc,KAAK,gBAAgB,CAAC,cAAc,EAAE,CAAC;YACvD,OAAO,MAAM,CAAC;QAChB,CAAC;QACD,OAAO,EAAE,GAAG,MAAM,EAAE,gBAAgB,EAAE,EAAE,GAAG,gBAAgB,EAAE,cAAc,EAAE,EAAE,CAAC;IAClF,CAAC,CAAC;IACF,OAAO,KAAK,CAAC;AACf,CAAC"}
@@ -4,6 +4,7 @@ import { writeConfigFileWithMessages } from '#src/utils/fileUtils.js';
4
4
  import { buildInitConfigContent, getCuratedFallbackModel } from '#src/providers/modelDiscovery.js';
5
5
  import { applyGeminiToolSchemaSanitizer } from '#src/providers/geminiSchemaSanitizer.js';
6
6
  import { applyGeminiThoughtSummaries } from '#src/providers/geminiThinking.js';
7
+ import { NATIVE_CLIENT_REASON, warnUnusedConfiguration, } from '#src/providers/configurationPassthrough.js';
7
8
  // Function to process JSON config and create Google GenAI LLM instance
8
9
  export async function processJsonConfig(llmConfig) {
9
10
  const { ChatGoogle } = await import('@langchain/google/node');
@@ -17,6 +18,16 @@ export async function processJsonConfig(llmConfig) {
17
18
  };
18
19
  delete configFields.type;
19
20
  delete configFields.apiKeyEnvironmentVariable;
21
+ // `ChatGoogle` is a native client for the Gemini API, so nothing in a `configuration` block
22
+ // reaches it — say so before dropping it.
23
+ warnUnusedConfiguration({
24
+ provider: 'google-genai',
25
+ configuration: llmConfig.configuration,
26
+ consumedPaths: [],
27
+ reason: NATIVE_CLIENT_REASON,
28
+ guidance: 'ChatGoogle talks to the Gemini API through its own client instead: set "customHeaders", ' +
29
+ '"endpoint" or "apiVersion" as top-level fields of the "llm" block beside "model".',
30
+ });
20
31
  // GS2-58: normalise every tool's JSON-Schema at the ChatGoogle boundary so Gemini's OpenAPI-3.0
21
32
  // subset accepts built-in, custom, and MCP tools alike (see geminiSchemaSanitizer).
22
33
  // CFG-33: ask for the thought summaries of the thinking Gemini already does and already bills,
@@ -1 +1 @@
1
- {"version":3,"file":"google-genai.js","sourceRoot":"","sources":["../../src/providers/google-genai.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAC5D,OAAO,EAAE,GAAG,EAAE,MAAM,2BAA2B,CAAC;AAIhD,OAAO,EAAE,2BAA2B,EAAE,MAAM,yBAAyB,CAAC;AACtE,OAAO,EAAE,sBAAsB,EAAE,uBAAuB,EAAE,MAAM,kCAAkC,CAAC;AACnG,OAAO,EAAE,8BAA8B,EAAE,MAAM,yCAAyC,CAAC;AACzF,OAAO,EAAE,2BAA2B,EAAE,MAAM,kCAAkC,CAAC;AAE/E,uEAAuE;AACvE,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,SAAmF;IAEnF,MAAM,EAAE,UAAU,EAAE,GAAG,MAAM,MAAM,CAAC,wBAAwB,CAAC,CAAC;IAC9D,wEAAwE;IACxE,MAAM,YAAY,GAAG,SAAS,CAAC,MAAM,IAAI,GAAG,CAAC,cAAc,CAAC;IAC5D,MAAM,YAAY,GAAG;QACnB,GAAG,SAAS;QACZ,MAAM,EAAE,YAAY;QACpB,KAAK,EAAE,SAAS,CAAC,KAAK,IAAI,uBAAuB,CAAC,cAAc,CAAC;QACjE,YAAY,EAAE,KAAc;KAC7B,CAAC;IACF,OAAO,YAAY,CAAC,IAAI,CAAC;IACzB,OAAO,YAAY,CAAC,yBAAyB,CAAC;IAC9C,gGAAgG;IAChG,oFAAoF;IACpF,+FAA+F;IAC/F,6EAA6E;IAC7E,OAAO,2BAA2B,CAAC,8BAA8B,CAAC,IAAI,UAAU,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;AACnG,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,cAAc,EAAE,KAAK,CAAC,EAAE,KAAK,CAAC,CAAC;IAClG,cAAc,CACZ,2BAA2B,cAAc,qCAAqC;QAC5E,gDAAgD,CACnD,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"google-genai.js","sourceRoot":"","sources":["../../src/providers/google-genai.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAC5D,OAAO,EAAE,GAAG,EAAE,MAAM,2BAA2B,CAAC;AAIhD,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,uEAAuE;AACvE,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,SAAmF;IAEnF,MAAM,EAAE,UAAU,EAAE,GAAG,MAAM,MAAM,CAAC,wBAAwB,CAAC,CAAC;IAC9D,wEAAwE;IACxE,MAAM,YAAY,GAAG,SAAS,CAAC,MAAM,IAAI,GAAG,CAAC,cAAc,CAAC;IAC5D,MAAM,YAAY,GAAG;QACnB,GAAG,SAAS;QACZ,MAAM,EAAE,YAAY;QACpB,KAAK,EAAE,SAAS,CAAC,KAAK,IAAI,uBAAuB,CAAC,cAAc,CAAC;QACjE,YAAY,EAAE,KAAc;KAC7B,CAAC;IACF,OAAO,YAAY,CAAC,IAAI,CAAC;IACzB,OAAO,YAAY,CAAC,yBAAyB,CAAC;IAC9C,4FAA4F;IAC5F,0CAA0C;IAC1C,uBAAuB,CAAC;QACtB,QAAQ,EAAE,cAAc;QACxB,aAAa,EAAG,SAAyC,CAAC,aAAa;QACvE,aAAa,EAAE,EAAE;QACjB,MAAM,EAAE,oBAAoB;QAC5B,QAAQ,EACN,0FAA0F;YAC1F,mFAAmF;KACtF,CAAC,CAAC;IACH,gGAAgG;IAChG,oFAAoF;IACpF,+FAA+F;IAC/F,6EAA6E;IAC7E,OAAO,2BAA2B,CAAC,8BAA8B,CAAC,IAAI,UAAU,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;AACnG,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,cAAc,EAAE,KAAK,CAAC,EAAE,KAAK,CAAC,CAAC;IAClG,cAAc,CACZ,2BAA2B,cAAc,qCAAqC;QAC5E,gDAAgD,CACnD,CAAC;AACJ,CAAC"}
@@ -2,11 +2,23 @@ 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
  // Function to process JSON config and create Groq LLM instance
6
7
  export async function processJsonConfig(llmConfig) {
7
8
  const groq = await import('@langchain/groq');
8
9
  // Use config value if available, otherwise use the environment variable
9
10
  const groqApiKey = llmConfig.apiKey || env.GROQ_API_KEY;
11
+ // `ChatGroq` builds a Groq SDK client from its own TOP-LEVEL fields, so nothing in a
12
+ // `configuration` block reaches it — say so before dropping it.
13
+ warnUnusedConfiguration({
14
+ provider: 'groq',
15
+ configuration: llmConfig.configuration,
16
+ consumedPaths: [],
17
+ reason: NATIVE_CLIENT_REASON,
18
+ guidance: 'ChatGroq builds a Groq SDK client from top-level fields of the "llm" block instead: set ' +
19
+ '"baseUrl" (note the lower-case "url"), "timeout", "defaultHeaders", "defaultQuery", ' +
20
+ '"httpAgent" or "fetch" beside "model".',
21
+ });
10
22
  return new groq.ChatGroq({
11
23
  ...llmConfig,
12
24
  apiKey: groqApiKey,
@@ -1 +1 @@
1
- {"version":3,"file":"groq.js","sourceRoot":"","sources":["../../src/providers/groq.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAC5D,OAAO,EAAE,GAAG,EAAE,MAAM,2BAA2B,CAAC;AAIhD,OAAO,EAAE,2BAA2B,EAAE,MAAM,yBAAyB,CAAC;AACtE,OAAO,EAAE,sBAAsB,EAAE,uBAAuB,EAAE,MAAM,kCAAkC,CAAC;AAEnG,+DAA+D;AAC/D,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,SAAwB;IAC9D,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,iBAAiB,CAAC,CAAC;IAC7C,wEAAwE;IACxE,MAAM,UAAU,GAAG,SAAS,CAAC,MAAM,IAAI,GAAG,CAAC,YAAY,CAAC;IACxD,OAAO,IAAI,IAAI,CAAC,QAAQ,CAAC;QACvB,GAAG,SAAS;QACZ,MAAM,EAAE,UAAU;QAClB,KAAK,EAAE,SAAS,CAAC,KAAK,IAAI,uBAAuB,CAAC,MAAM,CAAC;KAC1D,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,MAAM,EAAE,KAAK,CAAC,EAAE,KAAK,CAAC,CAAC;IAC1F,cAAc,CACZ,yBAAyB,cAAc,uBAAuB;QAC5D,8CAA8C,CACjD,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"groq.js","sourceRoot":"","sources":["../../src/providers/groq.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAC5D,OAAO,EAAE,GAAG,EAAE,MAAM,2BAA2B,CAAC;AAIhD,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,+DAA+D;AAC/D,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,SAAwB;IAC9D,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,iBAAiB,CAAC,CAAC;IAC7C,wEAAwE;IACxE,MAAM,UAAU,GAAG,SAAS,CAAC,MAAM,IAAI,GAAG,CAAC,YAAY,CAAC;IACxD,qFAAqF;IACrF,gEAAgE;IAChE,uBAAuB,CAAC;QACtB,QAAQ,EAAE,MAAM;QAChB,aAAa,EAAG,SAAyC,CAAC,aAAa;QACvE,aAAa,EAAE,EAAE;QACjB,MAAM,EAAE,oBAAoB;QAC5B,QAAQ,EACN,0FAA0F;YAC1F,sFAAsF;YACtF,wCAAwC;KAC3C,CAAC,CAAC;IACH,OAAO,IAAI,IAAI,CAAC,QAAQ,CAAC;QACvB,GAAG,SAAS;QACZ,MAAM,EAAE,UAAU;QAClB,KAAK,EAAE,SAAS,CAAC,KAAK,IAAI,uBAAuB,CAAC,MAAM,CAAC;KAC1D,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,MAAM,EAAE,KAAK,CAAC,EAAE,KAAK,CAAC,CAAC;IAC1F,cAAc,CACZ,yBAAyB,cAAc,uBAAuB;QAC5D,8CAA8C,CACjD,CAAC;AACJ,CAAC"}