salmon-loop 0.2.3 → 0.2.16
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/cli/argv/headless-detection.js +27 -0
- package/dist/cli/chat-flow.js +11 -0
- package/dist/cli/chat.js +161 -24
- package/dist/cli/commands/chat.js +30 -24
- package/dist/cli/commands/context.js +15 -3
- package/dist/cli/commands/flow-mode.js +63 -0
- package/dist/cli/commands/help-format.js +12 -0
- package/dist/cli/commands/registry.js +6 -7
- package/dist/cli/commands/run/benchmark-artifacts.js +41 -0
- package/dist/cli/commands/run/config-resolution.js +30 -24
- package/dist/cli/commands/run/early-errors.js +23 -0
- package/dist/cli/commands/run/handler.js +131 -44
- package/dist/cli/commands/run/headless-error-writer.js +8 -0
- package/dist/cli/commands/run/loop-params.js +3 -0
- package/dist/cli/commands/run/mode.js +2 -5
- package/dist/cli/commands/run/parse-options.js +18 -2
- package/dist/cli/commands/run/persist-session.js +10 -1
- package/dist/cli/commands/run/preflight.js +10 -0
- package/dist/cli/commands/run/reporter-factory.js +4 -0
- package/dist/cli/commands/run/runtime-llm.js +38 -11
- package/dist/cli/commands/run/runtime-options.js +2 -2
- package/dist/cli/commands/run/validate-options.js +0 -5
- package/dist/cli/commands/run/verbose.js +2 -7
- package/dist/cli/commands/serve.js +117 -90
- package/dist/cli/commands/tool-names.js +78 -78
- package/dist/cli/headless/anthropic-stream-normalized-encoder.js +6 -1
- package/dist/cli/headless/json-protocol.js +37 -0
- package/dist/cli/headless/native-stream-normalized-encoder.js +6 -1
- package/dist/cli/headless/protocol-metadata.js +22 -0
- package/dist/cli/headless/stream-json-protocol.js +34 -1
- package/dist/cli/index.js +6 -4
- package/dist/cli/locales/en.js +32 -6
- package/dist/cli/program-bootstrap.js +14 -4
- package/dist/cli/program-commands.js +9 -1
- package/dist/cli/program-options.js +1 -0
- package/dist/cli/reporters/anthropic-stream.js +7 -1
- package/dist/cli/reporters/json.js +4 -0
- package/dist/cli/reporters/stream-json.js +17 -2
- package/dist/cli/run-cli.js +5 -3
- package/dist/cli/slash/runtime.js +30 -15
- package/dist/cli/ui/components/CommandInput.js +7 -3
- package/dist/cli/ui/components/CommandSuggestionList.js +1 -1
- package/dist/cli/utils/command-option-source.js +13 -0
- package/dist/cli/utils/output-format.js +6 -0
- package/dist/cli/utils/resolve-cli-config.js +98 -0
- package/dist/cli/utils/verbose-level.js +8 -0
- package/dist/cli/utils/verify-resolver.js +8 -4
- package/dist/cli/utils/worktree-prepare-resolver.js +7 -3
- package/dist/core/adapters/fs/file-adapter.js +6 -0
- package/dist/core/adapters/fs/filesystem.js +2 -1
- package/dist/core/adapters/git/git-adapter.js +78 -1
- package/dist/core/benchmark/patch-artifact.js +124 -0
- package/dist/core/benchmark/swe-bench.js +25 -0
- package/dist/core/config/load.js +39 -18
- package/dist/core/config/merge.js +27 -0
- package/dist/core/config/paths.js +24 -5
- package/dist/core/config/resolve-llm.js +12 -0
- package/dist/core/config/resolve.js +7 -5
- package/dist/core/config/resolvers/server.js +0 -6
- package/dist/core/config/validate.js +94 -21
- package/dist/core/context/gatherers/metadata-gatherer.js +1 -0
- package/dist/core/context/gatherers/ripgrep-gatherer.js +84 -2
- package/dist/core/context/keywords.js +18 -4
- package/dist/core/context/service-deps.js +2 -2
- package/dist/core/context/service.js +8 -0
- package/dist/core/context/steps/context-gather.js +38 -0
- package/dist/core/context/summarization/summarizer.js +55 -12
- package/dist/core/context/targeting/target-resolver.js +4 -4
- package/dist/core/extensions/index.js +23 -5
- package/dist/core/extensions/paths.js +31 -0
- package/dist/core/extensions/schemas.js +8 -5
- package/dist/core/facades/cli-chat.js +6 -2
- package/dist/core/facades/cli-command-chat.js +2 -1
- package/dist/core/facades/cli-command-tool-names.js +2 -0
- package/dist/core/facades/cli-context.js +1 -0
- package/dist/core/facades/cli-observability.js +1 -1
- package/dist/core/facades/cli-run-handler.js +4 -2
- package/dist/core/facades/cli-run-persist-session.js +1 -0
- package/dist/core/facades/cli-serve.js +2 -4
- package/dist/core/facades/cli-utils-worktree.js +1 -1
- package/dist/core/failure/diagnostics.js +53 -1
- package/dist/core/grizzco/dsl/llm-strategy.js +4 -1
- package/dist/core/grizzco/engine/outcome/loop-result-mapper.js +67 -9
- package/dist/core/grizzco/engine/pipeline/pipeline.js +6 -2
- package/dist/core/grizzco/engine/transaction/attempt-failure.js +90 -15
- package/dist/core/grizzco/engine/transaction/report-mapper.js +17 -3
- package/dist/core/grizzco/engine/transaction/transaction-runner.js +173 -7
- package/dist/core/grizzco/flows/AutopilotFlow.js +18 -0
- package/dist/core/grizzco/flows/flow-dispatch.js +11 -0
- package/dist/core/grizzco/steps/answer.js +13 -14
- package/dist/core/grizzco/steps/autopilot.js +396 -0
- package/dist/core/grizzco/steps/cache-sharing.js +29 -0
- package/dist/core/grizzco/steps/explore.js +37 -21
- package/dist/core/grizzco/steps/generateReview.js +2 -5
- package/dist/core/grizzco/steps/patch/apply-check.js +10 -0
- package/dist/core/grizzco/steps/patch/diff-normalization.js +70 -0
- package/dist/core/grizzco/steps/patch/diff-salvage.js +46 -0
- package/dist/core/grizzco/steps/patch/prompt-input.js +42 -0
- package/dist/core/grizzco/steps/patch.js +105 -146
- package/dist/core/grizzco/steps/plan.js +101 -25
- package/dist/core/grizzco/steps/preflight.js +5 -3
- package/dist/core/grizzco/steps/request-assembly.js +78 -0
- package/dist/core/grizzco/steps/research.js +39 -36
- package/dist/core/grizzco/steps/tool-runtime.js +47 -0
- package/dist/core/grizzco/steps/verify-shared.js +23 -0
- package/dist/core/grizzco/steps/verify.js +13 -21
- package/dist/core/intent/chat-intent.js +0 -4
- package/dist/core/llm/ai-sdk/chat-executor.js +2 -0
- package/dist/core/llm/ai-sdk/high-level-phase-specs.js +63 -0
- package/dist/core/llm/ai-sdk/message-mapper.js +40 -10
- package/dist/core/llm/ai-sdk/provider-factory.js +14 -0
- package/dist/core/llm/ai-sdk/request-params.js +74 -1
- package/dist/core/llm/ai-sdk/result-mapper.js +16 -0
- package/dist/core/llm/ai-sdk.js +112 -27
- package/dist/core/llm/capabilities.js +12 -0
- package/dist/core/llm/contracts/repair.js +36 -30
- package/dist/core/llm/errors.js +83 -2
- package/dist/core/llm/message-composition.js +7 -22
- package/dist/core/llm/phase-router.js +29 -10
- package/dist/core/llm/redact.js +28 -3
- package/dist/core/llm/registry.js +2 -0
- package/dist/core/llm/request-augmentation.js +55 -0
- package/dist/core/llm/request-envelope.js +334 -0
- package/dist/core/llm/shared-request-assembly.js +35 -0
- package/dist/core/llm/stream-utils.js +13 -4
- package/dist/core/llm/utils.js +18 -29
- package/dist/core/memory/relevant-retrieval.js +144 -0
- package/dist/core/observability/logger.js +11 -2
- package/dist/core/patch/diff.js +1 -0
- package/dist/core/prompts/registry.js +39 -2
- package/dist/core/prompts/runtime.js +50 -12
- package/dist/core/prompts/templates/phases/patch_user.hbs +2 -5
- package/dist/core/prompts/templates/phases/research_user.hbs +11 -0
- package/dist/core/prompts/templates/phases/review_user.hbs +3 -0
- package/dist/core/prompts/templates/system/answer_system.hbs +5 -0
- package/dist/core/prompts/templates/system/autopilot_system.hbs +11 -0
- package/dist/core/prompts/templates/system/explore_system.hbs +14 -23
- package/dist/core/prompts/templates/system/main_system.hbs +4 -16
- package/dist/core/prompts/templates/system/patch_system.hbs +39 -8
- package/dist/core/prompts/templates/system/plan_system.hbs +86 -1
- package/dist/core/prompts/templates/system/research_system.hbs +2 -0
- package/dist/core/protocols/a2a/agent-card.js +3 -2
- package/dist/core/protocols/a2a/sdk/executor.js +8 -6
- package/dist/core/protocols/a2a/sdk/server.js +0 -1
- package/dist/core/protocols/acp/formal-agent.js +221 -55
- package/dist/core/protocols/acp/handlers.js +5 -1
- package/dist/core/protocols/acp/permission-provider.js +21 -1
- package/dist/core/protocols/shared/execution-request.js +24 -0
- package/dist/core/protocols/shared/flow-mode-mapping.js +23 -0
- package/dist/core/public-capabilities/flow-mode-metadata.js +39 -0
- package/dist/core/public-capabilities/projections.js +29 -0
- package/dist/core/public-capabilities/registry.js +26 -0
- package/dist/core/public-capabilities/types.js +2 -0
- package/dist/core/runtime/agent-server-runtime.js +47 -43
- package/dist/core/runtime/execution-profile.js +67 -0
- package/dist/core/session/artifact-state.js +160 -0
- package/dist/core/session/compaction/index.js +183 -0
- package/dist/core/session/compaction/microcompact.js +78 -0
- package/dist/core/session/compaction/tracking.js +48 -0
- package/dist/core/session/compaction/types.js +11 -0
- package/dist/core/session/compression.js +12 -4
- package/dist/core/session/manager.js +247 -10
- package/dist/core/session/pruning-strategy.js +55 -9
- package/dist/core/session/replacement-preview-provider.js +24 -0
- package/dist/core/session/replacement-state.js +131 -0
- package/dist/core/session/resume-repair/pipeline.js +79 -0
- package/dist/core/session/resume-repair/stages/load-raw-archive-state.js +40 -0
- package/dist/core/session/resume-repair/stages/reattach-runtime-state.js +8 -0
- package/dist/core/session/resume-repair/stages/recover-orphaned-branches.js +10 -0
- package/dist/core/session/resume-repair/stages/relink-boundary-and-tail.js +36 -0
- package/dist/core/session/resume-repair/stages/replay-startup-hooks.js +23 -0
- package/dist/core/session/resume-repair/stages/rescue-stale-metadata.js +17 -0
- package/dist/core/session/resume-repair/types.js +2 -0
- package/dist/core/session/summary-sync.js +164 -13
- package/dist/core/session/token-tracker.js +6 -0
- package/dist/core/skills/audit.js +34 -0
- package/dist/core/skills/bridge.js +84 -7
- package/dist/core/skills/discovery.js +94 -0
- package/dist/core/skills/feature-flags.js +52 -0
- package/dist/core/skills/index.js +1 -1
- package/dist/core/skills/loader.js +195 -20
- package/dist/core/skills/parser.js +296 -24
- package/dist/core/skills/permissions.js +117 -0
- package/dist/core/skills/runtime/MicroTaskRunner.js +10 -4
- package/dist/core/skills/runtime/SkillRunner.js +240 -61
- package/dist/core/strata/layers/shadow-driver/shadow-driver.js +37 -7
- package/dist/core/strata/layers/worktree.js +70 -13
- package/dist/core/strata/runtime/synchronizer.js +29 -2
- package/dist/core/streaming/stream-assembler.js +75 -31
- package/dist/core/sub-agent/context-snapshot.js +156 -0
- package/dist/core/sub-agent/core/loop.js +1 -1
- package/dist/core/sub-agent/core/manager.js +119 -20
- package/dist/core/sub-agent/dispatch-policy.js +29 -0
- package/dist/core/sub-agent/prefix-consistency.js +48 -0
- package/dist/core/sub-agent/registry-defaults.js +4 -0
- package/dist/core/sub-agent/tools/task-spawn.js +79 -2
- package/dist/core/sub-agent/types.js +134 -5
- package/dist/core/tools/audit.js +13 -4
- package/dist/core/tools/builtin/ast-grep.js +1 -1
- package/dist/core/tools/builtin/ast.js +1 -1
- package/dist/core/tools/builtin/benchmark.js +360 -0
- package/dist/core/tools/builtin/code-search/backends/rg.js +2 -1
- package/dist/core/tools/builtin/code-search/executor.js +6 -1
- package/dist/core/tools/builtin/code-search/spec.js +26 -2
- package/dist/core/tools/builtin/fs.js +256 -23
- package/dist/core/tools/builtin/git.js +2 -2
- package/dist/core/tools/builtin/index.js +51 -2
- package/dist/core/tools/builtin/interaction.js +8 -1
- package/dist/core/tools/builtin/plan.js +37 -15
- package/dist/core/tools/builtin/shell.js +1 -1
- package/dist/core/tools/loader.js +39 -16
- package/dist/core/tools/mapper.js +17 -3
- package/dist/core/tools/parallel/scheduler.js +35 -4
- package/dist/core/tools/permissions/permission-rules.js +5 -10
- package/dist/core/tools/policy.js +6 -1
- package/dist/core/tools/recoverable-tool-errors.js +10 -0
- package/dist/core/tools/router.js +24 -6
- package/dist/core/tools/session.js +458 -48
- package/dist/core/tools/tool-visibility.js +62 -0
- package/dist/core/tools/types.js +9 -1
- package/dist/core/types/execution.js +4 -0
- package/dist/core/types/flow-mode.js +8 -0
- package/dist/core/utils/path.js +52 -0
- package/dist/core/verification/runner.js +4 -1
- package/dist/interfaces/cli/task-runner.js +4 -3
- package/dist/languages/typescript/index.js +4 -1
- package/dist/locales/en.js +87 -2
- package/dist/utils/eol.js +1 -1
- package/package.json +15 -8
- package/scripts/fix-es-abstract-compat.js +77 -0
- package/dist/core/runtime/fastify-server-bundle.js +0 -26
- package/dist/core/runtime/sidecar-fastify-plugin.js +0 -35
- package/dist/core/runtime/sidecar-paths.js +0 -47
- package/dist/core/runtime/sidecar-route-catalog.js +0 -103
|
@@ -6,6 +6,7 @@ import { recordAuditEvent } from '../observability/audit-trail.js';
|
|
|
6
6
|
import { getLogger } from '../observability/logger.js';
|
|
7
7
|
import { CanonicalResponsesEventEmitter, } from '../streaming/canonical/canonical-responses-event-emitter.js';
|
|
8
8
|
import { mapLlmStreamChunkToCanonicalStreamParts } from '../streaming/canonical/parts-from-llm-stream-chunk.js';
|
|
9
|
+
import { ArtifactStore } from '../sub-agent/artifacts/store.js';
|
|
9
10
|
import { Phase } from '../types/runtime.js';
|
|
10
11
|
import { isSafeRelativePath, normalizePath } from '../utils/path.js';
|
|
11
12
|
import { buildHeadlessToolInputPayload } from './headless-payload.js';
|
|
@@ -13,7 +14,9 @@ import { toolToOpenAI } from './mapper.js';
|
|
|
13
14
|
import { InMemoryLockManager } from './parallel/lock-manager.js';
|
|
14
15
|
import { PlanPersistence } from './parallel/persistence.js';
|
|
15
16
|
import { ParallelScheduler } from './parallel/scheduler.js';
|
|
17
|
+
import { isRecoverableToolInputErrorCode } from './recoverable-tool-errors.js';
|
|
16
18
|
import { ToolCallAccumulator } from './streaming/ToolCallAccumulator.js';
|
|
19
|
+
import { resolveVisibleToolSpecs } from './tool-visibility.js';
|
|
17
20
|
function safeParseJson(argsText) {
|
|
18
21
|
if (typeof argsText !== 'string') {
|
|
19
22
|
return { ok: true, value: argsText };
|
|
@@ -80,7 +83,156 @@ function safeStringifyForAudit(value) {
|
|
|
80
83
|
return '[Unserializable]';
|
|
81
84
|
}
|
|
82
85
|
}
|
|
86
|
+
function buildToolCorrectionHint(result) {
|
|
87
|
+
const errorCode = result.error?.code;
|
|
88
|
+
const tool = result.toolName;
|
|
89
|
+
if (!isRecoverableToolInputErrorCode(errorCode) || !tool)
|
|
90
|
+
return undefined;
|
|
91
|
+
return {
|
|
92
|
+
kind: 'adjust_arguments',
|
|
93
|
+
tool,
|
|
94
|
+
hint: result.error?.message ||
|
|
95
|
+
`Adjust the arguments for ${tool} and retry with a valid JSON object.`,
|
|
96
|
+
retryable: true,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
function normalizeRecoverableToolResult(result) {
|
|
100
|
+
if (!result.error)
|
|
101
|
+
return result;
|
|
102
|
+
const retryHint = buildToolCorrectionHint(result);
|
|
103
|
+
if (!retryHint)
|
|
104
|
+
return result;
|
|
105
|
+
const nextMeta = {
|
|
106
|
+
...(result.meta ?? {}),
|
|
107
|
+
retryHint,
|
|
108
|
+
};
|
|
109
|
+
const nextError = {
|
|
110
|
+
...result.error,
|
|
111
|
+
retryable: retryHint.retryable || result.error.retryable,
|
|
112
|
+
};
|
|
113
|
+
return {
|
|
114
|
+
...result,
|
|
115
|
+
meta: nextMeta,
|
|
116
|
+
error: nextError,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
function isArtifactHandleRecord(value) {
|
|
120
|
+
if (!value || typeof value !== 'object')
|
|
121
|
+
return false;
|
|
122
|
+
const candidate = value;
|
|
123
|
+
return (typeof candidate.handle === 'string' &&
|
|
124
|
+
typeof candidate.mimeType === 'string' &&
|
|
125
|
+
typeof candidate.sha256 === 'string' &&
|
|
126
|
+
typeof candidate.size === 'number');
|
|
127
|
+
}
|
|
128
|
+
function extractArtifactHandlesFromToolOutput(output) {
|
|
129
|
+
if (!isObjectRecord(output)) {
|
|
130
|
+
return {};
|
|
131
|
+
}
|
|
132
|
+
const patchArtifact = isArtifactHandleRecord(output.patchArtifact)
|
|
133
|
+
? output.patchArtifact
|
|
134
|
+
: undefined;
|
|
135
|
+
const auditArtifact = isArtifactHandleRecord(output.auditArtifact)
|
|
136
|
+
? output.auditArtifact
|
|
137
|
+
: undefined;
|
|
138
|
+
return {
|
|
139
|
+
patchArtifact,
|
|
140
|
+
auditArtifact,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
function extractRecentReadResult(params) {
|
|
144
|
+
if (params.toolName !== 'fs.read' && params.toolName !== 'code.read') {
|
|
145
|
+
return undefined;
|
|
146
|
+
}
|
|
147
|
+
if (!isObjectRecord(params.output) || typeof params.output.content !== 'string') {
|
|
148
|
+
return undefined;
|
|
149
|
+
}
|
|
150
|
+
const args = safeParseJson(params.rawArgs);
|
|
151
|
+
const argsValue = args.ok ? args.value : params.rawArgs;
|
|
152
|
+
if (!isObjectRecord(argsValue))
|
|
153
|
+
return undefined;
|
|
154
|
+
const file = argsValue.file ?? argsValue.file_path ?? argsValue.filePath ?? argsValue.path;
|
|
155
|
+
if (typeof file !== 'string' || !file.trim())
|
|
156
|
+
return undefined;
|
|
157
|
+
return {
|
|
158
|
+
path: file,
|
|
159
|
+
content: params.output.content,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
async function persistRecentReadArtifact(params) {
|
|
163
|
+
const readResult = extractRecentReadResult(params);
|
|
164
|
+
if (!readResult)
|
|
165
|
+
return undefined;
|
|
166
|
+
const ext = path.extname(readResult.path).replace(/^\./, '') || 'txt';
|
|
167
|
+
const artifact = await ArtifactStore.saveText({
|
|
168
|
+
content: readResult.content,
|
|
169
|
+
mimeType: 'text/plain',
|
|
170
|
+
fileExt: ext,
|
|
171
|
+
});
|
|
172
|
+
return {
|
|
173
|
+
path: readResult.path,
|
|
174
|
+
artifact,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
const TOOL_RESULT_PREVIEW_MIN_CHARS = 1200;
|
|
178
|
+
function sanitizeToolResultPreviewLabel(label) {
|
|
179
|
+
const oneLine = label.replace(/\s+/g, ' ').trim();
|
|
180
|
+
if (!oneLine)
|
|
181
|
+
return 'Tool result preview';
|
|
182
|
+
if (oneLine.length <= 120)
|
|
183
|
+
return oneLine;
|
|
184
|
+
return `${oneLine.slice(0, 119).trimEnd()}…`;
|
|
185
|
+
}
|
|
186
|
+
function serializeToolResultOutputForArtifact(output) {
|
|
187
|
+
if (typeof output === 'string') {
|
|
188
|
+
const text = output.trim();
|
|
189
|
+
if (!text)
|
|
190
|
+
return undefined;
|
|
191
|
+
return {
|
|
192
|
+
content: output,
|
|
193
|
+
mimeType: 'text/plain',
|
|
194
|
+
fileExt: 'txt',
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
if (output === undefined)
|
|
198
|
+
return undefined;
|
|
199
|
+
try {
|
|
200
|
+
const json = JSON.stringify(output, null, 2);
|
|
201
|
+
if (!json || !json.trim())
|
|
202
|
+
return undefined;
|
|
203
|
+
return {
|
|
204
|
+
content: json,
|
|
205
|
+
mimeType: 'application/json',
|
|
206
|
+
fileExt: 'json',
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
catch {
|
|
210
|
+
return undefined;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
async function persistToolResultPreviewArtifact(params) {
|
|
214
|
+
if (params.toolName === 'fs.read' || params.toolName === 'code.read') {
|
|
215
|
+
return undefined;
|
|
216
|
+
}
|
|
217
|
+
const serialized = serializeToolResultOutputForArtifact(params.output);
|
|
218
|
+
if (!serialized)
|
|
219
|
+
return undefined;
|
|
220
|
+
if (serialized.content.length < TOOL_RESULT_PREVIEW_MIN_CHARS)
|
|
221
|
+
return undefined;
|
|
222
|
+
const artifact = await ArtifactStore.saveText({
|
|
223
|
+
content: serialized.content,
|
|
224
|
+
mimeType: serialized.mimeType,
|
|
225
|
+
fileExt: serialized.fileExt,
|
|
226
|
+
});
|
|
227
|
+
const detail = params.outputSummary ?? params.summary ?? `${params.toolName} output`;
|
|
228
|
+
return {
|
|
229
|
+
label: sanitizeToolResultPreviewLabel(`Tool result preview: ${detail}`),
|
|
230
|
+
artifact,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
83
233
|
function defaultMaxToolCallsTotalForPhase(phase) {
|
|
234
|
+
if (phase === Phase.AUTOPILOT)
|
|
235
|
+
return 32;
|
|
84
236
|
if (phase === Phase.EXPLORE)
|
|
85
237
|
return 18;
|
|
86
238
|
if (phase === Phase.PLAN)
|
|
@@ -90,6 +242,8 @@ function defaultMaxToolCallsTotalForPhase(phase) {
|
|
|
90
242
|
return 10;
|
|
91
243
|
}
|
|
92
244
|
function defaultMaxToolCallsPerRoundForPhase(phase) {
|
|
245
|
+
if (phase === Phase.AUTOPILOT)
|
|
246
|
+
return 8;
|
|
93
247
|
if (phase === Phase.EXPLORE)
|
|
94
248
|
return 6;
|
|
95
249
|
if (phase === Phase.PLAN)
|
|
@@ -98,7 +252,25 @@ function defaultMaxToolCallsPerRoundForPhase(phase) {
|
|
|
98
252
|
return 4;
|
|
99
253
|
return 4;
|
|
100
254
|
}
|
|
101
|
-
function
|
|
255
|
+
function defaultMaxAgentToolCallsTotalForPhase(phase) {
|
|
256
|
+
if (phase === Phase.AUTOPILOT)
|
|
257
|
+
return 4;
|
|
258
|
+
if (phase === Phase.PLAN)
|
|
259
|
+
return 2;
|
|
260
|
+
if (phase === Phase.CONTEXT)
|
|
261
|
+
return 1;
|
|
262
|
+
return 1;
|
|
263
|
+
}
|
|
264
|
+
function defaultMaxAgentToolCallsPerRoundForPhase(phase) {
|
|
265
|
+
if (phase === Phase.AUTOPILOT)
|
|
266
|
+
return 2;
|
|
267
|
+
if (phase === Phase.PLAN)
|
|
268
|
+
return 1;
|
|
269
|
+
if (phase === Phase.CONTEXT)
|
|
270
|
+
return 1;
|
|
271
|
+
return 1;
|
|
272
|
+
}
|
|
273
|
+
function getRegularToolCallBudget(session) {
|
|
102
274
|
const maxTotal = session.maxToolCallsTotal ?? defaultMaxToolCallsTotalForPhase(session.phase);
|
|
103
275
|
const maxPerRound = session.maxToolCallsPerRound ?? defaultMaxToolCallsPerRoundForPhase(session.phase);
|
|
104
276
|
return {
|
|
@@ -106,39 +278,62 @@ function getToolCallBudget(session) {
|
|
|
106
278
|
maxPerRound: Math.max(0, Math.floor(maxPerRound)),
|
|
107
279
|
};
|
|
108
280
|
}
|
|
281
|
+
function getAgentToolCallBudget(session) {
|
|
282
|
+
const maxTotal = session.maxAgentToolCallsTotal ?? defaultMaxAgentToolCallsTotalForPhase(session.phase);
|
|
283
|
+
const maxPerRound = session.maxAgentToolCallsPerRound ?? defaultMaxAgentToolCallsPerRoundForPhase(session.phase);
|
|
284
|
+
return {
|
|
285
|
+
maxTotal: Math.max(0, Math.floor(maxTotal)),
|
|
286
|
+
maxPerRound: Math.max(0, Math.floor(maxPerRound)),
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
function createToolCallBudgetBucketsState(session) {
|
|
290
|
+
return {
|
|
291
|
+
regular: { used: 0, ...getRegularToolCallBudget(session) },
|
|
292
|
+
agent: { used: 0, ...getAgentToolCallBudget(session) },
|
|
293
|
+
};
|
|
294
|
+
}
|
|
109
295
|
function resetToolCallBudgetState(session) {
|
|
110
|
-
const
|
|
111
|
-
const state = { used: 0, ...budget };
|
|
296
|
+
const state = createToolCallBudgetBucketsState(session);
|
|
112
297
|
session.__toolCallBudgetState = state;
|
|
113
298
|
return state;
|
|
114
299
|
}
|
|
115
300
|
function getToolCallBudgetState(session) {
|
|
116
|
-
const budget = getToolCallBudget(session);
|
|
117
301
|
const anySession = session;
|
|
118
302
|
const existing = anySession.__toolCallBudgetState;
|
|
119
303
|
if (!existing) {
|
|
120
|
-
const created =
|
|
304
|
+
const created = createToolCallBudgetBucketsState(session);
|
|
121
305
|
anySession.__toolCallBudgetState = created;
|
|
122
306
|
return created;
|
|
123
307
|
}
|
|
124
308
|
// Ensure runtime overrides are respected.
|
|
125
|
-
|
|
126
|
-
existing.
|
|
309
|
+
const regular = getRegularToolCallBudget(session);
|
|
310
|
+
existing.regular.maxTotal = regular.maxTotal;
|
|
311
|
+
existing.regular.maxPerRound = regular.maxPerRound;
|
|
312
|
+
const agent = getAgentToolCallBudget(session);
|
|
313
|
+
existing.agent.maxTotal = agent.maxTotal;
|
|
314
|
+
existing.agent.maxPerRound = agent.maxPerRound;
|
|
127
315
|
return existing;
|
|
128
316
|
}
|
|
129
317
|
function initToolCallRoundBudget(params) {
|
|
130
318
|
const budgetState = getToolCallBudgetState(params.session);
|
|
131
|
-
const
|
|
132
|
-
|
|
133
|
-
|
|
319
|
+
const roundCaps = {
|
|
320
|
+
regular: Math.min(budgetState.regular.maxPerRound, Math.max(0, budgetState.regular.maxTotal - budgetState.regular.used)),
|
|
321
|
+
agent: Math.min(budgetState.agent.maxPerRound, Math.max(0, budgetState.agent.maxTotal - budgetState.agent.used)),
|
|
322
|
+
};
|
|
323
|
+
budgetState.regular.used += params.preparedCounts.regular;
|
|
324
|
+
budgetState.agent.used += params.preparedCounts.agent;
|
|
325
|
+
for (const bucket of ['regular', 'agent']) {
|
|
326
|
+
const denied = params.preparedCounts[bucket] - roundCaps[bucket];
|
|
327
|
+
if (denied <= 0)
|
|
328
|
+
continue;
|
|
134
329
|
params.session.emit?.({
|
|
135
330
|
type: 'log',
|
|
136
331
|
level: 'warn',
|
|
137
|
-
message: `Tool call budget exceeded; denying ${
|
|
332
|
+
message: `Tool call budget exceeded; denying ${denied} ${bucket} tool calls (phase=${params.phase}, round=${params.round})`,
|
|
138
333
|
timestamp: new Date(),
|
|
139
334
|
});
|
|
140
335
|
}
|
|
141
|
-
return {
|
|
336
|
+
return { roundCaps, budgetState };
|
|
142
337
|
}
|
|
143
338
|
function isFunctionCallStreamPart(part) {
|
|
144
339
|
switch (part.type) {
|
|
@@ -192,6 +387,7 @@ async function consumeAssistantStreamTurn(params) {
|
|
|
192
387
|
toolChoice: params.openAITools.length > 0 ? 'auto' : undefined,
|
|
193
388
|
});
|
|
194
389
|
let content = '';
|
|
390
|
+
let reasoningContent = '';
|
|
195
391
|
let finishReason;
|
|
196
392
|
let finishUsage;
|
|
197
393
|
for await (const chunk of stream) {
|
|
@@ -233,6 +429,9 @@ async function consumeAssistantStreamTurn(params) {
|
|
|
233
429
|
}
|
|
234
430
|
content += chunk.contentDelta;
|
|
235
431
|
}
|
|
432
|
+
if (typeof chunk?.reasoningDelta === 'string' && chunk.reasoningDelta) {
|
|
433
|
+
reasoningContent += chunk.reasoningDelta;
|
|
434
|
+
}
|
|
236
435
|
params.toolCalls.append(chunk);
|
|
237
436
|
if (chunk?.done) {
|
|
238
437
|
finishReason = chunk.finishReason;
|
|
@@ -244,11 +443,21 @@ async function consumeAssistantStreamTurn(params) {
|
|
|
244
443
|
break;
|
|
245
444
|
}
|
|
246
445
|
}
|
|
247
|
-
return {
|
|
446
|
+
return {
|
|
447
|
+
content,
|
|
448
|
+
reasoningContent: reasoningContent.length > 0 ? reasoningContent : undefined,
|
|
449
|
+
finishReason,
|
|
450
|
+
finishUsage,
|
|
451
|
+
};
|
|
248
452
|
}
|
|
249
453
|
async function applyEmptyStreamFallback(params) {
|
|
250
454
|
if (params.content.trim() !== '' || params.collectedToolCalls.length > 0) {
|
|
251
|
-
return {
|
|
455
|
+
return {
|
|
456
|
+
usedFallback: false,
|
|
457
|
+
content: params.content,
|
|
458
|
+
reasoningContent: params.reasoningContent,
|
|
459
|
+
toolCalls: params.collectedToolCalls,
|
|
460
|
+
};
|
|
252
461
|
}
|
|
253
462
|
recordAuditEvent('llm.stream.empty_fallback', { phase: params.phase, round: params.round }, { source: 'llm', severity: 'low', scope: 'session', phase: params.phase });
|
|
254
463
|
const fallback = await params.session.llm.chat(params.messages, {
|
|
@@ -259,6 +468,7 @@ async function applyEmptyStreamFallback(params) {
|
|
|
259
468
|
toolChoice: params.openAITools.length > 0 ? 'auto' : undefined,
|
|
260
469
|
});
|
|
261
470
|
const finalContent = fallback.content || '';
|
|
471
|
+
const finalReasoningContent = fallback.reasoning_content;
|
|
262
472
|
const finalCalls = Array.isArray(fallback.tool_calls) ? fallback.tool_calls : [];
|
|
263
473
|
if (params.session.llmOutput && finalContent) {
|
|
264
474
|
emitLlmOutput({
|
|
@@ -269,7 +479,12 @@ async function applyEmptyStreamFallback(params) {
|
|
|
269
479
|
content: finalContent,
|
|
270
480
|
});
|
|
271
481
|
}
|
|
272
|
-
return {
|
|
482
|
+
return {
|
|
483
|
+
usedFallback: true,
|
|
484
|
+
content: finalContent,
|
|
485
|
+
reasoningContent: finalReasoningContent,
|
|
486
|
+
toolCalls: finalCalls,
|
|
487
|
+
};
|
|
273
488
|
}
|
|
274
489
|
function emitSynthesizedFunctionCallClosures(params) {
|
|
275
490
|
if (!params.canonicalEmitter)
|
|
@@ -335,22 +550,59 @@ function emitSynthesizedFunctionCallClosures(params) {
|
|
|
335
550
|
});
|
|
336
551
|
}
|
|
337
552
|
}
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
553
|
+
const PLAN_RUNTIME_UNAVAILABLE_MARKER = 'No plan.* tools are available in this session.';
|
|
554
|
+
const PLAN_RUNTIME_SESSION_ID_PATTERN = /- sessionId:\s*([^\s]+)/;
|
|
555
|
+
const PLAN_RUNTIME_PATH_HINT_PATTERN = /- planPathHint:\s*([^\s]+)/;
|
|
556
|
+
function inferToolVisibilityRuntimeFromMessages(messages) {
|
|
557
|
+
const systemMessage = messages.find((msg) => msg.role === 'system');
|
|
558
|
+
if (!systemMessage || typeof systemMessage.content !== 'string')
|
|
559
|
+
return undefined;
|
|
560
|
+
const content = systemMessage.content;
|
|
561
|
+
if (content.includes(PLAN_RUNTIME_UNAVAILABLE_MARKER)) {
|
|
562
|
+
return undefined;
|
|
563
|
+
}
|
|
564
|
+
const sessionIdMatch = content.match(PLAN_RUNTIME_SESSION_ID_PATTERN);
|
|
565
|
+
const planPathHintMatch = content.match(PLAN_RUNTIME_PATH_HINT_PATTERN);
|
|
566
|
+
if (!sessionIdMatch || !planPathHintMatch)
|
|
567
|
+
return undefined;
|
|
568
|
+
return {
|
|
569
|
+
plan: {
|
|
570
|
+
sessionId: sessionIdMatch[1],
|
|
571
|
+
planPathHint: planPathHintMatch[1],
|
|
572
|
+
},
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
function resolveToolVisibilityRuntime(session, messages) {
|
|
576
|
+
if (session.toolVisibility)
|
|
577
|
+
return session.toolVisibility;
|
|
578
|
+
if (!messages || messages.length === 0)
|
|
579
|
+
return undefined;
|
|
580
|
+
return inferToolVisibilityRuntimeFromMessages(messages);
|
|
581
|
+
}
|
|
582
|
+
function resolveToolCallingForSession(params) {
|
|
583
|
+
const visibilityRuntime = resolveToolVisibilityRuntime(params.session, params.messages);
|
|
584
|
+
const visibleSpecs = resolveVisibleToolSpecs({
|
|
585
|
+
phase: params.phase,
|
|
586
|
+
toolstack: params.session.toolstack,
|
|
587
|
+
worktreeRoot: params.session.runtime.worktreeRoot,
|
|
588
|
+
flowMode: params.session.runtime.flowMode,
|
|
589
|
+
runtime: visibilityRuntime,
|
|
343
590
|
});
|
|
344
|
-
const openAITools =
|
|
345
|
-
return { allowedSpecs, openAITools };
|
|
591
|
+
const openAITools = visibleSpecs.map(toolToOpenAI);
|
|
592
|
+
return { allowedSpecs: visibleSpecs, openAITools };
|
|
346
593
|
}
|
|
347
|
-
function emitToolCallingEnabledLogIfNeeded(session, openAITools) {
|
|
594
|
+
function emitToolCallingEnabledLogIfNeeded(session, openAITools, allowedSpecs) {
|
|
348
595
|
if (openAITools.length === 0)
|
|
349
596
|
return;
|
|
597
|
+
const toolNames = allowedSpecs.map((spec) => spec.name).sort();
|
|
598
|
+
const maxNames = 24;
|
|
599
|
+
const visible = toolNames.slice(0, maxNames);
|
|
600
|
+
const overflow = toolNames.length - visible.length;
|
|
601
|
+
const suffix = overflow > 0 ? ` (+${overflow} more)` : '';
|
|
350
602
|
session.emit?.({
|
|
351
603
|
type: 'log',
|
|
352
604
|
level: 'debug',
|
|
353
|
-
message: `Tool calling enabled (${openAITools.length} tools available)`,
|
|
605
|
+
message: `Tool calling enabled (${openAITools.length} tools available): ${visible.join(', ')}${suffix}`,
|
|
354
606
|
timestamp: new Date(),
|
|
355
607
|
});
|
|
356
608
|
}
|
|
@@ -400,9 +652,13 @@ export async function chatWithTools(initialMessages, chatOptions, session) {
|
|
|
400
652
|
const maxRounds = session.maxRounds ?? 6;
|
|
401
653
|
const phase = session.phase;
|
|
402
654
|
resetToolCallBudgetState(session);
|
|
403
|
-
const { allowedSpecs, openAITools } = resolveToolCallingForSession(session, phase);
|
|
404
|
-
emitToolCallingEnabledLogIfNeeded(session, openAITools);
|
|
405
655
|
const messages = [...initialMessages];
|
|
656
|
+
const { allowedSpecs, openAITools } = resolveToolCallingForSession({
|
|
657
|
+
session,
|
|
658
|
+
phase,
|
|
659
|
+
messages,
|
|
660
|
+
});
|
|
661
|
+
emitToolCallingEnabledLogIfNeeded(session, openAITools, allowedSpecs);
|
|
406
662
|
for (let round = 0; round < maxRounds; round++) {
|
|
407
663
|
// Check for abort before starting a new round
|
|
408
664
|
if (chatOptions.signal?.aborted) {
|
|
@@ -443,6 +699,7 @@ export async function chatWithTools(initialMessages, chatOptions, session) {
|
|
|
443
699
|
messages.push({
|
|
444
700
|
role: 'assistant',
|
|
445
701
|
content: assistant.content || '',
|
|
702
|
+
reasoning_content: assistant.reasoning_content,
|
|
446
703
|
tool_calls: assistant.tool_calls,
|
|
447
704
|
});
|
|
448
705
|
const toolCalls = assistant.tool_calls || [];
|
|
@@ -486,6 +743,52 @@ export async function chatWithTools(initialMessages, chatOptions, session) {
|
|
|
486
743
|
function isObjectRecord(value) {
|
|
487
744
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
488
745
|
}
|
|
746
|
+
function isPlainObject(value) {
|
|
747
|
+
if (!isObjectRecord(value))
|
|
748
|
+
return false;
|
|
749
|
+
const proto = Object.getPrototypeOf(value);
|
|
750
|
+
return proto === Object.prototype || proto === null;
|
|
751
|
+
}
|
|
752
|
+
function describeValueType(value) {
|
|
753
|
+
if (value === null)
|
|
754
|
+
return 'null';
|
|
755
|
+
if (Array.isArray(value))
|
|
756
|
+
return 'array';
|
|
757
|
+
return typeof value;
|
|
758
|
+
}
|
|
759
|
+
function formatPlanUpdatePatchTypeError(actualType) {
|
|
760
|
+
return (`Invalid field: patch (received ${actualType}). ` +
|
|
761
|
+
'Expected object with optional keys: status, checkbox, appendSubtasks, note. ' +
|
|
762
|
+
'Do not JSON-stringify patch.');
|
|
763
|
+
}
|
|
764
|
+
function coercePlanUpdatePatch(args) {
|
|
765
|
+
if (!Object.prototype.hasOwnProperty.call(args, 'patch'))
|
|
766
|
+
return { args };
|
|
767
|
+
const patch = args.patch;
|
|
768
|
+
if (isPlainObject(patch))
|
|
769
|
+
return { args };
|
|
770
|
+
if (typeof patch === 'string') {
|
|
771
|
+
const trimmed = patch.trim();
|
|
772
|
+
const looksLikeObjectLiteral = trimmed.startsWith('{') && trimmed.endsWith('}');
|
|
773
|
+
if (!looksLikeObjectLiteral) {
|
|
774
|
+
return { args, error: formatPlanUpdatePatchTypeError('string') };
|
|
775
|
+
}
|
|
776
|
+
try {
|
|
777
|
+
const parsed = JSON.parse(trimmed);
|
|
778
|
+
if (!isPlainObject(parsed)) {
|
|
779
|
+
return { args, error: formatPlanUpdatePatchTypeError(describeValueType(parsed)) };
|
|
780
|
+
}
|
|
781
|
+
return {
|
|
782
|
+
args: { ...args, patch: parsed },
|
|
783
|
+
coercedPatchSource: 'stringified',
|
|
784
|
+
};
|
|
785
|
+
}
|
|
786
|
+
catch {
|
|
787
|
+
return { args, error: formatPlanUpdatePatchTypeError('string') };
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
return { args, error: formatPlanUpdatePatchTypeError(describeValueType(patch)) };
|
|
791
|
+
}
|
|
489
792
|
function unwrapRetryError(err) {
|
|
490
793
|
if (!err || typeof err !== 'object')
|
|
491
794
|
return err;
|
|
@@ -680,18 +983,29 @@ function applyStrictToolOutputSchemaValidation(params) {
|
|
|
680
983
|
}
|
|
681
984
|
async function executeToolCalls(session, phase, round, calls, messages, signal) {
|
|
682
985
|
const prepared = prepareToolCallRequests(calls);
|
|
683
|
-
const
|
|
986
|
+
const bucketByCallId = new Map();
|
|
987
|
+
const preparedCounts = { regular: 0, agent: 0 };
|
|
988
|
+
for (const item of prepared) {
|
|
989
|
+
const spec = typeof item.toolName === 'string'
|
|
990
|
+
? session.toolstack.registry.listAll().find((s) => s.name === item.toolName)
|
|
991
|
+
: undefined;
|
|
992
|
+
const bucket = spec?.intent === 'AGENT' ? 'agent' : 'regular';
|
|
993
|
+
bucketByCallId.set(item.callId, bucket);
|
|
994
|
+
preparedCounts[bucket]++;
|
|
995
|
+
}
|
|
996
|
+
const { roundCaps } = initToolCallRoundBudget({
|
|
684
997
|
session,
|
|
685
998
|
phase,
|
|
686
999
|
round,
|
|
687
|
-
|
|
1000
|
+
preparedCounts,
|
|
688
1001
|
});
|
|
689
1002
|
const toolResults = new Map();
|
|
690
1003
|
const nodes = [];
|
|
691
1004
|
const toolArgsPreviewByCallId = new Map();
|
|
692
1005
|
const rawArgsPreviewByCallId = new Map();
|
|
693
1006
|
const rawArgsTypeByCallId = new Map();
|
|
694
|
-
|
|
1007
|
+
const patchCoercionByCallId = new Map();
|
|
1008
|
+
const allowedUsed = { regular: 0, agent: 0 };
|
|
695
1009
|
for (const item of prepared) {
|
|
696
1010
|
const { callId, toolName, rawArgs } = item;
|
|
697
1011
|
const normalizedToolName = typeof toolName === 'string' ? toolName : 'unknown';
|
|
@@ -725,6 +1039,17 @@ async function executeToolCalls(session, phase, round, calls, messages, signal)
|
|
|
725
1039
|
argsValue = { ...argsValue, file: inferred[0] };
|
|
726
1040
|
}
|
|
727
1041
|
}
|
|
1042
|
+
let planUpdatePatchError;
|
|
1043
|
+
if (parsedArgsOk && normalizedToolName === 'plan.update' && isObjectRecord(argsValue)) {
|
|
1044
|
+
const patchGuard = coercePlanUpdatePatch(argsValue);
|
|
1045
|
+
argsValue = patchGuard.args;
|
|
1046
|
+
if (patchGuard.coercedPatchSource) {
|
|
1047
|
+
patchCoercionByCallId.set(callId, patchGuard.coercedPatchSource);
|
|
1048
|
+
}
|
|
1049
|
+
if (patchGuard.error) {
|
|
1050
|
+
planUpdatePatchError = patchGuard.error;
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
728
1053
|
if (parsedArgsOk) {
|
|
729
1054
|
toolArgsPreviewByCallId.set(callId, safeStringifyForAudit(argsValue));
|
|
730
1055
|
}
|
|
@@ -754,7 +1079,8 @@ async function executeToolCalls(session, phase, round, calls, messages, signal)
|
|
|
754
1079
|
});
|
|
755
1080
|
// Hard budget: deny tool execution once the session exceeds the configured budget.
|
|
756
1081
|
// We still return a tool result for protocol completeness and observability.
|
|
757
|
-
|
|
1082
|
+
const budgetBucket = bucketByCallId.get(callId) ?? 'regular';
|
|
1083
|
+
if (allowedUsed[budgetBucket] >= roundCaps[budgetBucket]) {
|
|
758
1084
|
toolResults.set(callId, {
|
|
759
1085
|
id: callId,
|
|
760
1086
|
toolName: typeof toolName === 'string' ? toolName : 'unknown',
|
|
@@ -762,7 +1088,9 @@ async function executeToolCalls(session, phase, round, calls, messages, signal)
|
|
|
762
1088
|
status: 'error',
|
|
763
1089
|
error: {
|
|
764
1090
|
code: 'TOOL_CALL_BUDGET_EXCEEDED',
|
|
765
|
-
message:
|
|
1091
|
+
message: budgetBucket === 'agent'
|
|
1092
|
+
? 'Agent delegation denied: delegation budget exceeded for this session. Continue without additional agent dispatches or complete the task directly.'
|
|
1093
|
+
: 'Tool call denied: tool calling budget exceeded for this session. Continue without additional tool calls.',
|
|
766
1094
|
retryable: false,
|
|
767
1095
|
failurePhase: phase,
|
|
768
1096
|
},
|
|
@@ -770,7 +1098,7 @@ async function executeToolCalls(session, phase, round, calls, messages, signal)
|
|
|
770
1098
|
});
|
|
771
1099
|
continue;
|
|
772
1100
|
}
|
|
773
|
-
allowedUsed++;
|
|
1101
|
+
allowedUsed[budgetBucket]++;
|
|
774
1102
|
if (!toolName || typeof toolName !== 'string') {
|
|
775
1103
|
getLogger().warn('Received malformed tool call (missing function.name)');
|
|
776
1104
|
session.toolCallingAudit?.event({
|
|
@@ -835,7 +1163,7 @@ async function executeToolCalls(session, phase, round, calls, messages, signal)
|
|
|
835
1163
|
});
|
|
836
1164
|
continue;
|
|
837
1165
|
}
|
|
838
|
-
|
|
1166
|
+
const parsedAuditEntry = {
|
|
839
1167
|
timestamp: new Date().toISOString(),
|
|
840
1168
|
phase,
|
|
841
1169
|
round,
|
|
@@ -846,7 +1174,28 @@ async function executeToolCalls(session, phase, round, calls, messages, signal)
|
|
|
846
1174
|
rawArgsPreview: typeof rawArgs === 'string' ? redactJsonString(rawArgs) : undefined,
|
|
847
1175
|
parsedArgsOk: true,
|
|
848
1176
|
parsedArgsPreview: safeStringifyForAudit(argsValue),
|
|
849
|
-
}
|
|
1177
|
+
};
|
|
1178
|
+
const patchCoercionSource = patchCoercionByCallId.get(callId);
|
|
1179
|
+
if (patchCoercionSource) {
|
|
1180
|
+
parsedAuditEntry.coercedPatchSource = patchCoercionSource;
|
|
1181
|
+
}
|
|
1182
|
+
session.toolCallingAudit?.event(parsedAuditEntry);
|
|
1183
|
+
if (planUpdatePatchError) {
|
|
1184
|
+
toolResults.set(callId, normalizeRecoverableToolResult({
|
|
1185
|
+
id: callId,
|
|
1186
|
+
toolName,
|
|
1187
|
+
source: 'builtin',
|
|
1188
|
+
status: 'error',
|
|
1189
|
+
error: {
|
|
1190
|
+
code: 'INVALID_INPUT',
|
|
1191
|
+
message: planUpdatePatchError,
|
|
1192
|
+
retryable: true,
|
|
1193
|
+
failurePhase: phase,
|
|
1194
|
+
},
|
|
1195
|
+
durationMs: 0,
|
|
1196
|
+
}));
|
|
1197
|
+
continue;
|
|
1198
|
+
}
|
|
850
1199
|
nodes.push({ id: callId, toolName, args: argsValue, deps: [] });
|
|
851
1200
|
}
|
|
852
1201
|
if (nodes.length > 0) {
|
|
@@ -885,7 +1234,11 @@ async function executeToolCalls(session, phase, round, calls, messages, signal)
|
|
|
885
1234
|
}
|
|
886
1235
|
for (const item of prepared) {
|
|
887
1236
|
const { callId, toolName, rawArgs } = item;
|
|
888
|
-
const
|
|
1237
|
+
const rawResult = toolResults.get(callId);
|
|
1238
|
+
const result = rawResult ? normalizeRecoverableToolResult(rawResult) : undefined;
|
|
1239
|
+
if (result && result !== rawResult) {
|
|
1240
|
+
toolResults.set(callId, result);
|
|
1241
|
+
}
|
|
889
1242
|
if (!result)
|
|
890
1243
|
continue;
|
|
891
1244
|
// Strict output schema validation
|
|
@@ -924,7 +1277,7 @@ async function executeToolCalls(session, phase, round, calls, messages, signal)
|
|
|
924
1277
|
if (result.status !== 'ok') {
|
|
925
1278
|
const errorCode = result.error?.code;
|
|
926
1279
|
const attachArgsPreview = errorCode === 'INVALID_INPUT';
|
|
927
|
-
|
|
1280
|
+
const errorAuditEntry = {
|
|
928
1281
|
timestamp: new Date().toISOString(),
|
|
929
1282
|
phase,
|
|
930
1283
|
round,
|
|
@@ -939,6 +1292,45 @@ async function executeToolCalls(session, phase, round, calls, messages, signal)
|
|
|
939
1292
|
: undefined,
|
|
940
1293
|
rawArgsPreview: attachArgsPreview ? rawArgsPreviewByCallId.get(callId) : undefined,
|
|
941
1294
|
parsedArgsPreview: attachArgsPreview ? toolArgsPreviewByCallId.get(callId) : undefined,
|
|
1295
|
+
};
|
|
1296
|
+
const patchCoercionSource = patchCoercionByCallId.get(callId);
|
|
1297
|
+
if (patchCoercionSource) {
|
|
1298
|
+
errorAuditEntry.coercedPatchSource = patchCoercionSource;
|
|
1299
|
+
}
|
|
1300
|
+
session.toolCallingAudit?.event(errorAuditEntry);
|
|
1301
|
+
}
|
|
1302
|
+
else {
|
|
1303
|
+
const toolResultOutputOk = isObjectRecord(result.output) && typeof result.output.ok === 'boolean'
|
|
1304
|
+
? result.output.ok
|
|
1305
|
+
: undefined;
|
|
1306
|
+
const artifacts = extractArtifactHandlesFromToolOutput(result.output);
|
|
1307
|
+
const recentReadArtifact = await persistRecentReadArtifact({
|
|
1308
|
+
toolName: typeof toolName === 'string' ? toolName : 'unknown',
|
|
1309
|
+
rawArgs,
|
|
1310
|
+
output: result.output,
|
|
1311
|
+
});
|
|
1312
|
+
const toolResultPreviewArtifact = await persistToolResultPreviewArtifact({
|
|
1313
|
+
toolName: typeof toolName === 'string' ? toolName : 'unknown',
|
|
1314
|
+
output: result.output,
|
|
1315
|
+
summary: result.summary,
|
|
1316
|
+
outputSummary: result.outputSummary,
|
|
1317
|
+
});
|
|
1318
|
+
session.toolCallingAudit?.event({
|
|
1319
|
+
timestamp: new Date().toISOString(),
|
|
1320
|
+
phase,
|
|
1321
|
+
round,
|
|
1322
|
+
callId,
|
|
1323
|
+
toolName: typeof toolName === 'string' ? toolName : 'unknown',
|
|
1324
|
+
rawArgsType: rawArgsTypeByCallId.get(callId) ?? typeof rawArgs,
|
|
1325
|
+
parsedArgsOk: true,
|
|
1326
|
+
toolResultOutputOk,
|
|
1327
|
+
toolResultStatus: result.status,
|
|
1328
|
+
toolResultPatchArtifact: artifacts.patchArtifact,
|
|
1329
|
+
toolResultAuditArtifact: artifacts.auditArtifact,
|
|
1330
|
+
toolResultReadArtifact: recentReadArtifact?.artifact,
|
|
1331
|
+
toolResultReadArtifactPath: recentReadArtifact?.path,
|
|
1332
|
+
toolResultPreviewArtifact: toolResultPreviewArtifact?.artifact,
|
|
1333
|
+
toolResultPreviewLabel: toolResultPreviewArtifact?.label,
|
|
942
1334
|
});
|
|
943
1335
|
}
|
|
944
1336
|
messages.push({
|
|
@@ -965,9 +1357,13 @@ export async function chatWithToolsStreaming(initialMessages, chatOptions, sessi
|
|
|
965
1357
|
const maxRounds = session.maxRounds ?? 6;
|
|
966
1358
|
const phase = session.phase;
|
|
967
1359
|
resetToolCallBudgetState(session);
|
|
968
|
-
const { allowedSpecs, openAITools } = resolveToolCallingForSession(session, phase);
|
|
969
|
-
emitToolCallingEnabledLogIfNeeded(session, openAITools);
|
|
970
1360
|
const messages = [...initialMessages];
|
|
1361
|
+
const { allowedSpecs, openAITools } = resolveToolCallingForSession({
|
|
1362
|
+
session,
|
|
1363
|
+
phase,
|
|
1364
|
+
messages,
|
|
1365
|
+
});
|
|
1366
|
+
emitToolCallingEnabledLogIfNeeded(session, openAITools, allowedSpecs);
|
|
971
1367
|
const canonicalEmitters = createCanonicalEmitterRegistry(session);
|
|
972
1368
|
try {
|
|
973
1369
|
for (let round = 0; round < maxRounds; round++) {
|
|
@@ -980,6 +1376,7 @@ export async function chatWithToolsStreaming(initialMessages, chatOptions, sessi
|
|
|
980
1376
|
let finishReason;
|
|
981
1377
|
try {
|
|
982
1378
|
let streamContent = '';
|
|
1379
|
+
let streamReasoningContent;
|
|
983
1380
|
let finishUsage;
|
|
984
1381
|
try {
|
|
985
1382
|
const consumed = await consumeAssistantStreamTurn({
|
|
@@ -996,6 +1393,7 @@ export async function chatWithToolsStreaming(initialMessages, chatOptions, sessi
|
|
|
996
1393
|
toolCalls,
|
|
997
1394
|
});
|
|
998
1395
|
streamContent = consumed.content;
|
|
1396
|
+
streamReasoningContent = consumed.reasoningContent;
|
|
999
1397
|
finishReason = consumed.finishReason;
|
|
1000
1398
|
finishUsage = consumed.finishUsage;
|
|
1001
1399
|
}
|
|
@@ -1036,12 +1434,14 @@ export async function chatWithToolsStreaming(initialMessages, chatOptions, sessi
|
|
|
1036
1434
|
phase,
|
|
1037
1435
|
round,
|
|
1038
1436
|
content: streamContent,
|
|
1437
|
+
reasoningContent: streamReasoningContent,
|
|
1039
1438
|
collectedToolCalls: drainedToolCalls,
|
|
1040
1439
|
});
|
|
1041
1440
|
usedFallback = fallback.usedFallback;
|
|
1042
1441
|
const assistant = {
|
|
1043
1442
|
role: 'assistant',
|
|
1044
1443
|
content: fallback.content,
|
|
1444
|
+
reasoning_content: fallback.reasoningContent,
|
|
1045
1445
|
tool_calls: fallback.toolCalls.length > 0 ? fallback.toolCalls : undefined,
|
|
1046
1446
|
};
|
|
1047
1447
|
if (session.emit && session.llmOutput) {
|
|
@@ -1056,16 +1456,6 @@ export async function chatWithToolsStreaming(initialMessages, chatOptions, sessi
|
|
|
1056
1456
|
emittedModelToolCallIds,
|
|
1057
1457
|
});
|
|
1058
1458
|
}
|
|
1059
|
-
if (session.llmOutput) {
|
|
1060
|
-
emitLlmStreamEnd({
|
|
1061
|
-
emit: session.emit,
|
|
1062
|
-
policy: session.llmOutput.policy,
|
|
1063
|
-
kind: session.llmOutput.kind,
|
|
1064
|
-
step: session.llmOutput.step,
|
|
1065
|
-
streamId,
|
|
1066
|
-
finishReason,
|
|
1067
|
-
});
|
|
1068
|
-
}
|
|
1069
1459
|
recordAuditEvent('llm.round', {
|
|
1070
1460
|
status: 'ok',
|
|
1071
1461
|
streamed: true,
|
|
@@ -1081,9 +1471,29 @@ export async function chatWithToolsStreaming(initialMessages, chatOptions, sessi
|
|
|
1081
1471
|
messages.push(assistant);
|
|
1082
1472
|
const calls = assistant.tool_calls || [];
|
|
1083
1473
|
if (!Array.isArray(calls) || calls.length === 0) {
|
|
1474
|
+
if (session.llmOutput) {
|
|
1475
|
+
emitLlmStreamEnd({
|
|
1476
|
+
emit: session.emit,
|
|
1477
|
+
policy: session.llmOutput.policy,
|
|
1478
|
+
kind: session.llmOutput.kind,
|
|
1479
|
+
step: session.llmOutput.step,
|
|
1480
|
+
streamId,
|
|
1481
|
+
finishReason,
|
|
1482
|
+
});
|
|
1483
|
+
}
|
|
1084
1484
|
return assistant;
|
|
1085
1485
|
}
|
|
1086
1486
|
await executeToolCalls(session, phase, round, calls, messages, chatOptions.signal);
|
|
1487
|
+
if (session.llmOutput) {
|
|
1488
|
+
emitLlmStreamEnd({
|
|
1489
|
+
emit: session.emit,
|
|
1490
|
+
policy: session.llmOutput.policy,
|
|
1491
|
+
kind: session.llmOutput.kind,
|
|
1492
|
+
step: session.llmOutput.step,
|
|
1493
|
+
streamId,
|
|
1494
|
+
finishReason,
|
|
1495
|
+
});
|
|
1496
|
+
}
|
|
1087
1497
|
}
|
|
1088
1498
|
finally {
|
|
1089
1499
|
canonicalEmitters.release(streamId);
|