@gaunt-sloth/core 2.0.0-alpha.34 → 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.
- package/dist/config/configDiscovery.d.ts +79 -0
- package/dist/config/configDiscovery.js +80 -0
- package/dist/config/configDiscovery.js.map +1 -0
- package/dist/config/loader.d.ts +6 -5
- package/dist/config/loader.js +229 -92
- package/dist/config/loader.js.map +1 -1
- package/dist/config/schema.d.ts +3 -2
- package/dist/config/schema.js +57 -9
- package/dist/config/schema.js.map +1 -1
- package/dist/config/shell-policy.d.ts +58 -26
- package/dist/config/shell-policy.js +60 -27
- package/dist/config/shell-policy.js.map +1 -1
- package/dist/config/tool-descriptions.d.ts +6 -6
- package/dist/config/tool-descriptions.js +7 -7
- package/dist/config/tool-descriptions.js.map +1 -1
- package/dist/config/types.d.ts +34 -31
- package/dist/config/types.js.map +1 -1
- package/dist/config.d.ts +3 -0
- package/dist/config.js +3 -0
- package/dist/config.js.map +1 -1
- package/dist/core/GthAbstractAgent.d.ts +11 -13
- package/dist/core/GthAbstractAgent.js +9 -11
- package/dist/core/GthAbstractAgent.js.map +1 -1
- package/dist/core/GthAgentRunner.d.ts +17 -33
- package/dist/core/GthAgentRunner.js +31 -41
- package/dist/core/GthAgentRunner.js.map +1 -1
- package/dist/core/GthLangChainAgent.js +25 -34
- package/dist/core/GthLangChainAgent.js.map +1 -1
- package/dist/core/approvals/toolAnnotationSources.d.ts +4 -4
- package/dist/core/approvals/toolAnnotationSources.js +8 -8
- package/dist/core/debugCapture.d.ts +2 -3
- package/dist/core/debugCapture.js +1 -2
- package/dist/core/debugCapture.js.map +1 -1
- package/dist/core/gthLeanAgentFactory.d.ts +4 -4
- package/dist/core/gthLeanAgentFactory.js +4 -4
- package/dist/core/reasoningBlocks.d.ts +7 -2
- package/dist/core/reasoningBlocks.js +7 -2
- package/dist/core/reasoningBlocks.js.map +1 -1
- package/dist/core/shell/ShellCommandFailedError.d.ts +3 -4
- package/dist/core/shell/ShellCommandFailedError.js +3 -4
- package/dist/core/shell/ShellCommandFailedError.js.map +1 -1
- package/dist/core/shell/approvalStop.d.ts +68 -29
- package/dist/core/shell/approvalStop.js +178 -28
- package/dist/core/shell/approvalStop.js.map +1 -1
- package/dist/core/shell/hardline.d.ts +22 -0
- package/dist/core/shell/hardline.js +81 -0
- package/dist/core/shell/hardline.js.map +1 -1
- package/dist/core/shell/negotiation.d.ts +81 -2
- package/dist/core/shell/negotiation.js +144 -11
- package/dist/core/shell/negotiation.js.map +1 -1
- package/dist/core/shell/rater.d.ts +7 -69
- package/dist/core/shell/rater.js +5 -24
- package/dist/core/shell/rater.js.map +1 -1
- package/dist/core/shell/raterVocabulary.d.ts +119 -0
- package/dist/core/shell/raterVocabulary.js +114 -0
- package/dist/core/shell/raterVocabulary.js.map +1 -0
- package/dist/core/types.d.ts +17 -3
- package/dist/providers/anthropic.js +12 -0
- package/dist/providers/anthropic.js.map +1 -1
- package/dist/providers/configurationPassthrough.d.ts +107 -0
- package/dist/providers/configurationPassthrough.js +148 -0
- package/dist/providers/configurationPassthrough.js.map +1 -0
- package/dist/providers/geminiThinking.d.ts +8 -0
- package/dist/providers/geminiThinking.js +33 -13
- package/dist/providers/geminiThinking.js.map +1 -1
- package/dist/providers/google-genai.js +11 -0
- package/dist/providers/google-genai.js.map +1 -1
- package/dist/providers/groq.js +12 -0
- package/dist/providers/groq.js.map +1 -1
- package/dist/providers/ollama.js +12 -0
- package/dist/providers/ollama.js.map +1 -1
- package/dist/providers/openrouter.d.ts +23 -0
- package/dist/providers/openrouter.js +71 -4
- package/dist/providers/openrouter.js.map +1 -1
- package/dist/providers/vertexai.js +12 -0
- package/dist/providers/vertexai.js.map +1 -1
- package/dist/providers/xai.js +20 -0
- package/dist/providers/xai.js.map +1 -1
- package/dist/runtime/conversation.d.ts +2 -2
- package/dist/runtime/conversation.js +19 -4
- package/dist/runtime/conversation.js.map +1 -1
- package/dist/runtime/singleShot.d.ts +2 -3
- package/dist/runtime/singleShot.js +22 -7
- package/dist/runtime/singleShot.js.map +1 -1
- package/dist/utils/systemPromptNotes.d.ts +17 -20
- package/dist/utils/systemPromptNotes.js +22 -24
- package/dist/utils/systemPromptNotes.js.map +1 -1
- package/dist/utils/untrustedText.d.ts +24 -4
- package/dist/utils/untrustedText.js +25 -4
- package/dist/utils/untrustedText.js.map +1 -1
- package/package.json +4 -4
- 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"}
|
package/dist/core/types.d.ts
CHANGED
|
@@ -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
|
-
*
|
|
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
|
|
3
|
-
* send any thinking config for a 2.5 image model, so injecting one there would
|
|
4
|
-
* library deliberately withheld; image/tts generations have no reasoning panel to
|
|
5
|
-
* Skipping them keeps
|
|
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
|
-
|
|
41
|
-
|
|
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)
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
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'
|
|
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
|
|
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;
|
|
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"}
|
package/dist/providers/groq.js
CHANGED
|
@@ -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;
|
|
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"}
|