@google/gemini-cli-core 0.5.5 → 0.6.0-preview.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +2 -2
- package/README.md +12 -2
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/src/code_assist/converter.d.ts +1 -0
- package/dist/src/code_assist/converter.js +1 -0
- package/dist/src/code_assist/converter.js.map +1 -1
- package/dist/src/code_assist/converter.test.js +10 -0
- package/dist/src/code_assist/converter.test.js.map +1 -1
- package/dist/src/code_assist/oauth-credential-storage.d.ts +5 -7
- package/dist/src/code_assist/oauth-credential-storage.js +5 -8
- package/dist/src/code_assist/oauth-credential-storage.js.map +1 -1
- package/dist/src/code_assist/oauth-credential-storage.test.js +35 -33
- package/dist/src/code_assist/oauth-credential-storage.test.js.map +1 -1
- package/dist/src/code_assist/oauth2.js +28 -2
- package/dist/src/code_assist/oauth2.js.map +1 -1
- package/dist/src/code_assist/oauth2.test.js +674 -536
- package/dist/src/code_assist/oauth2.test.js.map +1 -1
- package/dist/src/config/config.d.ts +32 -1
- package/dist/src/config/config.js +73 -16
- package/dist/src/config/config.js.map +1 -1
- package/dist/src/config/config.test.js +104 -16
- package/dist/src/config/config.test.js.map +1 -1
- package/dist/src/config/models.d.ts +15 -0
- package/dist/src/config/models.js +27 -0
- package/dist/src/config/models.js.map +1 -1
- package/dist/src/config/models.test.d.ts +6 -0
- package/dist/src/config/models.test.js +55 -0
- package/dist/src/config/models.test.js.map +1 -0
- package/dist/src/confirmation-bus/index.d.ts +7 -0
- package/dist/src/confirmation-bus/index.js +8 -0
- package/dist/src/confirmation-bus/index.js.map +1 -0
- package/dist/src/confirmation-bus/message-bus.d.ts +17 -0
- package/dist/src/confirmation-bus/message-bus.js +81 -0
- package/dist/src/confirmation-bus/message-bus.js.map +1 -0
- package/dist/src/confirmation-bus/message-bus.test.d.ts +6 -0
- package/dist/src/confirmation-bus/message-bus.test.js +164 -0
- package/dist/src/confirmation-bus/message-bus.test.js.map +1 -0
- package/dist/src/confirmation-bus/types.d.ts +38 -0
- package/dist/src/confirmation-bus/types.js +15 -0
- package/dist/src/confirmation-bus/types.js.map +1 -0
- package/dist/src/core/baseLlmClient.d.ts +1 -0
- package/dist/src/core/baseLlmClient.js +24 -0
- package/dist/src/core/baseLlmClient.js.map +1 -1
- package/dist/src/core/baseLlmClient.test.js +63 -0
- package/dist/src/core/baseLlmClient.test.js.map +1 -1
- package/dist/src/core/client.d.ts +5 -4
- package/dist/src/core/client.js +80 -140
- package/dist/src/core/client.js.map +1 -1
- package/dist/src/core/client.test.js +247 -186
- package/dist/src/core/client.test.js.map +1 -1
- package/dist/src/core/contentGenerator.d.ts +0 -1
- package/dist/src/core/contentGenerator.js +0 -4
- package/dist/src/core/contentGenerator.js.map +1 -1
- package/dist/src/core/contentGenerator.test.js +0 -3
- package/dist/src/core/contentGenerator.test.js.map +1 -1
- package/dist/src/core/coreToolScheduler.d.ts +4 -3
- package/dist/src/core/coreToolScheduler.js +42 -5
- package/dist/src/core/coreToolScheduler.js.map +1 -1
- package/dist/src/core/coreToolScheduler.test.js +43 -0
- package/dist/src/core/coreToolScheduler.test.js.map +1 -1
- package/dist/src/core/geminiChat.d.ts +3 -30
- package/dist/src/core/geminiChat.js +32 -228
- package/dist/src/core/geminiChat.js.map +1 -1
- package/dist/src/core/geminiChat.test.js +58 -489
- package/dist/src/core/geminiChat.test.js.map +1 -1
- package/dist/src/core/loggingContentGenerator.js +5 -5
- package/dist/src/core/loggingContentGenerator.js.map +1 -1
- package/dist/src/core/nonInteractiveToolExecutor.test.js +49 -0
- package/dist/src/core/nonInteractiveToolExecutor.test.js.map +1 -1
- package/dist/src/core/subagent.js +1 -1
- package/dist/src/core/subagent.js.map +1 -1
- package/dist/src/core/subagent.test.js +9 -8
- package/dist/src/core/subagent.test.js.map +1 -1
- package/dist/src/core/turn.d.ts +2 -1
- package/dist/src/core/turn.js +2 -2
- package/dist/src/core/turn.js.map +1 -1
- package/dist/src/core/turn.test.js +18 -18
- package/dist/src/core/turn.test.js.map +1 -1
- package/dist/src/generated/git-commit.d.ts +2 -2
- package/dist/src/generated/git-commit.js +2 -2
- package/dist/src/generated/git-commit.js.map +1 -1
- package/dist/src/ide/constants.d.ts +1 -0
- package/dist/src/ide/constants.js +1 -0
- package/dist/src/ide/constants.js.map +1 -1
- package/dist/src/ide/ide-client.d.ts +51 -13
- package/dist/src/ide/ide-client.js +182 -35
- package/dist/src/ide/ide-client.js.map +1 -1
- package/dist/src/ide/ide-client.test.js +71 -0
- package/dist/src/ide/ide-client.test.js.map +1 -1
- package/dist/src/ide/ide-installer.js +8 -2
- package/dist/src/ide/ide-installer.js.map +1 -1
- package/dist/src/ide/ide-installer.test.js +13 -2
- package/dist/src/ide/ide-installer.test.js.map +1 -1
- package/dist/src/ide/ideContext.d.ts +34 -113
- package/dist/src/ide/ideContext.js +20 -78
- package/dist/src/ide/ideContext.js.map +1 -1
- package/dist/src/ide/ideContext.test.js +37 -39
- package/dist/src/ide/ideContext.test.js.map +1 -1
- package/dist/src/ide/types.d.ts +141 -0
- package/dist/src/ide/types.js +73 -0
- package/dist/src/ide/types.js.map +1 -1
- package/dist/src/index.d.ts +3 -1
- package/dist/src/index.js +3 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/mcp/oauth-token-storage.d.ts +2 -0
- package/dist/src/mcp/oauth-token-storage.js +25 -0
- package/dist/src/mcp/oauth-token-storage.js.map +1 -1
- package/dist/src/mcp/oauth-token-storage.test.js +251 -160
- package/dist/src/mcp/oauth-token-storage.test.js.map +1 -1
- package/dist/src/mcp/token-storage/index.d.ts +11 -0
- package/dist/src/mcp/token-storage/index.js +12 -0
- package/dist/src/mcp/token-storage/index.js.map +1 -0
- package/dist/src/output/json-formatter.d.ts +11 -0
- package/dist/src/output/json-formatter.js +30 -0
- package/dist/src/output/json-formatter.js.map +1 -0
- package/dist/src/output/json-formatter.test.d.ts +6 -0
- package/dist/src/output/json-formatter.test.js +266 -0
- package/dist/src/output/json-formatter.test.js.map +1 -0
- package/dist/src/output/types.d.ts +20 -0
- package/dist/src/output/types.js +11 -0
- package/dist/src/output/types.js.map +1 -0
- package/dist/src/policy/index.d.ts +7 -0
- package/dist/src/policy/index.js +8 -0
- package/dist/src/policy/index.js.map +1 -0
- package/dist/src/policy/policy-engine.d.ts +30 -0
- package/dist/src/policy/policy-engine.js +83 -0
- package/dist/src/policy/policy-engine.js.map +1 -0
- package/dist/src/policy/policy-engine.test.d.ts +6 -0
- package/dist/src/policy/policy-engine.test.js +470 -0
- package/dist/src/policy/policy-engine.test.js.map +1 -0
- package/dist/src/policy/stable-stringify.d.ts +58 -0
- package/dist/src/policy/stable-stringify.js +122 -0
- package/dist/src/policy/stable-stringify.js.map +1 -0
- package/dist/src/policy/types.d.ts +47 -0
- package/dist/src/policy/types.js +12 -0
- package/dist/src/policy/types.js.map +1 -0
- package/dist/src/routing/modelRouterService.d.ts +23 -0
- package/dist/src/routing/modelRouterService.js +70 -0
- package/dist/src/routing/modelRouterService.js.map +1 -0
- package/dist/src/routing/modelRouterService.test.d.ts +6 -0
- package/dist/src/routing/modelRouterService.test.js +98 -0
- package/dist/src/routing/modelRouterService.test.js.map +1 -0
- package/dist/src/routing/routingStrategy.d.ts +62 -0
- package/dist/src/routing/routingStrategy.js +7 -0
- package/dist/src/routing/routingStrategy.js.map +1 -0
- package/dist/src/routing/strategies/classifierStrategy.d.ts +12 -0
- package/dist/src/routing/strategies/classifierStrategy.js +173 -0
- package/dist/src/routing/strategies/classifierStrategy.js.map +1 -0
- package/dist/src/routing/strategies/classifierStrategy.test.d.ts +6 -0
- package/dist/src/routing/strategies/classifierStrategy.test.js +192 -0
- package/dist/src/routing/strategies/classifierStrategy.test.js.map +1 -0
- package/dist/src/routing/strategies/compositeStrategy.d.ts +26 -0
- package/dist/src/routing/strategies/compositeStrategy.js +67 -0
- package/dist/src/routing/strategies/compositeStrategy.js.map +1 -0
- package/dist/src/routing/strategies/compositeStrategy.test.d.ts +6 -0
- package/dist/src/routing/strategies/compositeStrategy.test.js +123 -0
- package/dist/src/routing/strategies/compositeStrategy.test.js.map +1 -0
- package/dist/src/routing/strategies/defaultStrategy.d.ts +12 -0
- package/dist/src/routing/strategies/defaultStrategy.js +20 -0
- package/dist/src/routing/strategies/defaultStrategy.js.map +1 -0
- package/dist/src/routing/strategies/defaultStrategy.test.d.ts +6 -0
- package/dist/src/routing/strategies/defaultStrategy.test.js +26 -0
- package/dist/src/routing/strategies/defaultStrategy.test.js.map +1 -0
- package/dist/src/routing/strategies/fallbackStrategy.d.ts +12 -0
- package/dist/src/routing/strategies/fallbackStrategy.js +25 -0
- package/dist/src/routing/strategies/fallbackStrategy.js.map +1 -0
- package/dist/src/routing/strategies/fallbackStrategy.test.d.ts +6 -0
- package/dist/src/routing/strategies/fallbackStrategy.test.js +55 -0
- package/dist/src/routing/strategies/fallbackStrategy.test.js.map +1 -0
- package/dist/src/routing/strategies/overrideStrategy.d.ts +15 -0
- package/dist/src/routing/strategies/overrideStrategy.js +28 -0
- package/dist/src/routing/strategies/overrideStrategy.js.map +1 -0
- package/dist/src/routing/strategies/overrideStrategy.test.d.ts +6 -0
- package/dist/src/routing/strategies/overrideStrategy.test.js +42 -0
- package/dist/src/routing/strategies/overrideStrategy.test.js.map +1 -0
- package/dist/src/services/chatRecordingService.d.ts +2 -1
- package/dist/src/services/chatRecordingService.js +3 -3
- package/dist/src/services/chatRecordingService.js.map +1 -1
- package/dist/src/services/chatRecordingService.test.js +8 -3
- package/dist/src/services/chatRecordingService.test.js.map +1 -1
- package/dist/src/services/fileDiscoveryService.d.ts +10 -0
- package/dist/src/services/fileDiscoveryService.js +31 -17
- package/dist/src/services/fileDiscoveryService.js.map +1 -1
- package/dist/src/services/gitService.js +9 -12
- package/dist/src/services/gitService.js.map +1 -1
- package/dist/src/services/gitService.test.js +10 -20
- package/dist/src/services/gitService.test.js.map +1 -1
- package/dist/src/services/loopDetectionService.d.ts +5 -0
- package/dist/src/services/loopDetectionService.js +36 -20
- package/dist/src/services/loopDetectionService.js.map +1 -1
- package/dist/src/services/loopDetectionService.test.js +41 -12
- package/dist/src/services/loopDetectionService.test.js.map +1 -1
- package/dist/src/services/shellExecutionService.d.ts +34 -2
- package/dist/src/services/shellExecutionService.js +177 -43
- package/dist/src/services/shellExecutionService.js.map +1 -1
- package/dist/src/services/shellExecutionService.test.js +153 -56
- package/dist/src/services/shellExecutionService.test.js.map +1 -1
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.d.ts +10 -2
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.js +85 -5
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.js.map +1 -1
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.test.js +63 -5
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.test.js.map +1 -1
- package/dist/src/telemetry/clearcut-logger/event-metadata-key.d.ts +12 -2
- package/dist/src/telemetry/clearcut-logger/event-metadata-key.js +31 -2
- package/dist/src/telemetry/clearcut-logger/event-metadata-key.js.map +1 -1
- package/dist/src/telemetry/constants.d.ts +3 -0
- package/dist/src/telemetry/constants.js +3 -0
- package/dist/src/telemetry/constants.js.map +1 -1
- package/dist/src/telemetry/gcp-exporters.d.ts +34 -0
- package/dist/src/telemetry/gcp-exporters.js +117 -0
- package/dist/src/telemetry/gcp-exporters.js.map +1 -0
- package/dist/src/telemetry/gcp-exporters.test.d.ts +6 -0
- package/dist/src/telemetry/gcp-exporters.test.js +318 -0
- package/dist/src/telemetry/gcp-exporters.test.js.map +1 -0
- package/dist/src/telemetry/index.d.ts +3 -2
- package/dist/src/telemetry/index.js +3 -2
- package/dist/src/telemetry/index.js.map +1 -1
- package/dist/src/telemetry/loggers.d.ts +4 -1
- package/dist/src/telemetry/loggers.js +42 -7
- package/dist/src/telemetry/loggers.js.map +1 -1
- package/dist/src/telemetry/loggers.test.js +84 -36
- package/dist/src/telemetry/loggers.test.js.map +1 -1
- package/dist/src/telemetry/metrics.d.ts +3 -1
- package/dist/src/telemetry/metrics.js +32 -3
- package/dist/src/telemetry/metrics.js.map +1 -1
- package/dist/src/telemetry/metrics.test.js +42 -0
- package/dist/src/telemetry/metrics.test.js.map +1 -1
- package/dist/src/telemetry/sdk.js +16 -1
- package/dist/src/telemetry/sdk.js.map +1 -1
- package/dist/src/telemetry/sdk.test.js +95 -0
- package/dist/src/telemetry/sdk.test.js.map +1 -1
- package/dist/src/telemetry/types.d.ts +47 -3
- package/dist/src/telemetry/types.js +67 -3
- package/dist/src/telemetry/types.js.map +1 -1
- package/dist/src/tools/edit.js +6 -5
- package/dist/src/tools/edit.js.map +1 -1
- package/dist/src/tools/edit.test.js +79 -9
- package/dist/src/tools/edit.test.js.map +1 -1
- package/dist/src/tools/glob.d.ts +5 -1
- package/dist/src/tools/glob.js +24 -17
- package/dist/src/tools/glob.js.map +1 -1
- package/dist/src/tools/glob.test.js +51 -0
- package/dist/src/tools/glob.test.js.map +1 -1
- package/dist/src/tools/ls.js +19 -32
- package/dist/src/tools/ls.js.map +1 -1
- package/dist/src/tools/ls.test.js +140 -280
- package/dist/src/tools/ls.test.js.map +1 -1
- package/dist/src/tools/read-many-files.d.ts +1 -1
- package/dist/src/tools/read-many-files.js +17 -49
- package/dist/src/tools/read-many-files.js.map +1 -1
- package/dist/src/tools/ripGrep.d.ts +4 -0
- package/dist/src/tools/ripGrep.js +11 -1
- package/dist/src/tools/ripGrep.js.map +1 -1
- package/dist/src/tools/ripGrep.test.js +51 -1
- package/dist/src/tools/ripGrep.test.js.map +1 -1
- package/dist/src/tools/shell.d.ts +12 -2
- package/dist/src/tools/shell.js +20 -27
- package/dist/src/tools/shell.js.map +1 -1
- package/dist/src/tools/shell.test.js +33 -68
- package/dist/src/tools/shell.test.js.map +1 -1
- package/dist/src/tools/smart-edit.d.ts +0 -1
- package/dist/src/tools/smart-edit.js +5 -18
- package/dist/src/tools/smart-edit.js.map +1 -1
- package/dist/src/tools/smart-edit.test.js +18 -9
- package/dist/src/tools/smart-edit.test.js.map +1 -1
- package/dist/src/tools/tools.d.ts +7 -5
- package/dist/src/tools/tools.js +2 -2
- package/dist/src/tools/tools.js.map +1 -1
- package/dist/src/tools/write-file.js +4 -5
- package/dist/src/tools/write-file.js.map +1 -1
- package/dist/src/tools/write-file.test.js +94 -10
- package/dist/src/tools/write-file.test.js.map +1 -1
- package/dist/src/utils/bfsFileSearch.js +11 -5
- package/dist/src/utils/bfsFileSearch.js.map +1 -1
- package/dist/src/utils/editCorrector.d.ts +7 -6
- package/dist/src/utils/editCorrector.js +61 -18
- package/dist/src/utils/editCorrector.js.map +1 -1
- package/dist/src/utils/editCorrector.test.js +30 -79
- package/dist/src/utils/editCorrector.test.js.map +1 -1
- package/dist/src/utils/editor.js +31 -44
- package/dist/src/utils/editor.js.map +1 -1
- package/dist/src/utils/editor.test.js +61 -75
- package/dist/src/utils/editor.test.js.map +1 -1
- package/dist/src/utils/errorParsing.js +2 -2
- package/dist/src/utils/errorParsing.js.map +1 -1
- package/dist/src/utils/errorParsing.test.js +7 -7
- package/dist/src/utils/errorParsing.test.js.map +1 -1
- package/dist/src/utils/errors.d.ts +6 -0
- package/dist/src/utils/errors.js +10 -0
- package/dist/src/utils/errors.js.map +1 -1
- package/dist/src/utils/fileUtils.test.js +17 -8
- package/dist/src/utils/fileUtils.test.js.map +1 -1
- package/dist/src/utils/geminiIgnoreParser.d.ts +18 -0
- package/dist/src/utils/geminiIgnoreParser.js +61 -0
- package/dist/src/utils/geminiIgnoreParser.js.map +1 -0
- package/dist/src/utils/geminiIgnoreParser.test.d.ts +6 -0
- package/dist/src/utils/geminiIgnoreParser.test.js +50 -0
- package/dist/src/utils/geminiIgnoreParser.test.js.map +1 -0
- package/dist/src/utils/gitIgnoreParser.d.ts +3 -9
- package/dist/src/utils/gitIgnoreParser.js +60 -69
- package/dist/src/utils/gitIgnoreParser.js.map +1 -1
- package/dist/src/utils/gitIgnoreParser.test.js +18 -53
- package/dist/src/utils/gitIgnoreParser.test.js.map +1 -1
- package/dist/src/utils/memoryDiscovery.test.js +12 -6
- package/dist/src/utils/memoryDiscovery.test.js.map +1 -1
- package/dist/src/utils/nextSpeakerChecker.d.ts +2 -2
- package/dist/src/utils/nextSpeakerChecker.js +8 -2
- package/dist/src/utils/nextSpeakerChecker.js.map +1 -1
- package/dist/src/utils/nextSpeakerChecker.test.js +40 -33
- package/dist/src/utils/nextSpeakerChecker.test.js.map +1 -1
- package/dist/src/utils/shell-utils.d.ts +5 -0
- package/dist/src/utils/shell-utils.js +23 -0
- package/dist/src/utils/shell-utils.js.map +1 -1
- package/dist/src/utils/terminalSerializer.d.ts +28 -0
- package/dist/src/utils/terminalSerializer.js +432 -0
- package/dist/src/utils/terminalSerializer.js.map +1 -0
- package/dist/src/utils/terminalSerializer.test.d.ts +6 -0
- package/dist/src/utils/terminalSerializer.test.js +176 -0
- package/dist/src/utils/terminalSerializer.test.js.map +1 -0
- package/dist/src/utils/textUtils.d.ts +5 -0
- package/dist/src/utils/textUtils.js +14 -0
- package/dist/src/utils/textUtils.js.map +1 -1
- package/dist/src/utils/textUtils.test.d.ts +6 -0
- package/dist/src/utils/textUtils.test.js +59 -0
- package/dist/src/utils/textUtils.test.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +5 -1
- package/dist/src/utils/ide-trust.d.ts +0 -10
- package/dist/src/utils/ide-trust.js +0 -14
- package/dist/src/utils/ide-trust.js.map +0 -1
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2025 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Produces a stable, deterministic JSON string representation with sorted keys.
|
|
8
|
+
*
|
|
9
|
+
* This method is critical for security policy matching. It ensures that the same
|
|
10
|
+
* object always produces the same string representation, regardless of property
|
|
11
|
+
* insertion order, which could vary across different JavaScript engines or
|
|
12
|
+
* runtime conditions.
|
|
13
|
+
*
|
|
14
|
+
* Key behaviors:
|
|
15
|
+
* 1. **Sorted Keys**: Object properties are always serialized in alphabetical order,
|
|
16
|
+
* ensuring deterministic output for pattern matching.
|
|
17
|
+
*
|
|
18
|
+
* 2. **Circular Reference Protection**: Uses ancestor chain tracking (not just
|
|
19
|
+
* object identity) to detect true circular references while correctly handling
|
|
20
|
+
* repeated non-circular object references. Circular references are replaced
|
|
21
|
+
* with "[Circular]" to prevent stack overflow attacks.
|
|
22
|
+
*
|
|
23
|
+
* 3. **JSON Spec Compliance**:
|
|
24
|
+
* - undefined values: Omitted from objects, converted to null in arrays
|
|
25
|
+
* - Functions: Omitted from objects, converted to null in arrays
|
|
26
|
+
* - toJSON methods: Respected and called when present (per JSON.stringify spec)
|
|
27
|
+
*
|
|
28
|
+
* 4. **Security Considerations**:
|
|
29
|
+
* - Prevents DoS via circular references that would cause infinite recursion
|
|
30
|
+
* - Ensures consistent policy rule matching by normalizing property order
|
|
31
|
+
* - Respects toJSON for objects that sanitize their output
|
|
32
|
+
* - Handles toJSON methods that throw errors gracefully
|
|
33
|
+
*
|
|
34
|
+
* @param obj - The object to stringify (typically toolCall.args)
|
|
35
|
+
* @returns A deterministic JSON string representation
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* // Different property orders produce the same output:
|
|
39
|
+
* stableStringify({b: 2, a: 1}) === stableStringify({a: 1, b: 2})
|
|
40
|
+
* // Returns: '{"a":1,"b":2}'
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* // Circular references are handled safely:
|
|
44
|
+
* const obj = {a: 1};
|
|
45
|
+
* obj.self = obj;
|
|
46
|
+
* stableStringify(obj)
|
|
47
|
+
* // Returns: '{"a":1,"self":"[Circular]"}'
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* // toJSON methods are respected:
|
|
51
|
+
* const obj = {
|
|
52
|
+
* sensitive: 'secret',
|
|
53
|
+
* toJSON: () => ({ safe: 'data' })
|
|
54
|
+
* };
|
|
55
|
+
* stableStringify(obj)
|
|
56
|
+
* // Returns: '{"safe":"data"}'
|
|
57
|
+
*/
|
|
58
|
+
export function stableStringify(obj) {
|
|
59
|
+
const stringify = (currentObj, ancestors) => {
|
|
60
|
+
// Handle primitives and null
|
|
61
|
+
if (currentObj === undefined) {
|
|
62
|
+
return 'null'; // undefined in arrays becomes null in JSON
|
|
63
|
+
}
|
|
64
|
+
if (currentObj === null) {
|
|
65
|
+
return 'null';
|
|
66
|
+
}
|
|
67
|
+
if (typeof currentObj === 'function') {
|
|
68
|
+
return 'null'; // functions in arrays become null in JSON
|
|
69
|
+
}
|
|
70
|
+
if (typeof currentObj !== 'object') {
|
|
71
|
+
return JSON.stringify(currentObj);
|
|
72
|
+
}
|
|
73
|
+
// Check for circular reference (object is in ancestor chain)
|
|
74
|
+
if (ancestors.has(currentObj)) {
|
|
75
|
+
return '"[Circular]"';
|
|
76
|
+
}
|
|
77
|
+
ancestors.add(currentObj);
|
|
78
|
+
try {
|
|
79
|
+
// Check for toJSON method and use it if present
|
|
80
|
+
const objWithToJSON = currentObj;
|
|
81
|
+
if (typeof objWithToJSON.toJSON === 'function') {
|
|
82
|
+
try {
|
|
83
|
+
const jsonValue = objWithToJSON.toJSON();
|
|
84
|
+
// The result of toJSON needs to be stringified recursively
|
|
85
|
+
if (jsonValue === null) {
|
|
86
|
+
return 'null';
|
|
87
|
+
}
|
|
88
|
+
return stringify(jsonValue, ancestors);
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
// If toJSON throws, treat as a regular object
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
if (Array.isArray(currentObj)) {
|
|
95
|
+
const items = currentObj.map((item) => {
|
|
96
|
+
// undefined and functions in arrays become null
|
|
97
|
+
if (item === undefined || typeof item === 'function') {
|
|
98
|
+
return 'null';
|
|
99
|
+
}
|
|
100
|
+
return stringify(item, ancestors);
|
|
101
|
+
});
|
|
102
|
+
return '[' + items.join(',') + ']';
|
|
103
|
+
}
|
|
104
|
+
// Handle objects - sort keys and filter out undefined/function values
|
|
105
|
+
const sortedKeys = Object.keys(currentObj).sort();
|
|
106
|
+
const pairs = [];
|
|
107
|
+
for (const key of sortedKeys) {
|
|
108
|
+
const value = currentObj[key];
|
|
109
|
+
// Skip undefined and function values in objects (per JSON spec)
|
|
110
|
+
if (value !== undefined && typeof value !== 'function') {
|
|
111
|
+
pairs.push(JSON.stringify(key) + ':' + stringify(value, ancestors));
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return '{' + pairs.join(',') + '}';
|
|
115
|
+
}
|
|
116
|
+
finally {
|
|
117
|
+
ancestors.delete(currentObj);
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
return stringify(obj, new Set());
|
|
121
|
+
}
|
|
122
|
+
//# sourceMappingURL=stable-stringify.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stable-stringify.js","sourceRoot":"","sources":["../../../src/policy/stable-stringify.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmDG;AACH,MAAM,UAAU,eAAe,CAAC,GAAY;IAC1C,MAAM,SAAS,GAAG,CAAC,UAAmB,EAAE,SAAuB,EAAU,EAAE;QACzE,6BAA6B;QAC7B,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;YAC7B,OAAO,MAAM,CAAC,CAAC,2CAA2C;QAC5D,CAAC;QACD,IAAI,UAAU,KAAK,IAAI,EAAE,CAAC;YACxB,OAAO,MAAM,CAAC;QAChB,CAAC;QACD,IAAI,OAAO,UAAU,KAAK,UAAU,EAAE,CAAC;YACrC,OAAO,MAAM,CAAC,CAAC,0CAA0C;QAC3D,CAAC;QACD,IAAI,OAAO,UAAU,KAAK,QAAQ,EAAE,CAAC;YACnC,OAAO,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC;QACpC,CAAC;QAED,6DAA6D;QAC7D,IAAI,SAAS,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC;YAC9B,OAAO,cAAc,CAAC;QACxB,CAAC;QAED,SAAS,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QAE1B,IAAI,CAAC;YACH,gDAAgD;YAChD,MAAM,aAAa,GAAG,UAAwC,CAAC;YAC/D,IAAI,OAAO,aAAa,CAAC,MAAM,KAAK,UAAU,EAAE,CAAC;gBAC/C,IAAI,CAAC;oBACH,MAAM,SAAS,GAAG,aAAa,CAAC,MAAM,EAAE,CAAC;oBACzC,2DAA2D;oBAC3D,IAAI,SAAS,KAAK,IAAI,EAAE,CAAC;wBACvB,OAAO,MAAM,CAAC;oBAChB,CAAC;oBACD,OAAO,SAAS,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;gBACzC,CAAC;gBAAC,MAAM,CAAC;oBACP,8CAA8C;gBAChD,CAAC;YACH,CAAC;YAED,IAAI,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,CAAC;gBAC9B,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;oBACpC,gDAAgD;oBAChD,IAAI,IAAI,KAAK,SAAS,IAAI,OAAO,IAAI,KAAK,UAAU,EAAE,CAAC;wBACrD,OAAO,MAAM,CAAC;oBAChB,CAAC;oBACD,OAAO,SAAS,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;gBACpC,CAAC,CAAC,CAAC;gBACH,OAAO,GAAG,GAAG,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC;YACrC,CAAC;YAED,sEAAsE;YACtE,MAAM,UAAU,GAAG,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,IAAI,EAAE,CAAC;YAClD,MAAM,KAAK,GAAa,EAAE,CAAC;YAE3B,KAAK,MAAM,GAAG,IAAI,UAAU,EAAE,CAAC;gBAC7B,MAAM,KAAK,GAAI,UAAsC,CAAC,GAAG,CAAC,CAAC;gBAC3D,gEAAgE;gBAChE,IAAI,KAAK,KAAK,SAAS,IAAI,OAAO,KAAK,KAAK,UAAU,EAAE,CAAC;oBACvD,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,GAAG,GAAG,SAAS,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC;gBACtE,CAAC;YACH,CAAC;YAED,OAAO,GAAG,GAAG,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC;QACrC,CAAC;gBAAS,CAAC;YACT,SAAS,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;QAC/B,CAAC;IACH,CAAC,CAAC;IAEF,OAAO,SAAS,CAAC,GAAG,EAAE,IAAI,GAAG,EAAE,CAAC,CAAC;AACnC,CAAC"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2025 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
export declare enum PolicyDecision {
|
|
7
|
+
ALLOW = "allow",
|
|
8
|
+
DENY = "deny",
|
|
9
|
+
ASK_USER = "ask_user"
|
|
10
|
+
}
|
|
11
|
+
export interface PolicyRule {
|
|
12
|
+
/**
|
|
13
|
+
* The name of the tool this rule applies to.
|
|
14
|
+
* If undefined, the rule applies to all tools.
|
|
15
|
+
*/
|
|
16
|
+
toolName?: string;
|
|
17
|
+
/**
|
|
18
|
+
* Pattern to match against tool arguments.
|
|
19
|
+
* Can be used for more fine-grained control.
|
|
20
|
+
*/
|
|
21
|
+
argsPattern?: RegExp;
|
|
22
|
+
/**
|
|
23
|
+
* The decision to make when this rule matches.
|
|
24
|
+
*/
|
|
25
|
+
decision: PolicyDecision;
|
|
26
|
+
/**
|
|
27
|
+
* Priority of this rule. Higher numbers take precedence.
|
|
28
|
+
* Default is 0.
|
|
29
|
+
*/
|
|
30
|
+
priority?: number;
|
|
31
|
+
}
|
|
32
|
+
export interface PolicyEngineConfig {
|
|
33
|
+
/**
|
|
34
|
+
* List of policy rules to apply.
|
|
35
|
+
*/
|
|
36
|
+
rules?: PolicyRule[];
|
|
37
|
+
/**
|
|
38
|
+
* Default decision when no rules match.
|
|
39
|
+
* Defaults to ASK_USER.
|
|
40
|
+
*/
|
|
41
|
+
defaultDecision?: PolicyDecision;
|
|
42
|
+
/**
|
|
43
|
+
* Whether to allow tools in non-interactive mode.
|
|
44
|
+
* When true, ASK_USER decisions become DENY.
|
|
45
|
+
*/
|
|
46
|
+
nonInteractive?: boolean;
|
|
47
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2025 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
export var PolicyDecision;
|
|
7
|
+
(function (PolicyDecision) {
|
|
8
|
+
PolicyDecision["ALLOW"] = "allow";
|
|
9
|
+
PolicyDecision["DENY"] = "deny";
|
|
10
|
+
PolicyDecision["ASK_USER"] = "ask_user";
|
|
11
|
+
})(PolicyDecision || (PolicyDecision = {}));
|
|
12
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/policy/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,CAAN,IAAY,cAIX;AAJD,WAAY,cAAc;IACxB,iCAAe,CAAA;IACf,+BAAa,CAAA;IACb,uCAAqB,CAAA;AACvB,CAAC,EAJW,cAAc,KAAd,cAAc,QAIzB"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2025 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
import type { Config } from '../config/config.js';
|
|
7
|
+
import type { RoutingContext, RoutingDecision } from './routingStrategy.js';
|
|
8
|
+
/**
|
|
9
|
+
* A centralized service for making model routing decisions.
|
|
10
|
+
*/
|
|
11
|
+
export declare class ModelRouterService {
|
|
12
|
+
private config;
|
|
13
|
+
private strategy;
|
|
14
|
+
constructor(config: Config);
|
|
15
|
+
private initializeDefaultStrategy;
|
|
16
|
+
/**
|
|
17
|
+
* Determines which model to use for a given request context.
|
|
18
|
+
*
|
|
19
|
+
* @param context The full context of the request.
|
|
20
|
+
* @returns A promise that resolves to a RoutingDecision.
|
|
21
|
+
*/
|
|
22
|
+
route(context: RoutingContext): Promise<RoutingDecision>;
|
|
23
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2025 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
import { DefaultStrategy } from './strategies/defaultStrategy.js';
|
|
7
|
+
import { ClassifierStrategy } from './strategies/classifierStrategy.js';
|
|
8
|
+
import { CompositeStrategy } from './strategies/compositeStrategy.js';
|
|
9
|
+
import { FallbackStrategy } from './strategies/fallbackStrategy.js';
|
|
10
|
+
import { OverrideStrategy } from './strategies/overrideStrategy.js';
|
|
11
|
+
import { logModelRouting } from '../telemetry/loggers.js';
|
|
12
|
+
import { ModelRoutingEvent } from '../telemetry/types.js';
|
|
13
|
+
/**
|
|
14
|
+
* A centralized service for making model routing decisions.
|
|
15
|
+
*/
|
|
16
|
+
export class ModelRouterService {
|
|
17
|
+
config;
|
|
18
|
+
strategy;
|
|
19
|
+
constructor(config) {
|
|
20
|
+
this.config = config;
|
|
21
|
+
this.strategy = this.initializeDefaultStrategy();
|
|
22
|
+
}
|
|
23
|
+
initializeDefaultStrategy() {
|
|
24
|
+
// Initialize the composite strategy with the desired priority order.
|
|
25
|
+
// The strategies are ordered in order of highest priority.
|
|
26
|
+
return new CompositeStrategy([
|
|
27
|
+
new FallbackStrategy(),
|
|
28
|
+
new OverrideStrategy(),
|
|
29
|
+
new ClassifierStrategy(),
|
|
30
|
+
new DefaultStrategy(),
|
|
31
|
+
], 'agent-router');
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Determines which model to use for a given request context.
|
|
35
|
+
*
|
|
36
|
+
* @param context The full context of the request.
|
|
37
|
+
* @returns A promise that resolves to a RoutingDecision.
|
|
38
|
+
*/
|
|
39
|
+
async route(context) {
|
|
40
|
+
const startTime = Date.now();
|
|
41
|
+
let decision;
|
|
42
|
+
try {
|
|
43
|
+
decision = await this.strategy.route(context, this.config, this.config.getBaseLlmClient());
|
|
44
|
+
const event = new ModelRoutingEvent(decision.model, decision.metadata.source, decision.metadata.latencyMs, decision.metadata.reasoning, false, // failed
|
|
45
|
+
undefined);
|
|
46
|
+
logModelRouting(this.config, event);
|
|
47
|
+
return decision;
|
|
48
|
+
}
|
|
49
|
+
catch (e) {
|
|
50
|
+
const failed = true;
|
|
51
|
+
const error_message = e instanceof Error ? e.message : String(e);
|
|
52
|
+
// Create a fallback decision for logging purposes
|
|
53
|
+
// We do not actually route here. This should never happen so we should
|
|
54
|
+
// fail loudly to catch any issues where this happens.
|
|
55
|
+
decision = {
|
|
56
|
+
model: this.config.getModel(),
|
|
57
|
+
metadata: {
|
|
58
|
+
source: 'router-exception',
|
|
59
|
+
latencyMs: Date.now() - startTime,
|
|
60
|
+
reasoning: 'An exception occurred during routing.',
|
|
61
|
+
error: error_message,
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
const event = new ModelRoutingEvent(decision.model, decision.metadata.source, decision.metadata.latencyMs, decision.metadata.reasoning, failed, error_message);
|
|
65
|
+
logModelRouting(this.config, event);
|
|
66
|
+
throw e;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=modelRouterService.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"modelRouterService.js","sourceRoot":"","sources":["../../../src/routing/modelRouterService.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAQH,OAAO,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;AAClE,OAAO,EAAE,kBAAkB,EAAE,MAAM,oCAAoC,CAAC;AACxE,OAAO,EAAE,iBAAiB,EAAE,MAAM,mCAAmC,CAAC;AACtE,OAAO,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AACpE,OAAO,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AAEpE,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAE1D;;GAEG;AACH,MAAM,OAAO,kBAAkB;IACrB,MAAM,CAAS;IACf,QAAQ,CAAmB;IAEnC,YAAY,MAAc;QACxB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,yBAAyB,EAAE,CAAC;IACnD,CAAC;IAEO,yBAAyB;QAC/B,qEAAqE;QACrE,2DAA2D;QAC3D,OAAO,IAAI,iBAAiB,CAC1B;YACE,IAAI,gBAAgB,EAAE;YACtB,IAAI,gBAAgB,EAAE;YACtB,IAAI,kBAAkB,EAAE;YACxB,IAAI,eAAe,EAAE;SACtB,EACD,cAAc,CACf,CAAC;IACJ,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,KAAK,CAAC,OAAuB;QACjC,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAC7B,IAAI,QAAyB,CAAC;QAE9B,IAAI,CAAC;YACH,QAAQ,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,KAAK,CAClC,OAAO,EACP,IAAI,CAAC,MAAM,EACX,IAAI,CAAC,MAAM,CAAC,gBAAgB,EAAE,CAC/B,CAAC;YAEF,MAAM,KAAK,GAAG,IAAI,iBAAiB,CACjC,QAAQ,CAAC,KAAK,EACd,QAAQ,CAAC,QAAQ,CAAC,MAAM,EACxB,QAAQ,CAAC,QAAQ,CAAC,SAAS,EAC3B,QAAQ,CAAC,QAAQ,CAAC,SAAS,EAC3B,KAAK,EAAE,SAAS;YAChB,SAAS,CACV,CAAC;YACF,eAAe,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;YAEpC,OAAO,QAAQ,CAAC;QAClB,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,MAAM,MAAM,GAAG,IAAI,CAAC;YACpB,MAAM,aAAa,GAAG,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;YACjE,kDAAkD;YAClD,uEAAuE;YACvE,sDAAsD;YACtD,QAAQ,GAAG;gBACT,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE;gBAC7B,QAAQ,EAAE;oBACR,MAAM,EAAE,kBAAkB;oBAC1B,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS;oBACjC,SAAS,EAAE,uCAAuC;oBAClD,KAAK,EAAE,aAAa;iBACrB;aACF,CAAC;YAEF,MAAM,KAAK,GAAG,IAAI,iBAAiB,CACjC,QAAQ,CAAC,KAAK,EACd,QAAQ,CAAC,QAAQ,CAAC,MAAM,EACxB,QAAQ,CAAC,QAAQ,CAAC,SAAS,EAC3B,QAAQ,CAAC,QAAQ,CAAC,SAAS,EAC3B,MAAM,EACN,aAAa,CACd,CAAC;YAEF,eAAe,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;YAEpC,MAAM,CAAC,CAAC;QACV,CAAC;IACH,CAAC;CACF"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2025 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
7
|
+
import { ModelRouterService } from './modelRouterService.js';
|
|
8
|
+
import { Config } from '../config/config.js';
|
|
9
|
+
import { DefaultStrategy } from './strategies/defaultStrategy.js';
|
|
10
|
+
import { CompositeStrategy } from './strategies/compositeStrategy.js';
|
|
11
|
+
import { FallbackStrategy } from './strategies/fallbackStrategy.js';
|
|
12
|
+
import { OverrideStrategy } from './strategies/overrideStrategy.js';
|
|
13
|
+
import { ClassifierStrategy } from './strategies/classifierStrategy.js';
|
|
14
|
+
import { logModelRouting } from '../telemetry/loggers.js';
|
|
15
|
+
import { ModelRoutingEvent } from '../telemetry/types.js';
|
|
16
|
+
vi.mock('../config/config.js');
|
|
17
|
+
vi.mock('../core/baseLlmClient.js');
|
|
18
|
+
vi.mock('./strategies/defaultStrategy.js');
|
|
19
|
+
vi.mock('./strategies/compositeStrategy.js');
|
|
20
|
+
vi.mock('./strategies/fallbackStrategy.js');
|
|
21
|
+
vi.mock('./strategies/overrideStrategy.js');
|
|
22
|
+
vi.mock('./strategies/classifierStrategy.js');
|
|
23
|
+
vi.mock('../telemetry/loggers.js');
|
|
24
|
+
vi.mock('../telemetry/types.js');
|
|
25
|
+
describe('ModelRouterService', () => {
|
|
26
|
+
let service;
|
|
27
|
+
let mockConfig;
|
|
28
|
+
let mockBaseLlmClient;
|
|
29
|
+
let mockContext;
|
|
30
|
+
let mockCompositeStrategy;
|
|
31
|
+
beforeEach(() => {
|
|
32
|
+
vi.clearAllMocks();
|
|
33
|
+
mockConfig = new Config({});
|
|
34
|
+
mockBaseLlmClient = {};
|
|
35
|
+
vi.spyOn(mockConfig, 'getBaseLlmClient').mockReturnValue(mockBaseLlmClient);
|
|
36
|
+
mockCompositeStrategy = new CompositeStrategy([
|
|
37
|
+
new FallbackStrategy(),
|
|
38
|
+
new OverrideStrategy(),
|
|
39
|
+
new ClassifierStrategy(),
|
|
40
|
+
new DefaultStrategy(),
|
|
41
|
+
], 'agent-router');
|
|
42
|
+
vi.mocked(CompositeStrategy).mockImplementation(() => mockCompositeStrategy);
|
|
43
|
+
service = new ModelRouterService(mockConfig);
|
|
44
|
+
mockContext = {
|
|
45
|
+
history: [],
|
|
46
|
+
request: [{ text: 'test prompt' }],
|
|
47
|
+
signal: new AbortController().signal,
|
|
48
|
+
};
|
|
49
|
+
});
|
|
50
|
+
it('should initialize with a CompositeStrategy', () => {
|
|
51
|
+
expect(CompositeStrategy).toHaveBeenCalled();
|
|
52
|
+
expect(service['strategy']).toBeInstanceOf(CompositeStrategy);
|
|
53
|
+
});
|
|
54
|
+
it('should initialize the CompositeStrategy with the correct child strategies in order', () => {
|
|
55
|
+
// This test relies on the mock implementation detail of the constructor
|
|
56
|
+
const compositeStrategyArgs = vi.mocked(CompositeStrategy).mock.calls[0];
|
|
57
|
+
const childStrategies = compositeStrategyArgs[0];
|
|
58
|
+
expect(childStrategies.length).toBe(4);
|
|
59
|
+
expect(childStrategies[0]).toBeInstanceOf(FallbackStrategy);
|
|
60
|
+
expect(childStrategies[1]).toBeInstanceOf(OverrideStrategy);
|
|
61
|
+
expect(childStrategies[2]).toBeInstanceOf(ClassifierStrategy);
|
|
62
|
+
expect(childStrategies[3]).toBeInstanceOf(DefaultStrategy);
|
|
63
|
+
expect(compositeStrategyArgs[1]).toBe('agent-router');
|
|
64
|
+
});
|
|
65
|
+
describe('route()', () => {
|
|
66
|
+
const strategyDecision = {
|
|
67
|
+
model: 'strategy-chosen-model',
|
|
68
|
+
metadata: {
|
|
69
|
+
source: 'test-router/fallback',
|
|
70
|
+
latencyMs: 10,
|
|
71
|
+
reasoning: 'Strategy reasoning',
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
it('should delegate routing to the composite strategy', async () => {
|
|
75
|
+
const strategySpy = vi
|
|
76
|
+
.spyOn(mockCompositeStrategy, 'route')
|
|
77
|
+
.mockResolvedValue(strategyDecision);
|
|
78
|
+
const decision = await service.route(mockContext);
|
|
79
|
+
expect(strategySpy).toHaveBeenCalledWith(mockContext, mockConfig, mockBaseLlmClient);
|
|
80
|
+
expect(decision).toEqual(strategyDecision);
|
|
81
|
+
});
|
|
82
|
+
it('should log a telemetry event on a successful decision', async () => {
|
|
83
|
+
vi.spyOn(mockCompositeStrategy, 'route').mockResolvedValue(strategyDecision);
|
|
84
|
+
await service.route(mockContext);
|
|
85
|
+
expect(ModelRoutingEvent).toHaveBeenCalledWith('strategy-chosen-model', 'test-router/fallback', 10, 'Strategy reasoning', false, undefined);
|
|
86
|
+
expect(logModelRouting).toHaveBeenCalledWith(mockConfig, expect.any(ModelRoutingEvent));
|
|
87
|
+
});
|
|
88
|
+
it('should log a telemetry event and re-throw on a failed decision', async () => {
|
|
89
|
+
const testError = new Error('Strategy failed');
|
|
90
|
+
vi.spyOn(mockCompositeStrategy, 'route').mockRejectedValue(testError);
|
|
91
|
+
vi.spyOn(mockConfig, 'getModel').mockReturnValue('default-model');
|
|
92
|
+
await expect(service.route(mockContext)).rejects.toThrow(testError);
|
|
93
|
+
expect(ModelRoutingEvent).toHaveBeenCalledWith('default-model', 'router-exception', expect.any(Number), 'An exception occurred during routing.', true, 'Strategy failed');
|
|
94
|
+
expect(logModelRouting).toHaveBeenCalledWith(mockConfig, expect.any(ModelRoutingEvent));
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
//# sourceMappingURL=modelRouterService.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"modelRouterService.test.js","sourceRoot":"","sources":["../../../src/routing/modelRouterService.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2025 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
import type { Content, PartListUnion } from '@google/genai';
|
|
7
|
+
import type { BaseLlmClient } from '../core/baseLlmClient.js';
|
|
8
|
+
import type { Config } from '../config/config.js';
|
|
9
|
+
/**
|
|
10
|
+
* The output of a routing decision. It specifies which model to use and why.
|
|
11
|
+
*/
|
|
12
|
+
export interface RoutingDecision {
|
|
13
|
+
/** The model identifier string to use for the next API call (e.g., 'gemini-2.5-pro'). */
|
|
14
|
+
model: string;
|
|
15
|
+
/**
|
|
16
|
+
* Metadata about the routing decision for logging purposes.
|
|
17
|
+
*/
|
|
18
|
+
metadata: {
|
|
19
|
+
source: string;
|
|
20
|
+
latencyMs: number;
|
|
21
|
+
reasoning: string;
|
|
22
|
+
error?: string;
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* The context provided to the router for making a decision.
|
|
27
|
+
*/
|
|
28
|
+
export interface RoutingContext {
|
|
29
|
+
/** The full history of the conversation. */
|
|
30
|
+
history: Content[];
|
|
31
|
+
/** The immediate request parts to be processed. */
|
|
32
|
+
request: PartListUnion;
|
|
33
|
+
/** An abort signal to cancel an LLM call during routing. */
|
|
34
|
+
signal: AbortSignal;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* The core interface that all routing strategies must implement.
|
|
38
|
+
* Strategies implementing this interface may decline a request by returning null.
|
|
39
|
+
*/
|
|
40
|
+
export interface RoutingStrategy {
|
|
41
|
+
/** The name of the strategy (e.g., 'fallback', 'override', 'composite'). */
|
|
42
|
+
readonly name: string;
|
|
43
|
+
/**
|
|
44
|
+
* Determines which model to use for a given request context.
|
|
45
|
+
* @param context The full context of the request.
|
|
46
|
+
* @param config The current configuration.
|
|
47
|
+
* @param client A reference to the GeminiClient, allowing the strategy to make its own API calls if needed.
|
|
48
|
+
* @returns A promise that resolves to a RoutingDecision, or null if the strategy is not applicable.
|
|
49
|
+
*/
|
|
50
|
+
route(context: RoutingContext, config: Config, baseLlmClient: BaseLlmClient): Promise<RoutingDecision | null>;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* A strategy that is guaranteed to return a decision. It must not return null.
|
|
54
|
+
* This is used to ensure that a composite chain always terminates.
|
|
55
|
+
*/
|
|
56
|
+
export interface TerminalStrategy extends RoutingStrategy {
|
|
57
|
+
/**
|
|
58
|
+
* Determines which model to use for a given request context.
|
|
59
|
+
* @returns A promise that resolves to a RoutingDecision.
|
|
60
|
+
*/
|
|
61
|
+
route(context: RoutingContext, config: Config, baseLlmClient: BaseLlmClient): Promise<RoutingDecision>;
|
|
62
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routingStrategy.js","sourceRoot":"","sources":["../../../src/routing/routingStrategy.ts"],"names":[],"mappings":"AAAA;;;;GAIG"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2025 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
import type { BaseLlmClient } from '../../core/baseLlmClient.js';
|
|
7
|
+
import type { RoutingContext, RoutingDecision, RoutingStrategy } from '../routingStrategy.js';
|
|
8
|
+
import type { Config } from '../../config/config.js';
|
|
9
|
+
export declare class ClassifierStrategy implements RoutingStrategy {
|
|
10
|
+
readonly name = "classifier";
|
|
11
|
+
route(context: RoutingContext, _config: Config, baseLlmClient: BaseLlmClient): Promise<RoutingDecision | null>;
|
|
12
|
+
}
|