@gaunt-sloth/core 2.0.0-alpha.3 → 2.0.0-alpha.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +52 -20
- package/dist/config/colour.d.ts +38 -0
- package/dist/config/colour.js +36 -0
- package/dist/config/colour.js.map +1 -0
- package/dist/config/defaults.d.ts +84 -0
- package/dist/config/defaults.js +99 -0
- package/dist/config/defaults.js.map +1 -0
- package/dist/config/filesystem-tools.d.ts +41 -0
- package/dist/config/filesystem-tools.js +56 -0
- package/dist/config/filesystem-tools.js.map +1 -0
- package/dist/config/jsonc.d.ts +12 -0
- package/dist/config/jsonc.js +41 -0
- package/dist/config/jsonc.js.map +1 -0
- package/dist/config/loader.d.ts +246 -0
- package/dist/config/loader.js +1321 -0
- package/dist/config/loader.js.map +1 -0
- package/dist/config/mouse.d.ts +50 -0
- package/dist/config/mouse.js +44 -0
- package/dist/config/mouse.js.map +1 -0
- package/dist/config/profiles.d.ts +68 -0
- package/dist/config/profiles.js +93 -0
- package/dist/config/profiles.js.map +1 -0
- package/dist/config/providerKeys.d.ts +69 -0
- package/dist/config/providerKeys.js +69 -0
- package/dist/config/providerKeys.js.map +1 -0
- package/dist/config/schema.d.ts +2999 -0
- package/dist/config/schema.js +1519 -0
- package/dist/config/schema.js.map +1 -0
- package/dist/config/shell-policy.d.ts +896 -0
- package/dist/config/shell-policy.js +750 -0
- package/dist/config/shell-policy.js.map +1 -0
- package/dist/config/tool-descriptions.d.ts +211 -0
- package/dist/config/tool-descriptions.js +272 -0
- package/dist/config/tool-descriptions.js.map +1 -0
- package/dist/config/types.d.ts +760 -0
- package/dist/config/types.js +13 -0
- package/dist/config/types.js.map +1 -0
- package/dist/config.d.ts +34 -827
- package/dist/config.js +25 -657
- package/dist/config.js.map +1 -1
- package/dist/constants.d.ts +46 -0
- package/dist/constants.js +46 -0
- package/dist/constants.js.map +1 -1
- package/dist/core/GthAbstractAgent.d.ts +135 -2
- package/dist/core/GthAbstractAgent.js +496 -30
- package/dist/core/GthAbstractAgent.js.map +1 -1
- package/dist/core/GthAgentRunner.d.ts +568 -54
- package/dist/core/GthAgentRunner.js +1537 -136
- package/dist/core/GthAgentRunner.js.map +1 -1
- package/dist/core/GthLangChainAgent.d.ts +115 -0
- package/dist/core/GthLangChainAgent.js +644 -17
- package/dist/core/GthLangChainAgent.js.map +1 -1
- package/dist/core/approvals/annotations.d.ts +122 -0
- package/dist/core/approvals/annotations.js +137 -0
- package/dist/core/approvals/annotations.js.map +1 -0
- package/dist/core/approvals/grants.d.ts +216 -0
- package/dist/core/approvals/grants.js +469 -0
- package/dist/core/approvals/grants.js.map +1 -0
- package/dist/core/approvals/matcher.d.ts +202 -0
- package/dist/core/approvals/matcher.js +267 -0
- package/dist/core/approvals/matcher.js.map +1 -0
- package/dist/core/approvals/mcpSubjects.d.ts +40 -0
- package/dist/core/approvals/mcpSubjects.js +99 -0
- package/dist/core/approvals/mcpSubjects.js.map +1 -0
- package/dist/core/approvals/toolAnnotationSources.d.ts +105 -0
- package/dist/core/approvals/toolAnnotationSources.js +277 -0
- package/dist/core/approvals/toolAnnotationSources.js.map +1 -0
- package/dist/core/approvals/toolHost.d.ts +46 -0
- package/dist/core/approvals/toolHost.js +108 -0
- package/dist/core/approvals/toolHost.js.map +1 -0
- package/dist/core/debugCapture.d.ts +75 -0
- package/dist/core/debugCapture.js +101 -0
- package/dist/core/debugCapture.js.map +1 -0
- package/dist/core/gthLeanAgentFactory.d.ts +9 -0
- package/dist/core/gthLeanAgentFactory.js +10 -0
- package/dist/core/gthLeanAgentFactory.js.map +1 -0
- package/dist/core/launchBanner.d.ts +120 -0
- package/dist/core/launchBanner.js +418 -0
- package/dist/core/launchBanner.js.map +1 -0
- package/dist/core/plainToolIndication.d.ts +15 -0
- package/dist/core/plainToolIndication.js +174 -0
- package/dist/core/plainToolIndication.js.map +1 -0
- package/dist/core/reasoningBlocks.d.ts +60 -0
- package/dist/core/reasoningBlocks.js +98 -0
- package/dist/core/reasoningBlocks.js.map +1 -0
- package/dist/core/refusal.d.ts +53 -0
- package/dist/core/refusal.js +133 -0
- package/dist/core/refusal.js.map +1 -0
- package/dist/core/runStats.d.ts +52 -0
- package/dist/core/runStats.js +118 -0
- package/dist/core/runStats.js.map +1 -0
- package/dist/core/shell/ShellCommandFailedError.d.ts +54 -0
- package/dist/core/shell/ShellCommandFailedError.js +68 -0
- package/dist/core/shell/ShellCommandFailedError.js.map +1 -0
- package/dist/core/shell/abstention.d.ts +88 -0
- package/dist/core/shell/abstention.js +184 -0
- package/dist/core/shell/abstention.js.map +1 -0
- package/dist/core/shell/approvalCapture.d.ts +271 -0
- package/dist/core/shell/approvalCapture.js +108 -0
- package/dist/core/shell/approvalCapture.js.map +1 -0
- package/dist/core/shell/approvalStop.d.ts +84 -0
- package/dist/core/shell/approvalStop.js +119 -0
- package/dist/core/shell/approvalStop.js.map +1 -0
- package/dist/core/shell/arity.d.ts +6 -0
- package/dist/core/shell/arity.js +20 -6
- package/dist/core/shell/arity.js.map +1 -1
- package/dist/core/shell/denylist.d.ts +11 -0
- package/dist/core/shell/denylist.js +37 -0
- package/dist/core/shell/denylist.js.map +1 -0
- package/dist/core/shell/escalationSeverity.d.ts +141 -0
- package/dist/core/shell/escalationSeverity.js +89 -0
- package/dist/core/shell/escalationSeverity.js.map +1 -0
- package/dist/core/shell/framing.d.ts +190 -0
- package/dist/core/shell/framing.js +633 -0
- package/dist/core/shell/framing.js.map +1 -0
- package/dist/core/shell/hardline.d.ts +81 -0
- package/dist/core/shell/hardline.js +607 -0
- package/dist/core/shell/hardline.js.map +1 -0
- package/dist/core/shell/negotiation.d.ts +249 -0
- package/dist/core/shell/negotiation.js +355 -0
- package/dist/core/shell/negotiation.js.map +1 -0
- package/dist/core/shell/normalize.d.ts +44 -4
- package/dist/core/shell/normalize.js +61 -7
- package/dist/core/shell/normalize.js.map +1 -1
- package/dist/core/shell/openWorld.d.ts +263 -0
- package/dist/core/shell/openWorld.js +1188 -0
- package/dist/core/shell/openWorld.js.map +1 -0
- package/dist/core/shell/rater.d.ts +935 -0
- package/dist/core/shell/rater.js +1473 -0
- package/dist/core/shell/rater.js.map +1 -0
- package/dist/core/shell/raterModel.d.ts +41 -0
- package/dist/core/shell/raterModel.js +51 -0
- package/dist/core/shell/raterModel.js.map +1 -0
- package/dist/core/shell/rejection.d.ts +69 -0
- package/dist/core/shell/rejection.js +38 -0
- package/dist/core/shell/rejection.js.map +1 -0
- package/dist/core/toolCallRepair/grammar.d.ts +41 -0
- package/dist/core/toolCallRepair/grammar.js +116 -0
- package/dist/core/toolCallRepair/grammar.js.map +1 -0
- package/dist/core/toolCallRepair/index.d.ts +2 -0
- package/dist/core/toolCallRepair/index.js +7 -0
- package/dist/core/toolCallRepair/index.js.map +1 -0
- package/dist/core/toolCallRepair/payload.d.ts +36 -0
- package/dist/core/toolCallRepair/payload.js +341 -0
- package/dist/core/toolCallRepair/payload.js.map +1 -0
- package/dist/core/toolCallRepair/promote.d.ts +45 -0
- package/dist/core/toolCallRepair/promote.js +90 -0
- package/dist/core/toolCallRepair/promote.js.map +1 -0
- package/dist/core/toolDisplay.d.ts +123 -0
- package/dist/core/toolDisplay.js +451 -0
- package/dist/core/toolDisplay.js.map +1 -0
- package/dist/core/toolOutputChannel.d.ts +95 -0
- package/dist/core/toolOutputChannel.js +165 -0
- package/dist/core/toolOutputChannel.js.map +1 -0
- package/dist/core/types.d.ts +294 -12
- package/dist/core/types.js.map +1 -1
- package/dist/history/historyFormat.d.ts +28 -0
- package/dist/history/historyFormat.js +127 -0
- package/dist/history/historyFormat.js.map +1 -0
- package/dist/history/historyStore.d.ts +198 -0
- package/dist/history/historyStore.js +482 -0
- package/dist/history/historyStore.js.map +1 -0
- package/dist/history/recordSession.d.ts +37 -0
- package/dist/history/recordSession.js +56 -0
- package/dist/history/recordSession.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/providers/anthropic.d.ts +1 -1
- package/dist/providers/anthropic.js +5 -10
- package/dist/providers/anthropic.js.map +1 -1
- package/dist/providers/deepseek.d.ts +1 -1
- package/dist/providers/deepseek.js +5 -10
- package/dist/providers/deepseek.js.map +1 -1
- package/dist/providers/geminiSchemaSanitizer.d.ts +55 -0
- package/dist/providers/geminiSchemaSanitizer.js +347 -0
- package/dist/providers/geminiSchemaSanitizer.js.map +1 -0
- package/dist/providers/geminiThinking.d.ts +52 -0
- package/dist/providers/geminiThinking.js +72 -0
- package/dist/providers/geminiThinking.js.map +1 -0
- package/dist/providers/google-genai.d.ts +1 -1
- package/dist/providers/google-genai.js +12 -11
- package/dist/providers/google-genai.js.map +1 -1
- package/dist/providers/groq.d.ts +1 -1
- package/dist/providers/groq.js +5 -10
- package/dist/providers/groq.js.map +1 -1
- package/dist/providers/huggingface.d.ts +25 -0
- package/dist/providers/huggingface.js +69 -0
- package/dist/providers/huggingface.js.map +1 -0
- package/dist/providers/modelCatalog.d.ts +109 -0
- package/dist/providers/modelCatalog.js +245 -0
- package/dist/providers/modelCatalog.js.map +1 -0
- package/dist/providers/modelDiscovery.d.ts +153 -6
- package/dist/providers/modelDiscovery.js +295 -41
- package/dist/providers/modelDiscovery.js.map +1 -1
- package/dist/providers/ollama.d.ts +19 -5
- package/dist/providers/ollama.js +60 -52
- package/dist/providers/ollama.js.map +1 -1
- package/dist/providers/openai.d.ts +1 -1
- package/dist/providers/openai.js +39 -10
- package/dist/providers/openai.js.map +1 -1
- package/dist/providers/openrouter.d.ts +4 -5
- package/dist/providers/openrouter.js +20 -35
- package/dist/providers/openrouter.js.map +1 -1
- package/dist/providers/vertexai.d.ts +1 -1
- package/dist/providers/vertexai.js +12 -11
- package/dist/providers/vertexai.js.map +1 -1
- package/dist/providers/xai.d.ts +1 -1
- package/dist/providers/xai.js +5 -10
- package/dist/providers/xai.js.map +1 -1
- package/dist/runtime/askStructured.d.ts +105 -0
- package/dist/runtime/askStructured.js +120 -0
- package/dist/runtime/askStructured.js.map +1 -0
- package/dist/runtime/conversation.d.ts +60 -0
- package/dist/runtime/conversation.js +151 -0
- package/dist/runtime/conversation.js.map +1 -0
- package/dist/runtime/singleShot.d.ts +25 -5
- package/dist/runtime/singleShot.js +95 -36
- package/dist/runtime/singleShot.js.map +1 -1
- package/dist/runtime/structuredOutput.d.ts +104 -0
- package/dist/runtime/structuredOutput.js +393 -0
- package/dist/runtime/structuredOutput.js.map +1 -0
- package/dist/utils/ProgressIndicator.d.ts +21 -0
- package/dist/utils/ProgressIndicator.js +30 -3
- package/dist/utils/ProgressIndicator.js.map +1 -1
- package/dist/utils/aiignoreUtils.js.map +1 -1
- package/dist/utils/binaryOutputUtils.js.map +1 -1
- package/dist/utils/consoleUtils.d.ts +52 -0
- package/dist/utils/consoleUtils.js +72 -2
- package/dist/utils/consoleUtils.js.map +1 -1
- package/dist/utils/crashHandler.d.ts +87 -0
- package/dist/utils/crashHandler.js +128 -0
- package/dist/utils/crashHandler.js.map +1 -0
- package/dist/utils/debugDump.d.ts +134 -0
- package/dist/utils/debugDump.js +381 -0
- package/dist/utils/debugDump.js.map +1 -0
- package/dist/utils/debugUtils.d.ts +13 -4
- package/dist/utils/debugUtils.js +36 -13
- package/dist/utils/debugUtils.js.map +1 -1
- package/dist/utils/displayWidth.d.ts +53 -0
- package/dist/utils/displayWidth.js +195 -0
- package/dist/utils/displayWidth.js.map +1 -0
- package/dist/utils/fileUtils.d.ts +24 -2
- package/dist/utils/fileUtils.js +54 -12
- package/dist/utils/fileUtils.js.map +1 -1
- package/dist/utils/llmUtils.d.ts +39 -8
- package/dist/utils/llmUtils.js +76 -8
- package/dist/utils/llmUtils.js.map +1 -1
- package/dist/utils/redactSecrets.d.ts +63 -0
- package/dist/utils/redactSecrets.js +286 -0
- package/dist/utils/redactSecrets.js.map +1 -0
- package/dist/utils/systemPromptNotes.d.ts +225 -0
- package/dist/utils/systemPromptNotes.js +340 -0
- package/dist/utils/systemPromptNotes.js.map +1 -0
- package/dist/utils/systemUtils.d.ts +49 -1
- package/dist/utils/systemUtils.js +67 -3
- package/dist/utils/systemUtils.js.map +1 -1
- package/dist/utils/toolMatching.d.ts +30 -0
- package/dist/utils/toolMatching.js +44 -0
- package/dist/utils/toolMatching.js.map +1 -0
- package/dist/utils/untrustedText.d.ts +66 -0
- package/dist/utils/untrustedText.js +80 -0
- package/dist/utils/untrustedText.js.map +1 -0
- package/package.json +22 -6
- package/schema/gsloth-config.schema.json +3134 -0
- package/dist/core/shell/allowlist.d.ts +0 -75
- package/dist/core/shell/allowlist.js +0 -187
- package/dist/core/shell/allowlist.js.map +0 -1
- package/dist/core/shell/judge.d.ts +0 -161
- package/dist/core/shell/judge.js +0 -261
- package/dist/core/shell/judge.js.map +0 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { displayWarning } from '#src/utils/consoleUtils.js';
|
|
2
|
-
import {
|
|
2
|
+
import { writeConfigFileWithMessages } from '#src/utils/fileUtils.js';
|
|
3
|
+
import { buildInitConfigContent, getCuratedFallbackModel } from '#src/providers/modelDiscovery.js';
|
|
3
4
|
import { env } from '#src/utils/systemUtils.js';
|
|
4
5
|
/**
|
|
5
6
|
* Function to process JSON config and create Anthropic LLM instance
|
|
@@ -12,22 +13,16 @@ export async function processJsonConfig(llmConfig) {
|
|
|
12
13
|
return new anthropic.ChatAnthropic({
|
|
13
14
|
...llmConfig,
|
|
14
15
|
apiKey: anthropicApiKey,
|
|
15
|
-
model: llmConfig.model || '
|
|
16
|
+
model: llmConfig.model || getCuratedFallbackModel('anthropic'),
|
|
16
17
|
});
|
|
17
18
|
}
|
|
18
|
-
const jsonContent = `{
|
|
19
|
-
"llm": {
|
|
20
|
-
"type": "anthropic",
|
|
21
|
-
"model": "claude-sonnet-4-6"
|
|
22
|
-
}
|
|
23
|
-
}`;
|
|
24
19
|
// noinspection JSUnusedGlobalSymbols
|
|
25
|
-
export function init(configFileName) {
|
|
20
|
+
export function init(configFileName, force = false, model) {
|
|
26
21
|
// Determine which content to use based on file extension
|
|
27
22
|
if (!configFileName.endsWith('.json')) {
|
|
28
23
|
throw new Error('Only JSON config is supported.');
|
|
29
24
|
}
|
|
30
|
-
|
|
25
|
+
writeConfigFileWithMessages(configFileName, buildInitConfigContent('anthropic', model), force);
|
|
31
26
|
displayWarning(`You need to update your ${configFileName} to add your Anthropic API key, ` +
|
|
32
27
|
'or define ANTHROPIC_API_KEY environment variable.');
|
|
33
28
|
}
|
|
@@ -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,
|
|
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,4 +1,4 @@
|
|
|
1
1
|
import type { BaseChatModel, BaseChatModelParams } from '@langchain/core/language_models/chat_models';
|
|
2
2
|
import { ChatDeepSeekInput } from '@langchain/deepseek';
|
|
3
3
|
export declare function processJsonConfig(llmConfig: ChatDeepSeekInput & BaseChatModelParams): Promise<BaseChatModel>;
|
|
4
|
-
export declare function init(configFileName: string): void;
|
|
4
|
+
export declare function init(configFileName: string, force?: boolean, model?: string): void;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { displayWarning } from '#src/utils/consoleUtils.js';
|
|
2
2
|
import { env } from '#src/utils/systemUtils.js';
|
|
3
|
-
import {
|
|
3
|
+
import { writeConfigFileWithMessages } from '#src/utils/fileUtils.js';
|
|
4
|
+
import { buildInitConfigContent, getCuratedFallbackModel } from '#src/providers/modelDiscovery.js';
|
|
4
5
|
// Function to process JSON config and create DeepSeek LLM instance
|
|
5
6
|
export async function processJsonConfig(llmConfig) {
|
|
6
7
|
const deepseek = await import('@langchain/deepseek');
|
|
@@ -9,21 +10,15 @@ export async function processJsonConfig(llmConfig) {
|
|
|
9
10
|
return new deepseek.ChatDeepSeek({
|
|
10
11
|
...llmConfig,
|
|
11
12
|
apiKey: deepseekApiKey,
|
|
12
|
-
model: llmConfig.model || 'deepseek
|
|
13
|
+
model: llmConfig.model || getCuratedFallbackModel('deepseek'),
|
|
13
14
|
});
|
|
14
15
|
}
|
|
15
|
-
|
|
16
|
-
"llm": {
|
|
17
|
-
"type": "deepseek",
|
|
18
|
-
"model": "deepseek-v4-pro"
|
|
19
|
-
}
|
|
20
|
-
}`;
|
|
21
|
-
export function init(configFileName) {
|
|
16
|
+
export function init(configFileName, force = false, model) {
|
|
22
17
|
// Determine which content to use based on file extension
|
|
23
18
|
if (!configFileName.endsWith('.json')) {
|
|
24
19
|
throw new Error('Only JSON config is supported.');
|
|
25
20
|
}
|
|
26
|
-
|
|
21
|
+
writeConfigFileWithMessages(configFileName, buildInitConfigContent('deepseek', model), force);
|
|
27
22
|
displayWarning(`You need to update your ${configFileName} to add your DeepSeek API key, ` +
|
|
28
23
|
'or define DEEPSEEK_API_KEY environment variable.');
|
|
29
24
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deepseek.js","sourceRoot":"","sources":["../../src/providers/deepseek.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAC5D,OAAO,EAAE,GAAG,EAAE,MAAM,2BAA2B,CAAC;AAOhD,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"deepseek.js","sourceRoot":"","sources":["../../src/providers/deepseek.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAC5D,OAAO,EAAE,GAAG,EAAE,MAAM,2BAA2B,CAAC;AAOhD,OAAO,EAAE,2BAA2B,EAAE,MAAM,yBAAyB,CAAC;AACtE,OAAO,EAAE,sBAAsB,EAAE,uBAAuB,EAAE,MAAM,kCAAkC,CAAC;AAEnG,mEAAmE;AACnE,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,SAAkD;IAElD,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,qBAAqB,CAAC,CAAC;IACrD,yEAAyE;IACzE,MAAM,cAAc,GAAG,SAAS,CAAC,MAAM,IAAI,GAAG,CAAC,gBAAgB,CAAC;IAChE,OAAO,IAAI,QAAQ,CAAC,YAAY,CAAC;QAC/B,GAAG,SAAS;QACZ,MAAM,EAAE,cAAc;QACtB,KAAK,EAAE,SAAS,CAAC,KAAK,IAAI,uBAAuB,CAAC,UAAU,CAAC;KAC9D,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,UAAU,EAAE,KAAK,CAAC,EAAE,KAAK,CAAC,CAAC;IAC9F,cAAc,CACZ,2BAA2B,cAAc,iCAAiC;QACxE,kDAAkD,CACrD,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @packageDocumentation
|
|
3
|
+
* GS2-58 — systemic Gemini tool-schema sanitizer at the `@langchain/google` provider boundary.
|
|
4
|
+
*
|
|
5
|
+
* Google Gemini's function-declaration schema is a SELECT SUBSET of OpenAPI 3.0. `@langchain/google`'s
|
|
6
|
+
* own `removeAdditionalProperties` strips only `additionalProperties`, so every other JSON-Schema-draft
|
|
7
|
+
* keyword the subset does not declare (`exclusiveMinimum`/`exclusiveMaximum`/`multipleOf`, `$defs`,
|
|
8
|
+
* `patternProperties`, `const`, `$ref`, `allOf`/`oneOf`/`not`, …) is passed straight to the wire, and
|
|
9
|
+
* Gemini 400s at tool-declaration send time — before any tool runs.
|
|
10
|
+
*
|
|
11
|
+
* This is the DURABLE fix (GS2-58, fix-cycle 1): rather than a denylist that is always one unknown
|
|
12
|
+
* keyword behind, {@link sanitizeGeminiToolSchema} is an ALLOWLIST — it keeps ONLY the fields the
|
|
13
|
+
* installed Gemini `Schema` type declares and drops everything else, so a future keyword in ANY tool
|
|
14
|
+
* (built-in, custom, or MCP — including schemas gaunt-sloth does not author) cannot re-break Gemini.
|
|
15
|
+
*
|
|
16
|
+
* The allowlist ({@link GEMINI_SUPPORTED_SCHEMA_KEYWORDS}) is derived DIRECTLY from the authoritative
|
|
17
|
+
* in-repo type — the `Gemini.Tools.Schema` interface in
|
|
18
|
+
* `node_modules/@langchain/google/dist/chat_models/api-types.d.{ts,cts}` (a `FunctionDeclaration`'s
|
|
19
|
+
* `parameters?: Schema`), "a select subset of an OpenAPI 3.0 schema object". Keeping it aligned with
|
|
20
|
+
* that type (not a remembered list) is what prevents a stale allowlist silently over-stripping.
|
|
21
|
+
*
|
|
22
|
+
* Scope is the google/gemini provider path ONLY: {@link applyGeminiToolSchemaSanitizer} is wired into
|
|
23
|
+
* the `google-genai` and `vertexai` presets' `processJsonConfig`. It leaves OpenAI/Anthropic/Ollama
|
|
24
|
+
* wiring untouched, and does not weaken the GS2-56/57 build-time denylist guard (that test still runs;
|
|
25
|
+
* this transform runs ahead of the wire send, so a sanitized tool is what Gemini sees).
|
|
26
|
+
*/
|
|
27
|
+
import type { BaseChatModel } from '@langchain/core/language_models/chat_models';
|
|
28
|
+
/**
|
|
29
|
+
* The EXACT set of schema keywords Gemini's function-declaration schema accepts, transcribed field-
|
|
30
|
+
* for-field from the `Gemini.Tools.Schema` interface in
|
|
31
|
+
* `@langchain/google/dist/chat_models/api-types.d.ts`. Anything not in this set is dropped (allowlist).
|
|
32
|
+
*
|
|
33
|
+
* Note `anyOf` IS supported (unions / nullable) and MUST survive — only `allOf`/`oneOf`/`not` are
|
|
34
|
+
* absent from the type and therefore dropped. `exclusiveMinimum`/`exclusiveMaximum` are NOT in the type
|
|
35
|
+
* either; they are handled specially by rewriting them to `minimum`/`maximum` (see {@link sanitizeNode})
|
|
36
|
+
* before the allowlist filter runs.
|
|
37
|
+
*/
|
|
38
|
+
export declare const GEMINI_SUPPORTED_SCHEMA_KEYWORDS: ReadonlySet<string>;
|
|
39
|
+
/**
|
|
40
|
+
* Pure, recursive normaliser: returns a cleaned DEEP COPY of a JSON-Schema containing only keywords
|
|
41
|
+
* Gemini's function-declaration `Schema` accepts, so its OpenAPI-3.0 subset accepts the tool. At each
|
|
42
|
+
* node the SAFE composition keywords are first RESOLVED into supported equivalents
|
|
43
|
+
* ({@link resolveComposition}: `const` → `enum`, clean `allOf` → shallow merge) and only then does the
|
|
44
|
+
* allowlist drop the rest. Does not mutate the input. `anyOf` unions survive; `$ref`/`oneOf`/`not` are
|
|
45
|
+
* dropped (see {@link resolveComposition} for why the last three are deferred, not resolved).
|
|
46
|
+
*/
|
|
47
|
+
export declare function sanitizeGeminiToolSchema<T = unknown>(schema: T): T;
|
|
48
|
+
/**
|
|
49
|
+
* Wire the sanitizer into a ChatGoogle model at the tool-binding boundary. Overrides the instance's
|
|
50
|
+
* `bindTools` so every tool passed to it — built-in, custom, or MCP, via `createAgent`/`createDeepAgent`
|
|
51
|
+
* which both call `model.bindTools(tools)` — is sanitized before it reaches `@langchain/google`'s
|
|
52
|
+
* Gemini converter. Provider-scoped: only the google-genai/vertexai presets call this, so no other
|
|
53
|
+
* provider's tools are affected. Returns the same model instance for convenient chaining.
|
|
54
|
+
*/
|
|
55
|
+
export declare function applyGeminiToolSchemaSanitizer<T extends BaseChatModel>(model: T): T;
|
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
import { toJsonSchema } from '@langchain/core/utils/json_schema';
|
|
2
|
+
import { isInteropZodSchema } from '@langchain/core/utils/types';
|
|
3
|
+
import { isSerializableSchema } from '@langchain/core/utils/standard_schema';
|
|
4
|
+
/**
|
|
5
|
+
* The EXACT set of schema keywords Gemini's function-declaration schema accepts, transcribed field-
|
|
6
|
+
* for-field from the `Gemini.Tools.Schema` interface in
|
|
7
|
+
* `@langchain/google/dist/chat_models/api-types.d.ts`. Anything not in this set is dropped (allowlist).
|
|
8
|
+
*
|
|
9
|
+
* Note `anyOf` IS supported (unions / nullable) and MUST survive — only `allOf`/`oneOf`/`not` are
|
|
10
|
+
* absent from the type and therefore dropped. `exclusiveMinimum`/`exclusiveMaximum` are NOT in the type
|
|
11
|
+
* either; they are handled specially by rewriting them to `minimum`/`maximum` (see {@link sanitizeNode})
|
|
12
|
+
* before the allowlist filter runs.
|
|
13
|
+
*/
|
|
14
|
+
export const GEMINI_SUPPORTED_SCHEMA_KEYWORDS = new Set([
|
|
15
|
+
'anyOf',
|
|
16
|
+
'default',
|
|
17
|
+
'description',
|
|
18
|
+
'enum',
|
|
19
|
+
'example',
|
|
20
|
+
'format',
|
|
21
|
+
'items',
|
|
22
|
+
'maxItems',
|
|
23
|
+
'maxLength',
|
|
24
|
+
'maxProperties',
|
|
25
|
+
'maximum',
|
|
26
|
+
'minItems',
|
|
27
|
+
'minLength',
|
|
28
|
+
'minProperties',
|
|
29
|
+
'minimum',
|
|
30
|
+
'nullable',
|
|
31
|
+
'pattern',
|
|
32
|
+
'properties',
|
|
33
|
+
'propertyOrdering',
|
|
34
|
+
'required',
|
|
35
|
+
'title',
|
|
36
|
+
'type',
|
|
37
|
+
]);
|
|
38
|
+
function isPlainObject(value) {
|
|
39
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
40
|
+
}
|
|
41
|
+
/** Deep-clone a literal (JSON-schema data) value so the returned schema never aliases the input. */
|
|
42
|
+
function cloneLiteral(value) {
|
|
43
|
+
if (value === null || typeof value !== 'object')
|
|
44
|
+
return value;
|
|
45
|
+
try {
|
|
46
|
+
return structuredClone(value);
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
// Non-cloneable (functions/symbols) never appear in JSON-schema data; keep the reference.
|
|
50
|
+
return value;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/** Structural equality by JSON serialisation. Used only for CONSERVATIVE conflict detection during
|
|
54
|
+
* an `allOf` merge: a false "not equal" (e.g. key-order differences) merely makes the merge abort to
|
|
55
|
+
* the safe drop, never produces an unsound merge — so a best-effort compare is sufficient here. */
|
|
56
|
+
function jsonEqual(a, b) {
|
|
57
|
+
return JSON.stringify(a) === JSON.stringify(b);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* A `const` is resolved to `enum` ONLY when its value is a SCALAR (string / number / boolean / null).
|
|
61
|
+
* An object- or array-valued `const` is deliberately NOT resolved: synthesising an object/array-valued
|
|
62
|
+
* `enum` is a plausible-but-unverified Gemini 400 path, so we leave it to fall through to the safe
|
|
63
|
+
* allowlist drop (typeless `{}`) — exactly the prior GS2-58 behaviour, never widened.
|
|
64
|
+
*/
|
|
65
|
+
function isScalarConst(value) {
|
|
66
|
+
return (value === null ||
|
|
67
|
+
typeof value === 'string' ||
|
|
68
|
+
typeof value === 'number' ||
|
|
69
|
+
typeof value === 'boolean');
|
|
70
|
+
}
|
|
71
|
+
/** Infer the Gemini `type` for a SCALAR `const` value when the schema declares none. Numbers map to
|
|
72
|
+
* the general `number` (integer vs number is not "obvious" from a literal); `null` yields no type. */
|
|
73
|
+
function inferTypeFromConst(value) {
|
|
74
|
+
switch (typeof value) {
|
|
75
|
+
case 'string':
|
|
76
|
+
return 'string';
|
|
77
|
+
case 'boolean':
|
|
78
|
+
return 'boolean';
|
|
79
|
+
case 'number':
|
|
80
|
+
return 'number';
|
|
81
|
+
default:
|
|
82
|
+
return undefined; // null (the only other scalar reaching here) → no type
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Attempt a shallow merge of `node.allOf` (a list of subschemas) into the parent node. Gemini has no
|
|
87
|
+
* `allOf`, so GS2-58 simply drops it — losing any content carried only by the branches. When every
|
|
88
|
+
* branch is a plain-object subschema that merges CLEANLY, we fold it in instead:
|
|
89
|
+
* - `properties` are unioned (a property name appearing in the parent or two branches with DIFFERENT
|
|
90
|
+
* schemas is a conflict → abort);
|
|
91
|
+
* - `required` arrays are unioned;
|
|
92
|
+
* - any other (scalar) keyword is copied, but a key already set — on the parent or an earlier branch —
|
|
93
|
+
* to a DIFFERENT value is a conflict → abort.
|
|
94
|
+
* On ANY conflict, or if a branch is not a plain object (a boolean subschema `true`/`false`), returns
|
|
95
|
+
* `null` so the caller leaves `allOf` in place for the allowlist to drop — the safe GS2-58 behaviour,
|
|
96
|
+
* never a guessed merge. Note a branch carrying an UNRESOLVED keyword (e.g. `{ $ref: '#/…' }`) is still
|
|
97
|
+
* a plain object, so the merge PROCEEDS: the other branches merge and the `$ref` is copied onto the
|
|
98
|
+
* parent, where the allowlist then drops it — still safe, and higher-fidelity than dropping every
|
|
99
|
+
* branch. Returns a fresh object (never mutates `node`) with `allOf` removed on success.
|
|
100
|
+
*/
|
|
101
|
+
function mergeAllOf(node) {
|
|
102
|
+
const branches = node.allOf;
|
|
103
|
+
if (!Array.isArray(branches) || branches.length === 0)
|
|
104
|
+
return null;
|
|
105
|
+
if (!branches.every(isPlainObject))
|
|
106
|
+
return null;
|
|
107
|
+
const acc = { ...node };
|
|
108
|
+
delete acc.allOf;
|
|
109
|
+
const props = isPlainObject(acc.properties) ? { ...acc.properties } : {};
|
|
110
|
+
let sawProps = isPlainObject(acc.properties);
|
|
111
|
+
const required = new Set(Array.isArray(acc.required) ? acc.required : []);
|
|
112
|
+
let sawRequired = Array.isArray(acc.required);
|
|
113
|
+
for (const branch of branches) {
|
|
114
|
+
for (const [key, value] of Object.entries(branch)) {
|
|
115
|
+
if (key === 'properties') {
|
|
116
|
+
if (!isPlainObject(value))
|
|
117
|
+
return null;
|
|
118
|
+
for (const [name, sub] of Object.entries(value)) {
|
|
119
|
+
if (name in props && !jsonEqual(props[name], sub))
|
|
120
|
+
return null; // conflicting property
|
|
121
|
+
props[name] = sub;
|
|
122
|
+
}
|
|
123
|
+
sawProps = true;
|
|
124
|
+
}
|
|
125
|
+
else if (key === 'required') {
|
|
126
|
+
if (Array.isArray(value))
|
|
127
|
+
for (const r of value)
|
|
128
|
+
required.add(r);
|
|
129
|
+
sawRequired = true;
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
// scalar / other keyword: parent and every branch must agree on it.
|
|
133
|
+
if (key in acc && !jsonEqual(acc[key], value))
|
|
134
|
+
return null;
|
|
135
|
+
acc[key] = value;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
if (sawProps)
|
|
140
|
+
acc.properties = props;
|
|
141
|
+
if (sawRequired)
|
|
142
|
+
acc.required = [...required];
|
|
143
|
+
return acc;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Resolve the SAFE structural-composition keywords Gemini rejects into supported equivalents, BEFORE
|
|
147
|
+
* {@link sanitizeNode}'s allowlist drops the rest. Its output feeds the same allowlist + `exclusive*`
|
|
148
|
+
* rewrite pass, so merged-in `properties`/`items`/`anyOf` are still recursed and a merged-in
|
|
149
|
+
* `exclusiveMinimum` is still rewritten. A strict NO-OP (returns the input node) when the node carries
|
|
150
|
+
* none of the handled keywords, so clean schemas pass through byte-identical.
|
|
151
|
+
*
|
|
152
|
+
* Implemented (high-fidelity, no external context needed):
|
|
153
|
+
* - SCALAR `const` → `enum: [value]` (+ infer `type` from the value when the node declares none).
|
|
154
|
+
* Gemini has no `const` but supports `enum`; a single-value `enum` is an exact model of `const`.
|
|
155
|
+
* Presence (`'const' in node`), not truthiness, so `const: 0 / false / '' / null` resolve too.
|
|
156
|
+
* An OBJECT/ARRAY-valued `const` is NOT resolved (see {@link isScalarConst}) — it falls through to
|
|
157
|
+
* the safe allowlist drop (typeless `{}`), never widening into an unverified object-`enum` 400 path.
|
|
158
|
+
* - `allOf` of plain-object branches → shallow-merged when clean (see {@link mergeAllOf}); otherwise
|
|
159
|
+
* left for the allowlist to drop.
|
|
160
|
+
*
|
|
161
|
+
* Deliberately NOT resolved — kept as the safe GS2-58 drop (see the GS2-68 characterization tests):
|
|
162
|
+
* - `oneOf` / `not`: `oneOf` is XOR, semantically distinct from `anyOf`'s OR, so remapping it would
|
|
163
|
+
* silently change a tool's contract; `not` has no Gemini equivalent. Both are dropped.
|
|
164
|
+
* - `$ref` / `$defs` / `definitions`: inlining a same-document `$ref` is only sound when fully self-
|
|
165
|
+
* contained AND cycle-guarded, and is NEAR-ZERO in practice — `@langchain/mcp-adapters` dereferences
|
|
166
|
+
* `$ref` and merges `allOf` UPSTREAM before tools reach this boundary, and gaunt-sloth's own zod
|
|
167
|
+
* tools inline+type their schemas. No live path authors a bare `$ref` here, so this is DEFERRED
|
|
168
|
+
* (GS2-68): a `$ref`-only property still sanitizes to a typeless `{}` — non-400 and callable, just
|
|
169
|
+
* without type fidelity. A raw non-adapter tool that needs it should dereference upstream, not here.
|
|
170
|
+
*/
|
|
171
|
+
function resolveComposition(node) {
|
|
172
|
+
let out = node;
|
|
173
|
+
if ('const' in node && !('enum' in node) && isScalarConst(node.const)) {
|
|
174
|
+
if (out === node)
|
|
175
|
+
out = { ...node };
|
|
176
|
+
out.enum = [cloneLiteral(node.const)];
|
|
177
|
+
if (!('type' in out)) {
|
|
178
|
+
const inferred = inferTypeFromConst(node.const);
|
|
179
|
+
if (inferred)
|
|
180
|
+
out.type = inferred;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
if (Array.isArray(out.allOf)) {
|
|
184
|
+
const merged = mergeAllOf(out);
|
|
185
|
+
if (merged)
|
|
186
|
+
out = merged;
|
|
187
|
+
// else: leave `allOf` in place → the allowlist drops it (safe GS2-58 behaviour).
|
|
188
|
+
}
|
|
189
|
+
return out;
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Recursively normalise one schema node to Gemini's supported subset:
|
|
193
|
+
* - rewrite `exclusiveMinimum`→`minimum` / `exclusiveMaximum`→`maximum` (Gemini has no exclusive
|
|
194
|
+
* bound), keeping the TIGHTER bound when an inclusive one is also present;
|
|
195
|
+
* - keep ONLY {@link GEMINI_SUPPORTED_SCHEMA_KEYWORDS}; drop everything else;
|
|
196
|
+
* - recurse ONLY through real subschema positions — `properties` (map), `items` (schema or tuple),
|
|
197
|
+
* and `anyOf` (array of schemas). Literal-data positions (`enum`, `default`, `example`, `required`,
|
|
198
|
+
* `propertyOrdering`) are copied verbatim, so a keyword that merely appears as DATA is untouched.
|
|
199
|
+
*/
|
|
200
|
+
function sanitizeNode(node) {
|
|
201
|
+
if (Array.isArray(node)) {
|
|
202
|
+
return node.map(sanitizeNode);
|
|
203
|
+
}
|
|
204
|
+
if (!isPlainObject(node)) {
|
|
205
|
+
return node;
|
|
206
|
+
}
|
|
207
|
+
// Resolve the safe composition keywords (const → enum, clean allOf → shallow merge) BEFORE the
|
|
208
|
+
// allowlist drop, so their content survives; the allowlist below then still guarantees no
|
|
209
|
+
// unsupported keyword escapes. `resolved` is `node` itself when nothing needed resolving.
|
|
210
|
+
const resolved = resolveComposition(node);
|
|
211
|
+
const out = {};
|
|
212
|
+
for (const [key, value] of Object.entries(resolved)) {
|
|
213
|
+
// Allowlist: silently drop any keyword Gemini's Schema type does not declare (this is where
|
|
214
|
+
// $defs / definitions / patternProperties / const / multipleOf / $ref / allOf / oneOf / not /
|
|
215
|
+
// additionalProperties / $schema / exclusive* are removed).
|
|
216
|
+
if (!GEMINI_SUPPORTED_SCHEMA_KEYWORDS.has(key)) {
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
if (key === 'properties' && isPlainObject(value)) {
|
|
220
|
+
const mapped = {};
|
|
221
|
+
for (const [name, sub] of Object.entries(value)) {
|
|
222
|
+
mapped[name] = sanitizeNode(sub);
|
|
223
|
+
}
|
|
224
|
+
out[key] = mapped;
|
|
225
|
+
}
|
|
226
|
+
else if (key === 'items') {
|
|
227
|
+
out[key] = Array.isArray(value) ? value.map(sanitizeNode) : sanitizeNode(value);
|
|
228
|
+
}
|
|
229
|
+
else if (key === 'anyOf' && Array.isArray(value)) {
|
|
230
|
+
out[key] = value.map(sanitizeNode);
|
|
231
|
+
}
|
|
232
|
+
else {
|
|
233
|
+
// Supported scalar / literal-data keyword (type, enum, required, description, default, …).
|
|
234
|
+
out[key] = cloneLiteral(value);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
// exclusive* → inclusive of the SAME value. Gemini has no exclusive bound; args are hints, so the
|
|
238
|
+
// loosening is accepted. When both an exclusive and an inclusive bound are present, keep the TIGHTER
|
|
239
|
+
// one (higher lower-bound / lower upper-bound) rather than letting the exclusive value clobber it.
|
|
240
|
+
// Read from `resolved` so a bound merged in from an `allOf` branch is rewritten too.
|
|
241
|
+
const exclusiveMinimum = resolved.exclusiveMinimum;
|
|
242
|
+
if (typeof exclusiveMinimum === 'number') {
|
|
243
|
+
out.minimum =
|
|
244
|
+
typeof out.minimum === 'number' ? Math.max(out.minimum, exclusiveMinimum) : exclusiveMinimum;
|
|
245
|
+
}
|
|
246
|
+
const exclusiveMaximum = resolved.exclusiveMaximum;
|
|
247
|
+
if (typeof exclusiveMaximum === 'number') {
|
|
248
|
+
out.maximum =
|
|
249
|
+
typeof out.maximum === 'number' ? Math.min(out.maximum, exclusiveMaximum) : exclusiveMaximum;
|
|
250
|
+
}
|
|
251
|
+
return out;
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Pure, recursive normaliser: returns a cleaned DEEP COPY of a JSON-Schema containing only keywords
|
|
255
|
+
* Gemini's function-declaration `Schema` accepts, so its OpenAPI-3.0 subset accepts the tool. At each
|
|
256
|
+
* node the SAFE composition keywords are first RESOLVED into supported equivalents
|
|
257
|
+
* ({@link resolveComposition}: `const` → `enum`, clean `allOf` → shallow merge) and only then does the
|
|
258
|
+
* allowlist drop the rest. Does not mutate the input. `anyOf` unions survive; `$ref`/`oneOf`/`not` are
|
|
259
|
+
* dropped (see {@link resolveComposition} for why the last three are deferred, not resolved).
|
|
260
|
+
*/
|
|
261
|
+
export function sanitizeGeminiToolSchema(schema) {
|
|
262
|
+
return sanitizeNode(schema);
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* The SINGLE normalization pass. Converts a schema to JSON (zod → JSON via the same `@langchain/core`
|
|
266
|
+
* converter `@langchain/google` uses internally) and runs the allowlist. Every tool-shape branch in
|
|
267
|
+
* {@link sanitizeToolForGemini} routes its schema(s) through here — so no branch can filter
|
|
268
|
+
* inconsistently, and a future tool format is covered the moment it calls this.
|
|
269
|
+
*/
|
|
270
|
+
function normalizeSchema(rawSchema) {
|
|
271
|
+
const jsonSchema = isInteropZodSchema(rawSchema) || isSerializableSchema(rawSchema)
|
|
272
|
+
? toJsonSchema(rawSchema)
|
|
273
|
+
: rawSchema;
|
|
274
|
+
return sanitizeGeminiToolSchema(jsonSchema);
|
|
275
|
+
}
|
|
276
|
+
/** Build a shallow copy of a tool that preserves its prototype (so it stays a recognisable
|
|
277
|
+
* LangChain tool) while overriding one own property (its schema) with the normalized value. */
|
|
278
|
+
function cloneWithOverride(source, key, value) {
|
|
279
|
+
const clone = Object.assign(Object.create(Object.getPrototypeOf(source)), source);
|
|
280
|
+
Object.defineProperty(clone, key, {
|
|
281
|
+
value,
|
|
282
|
+
enumerable: true,
|
|
283
|
+
writable: true,
|
|
284
|
+
configurable: true,
|
|
285
|
+
});
|
|
286
|
+
return clone;
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Normalise one tool's argument schema for Gemini. Handles the three shapes that reach a ChatGoogle
|
|
290
|
+
* model's `bindTools`: a LangChain structured tool (`.schema`, zod or JSON), an OpenAI-format tool
|
|
291
|
+
* (`.function.parameters`), and a Gemini-native `functionDeclarations` tool. Every schema position in
|
|
292
|
+
* every branch is passed through the SINGLE {@link normalizeSchema} pass; anything else is untouched.
|
|
293
|
+
*/
|
|
294
|
+
function sanitizeToolForGemini(tool) {
|
|
295
|
+
if (!tool || typeof tool !== 'object') {
|
|
296
|
+
return tool;
|
|
297
|
+
}
|
|
298
|
+
const record = tool;
|
|
299
|
+
// LangChain structured tool / StructuredToolParams — its arg schema is `.schema` (zod or JSON).
|
|
300
|
+
if ('schema' in record && record.schema != null) {
|
|
301
|
+
return cloneWithOverride(tool, 'schema', normalizeSchema(record.schema));
|
|
302
|
+
}
|
|
303
|
+
// OpenAI-format tool: { type: 'function', function: { parameters } }.
|
|
304
|
+
const fn = record.function;
|
|
305
|
+
if (fn && typeof fn === 'object' && 'parameters' in fn) {
|
|
306
|
+
const fnRecord = fn;
|
|
307
|
+
return {
|
|
308
|
+
...record,
|
|
309
|
+
function: { ...fnRecord, parameters: normalizeSchema(fnRecord.parameters) },
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
// Gemini-native tool: { functionDeclarations: [{ parameters }, …] }.
|
|
313
|
+
if (Array.isArray(record.functionDeclarations)) {
|
|
314
|
+
return {
|
|
315
|
+
...record,
|
|
316
|
+
functionDeclarations: record.functionDeclarations.map((decl) => {
|
|
317
|
+
if (decl && typeof decl === 'object' && 'parameters' in decl) {
|
|
318
|
+
const declRecord = decl;
|
|
319
|
+
return { ...declRecord, parameters: normalizeSchema(declRecord.parameters) };
|
|
320
|
+
}
|
|
321
|
+
return decl;
|
|
322
|
+
}),
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
return tool;
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Wire the sanitizer into a ChatGoogle model at the tool-binding boundary. Overrides the instance's
|
|
329
|
+
* `bindTools` so every tool passed to it — built-in, custom, or MCP, via `createAgent`/`createDeepAgent`
|
|
330
|
+
* which both call `model.bindTools(tools)` — is sanitized before it reaches `@langchain/google`'s
|
|
331
|
+
* Gemini converter. Provider-scoped: only the google-genai/vertexai presets call this, so no other
|
|
332
|
+
* provider's tools are affected. Returns the same model instance for convenient chaining.
|
|
333
|
+
*/
|
|
334
|
+
export function applyGeminiToolSchemaSanitizer(model) {
|
|
335
|
+
const holder = model;
|
|
336
|
+
const original = holder.bindTools;
|
|
337
|
+
if (typeof original !== 'function') {
|
|
338
|
+
return model;
|
|
339
|
+
}
|
|
340
|
+
const bound = original.bind(model);
|
|
341
|
+
holder.bindTools = function sanitizedBindTools(tools, kwargs) {
|
|
342
|
+
const nextTools = Array.isArray(tools) ? tools.map(sanitizeToolForGemini) : tools;
|
|
343
|
+
return bound(nextTools, kwargs);
|
|
344
|
+
};
|
|
345
|
+
return model;
|
|
346
|
+
}
|
|
347
|
+
//# sourceMappingURL=geminiSchemaSanitizer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"geminiSchemaSanitizer.js","sourceRoot":"","sources":["../../src/providers/geminiSchemaSanitizer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @packageDocumentation
|
|
3
|
+
* CFG-33 — ask Gemini to RETURN the thinking it is already doing.
|
|
4
|
+
*
|
|
5
|
+
* Two knobs are easy to conflate. The thinking BUDGET is what costs money, and Gemini thinks by
|
|
6
|
+
* default — those tokens are billed whether or not anyone sees them. Thought SUMMARIES
|
|
7
|
+
* (`thinkingConfig.includeThoughts`, default off) decide only whether any of that is returned. So
|
|
8
|
+
* without this, gaunt-sloth pays for reasoning on every Gemini turn and throws it away unseen, and
|
|
9
|
+
* the `/reasoning` panel is empty.
|
|
10
|
+
*
|
|
11
|
+
* Hence the rule here: wherever thinking is enabled, show it. This never changes the budget, and it
|
|
12
|
+
* is not opt-in — a knob belongs on the thing that actually costs (`thinkingBudget` /
|
|
13
|
+
* `thinkingLevel` / `reasoningEffort` in the `llm` config), never on whether the user may see what
|
|
14
|
+
* they have already paid for. Setting the budget to zero or minimal turns thinking off, and this
|
|
15
|
+
* respects that: there are then no thoughts to show.
|
|
16
|
+
*
|
|
17
|
+
* Why an `invocationParams` override rather than a constructor field: `@langchain/google` derives
|
|
18
|
+
* `generationConfig.thinkingConfig` from the budget/level fields on every call and does not read a
|
|
19
|
+
* `thinkingConfig` passed to the constructor, so the ONLY way to add `includeThoughts` without also
|
|
20
|
+
* pinning a budget is at the built params. Overriding the instance method (the shape
|
|
21
|
+
* {@link applyGeminiToolSchemaSanitizer} already uses for `bindTools`) keeps working through
|
|
22
|
+
* `bindTools`, which returns a `RunnableBinding` around this same instance.
|
|
23
|
+
*
|
|
24
|
+
* Gemini returns a thought summary as a content BLOCK marked `thought: true`, not in
|
|
25
|
+
* `additional_kwargs`; `#src/core/reasoningBlocks.js` is the half of this fix that reads it. That
|
|
26
|
+
* block is typed `text`, exactly like an answer block, so a surface that does NOT route content
|
|
27
|
+
* through gsloth's own reasoning bridge cannot tell thinking from answer — which is what
|
|
28
|
+
* {@link disableGeminiThoughtSummaries} is for.
|
|
29
|
+
*/
|
|
30
|
+
import type { BaseChatModel } from '@langchain/core/language_models/chat_models';
|
|
31
|
+
/**
|
|
32
|
+
* Wire thought summaries into a `ChatGoogle` model. Overrides the instance's `invocationParams` so
|
|
33
|
+
* that `generationConfig.thinkingConfig.includeThoughts` is set — but ONLY when the library derived
|
|
34
|
+
* no thinking config at all, which is the "user configured nothing, the API default budget applies"
|
|
35
|
+
* case. When the user DID configure a budget or level, `@langchain/google` has already decided
|
|
36
|
+
* `includeThoughts` from it (true when thinking is on, false when they asked for none/minimal) and
|
|
37
|
+
* that decision is left exactly as it stands. Returns the same model instance for chaining.
|
|
38
|
+
*/
|
|
39
|
+
export declare function applyGeminiThoughtSummaries<T extends BaseChatModel>(model: T): T;
|
|
40
|
+
/**
|
|
41
|
+
* The inverse, for a surface that must not receive thought summaries at all: keep whatever thinking
|
|
42
|
+
* budget or level applies and force `includeThoughts: false`, so the model still thinks and only the
|
|
43
|
+
* summary is withheld. Never express this as a zero/minimal budget — that turns THINKING off, which
|
|
44
|
+
* is a different and much larger change, and the coercion differs between the 2.5 and 3.x presets.
|
|
45
|
+
*
|
|
46
|
+
* This exists because a consumer outside {@link GthAbstractAgent} routes content blocks by `type`,
|
|
47
|
+
* and Gemini's thought summary is typed `text` exactly like an answer block — so a third party has
|
|
48
|
+
* no way to tell them apart and prints the thinking as the assistant's answer. Not asking for the
|
|
49
|
+
* summary is the only thing that reliably stops that; nothing is stripped, so the message kept in
|
|
50
|
+
* graph state (and any `thoughtSignature` riding on it) is untouched. Returns the same instance.
|
|
51
|
+
*/
|
|
52
|
+
export declare function disableGeminiThoughtSummaries<T extends BaseChatModel>(model: T): T;
|
|
@@ -0,0 +1,72 @@
|
|
|
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.
|
|
6
|
+
*/
|
|
7
|
+
function producesThoughtSummaries(model) {
|
|
8
|
+
if (typeof model !== 'string')
|
|
9
|
+
return true;
|
|
10
|
+
return !/image|tts/i.test(model);
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Wire thought summaries into a `ChatGoogle` model. Overrides the instance's `invocationParams` so
|
|
14
|
+
* that `generationConfig.thinkingConfig.includeThoughts` is set — but ONLY when the library derived
|
|
15
|
+
* no thinking config at all, which is the "user configured nothing, the API default budget applies"
|
|
16
|
+
* case. When the user DID configure a budget or level, `@langchain/google` has already decided
|
|
17
|
+
* `includeThoughts` from it (true when thinking is on, false when they asked for none/minimal) and
|
|
18
|
+
* that decision is left exactly as it stands. Returns the same model instance for chaining.
|
|
19
|
+
*/
|
|
20
|
+
export function applyGeminiThoughtSummaries(model) {
|
|
21
|
+
return overrideThinkingConfig(model, (thinkingConfig) =>
|
|
22
|
+
// `thinkingConfig` is always PRESENT as a key and may hold `undefined`; an explicit value means
|
|
23
|
+
// the user's budget/level was honoured and must win.
|
|
24
|
+
thinkingConfig === undefined ? { includeThoughts: true } : thinkingConfig);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The inverse, for a surface that must not receive thought summaries at all: keep whatever thinking
|
|
28
|
+
* budget or level applies and force `includeThoughts: false`, so the model still thinks and only the
|
|
29
|
+
* summary is withheld. Never express this as a zero/minimal budget — that turns THINKING off, which
|
|
30
|
+
* is a different and much larger change, and the coercion differs between the 2.5 and 3.x presets.
|
|
31
|
+
*
|
|
32
|
+
* This exists because a consumer outside {@link GthAbstractAgent} routes content blocks by `type`,
|
|
33
|
+
* and Gemini's thought summary is typed `text` exactly like an answer block — so a third party has
|
|
34
|
+
* no way to tell them apart and prints the thinking as the assistant's answer. Not asking for the
|
|
35
|
+
* summary is the only thing that reliably stops that; nothing is stripped, so the message kept in
|
|
36
|
+
* graph state (and any `thoughtSignature` riding on it) is untouched. Returns the same instance.
|
|
37
|
+
*/
|
|
38
|
+
export function disableGeminiThoughtSummaries(model) {
|
|
39
|
+
return overrideThinkingConfig(model, (thinkingConfig) => ({
|
|
40
|
+
...(typeof thinkingConfig === 'object' && thinkingConfig !== null ? thinkingConfig : {}),
|
|
41
|
+
includeThoughts: false,
|
|
42
|
+
}));
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* 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.
|
|
50
|
+
*/
|
|
51
|
+
function overrideThinkingConfig(model, next) {
|
|
52
|
+
const holder = model;
|
|
53
|
+
const original = holder.invocationParams;
|
|
54
|
+
if (typeof original !== 'function' || !producesThoughtSummaries(holder.model)) {
|
|
55
|
+
return model;
|
|
56
|
+
}
|
|
57
|
+
const bound = original.bind(model);
|
|
58
|
+
holder.invocationParams = function invocationParamsWithThinkingConfig(options) {
|
|
59
|
+
const params = bound(options);
|
|
60
|
+
const generationConfig = params?.generationConfig;
|
|
61
|
+
if (!generationConfig) {
|
|
62
|
+
return params;
|
|
63
|
+
}
|
|
64
|
+
const thinkingConfig = next(generationConfig.thinkingConfig);
|
|
65
|
+
if (thinkingConfig === generationConfig.thinkingConfig) {
|
|
66
|
+
return params;
|
|
67
|
+
}
|
|
68
|
+
return { ...params, generationConfig: { ...generationConfig, thinkingConfig } };
|
|
69
|
+
};
|
|
70
|
+
return model;
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=geminiThinking.js.map
|
|
@@ -0,0 +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"}
|