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
|
@@ -12,16 +12,9 @@ export function createPhaseRoutingLlm(params) {
|
|
|
12
12
|
chat(messages, options) {
|
|
13
13
|
return resolve(options?.phase).chat(messages, options);
|
|
14
14
|
},
|
|
15
|
-
getCapabilities() {
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
const hasStreaming = Object.values(phaseLlms).some((llm) => llm?.getCapabilities?.().streaming);
|
|
19
|
-
const hasJsonMode = Object.values(phaseLlms).some((llm) => llm?.getCapabilities?.().responseFormatJsonObject);
|
|
20
|
-
return {
|
|
21
|
-
toolCalling: base.toolCalling || hasToolCalling,
|
|
22
|
-
streaming: base.streaming || hasStreaming,
|
|
23
|
-
responseFormatJsonObject: base.responseFormatJsonObject || hasJsonMode,
|
|
24
|
-
};
|
|
15
|
+
getCapabilities(options) {
|
|
16
|
+
const selected = resolve(options?.phase);
|
|
17
|
+
return selected.getCapabilities?.(options) ?? {};
|
|
25
18
|
},
|
|
26
19
|
createPlan(context, instruction, lastError, signal) {
|
|
27
20
|
return resolve(Phase.PLAN).createPlan(context, instruction, lastError, signal);
|
|
@@ -33,11 +26,37 @@ export function createPhaseRoutingLlm(params) {
|
|
|
33
26
|
if (hasAnyStreaming) {
|
|
34
27
|
routed.chatStream = async function* (messages, options) {
|
|
35
28
|
const selected = resolve(options?.phase);
|
|
29
|
+
const capabilities = selected.getCapabilities?.(options) ?? {};
|
|
30
|
+
if (capabilities.streaming === false) {
|
|
31
|
+
const fallback = await selected.chat(messages, options);
|
|
32
|
+
if (fallback.reasoning_content) {
|
|
33
|
+
yield {
|
|
34
|
+
role: 'assistant',
|
|
35
|
+
source: 'synthesized',
|
|
36
|
+
reasoningDelta: fallback.reasoning_content,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
if (fallback.content) {
|
|
40
|
+
yield { role: 'assistant', source: 'synthesized', contentDelta: fallback.content };
|
|
41
|
+
}
|
|
42
|
+
if (Array.isArray(fallback.tool_calls) && fallback.tool_calls.length > 0) {
|
|
43
|
+
yield { role: 'assistant', source: 'synthesized', tool_calls: fallback.tool_calls };
|
|
44
|
+
}
|
|
45
|
+
yield { role: 'assistant', source: 'synthesized', done: true, finishReason: 'stop' };
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
36
48
|
if (selected.chatStream) {
|
|
37
49
|
yield* selected.chatStream(messages, options);
|
|
38
50
|
return;
|
|
39
51
|
}
|
|
40
52
|
const fallback = await selected.chat(messages, options);
|
|
53
|
+
if (fallback.reasoning_content) {
|
|
54
|
+
yield {
|
|
55
|
+
role: 'assistant',
|
|
56
|
+
source: 'synthesized',
|
|
57
|
+
reasoningDelta: fallback.reasoning_content,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
41
60
|
if (fallback.content) {
|
|
42
61
|
yield { role: 'assistant', source: 'synthesized', contentDelta: fallback.content };
|
|
43
62
|
}
|
package/dist/core/llm/redact.js
CHANGED
|
@@ -1,4 +1,22 @@
|
|
|
1
1
|
const SECRET_KEY_REGEX = /(api[-_]?key|authorization|token|secret|password|cookie)/i;
|
|
2
|
+
const STRING_SECRET_PATTERNS = [
|
|
3
|
+
{
|
|
4
|
+
pattern: /(authorization\s*:\s*bearer\s+)[^\s'",`]+/gi,
|
|
5
|
+
replacement: '$1[REDACTED]',
|
|
6
|
+
},
|
|
7
|
+
{
|
|
8
|
+
pattern: /\bbearer\s+[a-z0-9._~+/=-]{16,}\b/gi,
|
|
9
|
+
replacement: 'Bearer [REDACTED]',
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
pattern: /\bsk-[a-z0-9_-]{16,}\b/gi,
|
|
13
|
+
replacement: '[REDACTED]',
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
pattern: /\b(api[-_]?key|token|secret|password|cookie)\s*[:=]\s*("[^"]*"|'[^']*'|[^\s'",`]+)/gi,
|
|
17
|
+
replacement: '$1=[REDACTED]',
|
|
18
|
+
},
|
|
19
|
+
];
|
|
2
20
|
function isRecord(value) {
|
|
3
21
|
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
4
22
|
}
|
|
@@ -7,9 +25,16 @@ function truncate(value, max = 500) {
|
|
|
7
25
|
return value;
|
|
8
26
|
return value.slice(0, max) + '...';
|
|
9
27
|
}
|
|
28
|
+
function redactString(value) {
|
|
29
|
+
let redacted = value;
|
|
30
|
+
for (const { pattern, replacement } of STRING_SECRET_PATTERNS) {
|
|
31
|
+
redacted = redacted.replace(pattern, replacement);
|
|
32
|
+
}
|
|
33
|
+
return truncate(redacted, 500);
|
|
34
|
+
}
|
|
10
35
|
export function redactValue(value) {
|
|
11
36
|
if (typeof value === 'string')
|
|
12
|
-
return
|
|
37
|
+
return redactString(value);
|
|
13
38
|
if (typeof value === 'number' || typeof value === 'boolean' || value === null)
|
|
14
39
|
return value;
|
|
15
40
|
if (Array.isArray(value))
|
|
@@ -29,9 +54,9 @@ export function redactValue(value) {
|
|
|
29
54
|
return '[Unserializable]';
|
|
30
55
|
}
|
|
31
56
|
export function redactJsonString(raw) {
|
|
32
|
-
return
|
|
57
|
+
return redactString(raw);
|
|
33
58
|
}
|
|
34
59
|
export function redactErrorMessage(raw) {
|
|
35
|
-
return
|
|
60
|
+
return redactString(raw);
|
|
36
61
|
}
|
|
37
62
|
//# sourceMappingURL=redact.js.map
|
|
@@ -39,6 +39,7 @@ export function createDefaultLlmRegistry() {
|
|
|
39
39
|
headers: resolved.api.headers,
|
|
40
40
|
timeoutMs: resolved.api.timeoutMs,
|
|
41
41
|
langfuseEnabled: options?.langfuseEnabled,
|
|
42
|
+
capabilities: resolved.capabilities,
|
|
42
43
|
}),
|
|
43
44
|
backend: 'ai-sdk',
|
|
44
45
|
warnings,
|
|
@@ -73,6 +74,7 @@ export function createDefaultOpenAiFallback(resolved, options) {
|
|
|
73
74
|
headers: resolved.api.headers,
|
|
74
75
|
timeoutMs: resolved.api.timeoutMs,
|
|
75
76
|
langfuseEnabled: options?.langfuseEnabled,
|
|
77
|
+
capabilities: resolved.capabilities,
|
|
76
78
|
}),
|
|
77
79
|
backend: 'ai-sdk',
|
|
78
80
|
warnings,
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
const MEMORY_HEADER = '[Relevant memory]';
|
|
2
|
+
function defaultCountTokens(text) {
|
|
3
|
+
return Math.ceil(text.length / 4);
|
|
4
|
+
}
|
|
5
|
+
function normalizeEntry(entry) {
|
|
6
|
+
return {
|
|
7
|
+
path: String(entry.path),
|
|
8
|
+
title: String(entry.title),
|
|
9
|
+
summary: String(entry.summary),
|
|
10
|
+
tags: entry.tags,
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
function formatRelevantMemoryEntry(entry) {
|
|
14
|
+
return `- ${entry.path} | ${entry.title}\n ${entry.summary}`;
|
|
15
|
+
}
|
|
16
|
+
export function augmentPromptWithRelevantMemory(args) {
|
|
17
|
+
const basePrompt = String(args.basePrompt ?? '').trimEnd();
|
|
18
|
+
const selectedEntries = Array.isArray(args.selectedEntries)
|
|
19
|
+
? args.selectedEntries.map(normalizeEntry)
|
|
20
|
+
: [];
|
|
21
|
+
if (selectedEntries.length === 0) {
|
|
22
|
+
return { prompt: basePrompt, injectedEntries: [] };
|
|
23
|
+
}
|
|
24
|
+
const countTokens = args.countTokens ?? defaultCountTokens;
|
|
25
|
+
const budgetTokens = typeof args.budgetTokens === 'number'
|
|
26
|
+
? Math.max(0, Math.floor(args.budgetTokens))
|
|
27
|
+
: Number.MAX_SAFE_INTEGER;
|
|
28
|
+
const headerTokens = Math.max(0, Math.floor(countTokens(MEMORY_HEADER)));
|
|
29
|
+
if (headerTokens > budgetTokens) {
|
|
30
|
+
return { prompt: basePrompt, injectedEntries: [] };
|
|
31
|
+
}
|
|
32
|
+
let remainingBudget = budgetTokens - headerTokens;
|
|
33
|
+
const injectedEntries = [];
|
|
34
|
+
const renderedEntries = [];
|
|
35
|
+
for (const entry of selectedEntries) {
|
|
36
|
+
const rendered = formatRelevantMemoryEntry(entry);
|
|
37
|
+
const entryTokens = Math.max(0, Math.floor(countTokens(rendered)));
|
|
38
|
+
if (entryTokens > remainingBudget) {
|
|
39
|
+
break;
|
|
40
|
+
}
|
|
41
|
+
injectedEntries.push(entry);
|
|
42
|
+
renderedEntries.push(rendered);
|
|
43
|
+
remainingBudget -= entryTokens;
|
|
44
|
+
}
|
|
45
|
+
if (renderedEntries.length === 0) {
|
|
46
|
+
return { prompt: basePrompt, injectedEntries: [] };
|
|
47
|
+
}
|
|
48
|
+
const memoryBlock = [MEMORY_HEADER, ...renderedEntries].join('\n');
|
|
49
|
+
return {
|
|
50
|
+
prompt: basePrompt ? `${basePrompt}\n\n${memoryBlock}` : memoryBlock,
|
|
51
|
+
injectedEntries,
|
|
52
|
+
memoryBlock,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=request-augmentation.js.map
|
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
import { createHash } from 'crypto';
|
|
2
|
+
import { getPromptCachingManager } from '../context/cache/prompt-caching.js';
|
|
3
|
+
function isArtifactHandle(value) {
|
|
4
|
+
if (!value || typeof value !== 'object')
|
|
5
|
+
return false;
|
|
6
|
+
const candidate = value;
|
|
7
|
+
return (typeof candidate.handle === 'string' &&
|
|
8
|
+
typeof candidate.mimeType === 'string' &&
|
|
9
|
+
typeof candidate.sha256 === 'string' &&
|
|
10
|
+
typeof candidate.size === 'number');
|
|
11
|
+
}
|
|
12
|
+
function mergeArtifactHandles(existing, incoming, limit = 4) {
|
|
13
|
+
const merged = [];
|
|
14
|
+
const seen = new Set();
|
|
15
|
+
for (const artifact of [...(existing ?? []), ...(incoming ?? [])]) {
|
|
16
|
+
if (!artifact || seen.has(artifact.handle))
|
|
17
|
+
continue;
|
|
18
|
+
seen.add(artifact.handle);
|
|
19
|
+
merged.push(artifact);
|
|
20
|
+
}
|
|
21
|
+
if (merged.length === 0)
|
|
22
|
+
return undefined;
|
|
23
|
+
return merged.slice(-limit);
|
|
24
|
+
}
|
|
25
|
+
function mergeReadArtifactRefs(existing, incoming, limit = 6) {
|
|
26
|
+
const merged = [];
|
|
27
|
+
const seen = new Set();
|
|
28
|
+
for (const item of [...(existing ?? []), ...(incoming ?? [])]) {
|
|
29
|
+
if (!item?.path || !item.artifact?.handle)
|
|
30
|
+
continue;
|
|
31
|
+
const key = `${item.path}::${item.artifact.handle}`;
|
|
32
|
+
if (seen.has(key))
|
|
33
|
+
continue;
|
|
34
|
+
seen.add(key);
|
|
35
|
+
merged.push(item);
|
|
36
|
+
}
|
|
37
|
+
if (merged.length === 0)
|
|
38
|
+
return undefined;
|
|
39
|
+
return merged.slice(-limit);
|
|
40
|
+
}
|
|
41
|
+
function mergePreviewArtifactRefs(existing, incoming, limit = 6) {
|
|
42
|
+
const merged = [];
|
|
43
|
+
const seen = new Set();
|
|
44
|
+
for (const item of [...(existing ?? []), ...(incoming ?? [])]) {
|
|
45
|
+
if (!item?.label || !item.artifact?.handle)
|
|
46
|
+
continue;
|
|
47
|
+
const key = `${item.label}::${item.artifact.handle}`;
|
|
48
|
+
if (seen.has(key))
|
|
49
|
+
continue;
|
|
50
|
+
seen.add(key);
|
|
51
|
+
merged.push(item);
|
|
52
|
+
}
|
|
53
|
+
if (merged.length === 0)
|
|
54
|
+
return undefined;
|
|
55
|
+
return merged.slice(-limit);
|
|
56
|
+
}
|
|
57
|
+
export function resolveRequestArtifactHints(params) {
|
|
58
|
+
const direct = params.artifactHints;
|
|
59
|
+
const auditEntries = params.toolCallingAudit ?? [];
|
|
60
|
+
const auditPatchArtifacts = [];
|
|
61
|
+
const auditAuditArtifacts = [];
|
|
62
|
+
const auditReadArtifacts = [];
|
|
63
|
+
const auditPreviewArtifacts = [];
|
|
64
|
+
for (const entry of auditEntries) {
|
|
65
|
+
if (entry?.toolResultStatus === 'ok' && entry.toolName === 'agent_dispatch') {
|
|
66
|
+
if (isArtifactHandle(entry.toolResultPatchArtifact)) {
|
|
67
|
+
auditPatchArtifacts.push(entry.toolResultPatchArtifact);
|
|
68
|
+
}
|
|
69
|
+
if (isArtifactHandle(entry.toolResultAuditArtifact)) {
|
|
70
|
+
auditAuditArtifacts.push(entry.toolResultAuditArtifact);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
if (typeof entry.toolResultReadArtifactPath === 'string' &&
|
|
74
|
+
isArtifactHandle(entry.toolResultReadArtifact)) {
|
|
75
|
+
auditReadArtifacts.push({
|
|
76
|
+
path: entry.toolResultReadArtifactPath,
|
|
77
|
+
artifact: entry.toolResultReadArtifact,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
if (typeof entry.toolResultPreviewLabel === 'string' &&
|
|
81
|
+
isArtifactHandle(entry.toolResultPreviewArtifact)) {
|
|
82
|
+
auditPreviewArtifacts.push({
|
|
83
|
+
label: entry.toolResultPreviewLabel,
|
|
84
|
+
artifact: entry.toolResultPreviewArtifact,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
const resolved = {
|
|
89
|
+
verifyArtifact: direct?.verifyArtifact,
|
|
90
|
+
subAgentPatchArtifacts: mergeArtifactHandles(direct?.subAgentPatchArtifacts, auditPatchArtifacts),
|
|
91
|
+
subAgentAuditArtifacts: mergeArtifactHandles(direct?.subAgentAuditArtifacts, auditAuditArtifacts),
|
|
92
|
+
recentReadArtifacts: mergeReadArtifactRefs(direct?.recentReadArtifacts, auditReadArtifacts),
|
|
93
|
+
toolResultPreviewArtifacts: mergePreviewArtifactRefs(params.previewProvider?.getPreviewHints(), auditPreviewArtifacts),
|
|
94
|
+
};
|
|
95
|
+
if (!resolved.verifyArtifact &&
|
|
96
|
+
!resolved.subAgentPatchArtifacts?.length &&
|
|
97
|
+
!resolved.subAgentAuditArtifacts?.length &&
|
|
98
|
+
!resolved.recentReadArtifacts?.length &&
|
|
99
|
+
!resolved.toolResultPreviewArtifacts?.length) {
|
|
100
|
+
return undefined;
|
|
101
|
+
}
|
|
102
|
+
return resolved;
|
|
103
|
+
}
|
|
104
|
+
function toSafeMessage(message) {
|
|
105
|
+
if (!message || typeof message !== 'object')
|
|
106
|
+
return null;
|
|
107
|
+
if (message.role !== 'system' && message.role !== 'user' && message.role !== 'assistant') {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
if (typeof message.content !== 'string')
|
|
111
|
+
return null;
|
|
112
|
+
const content = message.content.trimEnd();
|
|
113
|
+
if (!content)
|
|
114
|
+
return null;
|
|
115
|
+
return {
|
|
116
|
+
role: message.role,
|
|
117
|
+
content,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
function estimateTokens(text) {
|
|
121
|
+
return Math.ceil(text.length / 4);
|
|
122
|
+
}
|
|
123
|
+
function resolvePromptCacheMode(mode) {
|
|
124
|
+
return mode === 'strict_full_prompt' ? 'strict_full_prompt' : 'cache_safe_only';
|
|
125
|
+
}
|
|
126
|
+
function serializeAttachmentForFingerprint(item) {
|
|
127
|
+
return [
|
|
128
|
+
item.key,
|
|
129
|
+
item.kind,
|
|
130
|
+
item.label ?? '',
|
|
131
|
+
item.content ?? '',
|
|
132
|
+
item.artifactHandle ?? '',
|
|
133
|
+
item.mimeType ?? '',
|
|
134
|
+
typeof item.size === 'number' ? String(item.size) : '',
|
|
135
|
+
].join('\u001f');
|
|
136
|
+
}
|
|
137
|
+
function createFingerprint(parts) {
|
|
138
|
+
if (parts.length === 0)
|
|
139
|
+
return undefined;
|
|
140
|
+
const hash = createHash('sha256');
|
|
141
|
+
for (const [index, part] of parts.entries()) {
|
|
142
|
+
hash.update(`part:${index}:${part.length}\n`);
|
|
143
|
+
hash.update(part);
|
|
144
|
+
hash.update('\n');
|
|
145
|
+
}
|
|
146
|
+
return hash.digest('hex');
|
|
147
|
+
}
|
|
148
|
+
function toArtifactAttachment(args) {
|
|
149
|
+
return {
|
|
150
|
+
key: args.key,
|
|
151
|
+
kind: 'artifact',
|
|
152
|
+
label: args.label,
|
|
153
|
+
content: '',
|
|
154
|
+
artifactHandle: args.artifact.handle,
|
|
155
|
+
mimeType: args.artifact.mimeType,
|
|
156
|
+
size: args.artifact.size,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
export function buildArtifactHintAttachments(hints) {
|
|
160
|
+
if (!hints)
|
|
161
|
+
return [];
|
|
162
|
+
const attachments = [];
|
|
163
|
+
if (hints.verifyArtifact) {
|
|
164
|
+
attachments.push(toArtifactAttachment({
|
|
165
|
+
key: 'previous-verify-output',
|
|
166
|
+
label: 'Previous verify output',
|
|
167
|
+
artifact: hints.verifyArtifact,
|
|
168
|
+
}));
|
|
169
|
+
}
|
|
170
|
+
for (const [index, artifact] of (hints.subAgentPatchArtifacts ?? []).entries()) {
|
|
171
|
+
attachments.push(toArtifactAttachment({
|
|
172
|
+
key: `previous-subagent-patch-${index}`,
|
|
173
|
+
label: `Previous sub-agent patch artifact ${index + 1}`,
|
|
174
|
+
artifact,
|
|
175
|
+
}));
|
|
176
|
+
}
|
|
177
|
+
for (const [index, artifact] of (hints.subAgentAuditArtifacts ?? []).entries()) {
|
|
178
|
+
attachments.push(toArtifactAttachment({
|
|
179
|
+
key: `previous-subagent-audit-${index}`,
|
|
180
|
+
label: `Previous sub-agent audit artifact ${index + 1}`,
|
|
181
|
+
artifact,
|
|
182
|
+
}));
|
|
183
|
+
}
|
|
184
|
+
for (const [index, item] of (hints.recentReadArtifacts ?? []).entries()) {
|
|
185
|
+
attachments.push(toArtifactAttachment({
|
|
186
|
+
key: `recent-read-${index}`,
|
|
187
|
+
label: `Recent file read: ${item.path}`,
|
|
188
|
+
artifact: item.artifact,
|
|
189
|
+
}));
|
|
190
|
+
}
|
|
191
|
+
for (const [index, item] of (hints.toolResultPreviewArtifacts ?? []).entries()) {
|
|
192
|
+
attachments.push(toArtifactAttachment({
|
|
193
|
+
key: `tool-result-preview-${index}`,
|
|
194
|
+
label: item.label,
|
|
195
|
+
artifact: item.artifact,
|
|
196
|
+
}));
|
|
197
|
+
}
|
|
198
|
+
return attachments;
|
|
199
|
+
}
|
|
200
|
+
function buildPromptCachingHints(surface) {
|
|
201
|
+
const policy = {
|
|
202
|
+
mode: surface.mode,
|
|
203
|
+
eligibility: surface.cacheEligibility,
|
|
204
|
+
namespace: surface.namespace,
|
|
205
|
+
contextHash: surface.contextHash,
|
|
206
|
+
cacheSafeFingerprint: surface.cacheSafeFingerprint,
|
|
207
|
+
lateInjectionFingerprint: surface.lateInjectionFingerprint,
|
|
208
|
+
};
|
|
209
|
+
if (surface.cacheEligibility !== 'eligible' ||
|
|
210
|
+
!surface.contextHash ||
|
|
211
|
+
!surface.cacheSafeFingerprint) {
|
|
212
|
+
return {
|
|
213
|
+
openAICachePolicy: policy,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
const components = [surface.contextHash, `stable:${surface.cacheSafeFingerprint}`];
|
|
217
|
+
if (surface.mode === 'strict_full_prompt' && surface.lateInjectionFingerprint) {
|
|
218
|
+
components.push(`late:${surface.lateInjectionFingerprint}`);
|
|
219
|
+
}
|
|
220
|
+
const manager = getPromptCachingManager();
|
|
221
|
+
return {
|
|
222
|
+
openAICacheHint: manager.generateOpenAICacheHint(surface.namespace ?? 'request-envelope', components),
|
|
223
|
+
openAICachePolicy: policy,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
export function buildRequestEnvelope(params) {
|
|
227
|
+
const systemSections = (Array.isArray(params.system) ? params.system : [params.system]).map((item) => String(item ?? '').trimEnd());
|
|
228
|
+
const attachments = Array.isArray(params.attachments)
|
|
229
|
+
? params.attachments
|
|
230
|
+
.filter((item) => item && (typeof item.content === 'string' || typeof item.artifactHandle === 'string'))
|
|
231
|
+
.map((item) => ({
|
|
232
|
+
...item,
|
|
233
|
+
content: typeof item.content === 'string' ? item.content.trimEnd() : '',
|
|
234
|
+
}))
|
|
235
|
+
.filter((item) => item.content.length > 0 || typeof item.artifactHandle === 'string')
|
|
236
|
+
: [];
|
|
237
|
+
const userMetaMessages = [];
|
|
238
|
+
const conversationMessages = [];
|
|
239
|
+
if (Array.isArray(params.conversationContext)) {
|
|
240
|
+
for (const message of params.conversationContext) {
|
|
241
|
+
const safe = toSafeMessage(message);
|
|
242
|
+
if (!safe)
|
|
243
|
+
continue;
|
|
244
|
+
if (safe.role === 'system') {
|
|
245
|
+
userMetaMessages.push(safe);
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
conversationMessages.push(safe);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
const cacheMode = resolvePromptCacheMode(params.cacheSafeSurface?.mode);
|
|
252
|
+
const cacheSafeAttachments = attachments.filter((item) => item.cacheSafe);
|
|
253
|
+
const lateInjectionAttachments = attachments.filter((item) => !item.cacheSafe);
|
|
254
|
+
const cacheSafeFingerprint = createFingerprint([
|
|
255
|
+
...systemSections.map((section, index) => `system:${index}\u001f${section}`),
|
|
256
|
+
...cacheSafeAttachments.map((item, index) => `attachment:${index}\u001f${serializeAttachmentForFingerprint(item)}`),
|
|
257
|
+
]);
|
|
258
|
+
const lateInjectionFingerprint = createFingerprint([
|
|
259
|
+
`userPrompt\u001f${String(params.user ?? '').trimEnd()}`,
|
|
260
|
+
...userMetaMessages.map((message, index) => `meta:${index}\u001f${message.content}`),
|
|
261
|
+
...conversationMessages.map((message, index) => `conversation:${index}\u001f${message.role}\u001f${message.content}`),
|
|
262
|
+
...lateInjectionAttachments.map((item, index) => `late-attachment:${index}\u001f${serializeAttachmentForFingerprint(item)}`),
|
|
263
|
+
]);
|
|
264
|
+
const cacheSafeText = [...systemSections, ...cacheSafeAttachments.map((item) => item.content)]
|
|
265
|
+
.filter(Boolean)
|
|
266
|
+
.join('\n\n');
|
|
267
|
+
const manager = getPromptCachingManager();
|
|
268
|
+
const cacheEligibility = !params.cacheSafeSurface?.contextHash
|
|
269
|
+
? 'missing_context_hash'
|
|
270
|
+
: !cacheSafeText.trim()
|
|
271
|
+
? 'empty_cache_safe_surface'
|
|
272
|
+
: !manager.shouldCache(estimateTokens(cacheSafeText))
|
|
273
|
+
? 'below_min_tokens'
|
|
274
|
+
: 'eligible';
|
|
275
|
+
const cacheSafeSurface = {
|
|
276
|
+
systemSections,
|
|
277
|
+
attachments: cacheSafeAttachments,
|
|
278
|
+
contextHash: params.cacheSafeSurface?.contextHash,
|
|
279
|
+
namespace: params.cacheSafeSurface?.namespace,
|
|
280
|
+
mode: cacheMode,
|
|
281
|
+
cacheEligibility,
|
|
282
|
+
cacheSafeFingerprint,
|
|
283
|
+
lateInjectionFingerprint,
|
|
284
|
+
};
|
|
285
|
+
return {
|
|
286
|
+
systemSections,
|
|
287
|
+
userPrompt: String(params.user ?? ''),
|
|
288
|
+
userMetaMessages,
|
|
289
|
+
conversationMessages,
|
|
290
|
+
attachments,
|
|
291
|
+
providerHints: {
|
|
292
|
+
...buildPromptCachingHints(cacheSafeSurface),
|
|
293
|
+
...(params.providerHints ?? {}),
|
|
294
|
+
},
|
|
295
|
+
cacheSafeSurface,
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
export function materializeRequestEnvelope(envelope) {
|
|
299
|
+
const artifactSection = (() => {
|
|
300
|
+
const artifactAttachments = envelope.attachments.filter((item) => item.kind === 'artifact' && typeof item.artifactHandle === 'string' && item.artifactHandle);
|
|
301
|
+
if (artifactAttachments.length === 0)
|
|
302
|
+
return '';
|
|
303
|
+
const lines = [
|
|
304
|
+
'# Available Artifacts',
|
|
305
|
+
'Use `artifact.read` with these handles when you need the full artifact contents.',
|
|
306
|
+
];
|
|
307
|
+
for (const item of artifactAttachments) {
|
|
308
|
+
const suffix = [
|
|
309
|
+
item.mimeType ? `mime=${item.mimeType}` : undefined,
|
|
310
|
+
typeof item.size === 'number' ? `size=${item.size}` : undefined,
|
|
311
|
+
]
|
|
312
|
+
.filter(Boolean)
|
|
313
|
+
.join(', ');
|
|
314
|
+
lines.push(`- ${item.label ?? item.key}: ${item.artifactHandle}${suffix ? ` (${suffix})` : ''}`);
|
|
315
|
+
}
|
|
316
|
+
return lines.join('\n');
|
|
317
|
+
})();
|
|
318
|
+
const out = [
|
|
319
|
+
{
|
|
320
|
+
role: 'system',
|
|
321
|
+
content: envelope.systemSections.filter(Boolean).join('\n\n'),
|
|
322
|
+
},
|
|
323
|
+
];
|
|
324
|
+
out.push(...envelope.userMetaMessages);
|
|
325
|
+
out.push(...envelope.conversationMessages);
|
|
326
|
+
out.push({
|
|
327
|
+
role: 'user',
|
|
328
|
+
content: artifactSection.trim().length > 0
|
|
329
|
+
? `${String(envelope.userPrompt ?? '')}\n\n${artifactSection}`
|
|
330
|
+
: String(envelope.userPrompt ?? ''),
|
|
331
|
+
});
|
|
332
|
+
return out;
|
|
333
|
+
}
|
|
334
|
+
//# sourceMappingURL=request-envelope.js.map
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { buildArtifactHintAttachments, buildRequestEnvelope, materializeRequestEnvelope, resolveRequestArtifactHints, } from './request-envelope.js';
|
|
2
|
+
export function buildSharedRequestEnvelope(args) {
|
|
3
|
+
const cacheSurface = {
|
|
4
|
+
namespace: args.defaultNamespace,
|
|
5
|
+
contextHash: args.contextHash,
|
|
6
|
+
};
|
|
7
|
+
const resolvedArtifactHints = resolveRequestArtifactHints({
|
|
8
|
+
artifactHints: args.artifactHints,
|
|
9
|
+
toolCallingAudit: args.toolCallingAudit,
|
|
10
|
+
previewProvider: args.previewProvider,
|
|
11
|
+
});
|
|
12
|
+
const envelope = buildRequestEnvelope({
|
|
13
|
+
system: args.systemPrompt,
|
|
14
|
+
user: args.userPrompt,
|
|
15
|
+
conversationContext: args.conversationContext,
|
|
16
|
+
attachments: [
|
|
17
|
+
...(args.attachments ?? []),
|
|
18
|
+
...buildArtifactHintAttachments(resolvedArtifactHints),
|
|
19
|
+
],
|
|
20
|
+
providerHints: args.providerHints,
|
|
21
|
+
cacheSafeSurface: {
|
|
22
|
+
contextHash: cacheSurface.contextHash,
|
|
23
|
+
namespace: cacheSurface.namespace,
|
|
24
|
+
mode: 'cache_safe_only',
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
const baseMessages = materializeRequestEnvelope(envelope);
|
|
28
|
+
return {
|
|
29
|
+
cacheSurface,
|
|
30
|
+
resolvedArtifactHints,
|
|
31
|
+
envelope,
|
|
32
|
+
baseMessages,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=shared-request-assembly.js.map
|
|
@@ -33,12 +33,20 @@ export function mapAiSdkStreamPartToChunk(part) {
|
|
|
33
33
|
return null;
|
|
34
34
|
}
|
|
35
35
|
switch (part.type) {
|
|
36
|
-
case 'text-delta':
|
|
37
|
-
|
|
38
|
-
if (typeof
|
|
39
|
-
return { role: 'assistant', source: 'provider', contentDelta:
|
|
36
|
+
case 'text-delta': {
|
|
37
|
+
const text = typeof part.text === 'string' ? part.text : part.delta;
|
|
38
|
+
if (typeof text === 'string' && text) {
|
|
39
|
+
return { role: 'assistant', source: 'provider', contentDelta: text };
|
|
40
40
|
}
|
|
41
41
|
return null;
|
|
42
|
+
}
|
|
43
|
+
case 'reasoning-delta': {
|
|
44
|
+
const text = typeof part.text === 'string' ? part.text : part.delta;
|
|
45
|
+
if (typeof text === 'string' && text) {
|
|
46
|
+
return { role: 'assistant', source: 'provider', reasoningDelta: text };
|
|
47
|
+
}
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
42
50
|
case 'tool-call':
|
|
43
51
|
return {
|
|
44
52
|
role: 'assistant',
|
|
@@ -51,6 +59,7 @@ export function mapAiSdkStreamPartToChunk(part) {
|
|
|
51
59
|
name: part.toolName || 'unknown',
|
|
52
60
|
arguments: JSON.stringify(normalizeToolInput(part.input ?? {})),
|
|
53
61
|
},
|
|
62
|
+
...(part.providerMetadata ? { providerMetadata: part.providerMetadata } : {}),
|
|
54
63
|
},
|
|
55
64
|
],
|
|
56
65
|
};
|
package/dist/core/llm/utils.js
CHANGED
|
@@ -10,32 +10,22 @@ export function formatContextForPrompt(context, options = {}) {
|
|
|
10
10
|
}
|
|
11
11
|
return formatContextForXmlPrompt(context);
|
|
12
12
|
}
|
|
13
|
-
export function
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
try {
|
|
18
|
-
return JSON.parse(jsonBlockMatch[1]);
|
|
19
|
-
}
|
|
20
|
-
catch (__e) {
|
|
21
|
-
// Fallback to raw content if block is invalid
|
|
22
|
-
}
|
|
13
|
+
export function parsePlanFromLLMContent(content) {
|
|
14
|
+
const trimmed = String(content ?? '').trim();
|
|
15
|
+
if (!trimmed.startsWith('{') || !trimmed.endsWith('}')) {
|
|
16
|
+
throw new Error(text.llm.planInvalidJson);
|
|
23
17
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
try {
|
|
28
|
-
return JSON.parse(jsonMatch[0]);
|
|
29
|
-
}
|
|
30
|
-
catch (__e) {
|
|
31
|
-
// Fallback
|
|
32
|
-
}
|
|
18
|
+
let parsed;
|
|
19
|
+
try {
|
|
20
|
+
parsed = JSON.parse(trimmed);
|
|
33
21
|
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
|
|
22
|
+
catch {
|
|
23
|
+
throw new Error(text.llm.planInvalidJson);
|
|
24
|
+
}
|
|
25
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
26
|
+
throw new Error(text.llm.planInvalidJson);
|
|
27
|
+
}
|
|
28
|
+
const plan = parsed;
|
|
39
29
|
if (!plan.goal || !Array.isArray(plan.files) || !Array.isArray(plan.changes) || !plan.verify) {
|
|
40
30
|
throw new Error(text.llm.planInvalid);
|
|
41
31
|
}
|
|
@@ -46,10 +36,9 @@ export function extractUnifiedDiffFromLLMContent(content) {
|
|
|
46
36
|
throw wrapPatchEmpty();
|
|
47
37
|
}
|
|
48
38
|
const looksLikeUnifiedDiff = (text) => {
|
|
49
|
-
return /^\s*
|
|
39
|
+
return /^\s*diff --git /m.test(text);
|
|
50
40
|
};
|
|
51
|
-
// 1) Prefer fenced code blocks and always pick the LAST diff
|
|
52
|
-
// Accept both git-style (`diff --git`) and minimal unified diffs (`--- a/...` + `+++ b/...`).
|
|
41
|
+
// 1) Prefer fenced code blocks and always pick the LAST canonical diff block (LLM may generate multiple attempts).
|
|
53
42
|
const fencedBlocks = [];
|
|
54
43
|
const fenceRegex = /```(?:diff)?\s*\n([\s\S]*?)\n```/gi;
|
|
55
44
|
let fenceMatch = null;
|
|
@@ -62,10 +51,10 @@ export function extractUnifiedDiffFromLLMContent(content) {
|
|
|
62
51
|
if (fencedBlocks.length > 0) {
|
|
63
52
|
return fencedBlocks[fencedBlocks.length - 1].trim();
|
|
64
53
|
}
|
|
65
|
-
// 2) Raw diff without markdown: keep the first diff
|
|
54
|
+
// 2) Raw diff without markdown: keep the first canonical diff section.
|
|
66
55
|
// In "pure diff" mode, LLMs typically return only the patch, so selecting the first marker
|
|
67
56
|
// avoids accidentally dropping the leading `diff --git` header.
|
|
68
|
-
const rawStart = content.search(/^\s*
|
|
57
|
+
const rawStart = content.search(/^\s*diff --git /m);
|
|
69
58
|
if (rawStart !== -1)
|
|
70
59
|
return content.slice(rawStart).trim();
|
|
71
60
|
// Final fallback: original simple cleanup
|