@vybestack/llxprt-code-core 0.4.7 → 0.5.0-nightly.251102.6bb3db7a
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/prompt-config/defaults/default-prompts.json +4 -17
- package/dist/src/auth/precedence.d.ts +69 -9
- package/dist/src/auth/precedence.js +467 -69
- package/dist/src/auth/precedence.js.map +1 -1
- package/dist/src/auth/types.d.ts +2 -2
- package/dist/src/config/config.d.ts +18 -1
- package/dist/src/config/config.js +123 -6
- package/dist/src/config/config.js.map +1 -1
- package/dist/src/config/index.d.ts +6 -0
- package/dist/src/config/index.js +5 -0
- package/dist/src/config/index.js.map +1 -1
- package/dist/src/config/profileManager.d.ts +23 -3
- package/dist/src/config/profileManager.js +54 -7
- package/dist/src/config/profileManager.js.map +1 -1
- package/dist/src/config/subagentManager.d.ts +96 -0
- package/dist/src/config/subagentManager.js +371 -0
- package/dist/src/config/subagentManager.js.map +1 -0
- package/dist/src/config/types.d.ts +18 -0
- package/dist/src/config/types.js +3 -0
- package/dist/src/config/types.js.map +1 -0
- package/dist/src/core/client.d.ts +27 -7
- package/dist/src/core/client.js +235 -56
- package/dist/src/core/client.js.map +1 -1
- package/dist/src/core/contentGenerator.d.ts +3 -1
- package/dist/src/core/contentGenerator.js +3 -0
- package/dist/src/core/contentGenerator.js.map +1 -1
- package/dist/src/core/coreToolScheduler.d.ts +1 -5
- package/dist/src/core/coreToolScheduler.js +95 -23
- package/dist/src/core/coreToolScheduler.js.map +1 -1
- package/dist/src/core/geminiChat.d.ts +42 -12
- package/dist/src/core/geminiChat.js +413 -207
- package/dist/src/core/geminiChat.js.map +1 -1
- package/dist/src/core/nonInteractiveToolExecutor.d.ts +3 -2
- package/dist/src/core/nonInteractiveToolExecutor.js +94 -10
- package/dist/src/core/nonInteractiveToolExecutor.js.map +1 -1
- package/dist/src/core/subagent.d.ts +86 -7
- package/dist/src/core/subagent.js +809 -79
- package/dist/src/core/subagent.js.map +1 -1
- package/dist/src/core/subagentOrchestrator.d.ts +73 -0
- package/dist/src/core/subagentOrchestrator.js +383 -0
- package/dist/src/core/subagentOrchestrator.js.map +1 -0
- package/dist/src/core/subagentScheduler.d.ts +16 -0
- package/dist/src/core/subagentScheduler.js +7 -0
- package/dist/src/core/subagentScheduler.js.map +1 -0
- package/dist/src/core/turn.d.ts +5 -1
- package/dist/src/core/turn.js +5 -1
- package/dist/src/core/turn.js.map +1 -1
- package/dist/src/hooks/tool-render-suppression-hook.js +6 -1
- package/dist/src/hooks/tool-render-suppression-hook.js.map +1 -1
- package/dist/src/ide/ideContext.d.ts +32 -32
- package/dist/src/index.d.ts +19 -1
- package/dist/src/index.js +15 -2
- package/dist/src/index.js.map +1 -1
- package/dist/src/interfaces/index.d.ts +1 -0
- package/dist/src/interfaces/index.js +4 -0
- package/dist/src/interfaces/index.js.map +1 -0
- package/dist/src/interfaces/nodejs-error.interface.d.ts +4 -0
- package/dist/src/interfaces/nodejs-error.interface.js +2 -0
- package/dist/src/interfaces/nodejs-error.interface.js.map +1 -0
- package/dist/src/parsers/TextToolCallParser.d.ts +17 -1
- package/dist/src/parsers/TextToolCallParser.js +542 -148
- package/dist/src/parsers/TextToolCallParser.js.map +1 -1
- package/dist/src/prompt-config/defaults/core.md +15 -0
- package/dist/src/prompt-config/defaults/providers/gemini/core.md +203 -119
- package/dist/src/prompt-config/defaults/tool-defaults.js +2 -0
- package/dist/src/prompt-config/defaults/tool-defaults.js.map +1 -1
- package/dist/src/prompt-config/defaults/tools/list-subagents.md +7 -0
- package/dist/src/prompt-config/defaults/tools/task.md +8 -0
- package/dist/src/providers/BaseProvider.d.ts +115 -30
- package/dist/src/providers/BaseProvider.js +445 -109
- package/dist/src/providers/BaseProvider.js.map +1 -1
- package/dist/src/providers/IProvider.d.ts +50 -18
- package/dist/src/providers/LoggingProviderWrapper.d.ts +60 -16
- package/dist/src/providers/LoggingProviderWrapper.js +213 -60
- package/dist/src/providers/LoggingProviderWrapper.js.map +1 -1
- package/dist/src/providers/ProviderManager.d.ts +73 -2
- package/dist/src/providers/ProviderManager.js +492 -40
- package/dist/src/providers/ProviderManager.js.map +1 -1
- package/dist/src/providers/anthropic/AnthropicProvider.d.ts +35 -38
- package/dist/src/providers/anthropic/AnthropicProvider.js +222 -227
- package/dist/src/providers/anthropic/AnthropicProvider.js.map +1 -1
- package/dist/src/providers/errors.d.ts +86 -0
- package/dist/src/providers/errors.js +89 -0
- package/dist/src/providers/errors.js.map +1 -1
- package/dist/src/providers/gemini/GeminiProvider.d.ts +101 -41
- package/dist/src/providers/gemini/GeminiProvider.js +386 -311
- package/dist/src/providers/gemini/GeminiProvider.js.map +1 -1
- package/dist/src/providers/openai/ConversationCache.d.ts +5 -3
- package/dist/src/providers/openai/ConversationCache.js +93 -32
- package/dist/src/providers/openai/ConversationCache.js.map +1 -1
- package/dist/src/providers/openai/OpenAIProvider.d.ts +82 -42
- package/dist/src/providers/openai/OpenAIProvider.js +392 -457
- package/dist/src/providers/openai/OpenAIProvider.js.map +1 -1
- package/dist/src/providers/openai/getOpenAIProviderInfo.d.ts +1 -1
- package/dist/src/providers/openai/getOpenAIProviderInfo.js +52 -22
- package/dist/src/providers/openai/getOpenAIProviderInfo.js.map +1 -1
- package/dist/src/providers/openai/openaiRequestParams.d.ts +7 -0
- package/dist/src/providers/openai/openaiRequestParams.js +66 -0
- package/dist/src/providers/openai/openaiRequestParams.js.map +1 -0
- package/dist/src/providers/openai-responses/OpenAIResponsesProvider.d.ts +6 -33
- package/dist/src/providers/openai-responses/OpenAIResponsesProvider.js +84 -183
- package/dist/src/providers/openai-responses/OpenAIResponsesProvider.js.map +1 -1
- package/dist/src/providers/types/providerRuntime.d.ts +17 -0
- package/dist/src/providers/types/providerRuntime.js +7 -0
- package/dist/src/providers/types/providerRuntime.js.map +1 -0
- package/dist/src/providers/utils/authToken.d.ts +12 -0
- package/dist/src/providers/utils/authToken.js +17 -0
- package/dist/src/providers/utils/authToken.js.map +1 -0
- package/dist/src/providers/utils/userMemory.d.ts +8 -0
- package/dist/src/providers/utils/userMemory.js +34 -0
- package/dist/src/providers/utils/userMemory.js.map +1 -0
- package/dist/src/runtime/AgentRuntimeContext.d.ts +213 -0
- package/dist/src/runtime/AgentRuntimeContext.js +17 -0
- package/dist/src/runtime/AgentRuntimeContext.js.map +1 -0
- package/dist/src/runtime/AgentRuntimeLoader.d.ts +47 -0
- package/dist/src/runtime/AgentRuntimeLoader.js +122 -0
- package/dist/src/runtime/AgentRuntimeLoader.js.map +1 -0
- package/dist/src/runtime/AgentRuntimeState.d.ts +232 -0
- package/dist/src/runtime/AgentRuntimeState.js +439 -0
- package/dist/src/runtime/AgentRuntimeState.js.map +1 -0
- package/dist/src/runtime/RuntimeInvocationContext.d.ts +51 -0
- package/dist/src/runtime/RuntimeInvocationContext.js +52 -0
- package/dist/src/runtime/RuntimeInvocationContext.js.map +1 -0
- package/dist/src/runtime/createAgentRuntimeContext.d.ts +7 -0
- package/dist/src/runtime/createAgentRuntimeContext.js +65 -0
- package/dist/src/runtime/createAgentRuntimeContext.js.map +1 -0
- package/dist/src/runtime/index.d.ts +13 -0
- package/dist/src/runtime/index.js +14 -0
- package/dist/src/runtime/index.js.map +1 -0
- package/dist/src/runtime/providerRuntimeContext.d.ts +30 -0
- package/dist/src/runtime/providerRuntimeContext.js +70 -0
- package/dist/src/runtime/providerRuntimeContext.js.map +1 -0
- package/dist/src/runtime/runtimeAdapters.d.ts +22 -0
- package/dist/src/runtime/runtimeAdapters.js +81 -0
- package/dist/src/runtime/runtimeAdapters.js.map +1 -0
- package/dist/src/runtime/runtimeStateFactory.d.ts +21 -0
- package/dist/src/runtime/runtimeStateFactory.js +104 -0
- package/dist/src/runtime/runtimeStateFactory.js.map +1 -0
- package/dist/src/services/history/ContentConverters.js +3 -5
- package/dist/src/services/history/ContentConverters.js.map +1 -1
- package/dist/src/services/shellExecutionService.js +2 -2
- package/dist/src/services/shellExecutionService.js.map +1 -1
- package/dist/src/services/todo-context-tracker.d.ts +10 -8
- package/dist/src/services/todo-context-tracker.js +26 -10
- package/dist/src/services/todo-context-tracker.js.map +1 -1
- package/dist/src/services/tool-call-tracker-service.d.ts +11 -7
- package/dist/src/services/tool-call-tracker-service.js +89 -29
- package/dist/src/services/tool-call-tracker-service.js.map +1 -1
- package/dist/src/settings/SettingsService.d.ts +4 -0
- package/dist/src/settings/SettingsService.js +65 -2
- package/dist/src/settings/SettingsService.js.map +1 -1
- package/dist/src/settings/settingsServiceInstance.d.ts +6 -1
- package/dist/src/settings/settingsServiceInstance.js +28 -8
- package/dist/src/settings/settingsServiceInstance.js.map +1 -1
- package/dist/src/telemetry/loggers.d.ts +5 -1
- package/dist/src/telemetry/loggers.js.map +1 -1
- package/dist/src/telemetry/loggers.test.circular.js +4 -0
- package/dist/src/telemetry/loggers.test.circular.js.map +1 -1
- package/dist/src/telemetry/metrics.d.ts +3 -1
- package/dist/src/telemetry/metrics.js.map +1 -1
- package/dist/src/telemetry/types.d.ts +1 -0
- package/dist/src/telemetry/types.js +3 -0
- package/dist/src/telemetry/types.js.map +1 -1
- package/dist/src/test-utils/index.d.ts +2 -0
- package/dist/src/test-utils/index.js +2 -0
- package/dist/src/test-utils/index.js.map +1 -1
- package/dist/src/test-utils/mockWorkspaceContext.d.ts +0 -3
- package/dist/src/test-utils/mockWorkspaceContext.js +3 -4
- package/dist/src/test-utils/mockWorkspaceContext.js.map +1 -1
- package/dist/src/test-utils/providerCallOptions.d.ts +43 -0
- package/dist/src/test-utils/providerCallOptions.js +137 -0
- package/dist/src/test-utils/providerCallOptions.js.map +1 -0
- package/dist/src/test-utils/runtime.d.ts +92 -0
- package/dist/src/test-utils/runtime.js +226 -0
- package/dist/src/test-utils/runtime.js.map +1 -0
- package/dist/src/test-utils/tools.d.ts +4 -4
- package/dist/src/test-utils/tools.js +20 -10
- package/dist/src/test-utils/tools.js.map +1 -1
- package/dist/src/tools/list-subagents.d.ts +31 -0
- package/dist/src/tools/list-subagents.js +109 -0
- package/dist/src/tools/list-subagents.js.map +1 -0
- package/dist/src/tools/task.d.ts +87 -0
- package/dist/src/tools/task.js +427 -0
- package/dist/src/tools/task.js.map +1 -0
- package/dist/src/tools/todo-read.js +1 -1
- package/dist/src/tools/todo-read.js.map +1 -1
- package/dist/src/tools/todo-store.js +4 -2
- package/dist/src/tools/todo-store.js.map +1 -1
- package/dist/src/tools/todo-write.js +4 -2
- package/dist/src/tools/todo-write.js.map +1 -1
- package/dist/src/tools/tool-error.d.ts +1 -0
- package/dist/src/tools/tool-error.js +1 -0
- package/dist/src/tools/tool-error.js.map +1 -1
- package/dist/src/tools/tool-registry.d.ts +2 -0
- package/dist/src/tools/tool-registry.js +46 -21
- package/dist/src/tools/tool-registry.js.map +1 -1
- package/dist/src/types/modelParams.d.ts +4 -0
- package/dist/src/utils/editor.js +10 -8
- package/dist/src/utils/editor.js.map +1 -1
- package/dist/src/utils/gitIgnoreParser.js +15 -3
- package/dist/src/utils/gitIgnoreParser.js.map +1 -1
- package/dist/src/utils/memoryImportProcessor.js +22 -3
- package/dist/src/utils/memoryImportProcessor.js.map +1 -1
- package/package.json +1 -1
- package/dist/src/prompt-config/defaults/providers/anthropic/core.md +0 -97
- package/dist/src/prompt-config/defaults/providers/anthropic/tools/glob.md +0 -34
- package/dist/src/prompt-config/defaults/providers/anthropic/tools/list-directory.md +0 -11
- package/dist/src/prompt-config/defaults/providers/anthropic/tools/read-file.md +0 -14
- package/dist/src/prompt-config/defaults/providers/anthropic/tools/read-many-files.md +0 -31
- package/dist/src/prompt-config/defaults/providers/anthropic/tools/replace.md +0 -41
- package/dist/src/prompt-config/defaults/providers/anthropic/tools/run-shell-command.md +0 -32
- package/dist/src/prompt-config/defaults/providers/anthropic/tools/save-memory.md +0 -35
- package/dist/src/prompt-config/defaults/providers/anthropic/tools/search-file-content.md +0 -44
- package/dist/src/prompt-config/defaults/providers/anthropic/tools/todo-write.md +0 -45
- package/dist/src/prompt-config/defaults/providers/anthropic/tools/write-file.md +0 -11
- package/dist/src/prompt-config/defaults/providers/openai/core.md +0 -97
- package/dist/src/prompt-config/defaults/providers/openai/tools/todo-pause.md +0 -28
- package/dist/src/prompt-config/defaults/providers/openai/tools/todo-read.md +0 -5
- package/dist/src/prompt-config/defaults/providers/openai/tools/todo-write.md +0 -45
|
@@ -3,12 +3,22 @@
|
|
|
3
3
|
* Copyright 2025 Google LLC
|
|
4
4
|
* SPDX-License-Identifier: Apache-2.0
|
|
5
5
|
*/
|
|
6
|
+
/**
|
|
7
|
+
* @plan PLAN-20251028-STATELESS6.P08
|
|
8
|
+
* @requirement REQ-STAT6-001.1, REQ-STAT6-003.1
|
|
9
|
+
* @pseudocode agent-runtime-context.md lines 92-101
|
|
10
|
+
*/
|
|
6
11
|
import { reportError } from '../utils/errorReporting.js';
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
12
|
+
import { DebugLogger } from '../debug/DebugLogger.js';
|
|
13
|
+
import { ApprovalMode } from '../config/config.js';
|
|
14
|
+
import { GeminiEventType, Turn } from './turn.js';
|
|
15
|
+
import { executeToolCall, } from './nonInteractiveToolExecutor.js';
|
|
10
16
|
import { Type, } from '@google/genai';
|
|
11
17
|
import { GeminiChat, StreamEventType } from './geminiChat.js';
|
|
18
|
+
import { GemmaToolCallParser } from '../parsers/TextToolCallParser.js';
|
|
19
|
+
import { TodoStore } from '../tools/todo-store.js';
|
|
20
|
+
import { CoreToolScheduler, } from './coreToolScheduler.js';
|
|
21
|
+
import { ToolErrorType } from '../tools/tool-error.js';
|
|
12
22
|
/**
|
|
13
23
|
* @fileoverview Defines the configuration interfaces for a subagent.
|
|
14
24
|
*
|
|
@@ -38,6 +48,7 @@ export var SubagentTerminateMode;
|
|
|
38
48
|
*/
|
|
39
49
|
SubagentTerminateMode["MAX_TURNS"] = "MAX_TURNS";
|
|
40
50
|
})(SubagentTerminateMode || (SubagentTerminateMode = {}));
|
|
51
|
+
const defaultEnvironmentContextLoader = async () => [];
|
|
41
52
|
/**
|
|
42
53
|
* Manages the runtime context state for the subagent.
|
|
43
54
|
* This class provides a mechanism to store and retrieve key-value pairs
|
|
@@ -73,6 +84,86 @@ export class ContextState {
|
|
|
73
84
|
return Object.keys(this.state);
|
|
74
85
|
}
|
|
75
86
|
}
|
|
87
|
+
const normalizeToolName = (name) => name.trim().toLowerCase();
|
|
88
|
+
function convertMetadataToFunctionDeclaration(fallbackName, metadata) {
|
|
89
|
+
const rawSchema = metadata.parameterSchema && typeof metadata.parameterSchema === 'object'
|
|
90
|
+
? { ...metadata.parameterSchema }
|
|
91
|
+
: {};
|
|
92
|
+
const properties = rawSchema.properties ?? {};
|
|
93
|
+
return {
|
|
94
|
+
name: metadata.name ?? fallbackName,
|
|
95
|
+
description: metadata.description ?? '',
|
|
96
|
+
parameters: {
|
|
97
|
+
...rawSchema,
|
|
98
|
+
type: rawSchema.type ?? Type.OBJECT,
|
|
99
|
+
properties,
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
async function validateToolsAgainstRuntime(params) {
|
|
104
|
+
const { toolConfig, toolRegistry, toolsView } = params;
|
|
105
|
+
const allowedNames = new Set((typeof toolsView.listToolNames === 'function'
|
|
106
|
+
? toolsView.listToolNames()
|
|
107
|
+
: []).map(normalizeToolName));
|
|
108
|
+
for (const toolEntry of toolConfig.tools) {
|
|
109
|
+
if (typeof toolEntry !== 'string') {
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
if (allowedNames.size > 0 &&
|
|
113
|
+
!allowedNames.has(normalizeToolName(toolEntry))) {
|
|
114
|
+
throw new Error(`Tool "${toolEntry}" is not permitted for this runtime bundle.`);
|
|
115
|
+
}
|
|
116
|
+
const tool = toolRegistry.getTool(toolEntry);
|
|
117
|
+
if (!tool) {
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
function createToolExecutionConfig(runtimeBundle, toolRegistry, settingsSnapshot, toolConfig) {
|
|
123
|
+
const ephemerals = buildEphemeralSettings(settingsSnapshot);
|
|
124
|
+
if (toolConfig && Array.isArray(toolConfig.tools)) {
|
|
125
|
+
const normalizedWhitelist = toolConfig.tools
|
|
126
|
+
.filter((entry) => typeof entry === 'string')
|
|
127
|
+
.map((entry) => entry.trim())
|
|
128
|
+
.filter((entry) => entry.length > 0)
|
|
129
|
+
.map((entry) => entry.toLowerCase());
|
|
130
|
+
if (normalizedWhitelist.length > 0) {
|
|
131
|
+
const existingAllowed = Array.isArray(ephemerals['tools.allowed'])
|
|
132
|
+
? ephemerals['tools.allowed']
|
|
133
|
+
.map((entry) => (typeof entry === 'string' ? entry.trim() : ''))
|
|
134
|
+
.filter((entry) => entry.length > 0)
|
|
135
|
+
.map((entry) => entry.toLowerCase())
|
|
136
|
+
: [];
|
|
137
|
+
const allowedSet = existingAllowed.length > 0
|
|
138
|
+
? normalizedWhitelist.filter((entry) => existingAllowed.includes(entry))
|
|
139
|
+
: normalizedWhitelist;
|
|
140
|
+
ephemerals['tools.allowed'] = Array.from(new Set(allowedSet));
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return {
|
|
144
|
+
getToolRegistry: () => toolRegistry,
|
|
145
|
+
getEphemeralSettings: () => ({ ...ephemerals }),
|
|
146
|
+
getEphemeralSetting: (key) => ephemerals[key],
|
|
147
|
+
getExcludeTools: () => [],
|
|
148
|
+
getSessionId: () => runtimeBundle.runtimeContext.state.sessionId,
|
|
149
|
+
getTelemetryLogPromptsEnabled: () => Boolean(settingsSnapshot?.telemetry?.enabled),
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
function buildEphemeralSettings(snapshot) {
|
|
153
|
+
const ephemerals = {
|
|
154
|
+
emojifilter: 'auto',
|
|
155
|
+
};
|
|
156
|
+
if (!snapshot) {
|
|
157
|
+
return ephemerals;
|
|
158
|
+
}
|
|
159
|
+
if (snapshot.tools?.allowed) {
|
|
160
|
+
ephemerals['tools.allowed'] = [...snapshot.tools.allowed];
|
|
161
|
+
}
|
|
162
|
+
if (snapshot.tools?.disabled) {
|
|
163
|
+
ephemerals['tools.disabled'] = [...snapshot.tools.disabled];
|
|
164
|
+
}
|
|
165
|
+
return ephemerals;
|
|
166
|
+
}
|
|
76
167
|
/**
|
|
77
168
|
* Replaces `${...}` placeholders in a template string with values from a context.
|
|
78
169
|
*
|
|
@@ -101,13 +192,21 @@ function templateString(template, context) {
|
|
|
101
192
|
* Represents the scope and execution environment for a subagent.
|
|
102
193
|
* This class orchestrates the subagent's lifecycle, managing its chat interactions,
|
|
103
194
|
* runtime context, and the collection of its outputs.
|
|
195
|
+
*
|
|
196
|
+
* @plan PLAN-20251028-STATELESS6.P08
|
|
197
|
+
* @requirement REQ-STAT6-001.1, REQ-STAT6-001.2, REQ-STAT6-003.1, REQ-STAT6-003.2
|
|
198
|
+
* @pseudocode agent-runtime-context.md line 93 (step 007.1)
|
|
104
199
|
*/
|
|
105
200
|
export class SubAgentScope {
|
|
106
201
|
name;
|
|
107
202
|
runtimeContext;
|
|
108
|
-
promptConfig;
|
|
109
203
|
modelConfig;
|
|
110
204
|
runConfig;
|
|
205
|
+
promptConfig;
|
|
206
|
+
contentGenerator;
|
|
207
|
+
toolExecutorContext;
|
|
208
|
+
environmentContextLoader;
|
|
209
|
+
config;
|
|
111
210
|
toolConfig;
|
|
112
211
|
outputConfig;
|
|
113
212
|
output = {
|
|
@@ -115,80 +214,318 @@ export class SubAgentScope {
|
|
|
115
214
|
emitted_vars: {},
|
|
116
215
|
};
|
|
117
216
|
subagentId;
|
|
217
|
+
logger = new DebugLogger('llxprt:subagent');
|
|
218
|
+
textToolParser = new GemmaToolCallParser();
|
|
219
|
+
activeAbortController = null;
|
|
118
220
|
/** Optional callback for streaming text messages during execution */
|
|
119
221
|
onMessage;
|
|
120
222
|
/**
|
|
121
223
|
* Constructs a new SubAgentScope instance.
|
|
224
|
+
*
|
|
225
|
+
* @plan PLAN-20251028-STATELESS6.P08
|
|
226
|
+
* @requirement REQ-STAT6-001.1
|
|
227
|
+
* @pseudocode agent-runtime-context.md line 93 (step 007.1)
|
|
228
|
+
*
|
|
122
229
|
* @param name - The name for the subagent, used for logging and identification.
|
|
123
|
-
* @param runtimeContext -
|
|
124
|
-
* @param promptConfig - Configuration for the subagent's prompt and behavior.
|
|
230
|
+
* @param runtimeContext - Immutable runtime context (replaces Config parameter).
|
|
125
231
|
* @param modelConfig - Configuration for the generative model parameters.
|
|
126
232
|
* @param runConfig - Configuration for the subagent's execution environment.
|
|
233
|
+
* @param promptConfig - Configuration for the subagent's prompt and behavior.
|
|
234
|
+
* @param contentGenerator - Pre-initialized content generator for this subagent.
|
|
235
|
+
* @param toolRegistry - Active tool registry for execution and validation.
|
|
236
|
+
* @param toolExecutorContext - Stateless execution context used for tool invocations.
|
|
237
|
+
* @param environmentContextLoader - Function that resolves environment context for prompts.
|
|
127
238
|
* @param toolConfig - Optional configuration for tools available to the subagent.
|
|
128
239
|
* @param outputConfig - Optional configuration for the subagent's expected outputs.
|
|
129
240
|
*/
|
|
130
|
-
constructor(name, runtimeContext, promptConfig,
|
|
241
|
+
constructor(name, runtimeContext, modelConfig, runConfig, promptConfig, contentGenerator, toolExecutorContext, environmentContextLoader, config, toolConfig, outputConfig) {
|
|
131
242
|
this.name = name;
|
|
132
243
|
this.runtimeContext = runtimeContext;
|
|
133
|
-
this.promptConfig = promptConfig;
|
|
134
244
|
this.modelConfig = modelConfig;
|
|
135
245
|
this.runConfig = runConfig;
|
|
246
|
+
this.promptConfig = promptConfig;
|
|
247
|
+
this.contentGenerator = contentGenerator;
|
|
248
|
+
this.toolExecutorContext = toolExecutorContext;
|
|
249
|
+
this.environmentContextLoader = environmentContextLoader;
|
|
250
|
+
this.config = config;
|
|
136
251
|
this.toolConfig = toolConfig;
|
|
137
252
|
this.outputConfig = outputConfig;
|
|
138
253
|
const randomPart = Math.random().toString(36).slice(2, 8);
|
|
139
254
|
this.subagentId = `${this.name}-${randomPart}`;
|
|
140
255
|
}
|
|
256
|
+
/**
|
|
257
|
+
* Returns the unique agent identifier assigned to this subagent scope.
|
|
258
|
+
*/
|
|
259
|
+
getAgentId() {
|
|
260
|
+
return this.subagentId;
|
|
261
|
+
}
|
|
141
262
|
/**
|
|
142
263
|
* Creates and validates a new SubAgentScope instance.
|
|
143
264
|
* This factory method ensures that all tools provided in the prompt configuration
|
|
144
265
|
* are valid for non-interactive use before creating the subagent instance.
|
|
266
|
+
*
|
|
267
|
+
* @plan PLAN-20251028-STATELESS6.P08
|
|
268
|
+
* @requirement REQ-STAT6-001.1, REQ-STAT6-003.1, REQ-STAT6-003.2
|
|
269
|
+
* @pseudocode agent-runtime-context.md lines 94-98 (steps 007.2-007.6)
|
|
270
|
+
*
|
|
145
271
|
* @param {string} name - The name of the subagent.
|
|
146
|
-
* @param {Config}
|
|
272
|
+
* @param {Config} foregroundConfig - Foreground configuration used for shared scheduler plumbing.
|
|
147
273
|
* @param {PromptConfig} promptConfig - Configuration for the subagent's prompt and behavior.
|
|
148
274
|
* @param {ModelConfig} modelConfig - Configuration for the generative model parameters.
|
|
149
275
|
* @param {RunConfig} runConfig - Configuration for the subagent's execution environment.
|
|
150
276
|
* @param {ToolConfig} [toolConfig] - Optional configuration for tools.
|
|
151
277
|
* @param {OutputConfig} [outputConfig] - Optional configuration for expected outputs.
|
|
278
|
+
* @param {SubAgentRuntimeOverrides} [overrides] - Optional stateless runtime inputs (provider runtime, adapters, settings) to bypass Config usage.
|
|
152
279
|
* @returns {Promise<SubAgentScope>} A promise that resolves to a valid SubAgentScope instance.
|
|
153
280
|
* @throws {Error} If any tool requires user confirmation.
|
|
154
281
|
*/
|
|
155
|
-
static async create(name,
|
|
282
|
+
static async create(name, foregroundConfig, promptConfig, modelConfig, runConfig, toolConfig, outputConfig, overrides = {}) {
|
|
283
|
+
const runtimeBundle = overrides.runtimeBundle;
|
|
284
|
+
if (!runtimeBundle) {
|
|
285
|
+
throw new Error('SubAgentScope.create requires a runtime bundle after initialization.');
|
|
286
|
+
}
|
|
287
|
+
const toolsView = runtimeBundle.runtimeContext.tools ?? runtimeBundle.toolsView;
|
|
288
|
+
if (!toolsView) {
|
|
289
|
+
throw new Error('SubAgentScope.create requires a ToolRegistryView from the runtime bundle.');
|
|
290
|
+
}
|
|
291
|
+
const toolRegistry = overrides.toolRegistry ?? runtimeBundle.toolRegistry;
|
|
292
|
+
if (!toolRegistry) {
|
|
293
|
+
throw new Error('SubAgentScope.create requires a ToolRegistry in the runtime bundle or overrides.');
|
|
294
|
+
}
|
|
156
295
|
if (toolConfig) {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
296
|
+
await validateToolsAgainstRuntime({
|
|
297
|
+
toolConfig,
|
|
298
|
+
toolRegistry,
|
|
299
|
+
toolsView,
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
const settingsSnapshot = overrides.settingsSnapshot ?? runtimeBundle.settingsSnapshot;
|
|
303
|
+
const toolExecutorContext = createToolExecutionConfig(runtimeBundle, toolRegistry, settingsSnapshot, toolConfig);
|
|
304
|
+
const environmentContextLoader = overrides.environmentContextLoader ?? defaultEnvironmentContextLoader;
|
|
305
|
+
return new SubAgentScope(name, runtimeBundle.runtimeContext, modelConfig, runConfig, promptConfig, runtimeBundle.contentGenerator, toolExecutorContext, environmentContextLoader, foregroundConfig, toolConfig, outputConfig);
|
|
306
|
+
}
|
|
307
|
+
buildInitialMessages(context) {
|
|
308
|
+
const behaviourPrompts = context.get('task_behaviour_prompts') ?? [];
|
|
309
|
+
const initialInstruction = behaviourPrompts.length > 0
|
|
310
|
+
? behaviourPrompts.join('\n')
|
|
311
|
+
: 'Follow the task directives provided in the system prompt.';
|
|
312
|
+
return [
|
|
313
|
+
{
|
|
314
|
+
role: 'user',
|
|
315
|
+
parts: [{ text: initialInstruction }],
|
|
316
|
+
},
|
|
317
|
+
];
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Executes the subagent in interactive mode by routing tool calls through the
|
|
321
|
+
* shared CoreToolScheduler. Tests may supply a custom schedulerFactory to
|
|
322
|
+
* observe scheduling behaviour without touching the real scheduler.
|
|
323
|
+
*/
|
|
324
|
+
async runInteractive(context, options) {
|
|
325
|
+
const chat = await this.createChatObject(context);
|
|
326
|
+
if (!chat) {
|
|
327
|
+
this.output.terminate_reason = SubagentTerminateMode.ERROR;
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
const abortController = new AbortController();
|
|
331
|
+
this.activeAbortController = abortController;
|
|
332
|
+
const functionDeclarations = this.buildRuntimeFunctionDeclarations();
|
|
333
|
+
if (this.outputConfig && this.outputConfig.outputs) {
|
|
334
|
+
functionDeclarations.push(...this.getScopeLocalFuncDefs());
|
|
335
|
+
}
|
|
336
|
+
if (functionDeclarations.length > 0 &&
|
|
337
|
+
typeof chat.setTools ===
|
|
338
|
+
'function') {
|
|
339
|
+
try {
|
|
340
|
+
chat.setTools?.([
|
|
341
|
+
{ functionDeclarations },
|
|
342
|
+
]);
|
|
163
343
|
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
344
|
+
catch (error) {
|
|
345
|
+
this.logger.warn(() => `Subagent ${this.subagentId} failed to register tools: ${error instanceof Error ? error.message : String(error)}`);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
const schedulerConfig = this.createSchedulerConfig({ interactive: true });
|
|
349
|
+
let pendingCompletedCalls = null;
|
|
350
|
+
let completionResolver = null;
|
|
351
|
+
const awaitCompletedCalls = () => {
|
|
352
|
+
if (pendingCompletedCalls) {
|
|
353
|
+
const calls = pendingCompletedCalls;
|
|
354
|
+
pendingCompletedCalls = null;
|
|
355
|
+
return Promise.resolve(calls);
|
|
356
|
+
}
|
|
357
|
+
return new Promise((resolve) => {
|
|
358
|
+
completionResolver = resolve;
|
|
359
|
+
});
|
|
360
|
+
};
|
|
361
|
+
const outputUpdateHandler = (_toolCallId, output) => {
|
|
362
|
+
if (output && this.onMessage) {
|
|
363
|
+
this.onMessage(output);
|
|
364
|
+
}
|
|
365
|
+
};
|
|
366
|
+
const handleCompletion = async (calls) => {
|
|
367
|
+
if (completionResolver) {
|
|
368
|
+
completionResolver(calls);
|
|
369
|
+
completionResolver = null;
|
|
370
|
+
}
|
|
371
|
+
else {
|
|
372
|
+
pendingCompletedCalls = calls;
|
|
373
|
+
}
|
|
374
|
+
};
|
|
375
|
+
const scheduler = options?.schedulerFactory
|
|
376
|
+
? options.schedulerFactory({
|
|
377
|
+
schedulerConfig,
|
|
378
|
+
onAllToolCallsComplete: handleCompletion,
|
|
379
|
+
outputUpdateHandler,
|
|
380
|
+
onToolCallsUpdate: undefined,
|
|
381
|
+
})
|
|
382
|
+
: new CoreToolScheduler({
|
|
383
|
+
config: schedulerConfig,
|
|
384
|
+
outputUpdateHandler,
|
|
385
|
+
onAllToolCallsComplete: handleCompletion,
|
|
386
|
+
onToolCallsUpdate: undefined,
|
|
387
|
+
getPreferredEditor: () => undefined,
|
|
388
|
+
onEditorClose: () => { },
|
|
389
|
+
});
|
|
390
|
+
const startTime = Date.now();
|
|
391
|
+
let turnCounter = 0;
|
|
392
|
+
let currentMessages = this.buildInitialMessages(context);
|
|
393
|
+
try {
|
|
394
|
+
while (true) {
|
|
395
|
+
if (this.runConfig.max_turns &&
|
|
396
|
+
turnCounter >= this.runConfig.max_turns) {
|
|
397
|
+
this.output.terminate_reason = SubagentTerminateMode.MAX_TURNS;
|
|
398
|
+
this.logger.warn(() => `Subagent ${this.subagentId} reached max turns (${this.runConfig.max_turns})`);
|
|
399
|
+
break;
|
|
400
|
+
}
|
|
401
|
+
let durationMin = (Date.now() - startTime) / (1000 * 60);
|
|
402
|
+
if (durationMin >= this.runConfig.max_time_minutes) {
|
|
403
|
+
this.output.terminate_reason = SubagentTerminateMode.TIMEOUT;
|
|
404
|
+
this.logger.warn(() => `Subagent ${this.subagentId} reached time limit (${this.runConfig.max_time_minutes} minutes)`);
|
|
405
|
+
break;
|
|
406
|
+
}
|
|
407
|
+
const currentTurn = turnCounter++;
|
|
408
|
+
const promptId = `${this.runtimeContext.state.sessionId}#${this.subagentId}#${currentTurn}`;
|
|
409
|
+
const providerName = this.runtimeContext.state.provider ?? 'backend';
|
|
410
|
+
const turn = new Turn(chat, promptId, this.subagentId, providerName);
|
|
411
|
+
let textResponse = '';
|
|
412
|
+
const parts = currentMessages[0]?.parts ?? [];
|
|
413
|
+
const stream = turn.run(parts, abortController.signal);
|
|
414
|
+
for await (const event of stream) {
|
|
415
|
+
if (abortController.signal.aborted) {
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
if (event.type === GeminiEventType.Content && event.value) {
|
|
419
|
+
textResponse += event.value;
|
|
420
|
+
if (this.onMessage) {
|
|
421
|
+
this.onMessage(event.value);
|
|
422
|
+
}
|
|
177
423
|
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
throw new Error(
|
|
424
|
+
else if (event.type === GeminiEventType.Error &&
|
|
425
|
+
event.value?.error) {
|
|
426
|
+
this.output.terminate_reason = SubagentTerminateMode.ERROR;
|
|
427
|
+
throw new Error(event.value.error.message);
|
|
182
428
|
}
|
|
183
429
|
}
|
|
430
|
+
if (textResponse.trim()) {
|
|
431
|
+
this.output.final_message = textResponse.trim();
|
|
432
|
+
}
|
|
433
|
+
const toolRequests = [...turn.pendingToolCalls];
|
|
434
|
+
if (toolRequests.length > 0) {
|
|
435
|
+
const manualParts = [];
|
|
436
|
+
const schedulerRequests = [];
|
|
437
|
+
for (const request of toolRequests) {
|
|
438
|
+
if (request.name === 'self.emitvalue') {
|
|
439
|
+
manualParts.push(...this.handleEmitValueCall(request));
|
|
440
|
+
}
|
|
441
|
+
else {
|
|
442
|
+
schedulerRequests.push(request);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
let responseParts = [...manualParts];
|
|
446
|
+
if (schedulerRequests.length > 0) {
|
|
447
|
+
const completionPromise = awaitCompletedCalls();
|
|
448
|
+
await scheduler.schedule(schedulerRequests, abortController.signal);
|
|
449
|
+
const completedCalls = await completionPromise;
|
|
450
|
+
responseParts = responseParts.concat(this.buildPartsFromCompletedCalls(completedCalls));
|
|
451
|
+
const fatalCall = completedCalls.find((call) => call.status === 'error' &&
|
|
452
|
+
this.isFatalToolError(call.response.errorType));
|
|
453
|
+
if (fatalCall) {
|
|
454
|
+
const fatalMessage = this.buildToolUnavailableMessage(fatalCall.request.name, fatalCall.response.resultDisplay, fatalCall.response.error);
|
|
455
|
+
this.logger.warn(() => `Subagent ${this.subagentId} cannot use tool '${fatalCall.request.name}': ${fatalMessage}`);
|
|
456
|
+
responseParts.push({ text: fatalMessage });
|
|
457
|
+
this.output.final_message = fatalMessage;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
if (responseParts.length === 0) {
|
|
461
|
+
responseParts.push({
|
|
462
|
+
text: 'All tool calls failed. Please analyze the errors and try an alternative approach.',
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
currentMessages = [{ role: 'user', parts: responseParts }];
|
|
466
|
+
continue;
|
|
467
|
+
}
|
|
468
|
+
durationMin = (Date.now() - startTime) / (1000 * 60);
|
|
469
|
+
if (durationMin >= this.runConfig.max_time_minutes) {
|
|
470
|
+
this.output.terminate_reason = SubagentTerminateMode.TIMEOUT;
|
|
471
|
+
this.logger.warn(() => `Subagent ${this.subagentId} reached time limit after turn ${currentTurn}`);
|
|
472
|
+
break;
|
|
473
|
+
}
|
|
474
|
+
const todoReminder = await this.buildTodoCompletionPrompt();
|
|
475
|
+
if (todoReminder) {
|
|
476
|
+
this.logger.debug(() => `Subagent ${this.subagentId} postponing completion until outstanding todos are addressed`);
|
|
477
|
+
currentMessages = [
|
|
478
|
+
{
|
|
479
|
+
role: 'user',
|
|
480
|
+
parts: [{ text: todoReminder }],
|
|
481
|
+
},
|
|
482
|
+
];
|
|
483
|
+
continue;
|
|
484
|
+
}
|
|
485
|
+
if (!this.outputConfig ||
|
|
486
|
+
Object.keys(this.outputConfig.outputs).length === 0) {
|
|
487
|
+
this.output.terminate_reason = SubagentTerminateMode.GOAL;
|
|
488
|
+
break;
|
|
489
|
+
}
|
|
490
|
+
const remainingVars = Object.keys(this.outputConfig.outputs).filter((key) => !(key in this.output.emitted_vars));
|
|
491
|
+
if (remainingVars.length === 0) {
|
|
492
|
+
this.output.terminate_reason = SubagentTerminateMode.GOAL;
|
|
493
|
+
this.logger.debug(() => `Subagent ${this.subagentId} satisfied output requirements on turn ${currentTurn}`);
|
|
494
|
+
break;
|
|
495
|
+
}
|
|
496
|
+
const nudgeMessage = `You have stopped calling tools but have not emitted the following required variables: ${remainingVars.join(', ')}. Please use the 'self.emitvalue' tool to emit them now, or continue working if necessary.`;
|
|
497
|
+
this.logger.debug(() => `Subagent ${this.subagentId} nudging for outputs: ${remainingVars.join(', ')}`);
|
|
498
|
+
currentMessages = [
|
|
499
|
+
{
|
|
500
|
+
role: 'user',
|
|
501
|
+
parts: [{ text: nudgeMessage }],
|
|
502
|
+
},
|
|
503
|
+
];
|
|
184
504
|
}
|
|
505
|
+
this.finalizeOutput();
|
|
506
|
+
}
|
|
507
|
+
catch (error) {
|
|
508
|
+
this.logger.warn(() => `Error during subagent execution for ${this.subagentId}: ${error instanceof Error ? error.message : String(error)}`);
|
|
509
|
+
this.output.terminate_reason = SubagentTerminateMode.ERROR;
|
|
510
|
+
if (!this.output.final_message) {
|
|
511
|
+
this.output.final_message =
|
|
512
|
+
error instanceof Error ? error.message : String(error);
|
|
513
|
+
}
|
|
514
|
+
this.finalizeOutput();
|
|
515
|
+
throw error;
|
|
516
|
+
}
|
|
517
|
+
finally {
|
|
518
|
+
this.activeAbortController = null;
|
|
185
519
|
}
|
|
186
|
-
return new SubAgentScope(name, runtimeContext, promptConfig, modelConfig, runConfig, toolConfig, outputConfig);
|
|
187
520
|
}
|
|
188
521
|
/**
|
|
189
522
|
* Runs the subagent in a non-interactive mode.
|
|
190
523
|
* This method orchestrates the subagent's execution loop, including prompt templating,
|
|
191
524
|
* tool execution, and termination conditions.
|
|
525
|
+
*
|
|
526
|
+
* @plan PLAN-20251028-STATELESS6.P08
|
|
527
|
+
* @requirement REQ-STAT6-001.1
|
|
528
|
+
*
|
|
192
529
|
* @param {ContextState} context - The current context state containing variables for prompt templating.
|
|
193
530
|
* @returns {Promise<void>} A promise that resolves when the subagent has completed its execution.
|
|
194
531
|
*/
|
|
@@ -199,28 +536,15 @@ export class SubAgentScope {
|
|
|
199
536
|
return;
|
|
200
537
|
}
|
|
201
538
|
const abortController = new AbortController();
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
const toolsList = [];
|
|
205
|
-
if (this.toolConfig) {
|
|
206
|
-
const toolsToLoad = [];
|
|
207
|
-
for (const tool of this.toolConfig.tools) {
|
|
208
|
-
if (typeof tool === 'string') {
|
|
209
|
-
toolsToLoad.push(tool);
|
|
210
|
-
}
|
|
211
|
-
else {
|
|
212
|
-
toolsList.push(tool);
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
toolsList.push(...toolRegistry.getFunctionDeclarationsFiltered(toolsToLoad));
|
|
216
|
-
}
|
|
217
|
-
// Add local scope functions if outputs are expected.
|
|
539
|
+
this.activeAbortController = abortController;
|
|
540
|
+
const toolsList = this.buildRuntimeFunctionDeclarations();
|
|
218
541
|
if (this.outputConfig && this.outputConfig.outputs) {
|
|
219
542
|
toolsList.push(...this.getScopeLocalFuncDefs());
|
|
220
543
|
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
544
|
+
this.logger.debug(() => `Subagent ${this.subagentId} (${this.name}) starting run with toolCount=${toolsList.length} requestedOutputs=${this.outputConfig
|
|
545
|
+
? Object.keys(this.outputConfig.outputs).join(', ')
|
|
546
|
+
: 'none'} runConfig=${JSON.stringify(this.runConfig)}`);
|
|
547
|
+
let currentMessages = this.buildInitialMessages(context);
|
|
224
548
|
const startTime = Date.now();
|
|
225
549
|
let turnCounter = 0;
|
|
226
550
|
try {
|
|
@@ -229,14 +553,20 @@ export class SubAgentScope {
|
|
|
229
553
|
if (this.runConfig.max_turns &&
|
|
230
554
|
turnCounter >= this.runConfig.max_turns) {
|
|
231
555
|
this.output.terminate_reason = SubagentTerminateMode.MAX_TURNS;
|
|
556
|
+
this.logger.warn(() => `Subagent ${this.subagentId} reached max turns (${this.runConfig.max_turns})`);
|
|
232
557
|
break;
|
|
233
558
|
}
|
|
234
559
|
let durationMin = (Date.now() - startTime) / (1000 * 60);
|
|
235
560
|
if (durationMin >= this.runConfig.max_time_minutes) {
|
|
236
561
|
this.output.terminate_reason = SubagentTerminateMode.TIMEOUT;
|
|
562
|
+
this.logger.warn(() => `Subagent ${this.subagentId} reached time limit (${this.runConfig.max_time_minutes} minutes)`);
|
|
237
563
|
break;
|
|
238
564
|
}
|
|
239
|
-
|
|
565
|
+
// @plan PLAN-20251028-STATELESS6.P08
|
|
566
|
+
// @requirement REQ-STAT6-001.1
|
|
567
|
+
const currentTurn = turnCounter++;
|
|
568
|
+
const promptId = `${this.runtimeContext.state.sessionId}#${this.subagentId}#${currentTurn}`;
|
|
569
|
+
this.logger.debug(() => `Subagent ${this.subagentId} turn=${currentTurn} promptId=${promptId}`);
|
|
240
570
|
const messageParams = {
|
|
241
571
|
message: currentMessages[0]?.parts || [],
|
|
242
572
|
config: {
|
|
@@ -245,24 +575,69 @@ export class SubAgentScope {
|
|
|
245
575
|
},
|
|
246
576
|
};
|
|
247
577
|
const responseStream = await chat.sendMessageStream(messageParams, promptId);
|
|
248
|
-
|
|
578
|
+
let functionCalls = [];
|
|
249
579
|
let textResponse = '';
|
|
250
580
|
for await (const resp of responseStream) {
|
|
251
581
|
if (abortController.signal.aborted)
|
|
252
582
|
return;
|
|
253
583
|
if (resp.type === StreamEventType.CHUNK && resp.value.functionCalls) {
|
|
254
|
-
|
|
584
|
+
const chunkCalls = resp.value.functionCalls ?? [];
|
|
585
|
+
if (chunkCalls.length > 0) {
|
|
586
|
+
functionCalls.push(...chunkCalls);
|
|
587
|
+
this.logger.debug(() => `Subagent ${this.subagentId} received ${chunkCalls.length} function calls on turn ${currentTurn}`);
|
|
588
|
+
}
|
|
255
589
|
}
|
|
256
590
|
if (resp.type === StreamEventType.CHUNK && resp.value.text) {
|
|
257
591
|
textResponse += resp.value.text;
|
|
258
592
|
}
|
|
259
593
|
}
|
|
260
|
-
if (
|
|
261
|
-
this.onMessage
|
|
594
|
+
if (textResponse) {
|
|
595
|
+
if (this.onMessage) {
|
|
596
|
+
this.onMessage(textResponse);
|
|
597
|
+
}
|
|
598
|
+
let cleanedText = textResponse;
|
|
599
|
+
try {
|
|
600
|
+
const parsedResult = this.textToolParser.parse(textResponse);
|
|
601
|
+
cleanedText = parsedResult.cleanedContent;
|
|
602
|
+
if (parsedResult.toolCalls.length > 0) {
|
|
603
|
+
const synthesizedCalls = [];
|
|
604
|
+
parsedResult.toolCalls.forEach((call, index) => {
|
|
605
|
+
const normalizedName = this.normalizeToolName(call.name);
|
|
606
|
+
if (!normalizedName) {
|
|
607
|
+
this.logger.debug(() => `Subagent ${this.subagentId} could not map textual tool name '${call.name}' to a registered tool`);
|
|
608
|
+
return;
|
|
609
|
+
}
|
|
610
|
+
synthesizedCalls.push({
|
|
611
|
+
id: `parsed_${this.subagentId}_${Date.now()}_${index}`,
|
|
612
|
+
name: normalizedName,
|
|
613
|
+
args: call.arguments ?? {},
|
|
614
|
+
});
|
|
615
|
+
});
|
|
616
|
+
if (synthesizedCalls.length > 0) {
|
|
617
|
+
functionCalls = [...functionCalls, ...synthesizedCalls];
|
|
618
|
+
this.logger.debug(() => `Subagent ${this.subagentId} extracted ${synthesizedCalls.length} tool call(s) from text: ${synthesizedCalls
|
|
619
|
+
.map((call) => call.name)
|
|
620
|
+
.join(', ')}`);
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
catch (error) {
|
|
625
|
+
this.logger.warn(() => `Subagent ${this.subagentId} failed to parse textual tool calls: ${error instanceof Error ? error.message : String(error)}`);
|
|
626
|
+
}
|
|
627
|
+
textResponse = cleanedText;
|
|
628
|
+
const trimmedText = textResponse.trim();
|
|
629
|
+
if (trimmedText.length > 0) {
|
|
630
|
+
this.output.final_message = trimmedText;
|
|
631
|
+
}
|
|
632
|
+
const preview = textResponse.length > 200
|
|
633
|
+
? `${textResponse.slice(0, 200)}…`
|
|
634
|
+
: textResponse;
|
|
635
|
+
this.logger.debug(() => `Subagent ${this.subagentId} model response (truncated): ${preview}`);
|
|
262
636
|
}
|
|
263
637
|
durationMin = (Date.now() - startTime) / (1000 * 60);
|
|
264
638
|
if (durationMin >= this.runConfig.max_time_minutes) {
|
|
265
639
|
this.output.terminate_reason = SubagentTerminateMode.TIMEOUT;
|
|
640
|
+
this.logger.warn(() => `Subagent ${this.subagentId} reached time limit after turn ${currentTurn}`);
|
|
266
641
|
break;
|
|
267
642
|
}
|
|
268
643
|
if (functionCalls.length > 0) {
|
|
@@ -270,6 +645,17 @@ export class SubAgentScope {
|
|
|
270
645
|
}
|
|
271
646
|
else {
|
|
272
647
|
// Model stopped calling tools. Check if goal is met.
|
|
648
|
+
const todoReminder = await this.buildTodoCompletionPrompt();
|
|
649
|
+
if (todoReminder) {
|
|
650
|
+
this.logger.debug(() => `Subagent ${this.subagentId} postponing completion until outstanding todos are addressed`);
|
|
651
|
+
currentMessages = [
|
|
652
|
+
{
|
|
653
|
+
role: 'user',
|
|
654
|
+
parts: [{ text: todoReminder }],
|
|
655
|
+
},
|
|
656
|
+
];
|
|
657
|
+
continue;
|
|
658
|
+
}
|
|
273
659
|
if (!this.outputConfig ||
|
|
274
660
|
Object.keys(this.outputConfig.outputs).length === 0) {
|
|
275
661
|
this.output.terminate_reason = SubagentTerminateMode.GOAL;
|
|
@@ -278,10 +664,11 @@ export class SubAgentScope {
|
|
|
278
664
|
const remainingVars = Object.keys(this.outputConfig.outputs).filter((key) => !(key in this.output.emitted_vars));
|
|
279
665
|
if (remainingVars.length === 0) {
|
|
280
666
|
this.output.terminate_reason = SubagentTerminateMode.GOAL;
|
|
667
|
+
this.logger.debug(() => `Subagent ${this.subagentId} satisfied output requirements on turn ${currentTurn}`);
|
|
281
668
|
break;
|
|
282
669
|
}
|
|
283
670
|
const nudgeMessage = `You have stopped calling tools but have not emitted the following required variables: ${remainingVars.join(', ')}. Please use the 'self.emitvalue' tool to emit them now, or continue working if necessary.`;
|
|
284
|
-
|
|
671
|
+
this.logger.debug(() => `Subagent ${this.subagentId} nudging for outputs: ${remainingVars.join(', ')}`);
|
|
285
672
|
currentMessages = [
|
|
286
673
|
{
|
|
287
674
|
role: 'user',
|
|
@@ -290,12 +677,34 @@ export class SubAgentScope {
|
|
|
290
677
|
];
|
|
291
678
|
}
|
|
292
679
|
}
|
|
680
|
+
this.finalizeOutput();
|
|
293
681
|
}
|
|
294
682
|
catch (error) {
|
|
295
|
-
|
|
683
|
+
this.logger.warn(() => `Error during subagent execution for ${this.subagentId}: ${error instanceof Error ? error.message : String(error)}`);
|
|
296
684
|
this.output.terminate_reason = SubagentTerminateMode.ERROR;
|
|
685
|
+
if (!this.output.final_message) {
|
|
686
|
+
this.output.final_message =
|
|
687
|
+
error instanceof Error ? error.message : String(error);
|
|
688
|
+
}
|
|
689
|
+
this.finalizeOutput();
|
|
297
690
|
throw error;
|
|
298
691
|
}
|
|
692
|
+
finally {
|
|
693
|
+
this.activeAbortController = null;
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
cancel(reason) {
|
|
697
|
+
if (this.activeAbortController?.signal.aborted) {
|
|
698
|
+
return;
|
|
699
|
+
}
|
|
700
|
+
if (!this.activeAbortController) {
|
|
701
|
+
return;
|
|
702
|
+
}
|
|
703
|
+
this.logger.warn(() => {
|
|
704
|
+
const suffix = reason ? `: ${reason}` : '';
|
|
705
|
+
return `Subagent ${this.subagentId} cancellation requested${suffix}`;
|
|
706
|
+
});
|
|
707
|
+
this.activeAbortController.abort();
|
|
299
708
|
}
|
|
300
709
|
/**
|
|
301
710
|
* Processes a list of function calls, executing each one and collecting their responses.
|
|
@@ -318,7 +727,9 @@ export class SubAgentScope {
|
|
|
318
727
|
args: (functionCall.args ?? {}),
|
|
319
728
|
isClientInitiated: true,
|
|
320
729
|
prompt_id: promptId,
|
|
730
|
+
agentId: this.subagentId,
|
|
321
731
|
};
|
|
732
|
+
this.logger.debug(() => `Subagent ${this.subagentId} executing tool '${requestInfo.name}' with args=${JSON.stringify(requestInfo.args)}`);
|
|
322
733
|
let toolResponse;
|
|
323
734
|
// Handle scope-local tools first.
|
|
324
735
|
if (functionCall.name === 'self.emitvalue') {
|
|
@@ -330,14 +741,31 @@ export class SubAgentScope {
|
|
|
330
741
|
responseParts: [{ text: `Emitted variable ${valName} successfully` }],
|
|
331
742
|
resultDisplay: `Emitted variable ${valName} successfully`,
|
|
332
743
|
error: undefined,
|
|
744
|
+
errorType: undefined,
|
|
745
|
+
agentId: requestInfo.agentId,
|
|
333
746
|
};
|
|
334
747
|
}
|
|
335
748
|
else {
|
|
336
|
-
|
|
749
|
+
// @plan PLAN-20251028-STATELESS6.P08
|
|
750
|
+
// @requirement REQ-STAT6-001.1
|
|
751
|
+
toolResponse = await executeToolCall(this.toolExecutorContext, requestInfo, abortController.signal);
|
|
337
752
|
}
|
|
338
753
|
if (toolResponse.error) {
|
|
339
754
|
console.error(`Error executing tool ${functionCall.name}: ${toolResponse.resultDisplay || toolResponse.error.message}`);
|
|
340
755
|
}
|
|
756
|
+
if (toolResponse.error) {
|
|
757
|
+
this.logger.warn(() => `Subagent ${this.subagentId} tool '${functionCall.name}' failed: ${toolResponse.error?.message}`);
|
|
758
|
+
}
|
|
759
|
+
else {
|
|
760
|
+
this.logger.debug(() => `Subagent ${this.subagentId} tool '${functionCall.name}' completed successfully`);
|
|
761
|
+
}
|
|
762
|
+
if (this.isFatalToolError(toolResponse.errorType)) {
|
|
763
|
+
const fatalMessage = this.buildToolUnavailableMessage(functionCall.name, toolResponse.resultDisplay, toolResponse.error);
|
|
764
|
+
this.logger.warn(() => `Subagent ${this.subagentId} cannot use tool '${functionCall.name}': ${fatalMessage}`);
|
|
765
|
+
toolResponseParts.push({ text: fatalMessage });
|
|
766
|
+
this.output.final_message = fatalMessage;
|
|
767
|
+
continue;
|
|
768
|
+
}
|
|
341
769
|
if (toolResponse.responseParts) {
|
|
342
770
|
toolResponseParts.push(...toolResponse.responseParts);
|
|
343
771
|
}
|
|
@@ -350,6 +778,236 @@ export class SubAgentScope {
|
|
|
350
778
|
}
|
|
351
779
|
return [{ role: 'user', parts: toolResponseParts }];
|
|
352
780
|
}
|
|
781
|
+
createSchedulerConfig(options) {
|
|
782
|
+
const isInteractive = options?.interactive ?? false;
|
|
783
|
+
const whitelist = !isInteractive && this.toolConfig
|
|
784
|
+
? this.toolConfig.tools.filter((entry) => typeof entry === 'string')
|
|
785
|
+
: [];
|
|
786
|
+
const getEphemeralSettings = typeof this.toolExecutorContext.getEphemeralSettings === 'function'
|
|
787
|
+
? () => ({
|
|
788
|
+
...this.toolExecutorContext.getEphemeralSettings(),
|
|
789
|
+
})
|
|
790
|
+
: () => this.config.getEphemeralSettings();
|
|
791
|
+
const getExcludeTools = typeof this.toolExecutorContext.getExcludeTools === 'function'
|
|
792
|
+
? () => this.toolExecutorContext.getExcludeTools()
|
|
793
|
+
: () => this.config.getExcludeTools?.() ?? [];
|
|
794
|
+
const getTelemetryLogPromptsEnabled = typeof this.toolExecutorContext.getTelemetryLogPromptsEnabled ===
|
|
795
|
+
'function'
|
|
796
|
+
? () => this.toolExecutorContext.getTelemetryLogPromptsEnabled()
|
|
797
|
+
: () => this.config.getTelemetryLogPromptsEnabled();
|
|
798
|
+
const allowedTools = isInteractive
|
|
799
|
+
? typeof this.config.getAllowedTools === 'function'
|
|
800
|
+
? this.config.getAllowedTools()
|
|
801
|
+
: undefined
|
|
802
|
+
: whitelist.length > 0
|
|
803
|
+
? whitelist
|
|
804
|
+
: typeof this.config.getAllowedTools === 'function'
|
|
805
|
+
? this.config.getAllowedTools()
|
|
806
|
+
: undefined;
|
|
807
|
+
return {
|
|
808
|
+
getToolRegistry: () => this.toolExecutorContext.getToolRegistry(),
|
|
809
|
+
getSessionId: () => this.toolExecutorContext.getSessionId(),
|
|
810
|
+
getEphemeralSettings,
|
|
811
|
+
getExcludeTools,
|
|
812
|
+
getTelemetryLogPromptsEnabled,
|
|
813
|
+
getAllowedTools: () => allowedTools,
|
|
814
|
+
getApprovalMode: () => typeof this.config.getApprovalMode === 'function'
|
|
815
|
+
? this.config.getApprovalMode()
|
|
816
|
+
: ApprovalMode.DEFAULT,
|
|
817
|
+
};
|
|
818
|
+
}
|
|
819
|
+
finalizeOutput() {
|
|
820
|
+
const message = this.output.final_message;
|
|
821
|
+
if (typeof message === 'string' && message.trim().length > 0) {
|
|
822
|
+
return;
|
|
823
|
+
}
|
|
824
|
+
const emittedVars = this.output.emitted_vars ?? {};
|
|
825
|
+
const emittedEntries = Object.entries(emittedVars)
|
|
826
|
+
.filter(([, value]) => value !== undefined &&
|
|
827
|
+
value !== null &&
|
|
828
|
+
String(value).trim().length > 0)
|
|
829
|
+
.map(([key, value]) => `${key}=${String(value)}`);
|
|
830
|
+
let baseMessage;
|
|
831
|
+
switch (this.output.terminate_reason) {
|
|
832
|
+
case SubagentTerminateMode.GOAL:
|
|
833
|
+
baseMessage = 'Completed the requested task.';
|
|
834
|
+
break;
|
|
835
|
+
case SubagentTerminateMode.TIMEOUT:
|
|
836
|
+
baseMessage = 'Stopped because the time limit was reached.';
|
|
837
|
+
break;
|
|
838
|
+
case SubagentTerminateMode.MAX_TURNS:
|
|
839
|
+
baseMessage =
|
|
840
|
+
'Stopped because the maximum number of turns was reached.';
|
|
841
|
+
break;
|
|
842
|
+
case SubagentTerminateMode.ERROR:
|
|
843
|
+
default:
|
|
844
|
+
baseMessage = 'Stopped due to an unrecoverable error.';
|
|
845
|
+
break;
|
|
846
|
+
}
|
|
847
|
+
const varsSuffix = emittedEntries.length > 0
|
|
848
|
+
? ` Emitted variables: ${emittedEntries.join(', ')}.`
|
|
849
|
+
: '';
|
|
850
|
+
this.output.final_message = `${baseMessage}${varsSuffix}`.trim();
|
|
851
|
+
}
|
|
852
|
+
isFatalToolError(errorType) {
|
|
853
|
+
return (errorType === ToolErrorType.TOOL_DISABLED ||
|
|
854
|
+
errorType === ToolErrorType.TOOL_NOT_REGISTERED);
|
|
855
|
+
}
|
|
856
|
+
buildToolUnavailableMessage(toolName, resultDisplay, error) {
|
|
857
|
+
const detail = this.extractToolDetail(resultDisplay, error);
|
|
858
|
+
const baseMessage = `Tool "${toolName}" is not available in this environment.`;
|
|
859
|
+
return detail
|
|
860
|
+
? `${baseMessage} ${detail}`
|
|
861
|
+
: `${baseMessage} Please continue without using it.`;
|
|
862
|
+
}
|
|
863
|
+
extractToolDetail(resultDisplay, error) {
|
|
864
|
+
if (error?.message) {
|
|
865
|
+
return error.message;
|
|
866
|
+
}
|
|
867
|
+
if (typeof resultDisplay === 'string') {
|
|
868
|
+
return resultDisplay;
|
|
869
|
+
}
|
|
870
|
+
if (resultDisplay &&
|
|
871
|
+
typeof resultDisplay === 'object' &&
|
|
872
|
+
'message' in resultDisplay &&
|
|
873
|
+
typeof resultDisplay.message === 'string') {
|
|
874
|
+
return resultDisplay.message;
|
|
875
|
+
}
|
|
876
|
+
return undefined;
|
|
877
|
+
}
|
|
878
|
+
handleEmitValueCall(request) {
|
|
879
|
+
const args = request.args ?? {};
|
|
880
|
+
const variableName = typeof args.emit_variable_name === 'string'
|
|
881
|
+
? args.emit_variable_name
|
|
882
|
+
: typeof args.emitVariableName === 'string'
|
|
883
|
+
? args.emitVariableName
|
|
884
|
+
: '';
|
|
885
|
+
const variableValue = typeof args.emit_variable_value === 'string'
|
|
886
|
+
? args.emit_variable_value
|
|
887
|
+
: typeof args.emitVariableValue === 'string'
|
|
888
|
+
? args.emitVariableValue
|
|
889
|
+
: '';
|
|
890
|
+
if (variableName && variableValue) {
|
|
891
|
+
this.output.emitted_vars[variableName] = variableValue;
|
|
892
|
+
const message = `Emitted variable ${variableName} successfully`;
|
|
893
|
+
if (this.onMessage) {
|
|
894
|
+
this.onMessage(`[${this.subagentId}] ${message}`);
|
|
895
|
+
}
|
|
896
|
+
return [
|
|
897
|
+
{
|
|
898
|
+
functionCall: {
|
|
899
|
+
id: request.callId,
|
|
900
|
+
name: request.name,
|
|
901
|
+
args: request.args,
|
|
902
|
+
},
|
|
903
|
+
},
|
|
904
|
+
{
|
|
905
|
+
functionResponse: {
|
|
906
|
+
id: request.callId,
|
|
907
|
+
name: request.name,
|
|
908
|
+
response: {
|
|
909
|
+
emit_variable_name: variableName,
|
|
910
|
+
emit_variable_value: variableValue,
|
|
911
|
+
message,
|
|
912
|
+
},
|
|
913
|
+
},
|
|
914
|
+
},
|
|
915
|
+
];
|
|
916
|
+
}
|
|
917
|
+
const errorMessage = 'self.emitvalue requires emit_variable_name and emit_variable_value arguments.';
|
|
918
|
+
this.logger.warn(() => `Subagent ${this.subagentId} failed to emit value: ${errorMessage}`);
|
|
919
|
+
return [
|
|
920
|
+
{
|
|
921
|
+
functionCall: {
|
|
922
|
+
id: request.callId,
|
|
923
|
+
name: request.name,
|
|
924
|
+
args: request.args,
|
|
925
|
+
},
|
|
926
|
+
},
|
|
927
|
+
{
|
|
928
|
+
functionResponse: {
|
|
929
|
+
id: request.callId,
|
|
930
|
+
name: request.name,
|
|
931
|
+
response: { error: errorMessage },
|
|
932
|
+
},
|
|
933
|
+
},
|
|
934
|
+
];
|
|
935
|
+
}
|
|
936
|
+
buildPartsFromCompletedCalls(completedCalls) {
|
|
937
|
+
const aggregate = [];
|
|
938
|
+
for (const call of completedCalls) {
|
|
939
|
+
if (call.response?.responseParts?.length) {
|
|
940
|
+
aggregate.push(...call.response.responseParts);
|
|
941
|
+
}
|
|
942
|
+
else {
|
|
943
|
+
aggregate.push({
|
|
944
|
+
text: `Tool ${call.request.name} completed without response.`,
|
|
945
|
+
});
|
|
946
|
+
}
|
|
947
|
+
if (call.status === 'error') {
|
|
948
|
+
const errorMessage = call.response?.error?.message ??
|
|
949
|
+
call.response?.resultDisplay ??
|
|
950
|
+
'Tool execution failed.';
|
|
951
|
+
this.logger.warn(() => `Subagent ${this.subagentId} tool '${call.request.name}' failed: ${errorMessage}`);
|
|
952
|
+
}
|
|
953
|
+
else if (call.status === 'cancelled') {
|
|
954
|
+
this.logger.warn(() => `Subagent ${this.subagentId} tool '${call.request.name}' was cancelled.`);
|
|
955
|
+
}
|
|
956
|
+
const display = call.response?.resultDisplay;
|
|
957
|
+
if (typeof display === 'string' && this.onMessage && display.trim()) {
|
|
958
|
+
this.onMessage(display);
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
return aggregate;
|
|
962
|
+
}
|
|
963
|
+
async buildTodoCompletionPrompt() {
|
|
964
|
+
const sessionId = this.runtimeContext.state.sessionId;
|
|
965
|
+
if (!sessionId) {
|
|
966
|
+
return null;
|
|
967
|
+
}
|
|
968
|
+
try {
|
|
969
|
+
let todos = await new TodoStore(sessionId, this.subagentId).readTodos();
|
|
970
|
+
if (todos.length === 0) {
|
|
971
|
+
todos = await new TodoStore(sessionId).readTodos();
|
|
972
|
+
}
|
|
973
|
+
if (todos.length === 0) {
|
|
974
|
+
return null;
|
|
975
|
+
}
|
|
976
|
+
const outstanding = todos.filter((todo) => todo.status !== 'completed');
|
|
977
|
+
if (outstanding.length === 0) {
|
|
978
|
+
return null;
|
|
979
|
+
}
|
|
980
|
+
const previewCount = Math.min(3, outstanding.length);
|
|
981
|
+
const previewLines = outstanding
|
|
982
|
+
.slice(0, previewCount)
|
|
983
|
+
.map((todo) => `- ${todo.content}`);
|
|
984
|
+
if (outstanding.length > previewCount) {
|
|
985
|
+
previewLines.push(`- ... and ${outstanding.length - previewCount} more`);
|
|
986
|
+
}
|
|
987
|
+
return [
|
|
988
|
+
'You still have todos in your todo list. Complete them before finishing.',
|
|
989
|
+
previewLines.length > 0
|
|
990
|
+
? `Outstanding items:\n${previewLines.join('\n')}`
|
|
991
|
+
: undefined,
|
|
992
|
+
]
|
|
993
|
+
.filter(Boolean)
|
|
994
|
+
.join('\n\n');
|
|
995
|
+
}
|
|
996
|
+
catch (error) {
|
|
997
|
+
this.logger.warn(() => `Subagent ${this.subagentId} could not inspect todos: ${error instanceof Error ? error.message : String(error)}`);
|
|
998
|
+
return null;
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
/**
|
|
1002
|
+
* Creates a GeminiChat instance for this subagent.
|
|
1003
|
+
*
|
|
1004
|
+
* @plan PLAN-20251028-STATELESS6.P08
|
|
1005
|
+
* @requirement REQ-STAT6-001.1, REQ-STAT6-003.1
|
|
1006
|
+
* @pseudocode agent-runtime-context.md line 99 (step 007.7)
|
|
1007
|
+
*
|
|
1008
|
+
* Step 007.7: Update GeminiChat instantiation to use AgentRuntimeContext
|
|
1009
|
+
* Step 007.8: REMOVE Config mutation (no setModel call)
|
|
1010
|
+
*/
|
|
353
1011
|
async createChatObject(context) {
|
|
354
1012
|
if (!this.promptConfig.systemPrompt && !this.promptConfig.initialMessages) {
|
|
355
1013
|
throw new Error('PromptConfig must have either `systemPrompt` or `initialMessages` defined.');
|
|
@@ -357,37 +1015,45 @@ export class SubAgentScope {
|
|
|
357
1015
|
if (this.promptConfig.systemPrompt && this.promptConfig.initialMessages) {
|
|
358
1016
|
throw new Error('PromptConfig cannot have both `systemPrompt` and `initialMessages` defined.');
|
|
359
1017
|
}
|
|
360
|
-
const envParts = await
|
|
1018
|
+
const envParts = await this.environmentContextLoader(this.runtimeContext);
|
|
361
1019
|
// Extract environment context text
|
|
362
1020
|
const envContextText = envParts
|
|
363
1021
|
.map((part) => ('text' in part ? part.text : ''))
|
|
364
1022
|
.join('\n');
|
|
365
1023
|
const start_history = [...(this.promptConfig.initialMessages ?? [])];
|
|
366
1024
|
// Build system instruction with environment context
|
|
367
|
-
|
|
1025
|
+
const personaPrompt = this.promptConfig.systemPrompt
|
|
368
1026
|
? this.buildChatSystemPrompt(context)
|
|
369
|
-
:
|
|
370
|
-
|
|
371
|
-
if (
|
|
372
|
-
systemInstruction =
|
|
1027
|
+
: '';
|
|
1028
|
+
let systemInstruction = personaPrompt;
|
|
1029
|
+
if (envContextText && envContextText.trim().length > 0) {
|
|
1030
|
+
systemInstruction = personaPrompt
|
|
1031
|
+
? `${personaPrompt}\n\n${envContextText.trim()}`
|
|
1032
|
+
: envContextText.trim();
|
|
373
1033
|
}
|
|
1034
|
+
this.logger.debug(() => {
|
|
1035
|
+
const preview = systemInstruction && systemInstruction.length > 0
|
|
1036
|
+
? systemInstruction.slice(0, 1200)
|
|
1037
|
+
: '<empty>';
|
|
1038
|
+
return `System instruction preview: ${preview}`;
|
|
1039
|
+
});
|
|
374
1040
|
try {
|
|
1041
|
+
// Step 007.7: Build generation config from runtime view ephemerals
|
|
1042
|
+
// @plan PLAN-20251028-STATELESS6.P08
|
|
1043
|
+
// @requirement REQ-STAT6-002.2
|
|
375
1044
|
const generationConfig = {
|
|
376
1045
|
temperature: this.modelConfig.temp,
|
|
377
1046
|
topP: this.modelConfig.top_p,
|
|
1047
|
+
systemInstruction: systemInstruction || undefined,
|
|
378
1048
|
};
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
}
|
|
388
|
-
const contentGenerator = await createContentGenerator(contentGenConfig, this.runtimeContext, this.runtimeContext.getSessionId());
|
|
389
|
-
this.runtimeContext.setModel(this.modelConfig.model);
|
|
390
|
-
return new GeminiChat(this.runtimeContext, contentGenerator, generationConfig, start_history);
|
|
1049
|
+
// Step 007.7: Instantiate GeminiChat with runtime view
|
|
1050
|
+
// @plan PLAN-20251028-STATELESS6.P10
|
|
1051
|
+
// @requirement REQ-STAT6-001.2, REQ-STAT6-003.1, REQ-STAT6-003.2
|
|
1052
|
+
// @pseudocode agent-runtime-context.md line 99 (step 007.7)
|
|
1053
|
+
// NOTE: NO Config.setModel() call - REQ-STAT6-003.1 (step 007.8)
|
|
1054
|
+
// NOTE: GeminiChat operates solely on runtime context
|
|
1055
|
+
return new GeminiChat(this.runtimeContext, // AgentRuntimeContext (replaces runtimeState+config+history)
|
|
1056
|
+
this.contentGenerator, generationConfig, start_history);
|
|
391
1057
|
}
|
|
392
1058
|
catch (error) {
|
|
393
1059
|
await reportError(error, 'Error initializing Gemini chat session.', start_history, 'startChat');
|
|
@@ -395,6 +1061,35 @@ export class SubAgentScope {
|
|
|
395
1061
|
return undefined;
|
|
396
1062
|
}
|
|
397
1063
|
}
|
|
1064
|
+
buildRuntimeFunctionDeclarations() {
|
|
1065
|
+
if (!this.toolConfig || this.toolConfig.tools.length === 0) {
|
|
1066
|
+
return [];
|
|
1067
|
+
}
|
|
1068
|
+
const toolsView = this.runtimeContext.tools;
|
|
1069
|
+
const listedNames = typeof toolsView.listToolNames === 'function'
|
|
1070
|
+
? toolsView.listToolNames()
|
|
1071
|
+
: [];
|
|
1072
|
+
const allowedNames = new Set(listedNames.map(normalizeToolName));
|
|
1073
|
+
const declarations = [];
|
|
1074
|
+
for (const entry of this.toolConfig.tools) {
|
|
1075
|
+
if (typeof entry !== 'string') {
|
|
1076
|
+
declarations.push(entry);
|
|
1077
|
+
continue;
|
|
1078
|
+
}
|
|
1079
|
+
if (allowedNames.size > 0 &&
|
|
1080
|
+
!allowedNames.has(normalizeToolName(entry))) {
|
|
1081
|
+
console.warn(`Tool "${entry}" is not permitted by the runtime view and will be skipped.`);
|
|
1082
|
+
continue;
|
|
1083
|
+
}
|
|
1084
|
+
const metadata = toolsView.getToolMetadata(entry);
|
|
1085
|
+
if (!metadata) {
|
|
1086
|
+
console.warn(`Tool "${entry}" is not available in the runtime view and will be skipped.`);
|
|
1087
|
+
continue;
|
|
1088
|
+
}
|
|
1089
|
+
declarations.push(convertMetadataToFunctionDeclaration(entry, metadata));
|
|
1090
|
+
}
|
|
1091
|
+
return declarations;
|
|
1092
|
+
}
|
|
398
1093
|
/**
|
|
399
1094
|
* Returns an array of FunctionDeclaration objects for tools that are local to the subagent's scope.
|
|
400
1095
|
* Currently, this includes the `self.emitvalue` tool for emitting variables.
|
|
@@ -422,6 +1117,41 @@ export class SubAgentScope {
|
|
|
422
1117
|
};
|
|
423
1118
|
return [emitValueTool];
|
|
424
1119
|
}
|
|
1120
|
+
normalizeToolName(rawName) {
|
|
1121
|
+
if (!rawName) {
|
|
1122
|
+
return null;
|
|
1123
|
+
}
|
|
1124
|
+
const candidates = new Set();
|
|
1125
|
+
const trimmed = rawName.trim();
|
|
1126
|
+
if (trimmed) {
|
|
1127
|
+
candidates.add(trimmed);
|
|
1128
|
+
candidates.add(trimmed.toLowerCase());
|
|
1129
|
+
}
|
|
1130
|
+
if (trimmed.endsWith('Tool')) {
|
|
1131
|
+
const withoutSuffix = trimmed.slice(0, -4);
|
|
1132
|
+
if (withoutSuffix) {
|
|
1133
|
+
candidates.add(withoutSuffix);
|
|
1134
|
+
candidates.add(withoutSuffix.toLowerCase());
|
|
1135
|
+
candidates.add(this.toSnakeCase(withoutSuffix));
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
candidates.add(this.toSnakeCase(trimmed));
|
|
1139
|
+
for (const candidate of candidates) {
|
|
1140
|
+
if (!candidate) {
|
|
1141
|
+
continue;
|
|
1142
|
+
}
|
|
1143
|
+
if (this.runtimeContext.tools.getToolMetadata(candidate)) {
|
|
1144
|
+
return candidate;
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
return null;
|
|
1148
|
+
}
|
|
1149
|
+
toSnakeCase(value) {
|
|
1150
|
+
return value
|
|
1151
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1_$2')
|
|
1152
|
+
.replace(/[\s-]+/g, '_')
|
|
1153
|
+
.toLowerCase();
|
|
1154
|
+
}
|
|
425
1155
|
/**
|
|
426
1156
|
* Builds the system prompt for the chat based on the provided configurations.
|
|
427
1157
|
* It templates the base system prompt and appends instructions for emitting
|