salmon-loop 0.3.2 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/authorization/non-interactive.js +9 -13
- package/dist/cli/authorization/provider.js +2 -10
- package/dist/cli/chat.js +12 -6
- package/dist/cli/commands/allowlist.js +1 -1
- package/dist/cli/commands/chat.js +13 -13
- package/dist/cli/commands/config.js +2 -2
- package/dist/cli/commands/mode.js +2 -2
- package/dist/cli/commands/parallel.js +1 -1
- package/dist/cli/commands/run/handler.js +9 -4
- package/dist/cli/commands/run/loop-params.js +2 -0
- package/dist/cli/commands/run/parse-options.js +14 -26
- package/dist/cli/commands/run/runtime-llm.js +15 -12
- package/dist/cli/commands/run/runtime-options.js +3 -1
- package/dist/cli/config.js +0 -8
- package/dist/cli/headless/openai-responses-canonical-applier.js +1 -7
- package/dist/cli/locales/en.js +2 -2
- package/dist/cli/reporters/standard.js +12 -3
- package/dist/cli/reporters/stream-json.js +2 -1
- package/dist/cli/slash/runtime.js +2 -2
- package/dist/cli/ui/hooks/useLoopEvents.js +1 -1
- package/dist/cli/ui/hooks/useLoopState.js +1 -1
- package/dist/core/adapters/fs/file-adapter.js +3 -1
- package/dist/core/adapters/git/git-adapter.js +6 -3
- package/dist/core/adapters/git/git-runner.js +5 -2
- package/dist/core/adapters/git/lock-manager.js +7 -4
- package/dist/core/ast/parser.js +18 -9
- package/dist/core/checkpoint-domain/manifest-store.js +21 -13
- package/dist/core/checkpoint-domain/service.js +3 -1
- package/dist/core/config/limits.js +1 -1
- package/dist/core/config/model-pricing.js +61 -0
- package/dist/core/config/schema.js +738 -0
- package/dist/core/config/validate.js +11 -922
- package/dist/core/context/ast/skeleton-extractor.js +225 -0
- package/dist/core/context/ast/source-outline.js +24 -1
- package/dist/core/context/budget/dynamic-adjuster.js +20 -5
- package/dist/core/context/builder.js +7 -3
- package/dist/core/context/cache/store-factory.js +3 -1
- package/dist/core/context/dependencies.js +2 -1
- package/dist/core/context/effectiveness/persistence.js +50 -0
- package/dist/core/context/effectiveness/tracker.js +24 -0
- package/dist/core/context/gatherers/architecture-gatherer.js +2 -1
- package/dist/core/context/gatherers/artifact-gatherer.js +7 -4
- package/dist/core/context/gatherers/ast-gatherer.js +34 -40
- package/dist/core/context/gatherers/ghost-dependency-gatherer.js +0 -1
- package/dist/core/context/gatherers/git-history-gatherer.js +3 -1
- package/dist/core/context/gatherers/knowledge-gatherer.js +21 -2
- package/dist/core/context/gatherers/metadata-gatherer.js +12 -7
- package/dist/core/context/gatherers/ripgrep-gatherer.js +6 -3
- package/dist/core/context/service.js +12 -2
- package/dist/core/context/steps/context-gather.js +14 -3
- package/dist/core/context/steps/context-targets.js +1 -0
- package/dist/core/context/targeting/target-resolver.js +29 -11
- package/dist/core/context/token/cache.js +5 -2
- package/dist/core/context/token/encoding-registry.js +7 -6
- package/dist/core/context/truncation/strategies/json.js +5 -2
- package/dist/core/context/truncation/type-detector.js +3 -1
- package/dist/core/extensions/index.js +48 -3
- package/dist/core/extensions/load.js +3 -2
- package/dist/core/extensions/merge.js +5 -1
- package/dist/core/extensions/paths.js +8 -2
- package/dist/core/extensions/schemas.js +21 -0
- package/dist/core/facades/cli-authorization-provider.js +1 -0
- package/dist/core/facades/cli-command-chat.js +2 -0
- package/dist/core/facades/cli-run-handler.js +1 -0
- package/dist/core/facades/cli-utils-serialize.js +2 -0
- package/dist/core/feedback/parsers.js +290 -1
- package/dist/core/grizzco/dsl/llm-strategy.js +4 -3
- package/dist/core/grizzco/engine/observability/loop-telemetry.js +5 -2
- package/dist/core/grizzco/engine/outcome/loop-result-mapper.js +30 -13
- package/dist/core/grizzco/engine/pipeline/pipeline.js +149 -240
- package/dist/core/grizzco/engine/transaction/attempt-failure.js +49 -24
- package/dist/core/grizzco/engine/transaction/authorization-summary.js +2 -1
- package/dist/core/grizzco/engine/transaction/transaction-runner.js +40 -34
- package/dist/core/grizzco/execution/RejectionManager.js +7 -5
- package/dist/core/grizzco/runtime/apply-back-runtime.js +5 -2
- package/dist/core/grizzco/services/implementations/default/GitConfigService.js +2 -1
- package/dist/core/grizzco/services/registry.js +18 -0
- package/dist/core/grizzco/steps/audit.js +20 -10
- package/dist/core/grizzco/steps/autopilot.js +21 -32
- package/dist/core/grizzco/steps/display-report.js +4 -11
- package/dist/core/grizzco/steps/explore.js +14 -4
- package/dist/core/grizzco/steps/generateReview.js +3 -1
- package/dist/core/grizzco/steps/patch/prompt-input.js +4 -1
- package/dist/core/grizzco/steps/patch.js +1 -0
- package/dist/core/grizzco/steps/plan.js +58 -49
- package/dist/core/grizzco/steps/research.js +3 -1
- package/dist/core/grizzco/steps/tool-runtime.js +3 -0
- package/dist/core/grizzco/steps/verify.js +7 -1
- package/dist/core/grizzco/validation/AstValidationService.js +3 -1
- package/dist/core/grizzco/workers/strata-sync-worker.js +2 -1
- package/dist/core/history/input-history.js +3 -1
- package/dist/core/intent/chat-intent.js +3 -1
- package/dist/core/llm/ai-sdk/message-mapper.js +37 -26
- package/dist/core/llm/ai-sdk/request-params.js +2 -6
- package/dist/core/llm/ai-sdk/result-mapper.js +14 -8
- package/dist/core/llm/ai-sdk/retry-classifier.js +17 -7
- package/dist/core/llm/ai-sdk/retry-executor.js +1 -1
- package/dist/core/llm/contracts/repair.js +16 -8
- package/dist/core/llm/errors.js +18 -14
- package/dist/core/llm/output-policy.js +8 -0
- package/dist/core/llm/redact.js +1 -3
- package/dist/core/llm/retry-utils.js +8 -2
- package/dist/core/llm/stream-utils.js +5 -3
- package/dist/core/llm/sub-agent-factory.js +51 -0
- package/dist/core/llm/tool-calling-stub.js +48 -0
- package/dist/core/llm/utils.js +17 -6
- package/dist/core/mcp/bridge/prompt-command-provider.js +4 -3
- package/dist/core/mcp/bridge/resource-context-provider.js +3 -1
- package/dist/core/mcp/bridge/tool-bridge.js +5 -14
- package/dist/core/mcp/catalog/discovery.js +3 -1
- package/dist/core/mcp/client/connection-manager.js +7 -4
- package/dist/core/mcp/client/transport-factory.js +7 -3
- package/dist/core/mcp/host/sampling-provider.js +1 -1
- package/dist/core/mcp/schema/json-schema-to-zod.js +2 -1
- package/dist/core/memory/relevant-retrieval.js +6 -4
- package/dist/core/observability/audit-file.js +2 -1
- package/dist/core/observability/audit-trail.js +3 -1
- package/dist/core/observability/authorization-decisions.js +13 -12
- package/dist/core/observability/error-mapping.js +2 -1
- package/dist/core/observability/logger.js +2 -1
- package/dist/core/observability/monitor.js +24 -0
- package/dist/core/observability/run-outcome-reporter.js +1 -0
- package/dist/core/observability/token-usage.js +5 -4
- package/dist/core/permission-gate/default-gate.js +5 -8
- package/dist/core/plan/storage.js +7 -4
- package/dist/core/plugin/loader.js +8 -5
- package/dist/core/prompts/registry.js +12 -30
- package/dist/core/prompts/runtime.js +3 -1
- package/dist/core/prompts/templates/system/autopilot_system.hbs +28 -4
- package/dist/core/protocols/a2a/sdk/executor.js +3 -1
- package/dist/core/protocols/a2a/sdk/server.js +5 -4
- package/dist/core/protocols/acp/acp-command-runner.js +7 -6
- package/dist/core/protocols/acp/acp-session-persistence.js +13 -10
- package/dist/core/protocols/acp/formal-agent.js +13 -6
- package/dist/core/protocols/acp/permission-provider.js +3 -2
- package/dist/core/protocols/acp/stdio-server.js +6 -6
- package/dist/core/reflection/engine.js +114 -14
- package/dist/core/runtime/agent-server-runtime.js +3 -2
- package/dist/core/runtime/batch-runner.js +81 -0
- package/dist/core/runtime/initialize.js +71 -6
- package/dist/core/runtime/loop-finalize.js +3 -0
- package/dist/core/runtime/loop-session-runner.js +5 -0
- package/dist/core/runtime/loop.js +4 -0
- package/dist/core/runtime/paths.js +9 -6
- package/dist/core/runtime/spawn-interactive.js +5 -4
- package/dist/core/security/redaction.js +3 -2
- package/dist/core/session/compaction/index.js +4 -3
- package/dist/core/session/compression.js +3 -1
- package/dist/core/session/manager.js +26 -38
- package/dist/core/session/pruning-strategy.js +2 -1
- package/dist/core/session/token-tracker.js +27 -9
- package/dist/core/skills/parser.js +3 -2
- package/dist/core/skills/permissions.js +2 -2
- package/dist/core/skills/runtime/MicroTaskRunner.js +1 -1
- package/dist/core/skills/runtime/SkillRunner.js +5 -2
- package/dist/core/slash/steps/slash-execute.js +7 -5
- package/dist/core/slash/strategy.js +1 -1
- package/dist/core/strata/checkpoint/manager.js +16 -10
- package/dist/core/strata/checkpoint/snapshot-create.js +5 -4
- package/dist/core/strata/checkpoint/snapshot-write-tree.js +7 -3
- package/dist/core/strata/engine/shadow-merge-engine.js +4 -2
- package/dist/core/strata/interaction/file-system-provider.js +2 -1
- package/dist/core/strata/layers/file-state-resolver.js +9 -7
- package/dist/core/strata/layers/immutable-git-layer.js +3 -1
- package/dist/core/strata/layers/shadow-driver/readonly-lock.js +8 -6
- package/dist/core/strata/layers/shadow-driver/shadow-driver.js +2 -1
- package/dist/core/strata/layers/worktree.js +9 -10
- package/dist/core/strata/runtime/environment.js +2 -1
- package/dist/core/strata/runtime/synchronizer.js +28 -26
- package/dist/core/streaming/canonical/parts-from-llm-stream-chunk.js +1 -11
- package/dist/core/structured-output/json-extract.js +3 -1
- package/dist/core/structured-output/json-schema-validator.js +1 -13
- package/dist/core/sub-agent/artifacts/store.js +2 -1
- package/dist/core/sub-agent/context-snapshot.js +12 -6
- package/dist/core/sub-agent/controller.js +70 -1
- package/dist/core/sub-agent/core/loop.js +25 -3
- package/dist/core/sub-agent/core/manager.js +343 -117
- package/dist/core/sub-agent/registry-defaults.js +12 -0
- package/dist/core/sub-agent/registry.js +8 -0
- package/dist/core/sub-agent/summary.js +96 -0
- package/dist/core/sub-agent/team.js +98 -0
- package/dist/core/sub-agent/tools/task-await.js +109 -0
- package/dist/core/sub-agent/tools/task-spawn.js +52 -7
- package/dist/core/sub-agent/tools/team.js +92 -0
- package/dist/core/sub-agent/types.js +11 -2
- package/dist/core/target-runtime/profile.js +3 -1
- package/dist/core/tools/audit.js +3 -2
- package/dist/core/tools/budget.js +7 -12
- package/dist/core/tools/builtin/ast.js +144 -0
- package/dist/core/tools/builtin/code-search/backends/powershell.js +3 -1
- package/dist/core/tools/builtin/code-search/backends/rg.js +3 -1
- package/dist/core/tools/builtin/code-search/executor.js +46 -43
- package/dist/core/tools/builtin/code-search/parse/plain-grep.js +3 -1
- package/dist/core/tools/builtin/code-search/parse/rg-json.js +3 -1
- package/dist/core/tools/builtin/fs.js +90 -7
- package/dist/core/tools/builtin/git.js +242 -0
- package/dist/core/tools/builtin/glob.js +79 -0
- package/dist/core/tools/builtin/index.js +53 -111
- package/dist/core/tools/builtin/interaction.js +13 -15
- package/dist/core/tools/builtin/knowledge.js +146 -4
- package/dist/core/tools/builtin/proposal.js +14 -3
- package/dist/core/tools/builtin/verify.js +35 -3
- package/dist/core/tools/capability/executor.js +5 -5
- package/dist/core/tools/headless-payload.js +1 -3
- package/dist/core/tools/mapper.js +8 -42
- package/dist/core/tools/parallel/persistence.js +17 -5
- package/dist/core/tools/parallel/scheduler.js +23 -21
- package/dist/core/tools/permissions/permission-rules.js +69 -115
- package/dist/core/tools/plugins/loader.js +4 -3
- package/dist/core/tools/router.js +112 -58
- package/dist/core/tools/session.js +64 -102
- package/dist/core/tools/streaming/ToolCallAccumulator.js +1 -3
- package/dist/core/tools/tool-visibility.js +2 -1
- package/dist/core/tools/types.js +10 -0
- package/dist/core/types/batch.js +2 -0
- package/dist/core/utils/error.js +79 -0
- package/dist/core/utils/sanitizer.js +5 -2
- package/dist/core/utils/serialize.js +66 -0
- package/dist/core/utils/zod.js +29 -0
- package/dist/core/verification/detect-runner.js +86 -0
- package/dist/core/verification/runner.js +76 -0
- package/dist/core/version.js +3 -1
- package/dist/core/workspace/capabilities.js +3 -2
- package/dist/integrations/langfuse/litellm-langfuse-outcome-reporter.js +9 -8
- package/dist/languages/python/index.js +154 -0
- package/dist/locales/en.js +8 -1
- package/package.json +2 -1
|
@@ -133,7 +133,10 @@ export const generatePlan = async (ctx) => {
|
|
|
133
133
|
flowMode: ctx.mode,
|
|
134
134
|
runtime: toolVisibility,
|
|
135
135
|
});
|
|
136
|
-
const
|
|
136
|
+
const baseSystemPrompt = await getPlanSystemPrompt(promptVisibleTools, toolVisibility);
|
|
137
|
+
const systemPrompt = ctx.options.subAgentSystemPrompt
|
|
138
|
+
? [baseSystemPrompt, ctx.options.subAgentSystemPrompt]
|
|
139
|
+
: baseSystemPrompt;
|
|
137
140
|
const requestEnvelope = await buildPhaseRequestEnvelope({
|
|
138
141
|
phase: Phase.PLAN,
|
|
139
142
|
defaultNamespace: 'plan',
|
|
@@ -192,62 +195,68 @@ export const generatePlan = async (ctx) => {
|
|
|
192
195
|
});
|
|
193
196
|
const content = response.content;
|
|
194
197
|
let finalContent = content || '';
|
|
198
|
+
// Codex-style self-healing: if plan JSON parsing fails, send the error back to the LLM
|
|
199
|
+
// and let it self-correct. Try up to MAX_REPAIR_ATTEMPTS times.
|
|
200
|
+
const MAX_REPAIR_ATTEMPTS = 2;
|
|
195
201
|
let plan;
|
|
196
|
-
|
|
197
|
-
if (!finalContent) {
|
|
198
|
-
throw new Error(text.llm.planEmpty);
|
|
199
|
-
}
|
|
200
|
-
plan = parsePlanFromLLMContent(finalContent);
|
|
201
|
-
}
|
|
202
|
-
catch (e) {
|
|
203
|
-
recordPlanRepairAttempt({
|
|
204
|
-
reason: sanitizeError(e),
|
|
205
|
-
badContentLength: finalContent.length,
|
|
206
|
-
});
|
|
207
|
-
let repaired;
|
|
208
|
-
try {
|
|
209
|
-
repaired = await repairToJsonObject({
|
|
210
|
-
llm: ctx.options.llm,
|
|
211
|
-
baseMessages,
|
|
212
|
-
chatOptions: { signal: ctx.options.signal },
|
|
213
|
-
badContent: finalContent,
|
|
214
|
-
reason: sanitizeError(e),
|
|
215
|
-
});
|
|
216
|
-
}
|
|
217
|
-
catch (repairError) {
|
|
218
|
-
recordPlanRepairResult({
|
|
219
|
-
ok: false,
|
|
220
|
-
contentLength: 0,
|
|
221
|
-
error: sanitizeError(repairError).slice(0, 400),
|
|
222
|
-
});
|
|
223
|
-
throw new Error(text.llm.planParseFailed(finalContent, sanitizeError(repairError)));
|
|
224
|
-
}
|
|
225
|
-
finalContent = repaired.content || '';
|
|
226
|
-
emitLlmOutput({
|
|
227
|
-
emit: ctx.emit,
|
|
228
|
-
policy: ctx.options.llmOutput,
|
|
229
|
-
kind: 'plan',
|
|
230
|
-
step: 'PLAN',
|
|
231
|
-
content: finalContent,
|
|
232
|
-
});
|
|
202
|
+
for (let attempt = 0; attempt <= MAX_REPAIR_ATTEMPTS; attempt++) {
|
|
233
203
|
try {
|
|
234
|
-
if (!finalContent)
|
|
204
|
+
if (!finalContent) {
|
|
235
205
|
throw new Error(text.llm.planEmpty);
|
|
206
|
+
}
|
|
236
207
|
plan = parsePlanFromLLMContent(finalContent);
|
|
208
|
+
break; // Success
|
|
237
209
|
}
|
|
238
|
-
catch (
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
210
|
+
catch (parseError) {
|
|
211
|
+
// On last attempt, throw
|
|
212
|
+
if (attempt >= MAX_REPAIR_ATTEMPTS) {
|
|
213
|
+
recordPlanRepairResult({
|
|
214
|
+
ok: false,
|
|
215
|
+
contentLength: finalContent.length,
|
|
216
|
+
error: sanitizeError(parseError).slice(0, 400),
|
|
217
|
+
});
|
|
218
|
+
throw new Error(text.llm.planParseFailed(finalContent, sanitizeError(parseError)));
|
|
219
|
+
}
|
|
220
|
+
// Repair attempt: send error feedback to LLM and let it self-correct
|
|
221
|
+
recordPlanRepairAttempt({
|
|
222
|
+
reason: sanitizeError(parseError),
|
|
223
|
+
badContentLength: finalContent.length,
|
|
224
|
+
});
|
|
225
|
+
let repaired;
|
|
226
|
+
try {
|
|
227
|
+
repaired = await repairToJsonObject({
|
|
228
|
+
llm: ctx.options.llm,
|
|
229
|
+
baseMessages,
|
|
230
|
+
chatOptions: { signal: ctx.options.signal },
|
|
231
|
+
badContent: finalContent,
|
|
232
|
+
reason: sanitizeError(parseError),
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
catch (repairError) {
|
|
236
|
+
recordPlanRepairResult({
|
|
237
|
+
ok: false,
|
|
238
|
+
contentLength: 0,
|
|
239
|
+
error: sanitizeError(repairError).slice(0, 400),
|
|
240
|
+
});
|
|
241
|
+
throw new Error(text.llm.planParseFailed(finalContent, sanitizeError(repairError)));
|
|
242
|
+
}
|
|
243
|
+
finalContent = repaired.content || '';
|
|
244
|
+
emitLlmOutput({
|
|
245
|
+
emit: ctx.emit,
|
|
246
|
+
policy: ctx.options.llmOutput,
|
|
247
|
+
kind: 'plan',
|
|
248
|
+
step: 'PLAN',
|
|
249
|
+
content: finalContent,
|
|
243
250
|
});
|
|
244
|
-
throw new Error(text.llm.planParseFailed(finalContent, sanitizeError(e2)));
|
|
245
251
|
}
|
|
246
|
-
recordPlanRepairResult({
|
|
247
|
-
ok: true,
|
|
248
|
-
contentLength: finalContent.length,
|
|
249
|
-
});
|
|
250
252
|
}
|
|
253
|
+
if (!plan) {
|
|
254
|
+
throw new Error(text.llm.planParseFailed(finalContent, 'Plan was not parsed successfully'));
|
|
255
|
+
}
|
|
256
|
+
recordPlanRepairResult({
|
|
257
|
+
ok: true,
|
|
258
|
+
contentLength: finalContent.length,
|
|
259
|
+
});
|
|
251
260
|
ctx.emit({
|
|
252
261
|
type: 'log',
|
|
253
262
|
level: 'debug',
|
|
@@ -2,6 +2,7 @@ import { text } from '../../../locales/index.js';
|
|
|
2
2
|
import { supportsLlmStreaming } from '../../llm/capabilities.js';
|
|
3
3
|
import { emitLlmOutput } from '../../llm/output-policy.js';
|
|
4
4
|
import { recordAuditEvent } from '../../observability/audit-trail.js';
|
|
5
|
+
import { getLogger } from '../../observability/logger.js';
|
|
5
6
|
import { getResearchPrompt, getResearchSystemPrompt } from '../../prompts/runtime.js';
|
|
6
7
|
import { SessionReplacementPreviewProvider } from '../../session/replacement-preview-provider.js';
|
|
7
8
|
import { chatWithTools, chatWithToolsStreaming } from '../../tools/session.js';
|
|
@@ -50,7 +51,8 @@ function parseResearchResponse(content, fallbackSources) {
|
|
|
50
51
|
try {
|
|
51
52
|
parsed = JSON.parse(content);
|
|
52
53
|
}
|
|
53
|
-
catch {
|
|
54
|
+
catch (error) {
|
|
55
|
+
getLogger().debug(`[Research] Failed to parse research response as JSON: ${error instanceof Error ? error.message : String(error)}`);
|
|
54
56
|
parsed = undefined;
|
|
55
57
|
}
|
|
56
58
|
const researchText = String(parsed?.researchText ?? content ?? text.grizzco.research.empty ?? '');
|
|
@@ -14,6 +14,9 @@ export function buildPhaseToolRuntimeContext(ctx, phase, cacheSurface) {
|
|
|
14
14
|
agentKind: ctx.options.agentKind ?? 'primary',
|
|
15
15
|
languagePlugins: ctx.options.languagePlugins,
|
|
16
16
|
subAgentController: ctx.options.subAgentController,
|
|
17
|
+
llmFactory: ctx.options.llmFactory,
|
|
18
|
+
onSubAgentComplete: ctx.options.onSubAgentComplete,
|
|
19
|
+
agentId: ctx.options.agentId,
|
|
17
20
|
phase,
|
|
18
21
|
contextSnapshot: {
|
|
19
22
|
conversationContext: ctx.options.conversationContext,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { text } from '../../../locales/index.js';
|
|
2
2
|
import { collectBudgetMetrics, evaluateBudgetAlert, getGlobalAdjuster, recordBudgetAlert, } from '../../context/budget/integration.js';
|
|
3
|
+
import { getEffectivenessTracker } from '../../context/effectiveness/tracker.js';
|
|
3
4
|
import { recordAuditEvent } from '../../observability/audit-trail.js';
|
|
4
5
|
import { executeVerifyForWorkspace } from './verify-shared.js';
|
|
5
6
|
function extractCommandProgram(command) {
|
|
@@ -32,7 +33,12 @@ export const runVerify = async (ctx) => {
|
|
|
32
33
|
scope: 'session',
|
|
33
34
|
phase: 'VERIFY',
|
|
34
35
|
});
|
|
35
|
-
// Collect budget metrics after verification
|
|
36
|
+
// Collect budget metrics and effectiveness data after verification
|
|
37
|
+
const effectivenessTracker = getEffectivenessTracker();
|
|
38
|
+
effectivenessTracker.recordExecution(verifyResult.ok, 0);
|
|
39
|
+
if (!verifyResult.ok) {
|
|
40
|
+
effectivenessTracker.recordFailure('missing_context', `Verification failed: ${verifyResult.output?.slice(0, 200) ?? 'unknown'}`);
|
|
41
|
+
}
|
|
36
42
|
if (ctx.contextResult) {
|
|
37
43
|
const metrics = collectBudgetMetrics({
|
|
38
44
|
contextResult: ctx.contextResult,
|
|
@@ -4,6 +4,7 @@ import path from 'path';
|
|
|
4
4
|
import { readFile, rm } from '../../adapters/fs/node-fs.js';
|
|
5
5
|
import { GitAdapter } from '../../adapters/git/git-adapter.js';
|
|
6
6
|
import { AstParser } from '../../ast/index.js';
|
|
7
|
+
import { getLogger } from '../../observability/logger.js';
|
|
7
8
|
import { convertDiffToShadowOperations } from '../../patch/diff.js';
|
|
8
9
|
import { tryGetPluginRegistry } from '../../plugin/registry.js';
|
|
9
10
|
import { OpType } from '../domain/grizzco-types.js';
|
|
@@ -70,7 +71,8 @@ async function defaultBuildProposedSource(workPath, operation) {
|
|
|
70
71
|
return null;
|
|
71
72
|
return showResult.stdout.toString('utf8');
|
|
72
73
|
}
|
|
73
|
-
catch {
|
|
74
|
+
catch (error) {
|
|
75
|
+
getLogger().debug(`[AstValidationService] Failed to build proposed source for "${operation.path}": ${error instanceof Error ? error.message : String(error)}`);
|
|
74
76
|
return null;
|
|
75
77
|
}
|
|
76
78
|
finally {
|
|
@@ -44,7 +44,8 @@ export class StrataSyncWorker {
|
|
|
44
44
|
const aiContent = op.content;
|
|
45
45
|
// Invoke legacy merge logic
|
|
46
46
|
// private method: async mergeFileContents(repoPath, base, user, ai, options?)
|
|
47
|
-
const
|
|
47
|
+
const engineMethods = engine;
|
|
48
|
+
const result = await engineMethods.mergeFileContents(this.git.repoPath, baseContent, userContent, aiContent);
|
|
48
49
|
return {
|
|
49
50
|
path: state.path,
|
|
50
51
|
success: !result.conflict,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { join } from 'path';
|
|
2
2
|
import { FileAdapter } from '../adapters/fs/index.js';
|
|
3
|
+
import { getLogger } from '../observability/logger.js';
|
|
3
4
|
/**
|
|
4
5
|
* Manages persistence of user input history isolated by Session.
|
|
5
6
|
* Storage path: .salmonloop/ui-history/{sessionId}.json
|
|
@@ -26,7 +27,8 @@ export class InputHistoryManager {
|
|
|
26
27
|
const data = await this.fileAdapter.readFile(filePath);
|
|
27
28
|
return JSON.parse(data);
|
|
28
29
|
}
|
|
29
|
-
catch {
|
|
30
|
+
catch (error) {
|
|
31
|
+
getLogger().debug(`[InputHistory] Failed to load history for session "${sessionId}": ${error instanceof Error ? error.message : String(error)}`);
|
|
30
32
|
return [];
|
|
31
33
|
}
|
|
32
34
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
+
import { getLogger } from '../observability/logger.js';
|
|
2
3
|
const LlmDecisionSchema = z
|
|
3
4
|
.object({
|
|
4
5
|
intent: z.enum(['answer', 'review', 'patch', 'debug', 'research']),
|
|
@@ -165,8 +166,9 @@ async function routeByLlm(input, options) {
|
|
|
165
166
|
try {
|
|
166
167
|
return JSON.parse(candidate);
|
|
167
168
|
}
|
|
168
|
-
catch {
|
|
169
|
+
catch (error) {
|
|
169
170
|
// Best-effort: extract the first JSON object from mixed output.
|
|
171
|
+
getLogger().debug(`[ChatIntent] Primary JSON parse failed, attempting extraction: ${error instanceof Error ? error.message : String(error)}`);
|
|
170
172
|
const start = candidate.indexOf('{');
|
|
171
173
|
const end = candidate.lastIndexOf('}');
|
|
172
174
|
if (start >= 0 && end > start) {
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { jsonSchema, tool } from 'ai';
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
import { zodToJsonSchema } from 'zod-to-json-schema';
|
|
4
|
+
import { getLogger } from '../../observability/logger.js';
|
|
4
5
|
import { toolToOpenAI } from '../../tools/mapper.js';
|
|
6
|
+
import { isRecord } from '../../utils/serialize.js';
|
|
5
7
|
function formatOutputSchema(schema) {
|
|
6
8
|
if (!schema)
|
|
7
9
|
return 'any (dynamic)';
|
|
8
|
-
const def = schema.
|
|
9
|
-
if (def?.description) {
|
|
10
|
+
const def = schema.def;
|
|
11
|
+
if (typeof def?.description === 'string') {
|
|
10
12
|
return def.description;
|
|
11
13
|
}
|
|
12
14
|
try {
|
|
@@ -19,8 +21,9 @@ function formatOutputSchema(schema) {
|
|
|
19
21
|
return JSON.stringify(cleanSchema);
|
|
20
22
|
}
|
|
21
23
|
}
|
|
22
|
-
catch {
|
|
24
|
+
catch (error) {
|
|
23
25
|
// Fallback to generic description for invalid/unsupported schema.
|
|
26
|
+
getLogger().debug(`[MessageMapper] Failed to format output schema to JSON: ${error instanceof Error ? error.message : String(error)}`);
|
|
24
27
|
}
|
|
25
28
|
return 'complex object';
|
|
26
29
|
}
|
|
@@ -30,8 +33,8 @@ function safeParseJsonObject(textValue) {
|
|
|
30
33
|
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed))
|
|
31
34
|
return parsed;
|
|
32
35
|
}
|
|
33
|
-
catch {
|
|
34
|
-
|
|
36
|
+
catch (error) {
|
|
37
|
+
getLogger().debug(`[MessageMapper] Failed to parse JSON object: ${error instanceof Error ? error.message : String(error)}`);
|
|
35
38
|
}
|
|
36
39
|
return {};
|
|
37
40
|
}
|
|
@@ -42,13 +45,12 @@ function deepCloneJson(value, fallback) {
|
|
|
42
45
|
return fallback;
|
|
43
46
|
return JSON.parse(serialized);
|
|
44
47
|
}
|
|
45
|
-
catch {
|
|
48
|
+
catch (error) {
|
|
49
|
+
getLogger().debug(`[MessageMapper] deepCloneJson failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
46
50
|
return fallback;
|
|
47
51
|
}
|
|
48
52
|
}
|
|
49
|
-
|
|
50
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
51
|
-
}
|
|
53
|
+
const isObjectRecord = isRecord;
|
|
52
54
|
export function extractUsageFromAiSdkResult(result) {
|
|
53
55
|
if (!isObjectRecord(result))
|
|
54
56
|
return null;
|
|
@@ -93,7 +95,9 @@ function toAiSdkToolResultOutput(value) {
|
|
|
93
95
|
};
|
|
94
96
|
}
|
|
95
97
|
export function toAiSdkMessages(messages) {
|
|
96
|
-
|
|
98
|
+
// Each branch returns a structurally valid ModelMessage; the union is too
|
|
99
|
+
// complex for TS to verify inline, so we assert the array at the end.
|
|
100
|
+
const result = messages.map((m) => {
|
|
97
101
|
if (m.role === 'tool') {
|
|
98
102
|
const toolCallId = m.tool_call_id || 'unknown';
|
|
99
103
|
const toolName = m.name || 'unknown';
|
|
@@ -101,7 +105,8 @@ export function toAiSdkMessages(messages) {
|
|
|
101
105
|
try {
|
|
102
106
|
parsedContent = JSON.parse(m.content);
|
|
103
107
|
}
|
|
104
|
-
catch {
|
|
108
|
+
catch (error) {
|
|
109
|
+
getLogger().debug(`[MessageMapper] Failed to parse tool message content as JSON: ${error instanceof Error ? error.message : String(error)}`);
|
|
105
110
|
parsedContent = m.content;
|
|
106
111
|
}
|
|
107
112
|
if (isToolApprovalResponse(parsedContent)) {
|
|
@@ -143,7 +148,7 @@ export function toAiSdkMessages(messages) {
|
|
|
143
148
|
content = JSON.stringify(content);
|
|
144
149
|
}
|
|
145
150
|
return {
|
|
146
|
-
role:
|
|
151
|
+
role: 'assistant',
|
|
147
152
|
content: content,
|
|
148
153
|
};
|
|
149
154
|
}
|
|
@@ -191,6 +196,7 @@ export function toAiSdkMessages(messages) {
|
|
|
191
196
|
content: content,
|
|
192
197
|
};
|
|
193
198
|
});
|
|
199
|
+
return result;
|
|
194
200
|
}
|
|
195
201
|
export function toAiSdkToolSet(openAiTools, toolSpecs) {
|
|
196
202
|
const tools = {};
|
|
@@ -199,12 +205,14 @@ export function toAiSdkToolSet(openAiTools, toolSpecs) {
|
|
|
199
205
|
const outputDesc = formatOutputSchema(spec.outputSchema);
|
|
200
206
|
const description = `${spec.description}\n\nReturns: ${outputDesc}`;
|
|
201
207
|
const openAiDef = toolToOpenAI(spec);
|
|
202
|
-
const parameters = jsonSchema(openAiDef.function?.parameters
|
|
203
|
-
tools[spec.name] =
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
+
const parameters = jsonSchema(openAiDef.function?.parameters ?? {});
|
|
209
|
+
tools[spec.name] = {
|
|
210
|
+
...tool({
|
|
211
|
+
description,
|
|
212
|
+
inputSchema: parameters,
|
|
213
|
+
}),
|
|
214
|
+
outputSchema: spec.outputSchema ?? z.any(),
|
|
215
|
+
};
|
|
208
216
|
}
|
|
209
217
|
}
|
|
210
218
|
if (Array.isArray(openAiTools)) {
|
|
@@ -215,11 +223,13 @@ export function toAiSdkToolSet(openAiTools, toolSpecs) {
|
|
|
215
223
|
continue;
|
|
216
224
|
const rawDesc = typeof fn?.description === 'string' ? fn.description : '';
|
|
217
225
|
const description = `${rawDesc}\n\nReturns: any (dynamic)`.trim();
|
|
218
|
-
tools[name] =
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
226
|
+
tools[name] = {
|
|
227
|
+
...tool({
|
|
228
|
+
description,
|
|
229
|
+
inputSchema: jsonSchema(fn?.parameters ?? { type: 'object', properties: {} }),
|
|
230
|
+
}),
|
|
231
|
+
outputSchema: z.any(),
|
|
232
|
+
};
|
|
223
233
|
}
|
|
224
234
|
}
|
|
225
235
|
return Object.keys(tools).length > 0 ? tools : undefined;
|
|
@@ -241,14 +251,15 @@ export function toOpenAiToolCalls(toolCalls) {
|
|
|
241
251
|
try {
|
|
242
252
|
parsed = JSON.parse(nested);
|
|
243
253
|
}
|
|
244
|
-
catch {
|
|
245
|
-
|
|
254
|
+
catch (error) {
|
|
255
|
+
getLogger().debug(`[MessageMapper] Failed to parse nested JSON string: ${error instanceof Error ? error.message : String(error)}`);
|
|
246
256
|
}
|
|
247
257
|
}
|
|
248
258
|
}
|
|
249
259
|
return parsed;
|
|
250
260
|
}
|
|
251
|
-
catch {
|
|
261
|
+
catch (error) {
|
|
262
|
+
getLogger().debug(`[MessageMapper] Failed to normalize tool input JSON: ${error instanceof Error ? error.message : String(error)}`);
|
|
252
263
|
return raw;
|
|
253
264
|
}
|
|
254
265
|
};
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
3
|
-
}
|
|
1
|
+
import { isRecord } from '../../utils/serialize.js';
|
|
4
2
|
function isJsonValue(value) {
|
|
5
3
|
if (value === null ||
|
|
6
4
|
typeof value === 'string' ||
|
|
@@ -115,9 +113,7 @@ export function buildAiSdkRequestParams(params) {
|
|
|
115
113
|
responseFormat: resolveResponseFormat(params.options),
|
|
116
114
|
toolChoice: (params.options.toolChoice === 'none'
|
|
117
115
|
? 'none'
|
|
118
|
-
: params.tools
|
|
119
|
-
? 'auto'
|
|
120
|
-
: undefined),
|
|
116
|
+
: (params.options.toolChoice ?? (params.tools ? 'auto' : undefined))),
|
|
121
117
|
providerOptions: mergeProviderOptions({
|
|
122
118
|
providerOptions: params.options.providerOptions,
|
|
123
119
|
providerHints: params.options.providerHints,
|
|
@@ -1,32 +1,38 @@
|
|
|
1
|
+
import { isRecord } from '../../utils/serialize.js';
|
|
1
2
|
import { mapAiSdkStreamPartToChunk } from '../stream-utils.js';
|
|
2
3
|
import { toOpenAiToolCalls } from './message-mapper.js';
|
|
3
4
|
function extractReasoningContent(result) {
|
|
4
|
-
if (
|
|
5
|
+
if (!isRecord(result))
|
|
6
|
+
return undefined;
|
|
7
|
+
if (typeof result.reasoningText === 'string' && result.reasoningText.length > 0) {
|
|
5
8
|
return result.reasoningText;
|
|
6
9
|
}
|
|
7
|
-
const reasoningParts = Array.isArray(result
|
|
10
|
+
const reasoningParts = Array.isArray(result.reasoning)
|
|
8
11
|
? result.reasoning
|
|
9
|
-
: Array.isArray(result
|
|
10
|
-
? result.content.filter((part) => part
|
|
12
|
+
: Array.isArray(result.content)
|
|
13
|
+
? result.content.filter((part) => isRecord(part) && part.type === 'reasoning')
|
|
11
14
|
: [];
|
|
12
15
|
const text = reasoningParts
|
|
13
|
-
.map((part) => (typeof part
|
|
16
|
+
.map((part) => (typeof part.text === 'string' ? part.text : ''))
|
|
14
17
|
.join('');
|
|
15
18
|
return text.length > 0 ? text : undefined;
|
|
16
19
|
}
|
|
17
20
|
export function mapAiSdkGenerateResultToMessage(result) {
|
|
18
21
|
const reasoningContent = extractReasoningContent(result);
|
|
22
|
+
const r = isRecord(result) ? result : {};
|
|
19
23
|
return {
|
|
20
24
|
role: 'assistant',
|
|
21
|
-
content:
|
|
25
|
+
content: typeof r.text === 'string' ? r.text : '',
|
|
22
26
|
...(reasoningContent ? { reasoning_content: reasoningContent } : {}),
|
|
23
|
-
tool_calls: toOpenAiToolCalls(
|
|
27
|
+
tool_calls: toOpenAiToolCalls(Array.isArray(r.toolCalls)
|
|
28
|
+
? r.toolCalls
|
|
29
|
+
: undefined),
|
|
24
30
|
};
|
|
25
31
|
}
|
|
26
32
|
export async function* mapAiSdkStreamResultToChunks(fullStream) {
|
|
27
33
|
let doneEmitted = false;
|
|
28
34
|
for await (const part of fullStream) {
|
|
29
|
-
if (!part)
|
|
35
|
+
if (!part || !isRecord(part))
|
|
30
36
|
continue;
|
|
31
37
|
if (part.type === 'error')
|
|
32
38
|
throw part.error;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isRecord } from '../../utils/serialize.js';
|
|
1
2
|
function unwrapRetryError(err) {
|
|
2
3
|
if (!err || typeof err !== 'object')
|
|
3
4
|
return err;
|
|
@@ -15,7 +16,7 @@ function findStatusCode(err) {
|
|
|
15
16
|
if (typeof direct === 'number' && Number.isFinite(direct))
|
|
16
17
|
return direct;
|
|
17
18
|
const response = obj.response;
|
|
18
|
-
if (response
|
|
19
|
+
if (isRecord(response)) {
|
|
19
20
|
const status = response.status;
|
|
20
21
|
if (typeof status === 'number' && Number.isFinite(status))
|
|
21
22
|
return status;
|
|
@@ -31,23 +32,25 @@ function findNetworkCode(err) {
|
|
|
31
32
|
if (typeof code === 'string')
|
|
32
33
|
return code;
|
|
33
34
|
const cause = obj.cause;
|
|
34
|
-
if (cause && typeof cause
|
|
35
|
+
if (isRecord(cause) && typeof cause.code === 'string') {
|
|
35
36
|
return cause.code;
|
|
36
37
|
}
|
|
37
38
|
return undefined;
|
|
38
39
|
}
|
|
39
|
-
function
|
|
40
|
+
function isUserAbortError(err) {
|
|
40
41
|
const unwrapped = unwrapRetryError(err);
|
|
41
42
|
const name = unwrapped instanceof Error ? unwrapped.name : '';
|
|
42
|
-
|
|
43
|
-
|
|
43
|
+
// Only treat as user abort if the error name is AbortError (from AbortController).
|
|
44
|
+
// Provider-side "aborted" messages are transient and should be retried.
|
|
45
|
+
return name === 'AbortError';
|
|
44
46
|
}
|
|
45
47
|
export function classifyRetryableApiError(err) {
|
|
46
|
-
if (
|
|
48
|
+
if (isUserAbortError(err))
|
|
47
49
|
return { retryable: false, reason: 'aborted' };
|
|
48
50
|
const statusCode = findStatusCode(err);
|
|
49
51
|
const networkCode = findNetworkCode(err);
|
|
50
|
-
const
|
|
52
|
+
const unwrapped = unwrapRetryError(err);
|
|
53
|
+
const msg = String((isRecord(unwrapped) ? unwrapped.message : undefined) ?? err).toLowerCase();
|
|
51
54
|
if (statusCode === 408)
|
|
52
55
|
return { retryable: true, reason: 'timeout', statusCode, networkCode };
|
|
53
56
|
if (statusCode === 429)
|
|
@@ -67,6 +70,13 @@ export function classifyRetryableApiError(err) {
|
|
|
67
70
|
if (msg.includes('overloaded')) {
|
|
68
71
|
return { retryable: true, reason: 'overloaded', statusCode, networkCode };
|
|
69
72
|
}
|
|
73
|
+
// Provider-side aborts and unexpected errors are transient — retry them.
|
|
74
|
+
if (msg.includes('aborted')) {
|
|
75
|
+
return { retryable: true, reason: 'provider_abort', statusCode, networkCode };
|
|
76
|
+
}
|
|
77
|
+
if (msg.includes('unexpected error')) {
|
|
78
|
+
return { retryable: true, reason: 'unexpected', statusCode, networkCode };
|
|
79
|
+
}
|
|
70
80
|
if (typeof networkCode === 'string') {
|
|
71
81
|
const normalized = networkCode.toUpperCase();
|
|
72
82
|
if (normalized === 'ECONNRESET' ||
|
|
@@ -2,7 +2,7 @@ import { toLlmError } from '../errors.js';
|
|
|
2
2
|
import { withRetry, withStreamRetry } from '../retry-utils.js';
|
|
3
3
|
import { createAiSdkRetryLogger, isRetryableAiSdkError } from './request-runtime.js';
|
|
4
4
|
const DEFAULT_AI_SDK_RETRY_OPTIONS = {
|
|
5
|
-
maxRetries:
|
|
5
|
+
maxRetries: 3,
|
|
6
6
|
jitterRatio: 0.2,
|
|
7
7
|
};
|
|
8
8
|
export async function executeWithAiSdkRetry(params) {
|
|
@@ -10,18 +10,26 @@ function truncateForPrompt(text, maxChars) {
|
|
|
10
10
|
export async function repairToJsonObject(args) {
|
|
11
11
|
const { llm, baseMessages, chatOptions, badContent, reason } = args;
|
|
12
12
|
const prompt = [
|
|
13
|
-
'Your previous response
|
|
14
|
-
`Reason: ${reason}`,
|
|
13
|
+
'Your previous response failed to parse as a valid plan JSON object.',
|
|
15
14
|
'',
|
|
16
|
-
|
|
17
|
-
'The first non-whitespace character must be {.',
|
|
18
|
-
'The last non-whitespace character must be }.',
|
|
15
|
+
`Specific error: ${reason}`,
|
|
19
16
|
'',
|
|
20
|
-
'
|
|
17
|
+
'To fix this, return EXACTLY one JSON object with these keys:',
|
|
18
|
+
' - goal: string (what this plan accomplishes)',
|
|
19
|
+
' - files: string[] (list of file paths to modify)',
|
|
20
|
+
' - changes: string[] (list of change descriptions)',
|
|
21
|
+
' - verify: string (command to verify the changes)',
|
|
21
22
|
'',
|
|
22
|
-
'
|
|
23
|
+
'Example of a valid response:',
|
|
24
|
+
'{"goal":"Add error handling","files":["src/index.ts"],"changes":["Add try-catch to main"],"verify":"npm test"}',
|
|
23
25
|
'',
|
|
24
|
-
'
|
|
26
|
+
'Rules:',
|
|
27
|
+
'- The first non-whitespace character must be {',
|
|
28
|
+
'- The last non-whitespace character must be }',
|
|
29
|
+
'- No markdown fences, commentary, or leading/trailing text',
|
|
30
|
+
'- All four keys (goal, files, changes, verify) are REQUIRED',
|
|
31
|
+
'',
|
|
32
|
+
'Your previous invalid response (truncated):',
|
|
25
33
|
truncateForPrompt(badContent, Math.min(1200, Math.max(400, LIMITS.maxContextChars / 100))),
|
|
26
34
|
].join('\n');
|
|
27
35
|
return llm.chat([
|
package/dist/core/llm/errors.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { getLogger } from '../observability/logger.js';
|
|
1
2
|
import { SalmonError } from '../types/errors.js';
|
|
2
3
|
import { sanitizeErrorMessage } from '../utils/sanitizer.js';
|
|
4
|
+
import { isRecord } from '../utils/serialize.js';
|
|
3
5
|
export class LlmError extends SalmonError {
|
|
4
6
|
llmCode;
|
|
5
7
|
meta;
|
|
@@ -44,8 +46,8 @@ function extractProviderDetails(err) {
|
|
|
44
46
|
details.providerMessage = sanitizeError(parsed.message);
|
|
45
47
|
}
|
|
46
48
|
}
|
|
47
|
-
catch {
|
|
48
|
-
|
|
49
|
+
catch (error) {
|
|
50
|
+
getLogger().debug(`[LlmErrors] Failed to parse responseBody as JSON: ${error instanceof Error ? error.message : String(error)}`);
|
|
49
51
|
}
|
|
50
52
|
}
|
|
51
53
|
else if (candidate.responseBody) {
|
|
@@ -72,8 +74,8 @@ function extractProviderDetails(err) {
|
|
|
72
74
|
details.providerMessage = sanitizeError(parsed.error.message);
|
|
73
75
|
}
|
|
74
76
|
}
|
|
75
|
-
catch {
|
|
76
|
-
|
|
77
|
+
catch (error) {
|
|
78
|
+
getLogger().debug(`[LlmErrors] Failed to parse embedded JSON in message: ${error instanceof Error ? error.message : String(error)}`);
|
|
77
79
|
}
|
|
78
80
|
}
|
|
79
81
|
}
|
|
@@ -88,9 +90,8 @@ function extractNetworkCode(err) {
|
|
|
88
90
|
if (typeof direct === 'string' && direct.trim())
|
|
89
91
|
return direct;
|
|
90
92
|
const cause = candidate.cause;
|
|
91
|
-
if (cause && typeof cause
|
|
92
|
-
|
|
93
|
-
return code.trim() ? code : undefined;
|
|
93
|
+
if (isRecord(cause) && typeof cause.code === 'string') {
|
|
94
|
+
return cause.code.trim() || undefined;
|
|
94
95
|
}
|
|
95
96
|
return undefined;
|
|
96
97
|
}
|
|
@@ -124,19 +125,20 @@ export function sanitizeError(err) {
|
|
|
124
125
|
return sanitizeErrorMessage(err);
|
|
125
126
|
}
|
|
126
127
|
export function toLlmError(err, provider) {
|
|
128
|
+
const errObj = isRecord(err) ? err : null;
|
|
127
129
|
let name = err instanceof Error
|
|
128
130
|
? err.name
|
|
129
|
-
: typeof
|
|
130
|
-
?
|
|
131
|
+
: typeof errObj?.name === 'string'
|
|
132
|
+
? errObj.name
|
|
131
133
|
: 'UnknownError';
|
|
132
134
|
let message = err instanceof Error
|
|
133
135
|
? err.message
|
|
134
|
-
: typeof
|
|
135
|
-
?
|
|
136
|
+
: typeof errObj?.message === 'string'
|
|
137
|
+
? errObj.message
|
|
136
138
|
: String(err);
|
|
137
139
|
// Unwrap RetryError to get the last error's message if available
|
|
138
|
-
if (name === 'AI_RetryError' ||
|
|
139
|
-
const lastError =
|
|
140
|
+
if (name === 'AI_RetryError' || errObj?.lastError) {
|
|
141
|
+
const lastError = errObj?.lastError;
|
|
140
142
|
// Update the error reference so subsequent checks work on the actual cause
|
|
141
143
|
err = lastError;
|
|
142
144
|
if (lastError instanceof Error) {
|
|
@@ -149,7 +151,9 @@ export function toLlmError(err, provider) {
|
|
|
149
151
|
name === 'ZodError' ||
|
|
150
152
|
name.includes('TypeValidationError') ||
|
|
151
153
|
message.includes('TypeValidationError') ||
|
|
152
|
-
err
|
|
154
|
+
(err != null &&
|
|
155
|
+
typeof err === 'object' &&
|
|
156
|
+
Symbol.for('vercel.ai.error.AI_TypeValidationError') in err)) {
|
|
153
157
|
return new LlmError('LLM validation failed', 'LLM_VALIDATION_FAILED', {
|
|
154
158
|
provider,
|
|
155
159
|
causeName: name,
|