minimal-agent 0.10.0 → 0.12.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/README.md +11 -3
- package/package.json +52 -5
- package/plugins/computer_use/.claude-plugin/plugin.json +6 -0
- package/plugins/computer_use/README.md +94 -0
- package/plugins/computer_use/app/_template.md +142 -0
- package/plugins/computer_use/app/chrome.md +165 -0
- package/plugins/computer_use/app/finder.md +76 -0
- package/plugins/computer_use/app/macos-system-settings.md +81 -0
- package/plugins/computer_use/app/microsoft-excel.md +80 -0
- package/plugins/computer_use/app/microsoft-powerpoint.md +80 -0
- package/plugins/computer_use/app/microsoft-word.md +79 -0
- package/plugins/computer_use/app/wechat.md +413 -0
- package/plugins/computer_use/app/weread.md +319 -0
- package/plugins/computer_use/commands/computeruse.md +25 -0
- package/plugins/computer_use/commands/cu.md +9 -0
- package/plugins/computer_use/plugin.js +26 -0
- package/plugins/computer_use/plugin.ts +30 -0
- package/plugins/computer_use/src/capture.js +90 -0
- package/plugins/computer_use/src/capture.ts +104 -0
- package/plugins/computer_use/src/commands.js +91 -0
- package/plugins/computer_use/src/commands.ts +103 -0
- package/plugins/computer_use/src/daemon.js +134 -0
- package/plugins/computer_use/src/daemon.ts +161 -0
- package/plugins/computer_use/src/manuals.js +135 -0
- package/plugins/computer_use/src/manuals.ts +159 -0
- package/plugins/computer_use/src/verify.js +168 -0
- package/plugins/computer_use/src/verify.ts +219 -0
- package/plugins/computer_use/test/commands.test.ts +73 -0
- package/plugins/cron-runner/plugin.ts +38 -0
- package/plugins/cron-runner/src/commands.ts +373 -0
- package/plugins/cron-runner/src/cronTime.ts +155 -0
- package/plugins/cron-runner/src/lock.ts +79 -0
- package/plugins/cron-runner/src/runs.ts +99 -0
- package/plugins/cron-runner/src/scheduleEngine.ts +76 -0
- package/plugins/cron-runner/src/scheduler.ts +376 -0
- package/plugins/cron-runner/src/schema.ts +139 -0
- package/plugins/cron-runner/src/service.ts +193 -0
- package/plugins/cron-runner/src/store.ts +333 -0
- package/plugins/cron-runner/src/types.ts +141 -0
- package/plugins/cron-runner/test/cronTime.test.ts +41 -0
- package/plugins/cron-runner/test/plugin-integration.test.ts +47 -0
- package/plugins/cron-runner/test/scheduleEngine.test.ts +41 -0
- package/plugins/cron-runner/test/service.test.ts +76 -0
- package/plugins/cron-runner/test/store-and-scheduler.test.ts +501 -0
- package/plugins/workflow-runner/plugin.js +56 -2
- package/plugins/workflow-runner/plugin.ts +113 -0
- package/plugins/workflow-runner/src/agentCommon.js +43 -4
- package/plugins/workflow-runner/src/agentCommon.ts +435 -0
- package/plugins/workflow-runner/src/controlFlow/branch.js +40 -0
- package/plugins/workflow-runner/src/controlFlow/branch.ts +46 -0
- package/plugins/workflow-runner/src/controlFlow/loop.js +211 -0
- package/plugins/workflow-runner/src/controlFlow/loop.ts +248 -0
- package/plugins/workflow-runner/src/controlFlow/pause.js +129 -0
- package/plugins/workflow-runner/src/controlFlow/pause.ts +141 -0
- package/plugins/workflow-runner/src/controlFlow/signal.js +21 -0
- package/plugins/workflow-runner/src/controlFlow/signal.ts +29 -0
- package/plugins/workflow-runner/src/controlFlow/types.js +18 -0
- package/plugins/workflow-runner/src/controlFlow/types.ts +63 -0
- package/plugins/workflow-runner/src/dslManifest.ts +127 -0
- package/plugins/workflow-runner/src/expressions.ts +622 -0
- package/plugins/workflow-runner/src/index.js +13 -4
- package/plugins/workflow-runner/src/index.ts +338 -0
- package/plugins/workflow-runner/src/jsonPayload.ts +84 -0
- package/plugins/workflow-runner/src/loader.js +23 -0
- package/plugins/workflow-runner/src/loader.ts +721 -0
- package/plugins/workflow-runner/src/multimodal.js +2 -1
- package/plugins/workflow-runner/src/multimodal.ts +64 -0
- package/plugins/workflow-runner/src/recovery.ts +149 -0
- package/plugins/workflow-runner/src/runner.js +75 -659
- package/plugins/workflow-runner/src/runner.ts +604 -0
- package/plugins/workflow-runner/src/stepExecutors/agent.ts +234 -0
- package/plugins/workflow-runner/src/stepExecutors/assert.ts +50 -0
- package/plugins/workflow-runner/src/stepExecutors/echo.js +31 -0
- package/plugins/workflow-runner/src/stepExecutors/echo.ts +38 -0
- package/plugins/workflow-runner/src/stepExecutors/llm.ts +133 -0
- package/plugins/workflow-runner/src/stepExecutors/set.js +49 -0
- package/plugins/workflow-runner/src/stepExecutors/set.ts +56 -0
- package/plugins/workflow-runner/src/stepExecutors/skill.js +5 -6
- package/plugins/workflow-runner/src/stepExecutors/skill.ts +140 -0
- package/plugins/workflow-runner/src/stepExecutors/tool.js +80 -8
- package/plugins/workflow-runner/src/stepExecutors/tool.ts +256 -0
- package/plugins/workflow-runner/src/stepExecutors/verify.ts +68 -0
- package/plugins/workflow-runner/src/stepPipeline.js +277 -0
- package/plugins/workflow-runner/src/stepPipeline.ts +366 -0
- package/plugins/workflow-runner/src/taskIdentity.ts +405 -0
- package/plugins/workflow-runner/src/toolRuntime.js +48 -0
- package/plugins/workflow-runner/src/toolRuntime.ts +64 -0
- package/plugins/workflow-runner/src/types.js +37 -1
- package/plugins/workflow-runner/src/types.ts +602 -0
- package/plugins/workflow-runner/src/workflowState.ts +733 -0
- package/plugins/workflow-runner/test/agent.test.ts +587 -0
- package/plugins/workflow-runner/test/agentLlmError.test.ts +373 -0
- package/plugins/workflow-runner/test/bashBlacklist.sweep.test.ts +65 -0
- package/plugins/workflow-runner/test/captureMisbind.test.ts +99 -0
- package/plugins/workflow-runner/test/cli.e2e.test.ts +147 -0
- package/plugins/workflow-runner/test/context-files.test.ts +320 -0
- package/plugins/workflow-runner/test/e2e.test.ts +370 -0
- package/plugins/workflow-runner/test/engineRobustness.test.ts +226 -0
- package/plugins/workflow-runner/test/expressions.test.ts +397 -0
- package/plugins/workflow-runner/test/foundation.test.ts +126 -0
- package/plugins/workflow-runner/test/graceful.test.ts +139 -0
- package/plugins/workflow-runner/test/greedyInputs.load.test.ts +47 -0
- package/plugins/workflow-runner/test/hostCapability.test.ts +108 -0
- package/plugins/workflow-runner/test/illustrate.load.test.ts +183 -0
- package/plugins/workflow-runner/test/jsonPayload.test.ts +83 -0
- package/plugins/workflow-runner/test/loader.test.ts +617 -0
- package/plugins/workflow-runner/test/maxTurnsRetryDelay.test.ts +33 -0
- package/plugins/workflow-runner/test/multimodal.test.ts +342 -0
- package/plugins/workflow-runner/test/output-schema.test.ts +213 -0
- package/plugins/workflow-runner/test/parseWorkflowArgs.test.ts +87 -0
- package/plugins/workflow-runner/test/pluginRunner.isolation.test.ts +269 -0
- package/plugins/workflow-runner/test/ppt.load.test.ts +1220 -0
- package/plugins/workflow-runner/test/ralphLoop.e2e.test.ts +220 -0
- package/plugins/workflow-runner/test/recovery.test.ts +265 -0
- package/plugins/workflow-runner/test/runner.test.ts +911 -0
- package/plugins/workflow-runner/test/runnerPreflight.test.ts +379 -0
- package/plugins/workflow-runner/test/skillTurnDone.test.ts +71 -0
- package/plugins/workflow-runner/test/stateLifecycle.test.ts +150 -0
- package/plugins/workflow-runner/test/stepPipeline.test.ts +114 -0
- package/plugins/workflow-runner/test/taskArbitration.test.ts +338 -0
- package/plugins/workflow-runner/test/taskIdentity.test.ts +237 -0
- package/plugins/workflow-runner/test/taskLifecycle.test.ts +270 -0
- package/plugins/workflow-runner/test/toolArtifactOverwrite.test.ts +221 -0
- package/plugins/workflow-runner/test/toolCapabilityBoundary.test.ts +29 -0
- package/plugins/workflow-runner/test/toolRuntimeIntegration.test.ts +168 -0
- package/plugins/workflow-runner/test/webppt.load.test.ts +250 -0
- package/plugins/workflow-runner/test/word.load.test.ts +208 -0
- package/plugins/workflow-runner/test/workflows.smoke.test.ts +76 -0
- package/plugins/workflow-runner/test/yamlGuardConditions.test.ts +207 -0
- package/skills/browser-automation/SKILL.md +9 -19
- package/skills/web-crawler/SKILL.md +9 -11
- package/src/askBridge.ts +249 -0
- package/src/bootstrap/cwdArg.js +10 -3
- package/src/bootstrap/cwdArg.ts +31 -0
- package/src/bootstrap/workingDir.ts +48 -0
- package/src/cli/args.js +70 -7
- package/src/cli/args.ts +146 -0
- package/src/cli/computerUse.js +185 -0
- package/src/cli/computerUse.ts +221 -0
- package/src/cli/configMenu.js +274 -202
- package/src/cli/configMenu.tsx +521 -0
- package/src/cli/memoryAccess.js +45 -0
- package/src/cli/memoryAccess.ts +53 -0
- package/src/cli/print.js +119 -5
- package/src/cli/print.ts +597 -0
- package/src/cli/quickModelSetup.js +60 -0
- package/src/cli/quickModelSetup.tsx +96 -0
- package/src/cli/streamJson.js +80 -4
- package/src/cli/streamJson.ts +433 -0
- package/src/cli/wizards/advisorWizard.js +1 -0
- package/src/cli/wizards/advisorWizard.tsx +195 -0
- package/src/cli/wizards/agentRuntimeWizard.js +84 -0
- package/src/cli/wizards/agentRuntimeWizard.tsx +134 -0
- package/src/cli/wizards/browserWizard.js +55 -0
- package/src/cli/wizards/browserWizard.tsx +77 -0
- package/src/cli/wizards/contextAdvisorWizard.js +84 -0
- package/src/cli/wizards/contextAdvisorWizard.tsx +124 -0
- package/src/cli/wizards/fieldForm.js +7 -4
- package/src/cli/wizards/fieldForm.tsx +250 -0
- package/src/cli/wizards/imageWizard.js +4 -5
- package/src/cli/wizards/imageWizard.tsx +96 -0
- package/src/cli/wizards/openRouterWizard.tsx +48 -0
- package/src/cli/wizards/providerPresets.js +1 -8
- package/src/cli/wizards/providerPresets.ts +106 -0
- package/src/cli/wizards/providerWizard.js +152 -205
- package/src/cli/wizards/providerWizard.tsx +266 -0
- package/src/cli/wizards/searchWizard.tsx +47 -0
- package/src/cli/wizards/ttsWizard.js +4 -3
- package/src/cli/wizards/ttsWizard.tsx +61 -0
- package/src/config/configDefaults.js +25 -0
- package/src/config/configDefaults.ts +33 -0
- package/src/config/configDiagnostics.js +117 -0
- package/src/config/configDiagnostics.ts +166 -0
- package/src/config/configFile.js +52 -32
- package/src/config/configFile.ts +633 -0
- package/src/config/configManagement.js +126 -0
- package/src/config/configManagement.ts +159 -0
- package/src/config/configService.js +66 -11
- package/src/config/configService.ts +554 -0
- package/src/config/numericLimits.js +61 -0
- package/src/config/numericLimits.ts +80 -0
- package/src/config.ts +109 -0
- package/src/context/agentHome.ts +11 -0
- package/src/context/archive.ts +144 -0
- package/src/context/budgetPlan.js +54 -0
- package/src/context/budgetPlan.ts +88 -0
- package/src/context/compact.js +179 -106
- package/src/context/compact.ts +584 -0
- package/src/context/contextBundle.js +119 -0
- package/src/context/contextBundle.ts +176 -0
- package/src/context/contextLock.ts +84 -0
- package/src/context/microCompactLite.ts +218 -0
- package/src/context/persistContext.ts +177 -0
- package/src/context/reactiveCompact.js +19 -4
- package/src/context/reactiveCompact.ts +240 -0
- package/src/context/recentDirs.ts +42 -0
- package/src/context/sessionPath.ts +206 -0
- package/src/context/sessionRegistry.js +8 -2
- package/src/context/sessionRegistry.ts +414 -0
- package/src/context/snipCompact.ts +163 -0
- package/src/context/tokenCounter.ts +106 -0
- package/src/context/workspaceIndex.js +2 -2
- package/src/context/workspaceIndex.ts +751 -0
- package/src/entry.compiled.tsx +20 -0
- package/src/harness/claimValidation.js +9 -0
- package/src/harness/claimValidation.ts +22 -0
- package/src/harness/evidenceLedger.js +92 -0
- package/src/harness/evidenceLedger.ts +96 -0
- package/src/harness/stallGuard.js +60 -0
- package/src/harness/stallGuard.ts +81 -0
- package/src/humanIntervention.js +129 -0
- package/src/humanIntervention.ts +216 -0
- package/src/infra/verification.ts +295 -0
- package/src/irreversible/rules.js +216 -0
- package/src/irreversible/rules.ts +261 -0
- package/src/llm/client.js +40 -3
- package/src/llm/client.ts +621 -0
- package/src/llm/promptCache.js +27 -0
- package/src/llm/promptCache.ts +41 -0
- package/src/llm/providers/anthropicFormat.js +41 -5
- package/src/llm/providers/anthropicFormat.ts +290 -0
- package/src/llm/providers/openaiFormat.js +186 -8
- package/src/llm/providers/openaiFormat.ts +428 -0
- package/src/loop.js +614 -63
- package/src/loop.ts +1454 -0
- package/src/main.js +67 -10
- package/src/main.tsx +344 -0
- package/src/memory/codec.js +204 -0
- package/src/memory/codec.ts +249 -0
- package/src/memory/constants.js +34 -0
- package/src/memory/constants.ts +50 -0
- package/src/memory/episodicMemory.js +684 -0
- package/src/memory/episodicMemory.ts +815 -0
- package/src/memory/fileStore.js +148 -0
- package/src/memory/fileStore.ts +184 -0
- package/src/memory/memoryService.js +570 -0
- package/src/memory/memoryService.ts +859 -0
- package/src/memory/migration.js +128 -0
- package/src/memory/migration.ts +160 -0
- package/src/memory/repository.js +789 -0
- package/src/memory/repository.ts +893 -0
- package/src/memory/retrieval.js +108 -0
- package/src/memory/retrieval.ts +145 -0
- package/src/memory/searchIndex.js +107 -0
- package/src/memory/searchIndex.ts +153 -0
- package/src/memory/topicView.js +82 -0
- package/src/memory/topicView.ts +95 -0
- package/src/memory/turnJournal.js +95 -0
- package/src/memory/turnJournal.ts +120 -0
- package/src/memory/types.js +2 -0
- package/src/memory/types.ts +186 -0
- package/src/messageQueue.ts +123 -0
- package/src/multimodal.js +128 -46
- package/src/multimodal.ts +349 -0
- package/src/plugin-sdk.js +21 -1
- package/src/plugin-sdk.ts +134 -0
- package/src/plugins/builtinPluginIds.js +47 -0
- package/src/plugins/builtinPluginIds.ts +51 -0
- package/src/plugins/builtinRegistry.ts +43 -0
- package/src/plugins/commandRouter.js +40 -26
- package/src/plugins/commandRouter.ts +261 -0
- package/src/plugins/hookEngine.ts +341 -0
- package/src/plugins/pluginApi.ts +133 -0
- package/src/plugins/pluginLifecycle.ts +34 -0
- package/src/plugins/pluginLoader.ts +147 -0
- package/src/plugins/pluginManifest.js +127 -0
- package/src/plugins/pluginManifest.ts +152 -0
- package/src/plugins/pluginRunner.js +47 -2
- package/src/plugins/pluginRunner.ts +207 -0
- package/src/plugins/transcript.ts +208 -0
- package/src/prompts/lint.js +169 -0
- package/src/prompts/lint.ts +220 -0
- package/src/prompts/pluginList.js +32 -16
- package/src/prompts/pluginList.ts +86 -0
- package/src/prompts/policyRegistry.js +318 -0
- package/src/prompts/policyRegistry.ts +358 -0
- package/src/prompts/projectInstructions.js +24 -0
- package/src/prompts/projectInstructions.ts +81 -0
- package/src/prompts/promptAssembly.js +311 -202
- package/src/prompts/promptAssembly.ts +510 -0
- package/src/prompts/skillList.js +106 -45
- package/src/prompts/skillList.ts +198 -0
- package/src/prompts/system.js +31 -6
- package/src/prompts/system.ts +81 -0
- package/src/runtime/agentRuntimeContext.js +53 -0
- package/src/runtime/agentRuntimeContext.ts +112 -0
- package/src/session/runAgentTurn.js +71 -12
- package/src/session/runAgentTurn.ts +181 -0
- package/src/session/runTurn.js +29 -0
- package/src/session/runTurn.ts +160 -0
- package/src/session/runtimeStore.js +149 -0
- package/src/session/runtimeStore.ts +197 -0
- package/src/session/sessionState.js +79 -3
- package/src/session/sessionState.ts +134 -0
- package/src/toolGate.ts +50 -0
- package/src/tools/advisor/advisor.js +76 -15
- package/src/tools/advisor/advisor.ts +512 -0
- package/src/tools/agent/agent.js +245 -29
- package/src/tools/agent/agent.ts +837 -0
- package/src/tools/agent/agentTaskService.js +60 -0
- package/src/tools/agent/agentTaskService.ts +113 -0
- package/src/tools/agent/agentTypes.js +20 -8
- package/src/tools/agent/agentTypes.ts +94 -0
- package/src/tools/agent/coordinator.ts +111 -0
- package/src/tools/agent/worktree.ts +296 -0
- package/src/tools/ask/ask.js +17 -0
- package/src/tools/ask/ask.ts +122 -0
- package/src/tools/bash/attachImage.js +223 -0
- package/src/tools/bash/attachImage.ts +281 -0
- package/src/tools/bash/bash.js +132 -14
- package/src/tools/bash/bash.ts +642 -0
- package/src/tools/bash/semantics.ts +103 -0
- package/src/tools/bash/warnings.js +17 -11
- package/src/tools/bash/warnings.ts +117 -0
- package/src/tools/capabilities.js +42 -0
- package/src/tools/capabilities.ts +69 -0
- package/src/tools/computeruse/accessibilityPermission.js +261 -0
- package/src/tools/computeruse/accessibilityPermission.ts +294 -0
- package/src/tools/computeruse/axSearch.js +96 -0
- package/src/tools/computeruse/axSearch.ts +120 -0
- package/src/tools/computeruse/axSnap.js +272 -0
- package/src/tools/computeruse/axSnap.ts +349 -0
- package/src/tools/computeruse/capabilityLabels.js +95 -0
- package/src/tools/computeruse/capabilityLabels.ts +115 -0
- package/src/tools/computeruse/computerUseService.js +2235 -0
- package/src/tools/computeruse/computerUseService.ts +2686 -0
- package/src/tools/computeruse/computeruse.js +2742 -0
- package/src/tools/computeruse/computeruse.ts +3265 -0
- package/src/tools/computeruse/diagnosticTrace.js +154 -0
- package/src/tools/computeruse/diagnosticTrace.ts +195 -0
- package/src/tools/computeruse/diagnostics.js +369 -0
- package/src/tools/computeruse/diagnostics.ts +453 -0
- package/src/tools/computeruse/grants.js +53 -0
- package/src/tools/computeruse/grants.ts +70 -0
- package/src/tools/computeruse/humanBoundary.js +159 -0
- package/src/tools/computeruse/humanBoundary.ts +199 -0
- package/src/tools/computeruse/intervention.js +77 -0
- package/src/tools/computeruse/intervention.ts +110 -0
- package/src/tools/computeruse/manualSchema.js +709 -0
- package/src/tools/computeruse/manualSchema.ts +916 -0
- package/src/tools/computeruse/observationImages.js +705 -0
- package/src/tools/computeruse/observationImages.ts +889 -0
- package/src/tools/computeruse/protocol.js +60 -0
- package/src/tools/computeruse/protocol.ts +515 -0
- package/src/tools/computeruse/telemetryLog.js +57 -0
- package/src/tools/computeruse/telemetryLog.ts +67 -0
- package/src/tools/computeruse/vision/appGuide.js +325 -0
- package/src/tools/computeruse/vision/appGuide.ts +482 -0
- package/src/tools/computeruse/vision/focus.js +291 -0
- package/src/tools/computeruse/vision/focus.ts +382 -0
- package/src/tools/computeruse/vision/frames.js +248 -0
- package/src/tools/computeruse/vision/frames.ts +329 -0
- package/src/tools/computeruse/vision/geometry.js +131 -0
- package/src/tools/computeruse/vision/geometry.ts +163 -0
- package/src/tools/computeruse/vision/imaging.js +201 -0
- package/src/tools/computeruse/vision/imaging.ts +281 -0
- package/src/tools/computeruse/vision/png.js +274 -0
- package/src/tools/computeruse/vision/png.ts +317 -0
- package/src/tools/computeruse/vision/raster.js +320 -0
- package/src/tools/computeruse/vision/raster.ts +408 -0
- package/src/tools/computeruse/vision/settle.js +131 -0
- package/src/tools/computeruse/vision/settle.ts +220 -0
- package/src/tools/computeruse/vision/telemetry.js +196 -0
- package/src/tools/computeruse/vision/telemetry.ts +241 -0
- package/src/tools/computeruse/visionOcr.js +115 -0
- package/src/tools/computeruse/visionOcr.ts +128 -0
- package/src/tools/computeruse/waitFor.js +237 -0
- package/src/tools/computeruse/waitFor.ts +291 -0
- package/src/tools/download/download.js +10 -6
- package/src/tools/download/download.ts +187 -0
- package/src/tools/edit/edit.js +30 -18
- package/src/tools/edit/edit.ts +349 -0
- package/src/tools/edit/multi-edit.js +25 -14
- package/src/tools/edit/multi-edit.ts +223 -0
- package/src/tools/effectiveToolSet.js +73 -0
- package/src/tools/effectiveToolSet.ts +91 -0
- package/src/tools/genimage/genimage.ts +257 -0
- package/src/tools/genspeech/genspeech.ts +83 -0
- package/src/tools/glob/glob.js +176 -29
- package/src/tools/glob/glob.ts +283 -0
- package/src/tools/grep/grep.js +61 -19
- package/src/tools/grep/grep.ts +239 -0
- package/src/tools/grep/rgPath.ts +206 -0
- package/src/tools/index.js +99 -5
- package/src/tools/index.ts +293 -0
- package/src/tools/lsp/config.js +113 -0
- package/src/tools/lsp/config.ts +113 -0
- package/src/tools/lsp/lsp.js +256 -0
- package/src/tools/lsp/lsp.ts +274 -0
- package/src/tools/lsp/service.js +303 -0
- package/src/tools/lsp/service.ts +296 -0
- package/src/tools/memory/memory.js +520 -0
- package/src/tools/memory/memory.ts +625 -0
- package/src/tools/memory/policy.js +53 -0
- package/src/tools/memory/policy.ts +72 -0
- package/src/tools/notebook/notebookEdit.js +114 -0
- package/src/tools/notebook/notebookEdit.ts +126 -0
- package/src/tools/powershell/powershell.js +210 -0
- package/src/tools/powershell/powershell.ts +239 -0
- package/src/tools/presentresult/presentresult.js +145 -0
- package/src/tools/presentresult/presentresult.ts +156 -0
- package/src/tools/read/read.js +371 -163
- package/src/tools/read/read.ts +558 -0
- package/src/tools/shared/diffSummary.js +44 -0
- package/src/tools/shared/diffSummary.ts +63 -0
- package/src/tools/shared/fileActivity.js +51 -0
- package/src/tools/shared/fileActivity.ts +66 -0
- package/src/tools/shared/fileMutationBus.js +13 -0
- package/src/tools/shared/fileMutationBus.ts +14 -0
- package/src/tools/shared/fileState.js +73 -65
- package/src/tools/shared/fileState.ts +145 -0
- package/src/tools/shared/fileUtils.js +81 -4
- package/src/tools/shared/fileUtils.ts +504 -0
- package/src/tools/shared/killTree.ts +50 -0
- package/src/tools/shared/permanentDeleteGuard.js +26 -0
- package/src/tools/shared/permanentDeleteGuard.ts +34 -0
- package/src/tools/shared/schemas.ts +20 -0
- package/src/tools/shared/toolOutputStore.js +97 -0
- package/src/tools/shared/toolOutputStore.ts +110 -0
- package/src/tools/skill/skill.js +86 -0
- package/src/tools/skill/skill.ts +94 -0
- package/src/tools/snip/snip.js +67 -0
- package/src/tools/snip/snip.ts +80 -0
- package/src/tools/tasks/backgroundTaskService.js +275 -0
- package/src/tools/tasks/backgroundTaskService.ts +369 -0
- package/src/tools/tasks/processEvidence.js +24 -0
- package/src/tools/tasks/processEvidence.ts +34 -0
- package/src/tools/tasks/taskManagement.js +163 -0
- package/src/tools/tasks/taskManagement.ts +180 -0
- package/src/tools/tasks/taskOutput.js +113 -0
- package/src/tools/tasks/taskOutput.ts +145 -0
- package/src/tools/tasks/taskStop.js +74 -0
- package/src/tools/tasks/taskStop.ts +99 -0
- package/src/tools/tasks/taskStore.js +46 -0
- package/src/tools/tasks/taskStore.ts +77 -0
- package/src/tools/terminal/terminalCapture.js +167 -0
- package/src/tools/terminal/terminalCapture.ts +179 -0
- package/src/tools/trash/trash.js +233 -0
- package/src/tools/trash/trash.ts +283 -0
- package/src/tools/types.js +1 -1
- package/src/tools/types.ts +24 -0
- package/src/tools/webbrowser/agentBrowserPath.ts +286 -0
- package/src/tools/webbrowser/browser.js +214 -11
- package/src/tools/webbrowser/browser.ts +293 -0
- package/src/tools/webbrowser/browserControl.js +153 -48
- package/src/tools/webbrowser/browserControl.ts +262 -0
- package/src/tools/webbrowser/browserDaemon.js +147 -39
- package/src/tools/webbrowser/browserDaemon.ts +270 -0
- package/src/tools/webbrowser/browserOutcome.js +273 -0
- package/src/tools/webbrowser/browserOutcome.ts +306 -0
- package/src/tools/webbrowser/browserRunRegistry.js +150 -0
- package/src/tools/webbrowser/browserRunRegistry.ts +179 -0
- package/src/tools/webbrowser/browserService.js +20 -6
- package/src/tools/webbrowser/browserService.ts +56 -0
- package/src/tools/webbrowser/browserStream.js +30 -1
- package/src/tools/webbrowser/browserStream.ts +216 -0
- package/src/tools/webbrowser/browserTabs.js +143 -0
- package/src/tools/webbrowser/browserTabs.ts +210 -0
- package/src/tools/webbrowser/webbrowser.js +831 -150
- package/src/tools/webbrowser/webbrowser.ts +1537 -0
- package/src/tools/webfetch/preapproved.ts +274 -0
- package/src/tools/webfetch/webfetch.js +24 -23
- package/src/tools/webfetch/webfetch.ts +383 -0
- package/src/tools/websearch/websearch.js +12 -11
- package/src/tools/websearch/websearch.ts +292 -0
- package/src/tools/worktree/sessionWorktree.js +128 -0
- package/src/tools/worktree/sessionWorktree.ts +158 -0
- package/src/tools/write/write.js +27 -44
- package/src/tools/write/write.ts +136 -0
- package/src/types.ts +960 -0
- package/src/ui/App.js +216 -12
- package/src/ui/App.tsx +879 -0
- package/src/ui/AskQuestion.tsx +300 -0
- package/src/ui/InputBox.js +77 -1
- package/src/ui/InputBox.tsx +567 -0
- package/src/ui/LiveArea.tsx +344 -0
- package/src/ui/MessageList.js +27 -6
- package/src/ui/MessageList.tsx +559 -0
- package/src/ui/ModelPicker.tsx +107 -0
- package/src/ui/QueuedMessagesBanner.tsx +51 -0
- package/src/ui/Root.js +6 -8
- package/src/ui/Root.tsx +100 -0
- package/src/ui/SessionPicker.tsx +169 -0
- package/src/ui/StatusLine.js +22 -1
- package/src/ui/StatusLine.tsx +145 -0
- package/src/ui/WorkspacePicker.tsx +84 -0
- package/src/ui/advisorCommand.js +54 -0
- package/src/ui/advisorCommand.ts +63 -0
- package/src/ui/computerCommand.js +19 -0
- package/src/ui/computerCommand.ts +35 -0
- package/src/ui/figures.ts +45 -0
- package/src/ui/hooks/askSlot.ts +112 -0
- package/src/ui/hooks/useChat.js +271 -7
- package/src/ui/hooks/useChat.ts +1578 -0
- package/src/ui/hooks/useInputHistory.ts +235 -0
- package/src/ui/hooks/usePasteHandler.ts +220 -0
- package/src/ui/hooks/useTerminalSize.ts +41 -0
- package/src/ui/hooks/useTextBuffer.ts +108 -0
- package/src/ui/hooks/useTokenUsage.ts +44 -0
- package/src/ui/liveViewport.ts +189 -0
- package/src/ui/markdown.ts +230 -0
- package/src/ui/textBuffer.ts +239 -0
- package/src/ui/theme.ts +35 -0
- package/src/utils/clipboardImage.js +358 -0
- package/src/utils/clipboardImage.ts +420 -0
- package/src/utils/debugLog.ts +50 -0
- package/src/utils/greenRoot.ts +35 -0
- package/src/utils/packageRoot.ts +40 -0
- package/src/utils/pathContains.js +28 -0
- package/src/utils/pathContains.ts +31 -0
- package/src/utils/resourcePaths.ts +71 -0
- package/src/utils/stripThink.ts +49 -0
- package/src/utils/zodToJson.ts +73 -0
- package/src/web/browserBridge.js +365 -66
- package/src/web/browserBridge.ts +672 -0
- package/src/web/protocol.ts +1804 -0
- package/vendor/computer-use/arm64-darwin/cu-helperd +0 -0
- package/workflows/ppt.yaml +13 -2
- package/workflows/wechat-reading-shots.yaml +315 -0
- package/workflows/word.yaml +11 -1
- package/skills/docx/scripts/office/helpers/__pycache__/__init__.cpython-314.pyc +0 -0
- package/skills/docx/scripts/office/helpers/__pycache__/merge_runs.cpython-314.pyc +0 -0
- package/skills/docx/scripts/office/helpers/__pycache__/simplify_redlines.cpython-314.pyc +0 -0
- package/skills/docx/scripts/office/validators/__pycache__/__init__.cpython-314.pyc +0 -0
- package/skills/docx/scripts/office/validators/__pycache__/base.cpython-314.pyc +0 -0
- package/skills/docx/scripts/office/validators/__pycache__/docx.cpython-314.pyc +0 -0
- package/skills/docx/scripts/office/validators/__pycache__/pptx.cpython-314.pyc +0 -0
- package/skills/docx/scripts/office/validators/__pycache__/redlining.cpython-314.pyc +0 -0
- package/skills/image-stats/scripts/__pycache__/image_stats.cpython-314.pyc +0 -0
- package/skills/image-stats/scripts/__pycache__/image_stats_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/assemble_material.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/assemble_material_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/chart_sanity_check.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/check_language_policy.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/check_ui_artifact.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/check_ui_artifact_pptx_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/deck_pages_check.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/deck_pages_check_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/deck_visual_style_check.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/deck_visual_style_check_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/design_budget_check.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/design_budget_check_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/design_repair_hints.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/design_repair_hints_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/design_style_check.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/design_style_check_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/draft_coverage_check.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/extract_source_assets.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/extract_source_assets_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/generated_image_review_collect.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/generated_image_review_collect_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/image_plan_lint.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/image_plan_lint_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/image_prompt_rewrite.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/image_prompt_rewrite_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/layout_balance.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/layout_balance_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/normalize_design_json.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/normalize_design_json_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/plan_source_layout.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/plan_source_layout_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/resolve_source_hint.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/resolve_source_hint_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/resolve_source_input.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/resolve_source_input_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/scaffold_ui_artifact.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/source_usage_check.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/source_usage_check_test.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/text_readability_pixel.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/visual_quality_gate.cpython-314.pyc +0 -0
- package/skills/ui-design/scripts/__pycache__/visual_quality_gate_test.cpython-314.pyc +0 -0
- /package/skills/ai-image/scripts/{generate_test.mjs → generate.selftest.mjs} +0 -0
- /package/skills/ai-image/scripts/{generate_from_plan_test.mjs → generate_from_plan.selftest.mjs} +0 -0
|
@@ -0,0 +1,621 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ============================================================
|
|
3
|
+
* src/llm/client.ts —— LLM 客户端(流式,双协议)
|
|
4
|
+
* ------------------------------------------------------------
|
|
5
|
+
* 这个文件做一件事:把"消息历史 + 可用工具"发给 LLM,把"流式响应"逐 chunk
|
|
6
|
+
* yield 回上层。它是整个 agent 系统里唯一会 fetch() 远端 API 的地方。
|
|
7
|
+
*
|
|
8
|
+
* 支持两种协议格式(`provider.apiFormat`,缺省 'openai'):
|
|
9
|
+
* - OpenAI Chat Completions —— DeepSeek、月之暗面、智谱、火山、OpenRouter 都按它兼容
|
|
10
|
+
* - Anthropic Messages API —— ⚠️ 未经真实端点验证,见 providers/anthropicFormat.ts 头注
|
|
11
|
+
*
|
|
12
|
+
* 格式相关的请求体构造 + SSE chunk 解析拆到 `providers/openaiFormat.ts` /
|
|
13
|
+
* `providers/anthropicFormat.ts`;本文件只保留协议无关的共享骨架:重试/自适应退避/
|
|
14
|
+
* 空闲看门狗/中断处理——这部分历史上踩过真实事故(P0-1),两种格式共用同一份,
|
|
15
|
+
* 不重复实现以免两份retry逻辑逐渐漂移出不一致的行为。
|
|
16
|
+
*
|
|
17
|
+
* SSE(Server-Sent Events)是什么?
|
|
18
|
+
* 一种很简单的流式协议:服务器逐行返回 "data: <json>\n\n",
|
|
19
|
+
* OpenAI 用 "data: [DONE]\n\n" 表示结束;Anthropic 用一个 data payload 里
|
|
20
|
+
* `type:"message_stop"` 的事件表示结束(两者的 data 载荷都自解释,行拆分逻辑
|
|
21
|
+
* 不需要为格式差异特殊处理,见 anthropicFormat.ts 头注)。
|
|
22
|
+
*
|
|
23
|
+
* 本文件不依赖任何 npm 包(fetch 是 Bun/Node 20+ 内置)。
|
|
24
|
+
* ============================================================
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import type { LlmStreamEvent, Message, Provider, Tool } from '../types.js';
|
|
28
|
+
import { dlog } from '../utils/debugLog.js';
|
|
29
|
+
import {
|
|
30
|
+
buildOpenAIRequest,
|
|
31
|
+
createOpenAISSEParser,
|
|
32
|
+
isImageInputRejected,
|
|
33
|
+
isMaxTokensRejected,
|
|
34
|
+
isPromptCacheKeyRejected,
|
|
35
|
+
isResponseFormatUnsupported,
|
|
36
|
+
} from './providers/openaiFormat.js';
|
|
37
|
+
import { promptMaterialHash, shortCacheScope } from './promptCache.js';
|
|
38
|
+
import { buildAnthropicRequest, createAnthropicSSEParser } from './providers/anthropicFormat.js';
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* 记住"这个 baseURL::model 不支持 response_format.json_schema",避免每次都先撞 400。
|
|
42
|
+
* 单进程内有效;每个 -p 子进程是全新进程 → 首个结构化调用吃一次 400 再退化,可接受。
|
|
43
|
+
* 只对 openai 格式有意义(anthropic 无此就地退化概念,见下)。
|
|
44
|
+
*/
|
|
45
|
+
const rfUnsupported = new Set<string>();
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* 记住"这个 baseURL::model 拒绝我们发的 max_tokens",避免每次都先撞 400。
|
|
49
|
+
* 与 rfUnsupported 同理,单进程内有效。绝大多数 provider 接受 max_tokens(实测豆包到
|
|
50
|
+
* ≥131072 仍正常),这只是给个别挑剔 provider 的兜底,命中后该进程不再发 max_tokens。
|
|
51
|
+
*/
|
|
52
|
+
const maxTokensUnsupported = new Set<string>();
|
|
53
|
+
const promptCacheKeyUnsupported = new Set<string>();
|
|
54
|
+
|
|
55
|
+
/** 调试日志用的请求自增号(vote 并发多 voter 时靠它在日志里区分是哪一路)。 */
|
|
56
|
+
let reqCounter = 0;
|
|
57
|
+
|
|
58
|
+
// ============================================================
|
|
59
|
+
// 重试 + 超时(无网络重试 → 整条 IM 链卡死;这是 P0-1 的根治)
|
|
60
|
+
// ------------------------------------------------------------
|
|
61
|
+
// 背景:之前 fetch 直接 await,没有 timeout 也没有重试。
|
|
62
|
+
// 后果:① 断网 1 帧流式 60+ chunk 报废;② 429/5xx 直接抛错;
|
|
63
|
+
// ③ 401/403 错误文案不带"key 失效"提示;④ gateway 桥接 stream-json 后
|
|
64
|
+
// 子进程挂起会整条 IM 通道 dir 锁卡死。
|
|
65
|
+
//
|
|
66
|
+
// 设计:
|
|
67
|
+
// - 自适应退避(拥塞控制式):进程级档位 0.5→1→2→5→10→30→60s,失败逐级抬高、
|
|
68
|
+
// 成功(拿到 200)立即回落到最小值;封顶 60s(超 1min 就不是拥塞、是真死,无谓再等)。
|
|
69
|
+
// 跨请求记忆当前拥塞程度——连续失败不会从头傻试,刚成功又不会被旧拥塞拖慢。
|
|
70
|
+
// - 5xx / 408 / 425 / 429 / 网络错误全部重试
|
|
71
|
+
// - 401 / 403 / 404 等 4xx 不重试(重试无意义)+ 给清晰"key 失效"提示
|
|
72
|
+
// - 429 优先解析 Retry-After 头(秒),封顶 60s
|
|
73
|
+
// - 空闲 watchdog 兜底卡死(语义见 IDLE_TIMEOUT_MS 注释——是【空闲】不是【总时长】)
|
|
74
|
+
// - 用户 signal 触发的 abort 立即退出,不重试
|
|
75
|
+
// - 流式响应中途中断(reader.read reject)走同一个错误路径
|
|
76
|
+
// ============================================================
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* 流式空闲超时(毫秒):自上一个字节到达起算,超过仍无任何新数据才判死。
|
|
80
|
+
* 可用 env `MINIMAL_AGENT_IDLE_TIMEOUT_MS` 覆盖。
|
|
81
|
+
*
|
|
82
|
+
* ⚠️ 语义是【空闲】而非【总时长】:重思考模型(MiniMax-M3/DeepSeek-R1 等)
|
|
83
|
+
* 带大上下文的流式响应总时长可以远超 2 分钟,但只要还在持续吐 chunk
|
|
84
|
+
* (reasoning_delta 也算)就不该掐。v0.9.x 之前用 AbortSignal.timeout 对
|
|
85
|
+
* 整个请求硬超时 120s——design 类长步骤被恰好 120s 掐死、重试必然同败,是 bug。
|
|
86
|
+
*/
|
|
87
|
+
const IDLE_TIMEOUT_MS = (() => {
|
|
88
|
+
const v = Number(process.env.MINIMAL_AGENT_IDLE_TIMEOUT_MS);
|
|
89
|
+
return Number.isFinite(v) && v > 0 ? v : 120_000;
|
|
90
|
+
})();
|
|
91
|
+
|
|
92
|
+
interface RetryConfig {
|
|
93
|
+
maxAttempts: number;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* 网络/5xx/429 的重试预算(单次请求最多尝试几次)。配合下面的自适应档位,
|
|
98
|
+
* 默认 6 次足以「骑过一次网络拥塞波」:实测某些 provider 端点(如 api.minimax.chat)
|
|
99
|
+
* 经路存在分钟级拥塞波——TLS 握手在 0.15s 与 6.8s 间剧烈摆动,坏的时候连接直接 RST
|
|
100
|
+
* (Bun fetch 报 "Unable to connect")。不稳网可用 env `MINIMAL_AGENT_LLM_MAX_RETRIES`
|
|
101
|
+
* 加码(如 10)。注意:现在「等多久」由自适应档位决定,本值只管「试几次」。
|
|
102
|
+
*/
|
|
103
|
+
const DEFAULT_RETRY: RetryConfig = {
|
|
104
|
+
maxAttempts: (() => {
|
|
105
|
+
const v = Number(process.env.MINIMAL_AGENT_LLM_MAX_RETRIES);
|
|
106
|
+
return Number.isFinite(v) && v >= 1 ? Math.floor(v) : 6;
|
|
107
|
+
})(),
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
// ------------------------------------------------------------
|
|
111
|
+
// 自适应退避(拥塞控制式 AIMD 的简化版)
|
|
112
|
+
// ------------------------------------------------------------
|
|
113
|
+
// 动机:各家模型的 token plan 现在普遍会拥堵。固定指数退避有两个毛病:
|
|
114
|
+
// ① 每个新请求都从最小值重来 → 端点正堵时仍一上来就猛打、徒增失败;
|
|
115
|
+
// ② 退避封顶才 8s → 真遇上分钟级拥塞波,整段重试落在同一坏波里仍败。
|
|
116
|
+
// 对策:一个【进程级档位】记忆「当前网络有多堵」:
|
|
117
|
+
// - 每次重试前取当前档位延时(带抖动)并抬高一档(失败 = 乘性增);
|
|
118
|
+
// - 任一请求拿到 200 → 档位立即清零回最小值(成功 = 立即恢复,不永久拉长);
|
|
119
|
+
// - 档位封顶 60s:超过 1 分钟就不是「拥塞波」而是端点真死,再等无意义,让其 honest 失败。
|
|
120
|
+
// 跨请求共享:A 请求把档位顶到 30s,紧接着 B 请求重试就从 30s 起(别再傻试),
|
|
121
|
+
// 直到某次成功才回落。-p 子进程各自全新 → 从最小档位起,互不影响。
|
|
122
|
+
|
|
123
|
+
/** 退避档位(毫秒):0.5→1→2→5→10→30→60s。近似 log 曲线的「几个档位」,封顶 60s。 */
|
|
124
|
+
const BACKOFF_TIERS_MS = [500, 1000, 2000, 5000, 10_000, 30_000, 60_000];
|
|
125
|
+
|
|
126
|
+
/** 进程级当前档位索引(跨请求记忆当前拥塞程度;成功清零,失败抬高)。 */
|
|
127
|
+
let backoffTier = 0;
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* 取「这次重试该等多久」:当前档位延时 + ±15% 抖动(防 vote/parallel 并发请求
|
|
131
|
+
* 同步重试踩同一波),随后把档位抬高一档(封顶最后一档)。
|
|
132
|
+
* retryAfterMs 有值(429 的 Retry-After 头)时听服务器的(仍封顶 60s、仍抬档)。
|
|
133
|
+
*/
|
|
134
|
+
function nextRetryDelayMs(retryAfterMs?: number): number {
|
|
135
|
+
const tierMs = BACKOFF_TIERS_MS[Math.min(backoffTier, BACKOFF_TIERS_MS.length - 1)];
|
|
136
|
+
backoffTier = Math.min(backoffTier + 1, BACKOFF_TIERS_MS.length - 1);
|
|
137
|
+
if (retryAfterMs !== undefined) return Math.min(retryAfterMs, 60_000);
|
|
138
|
+
const jittered = tierMs * (0.85 + Math.random() * 0.3);
|
|
139
|
+
return Math.min(Math.round(jittered), 60_000);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** 任一请求成功(拿到 200)→ 拥塞已缓解,档位立即回落到最小值。 */
|
|
143
|
+
function resetBackoff(): void {
|
|
144
|
+
backoffTier = 0;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** 仅供测试:读取/重置进程级档位(生产代码勿调)。 */
|
|
148
|
+
export function _getBackoffTier(): number {
|
|
149
|
+
return backoffTier;
|
|
150
|
+
}
|
|
151
|
+
export function _resetBackoffTier(): void {
|
|
152
|
+
backoffTier = 0;
|
|
153
|
+
}
|
|
154
|
+
/** 仅供测试:暴露纯退避计算,供单测验证档位曲线/封顶/Retry-After(不触网)。 */
|
|
155
|
+
export function _nextRetryDelayMs(retryAfterMs?: number): number {
|
|
156
|
+
return nextRetryDelayMs(retryAfterMs);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** 可重试的 HTTP 状态码。4xx 除 408/425/429 外不重试(重试无意义)。 */
|
|
160
|
+
function isRetryableStatus(status: number): boolean {
|
|
161
|
+
return status === 408 || status === 425 || status === 429 || (status >= 500 && status < 600);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** 鉴权类错误:明确告诉用户"key 失效/过期/无权访问",避免重试无意义。 */
|
|
165
|
+
function isClientAuthError(status: number): boolean {
|
|
166
|
+
return status === 401 || status === 403;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** 解析 Retry-After 头(秒数字符串),封顶 60s。无效/缺省返回 undefined。 */
|
|
170
|
+
function parseRetryAfter(resp: Response): number | undefined {
|
|
171
|
+
const h = resp.headers.get('retry-after');
|
|
172
|
+
if (!h) return undefined;
|
|
173
|
+
const sec = parseFloat(h);
|
|
174
|
+
if (!Number.isFinite(sec) || sec < 0) return undefined;
|
|
175
|
+
return Math.min(sec * 1000, 60_000);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* 构造带正确 .name 的中断错误。
|
|
180
|
+
*
|
|
181
|
+
* 为什么需要这个而不是直接 `new Error('aborted')`:loop.ts 靠 `e.name === 'AbortError'`
|
|
182
|
+
* 判断"这是用户主动中断",走优雅的 interrupted 收尾(不显示红色错误)。普通 `new Error(...)`
|
|
183
|
+
* 的 `.name` 是 `'Error'`,会让 loop.ts 把真实的用户中断误判成普通 LLM 报错
|
|
184
|
+
* (`code:'llm_error'`,红色错误)。本文件里所有"确认是用户 signal 触发的 abort"都必须
|
|
185
|
+
* 用这个构造,保持 `.name` 与真实 DOMException AbortError 一致。
|
|
186
|
+
*/
|
|
187
|
+
function makeAbortError(message = 'aborted'): Error {
|
|
188
|
+
const err = new Error(message);
|
|
189
|
+
err.name = 'AbortError';
|
|
190
|
+
return err;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/** 可中断的 sleep:用户 signal 触发 abort 立刻 reject,不阻塞退出。 */
|
|
194
|
+
function sleep(ms: number, signal?: AbortSignal): Promise<void> {
|
|
195
|
+
return new Promise((resolve, reject) => {
|
|
196
|
+
if (signal?.aborted) {
|
|
197
|
+
reject(makeAbortError());
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
const timer = setTimeout(() => {
|
|
201
|
+
signal?.removeEventListener('abort', onAbort);
|
|
202
|
+
resolve();
|
|
203
|
+
}, ms);
|
|
204
|
+
const onAbort = () => {
|
|
205
|
+
clearTimeout(timer);
|
|
206
|
+
reject(makeAbortError());
|
|
207
|
+
};
|
|
208
|
+
signal?.addEventListener('abort', onAbort, { once: true });
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/** 构造带诊断的 Error;401/403 走专属文案,避免"重试 5 次才发现是 key 失效"。 */
|
|
213
|
+
function buildChatError(resp: Response, body: string): Error {
|
|
214
|
+
const status = resp.status;
|
|
215
|
+
const head = `[${status} ${resp.statusText}]`;
|
|
216
|
+
if (isClientAuthError(status)) {
|
|
217
|
+
return new Error(
|
|
218
|
+
`LLM 鉴权失败 ${head}:API key 可能失效、过期或无权访问 model "${resp.url.split('/').pop()}".\n` +
|
|
219
|
+
`请检查 .env / ~/.minimal-agent/config.json 的模型配置。\n` +
|
|
220
|
+
`原始错误:${body.slice(0, 500)}`,
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
return new Error(`LLM 请求失败 ${head}:${body.slice(0, 500)}`);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* 空闲 watchdog:kick() 重置计时;IDLE_TIMEOUT_MS 内没有任何 kick 才 abort。
|
|
228
|
+
* 与用户 signal 组合(AbortSignal.any,Node 20.3+/Bun 内置),任一触发即中断。
|
|
229
|
+
* 调用方契约:fetch 等待期自动计时;流式期间每收到一个 chunk 调一次 kick();
|
|
230
|
+
* 结束(无论成败)调 dispose() 清掉定时器。
|
|
231
|
+
*/
|
|
232
|
+
export function createIdleWatchdog(
|
|
233
|
+
userSignal: AbortSignal | undefined,
|
|
234
|
+
timeoutMs: number = IDLE_TIMEOUT_MS, // 测试注入用;生产走默认
|
|
235
|
+
): {
|
|
236
|
+
signal: AbortSignal;
|
|
237
|
+
kick: () => void;
|
|
238
|
+
dispose: () => void;
|
|
239
|
+
} {
|
|
240
|
+
const ctrl = new AbortController();
|
|
241
|
+
let timer: ReturnType<typeof setTimeout> | null = null;
|
|
242
|
+
const arm = (): void => {
|
|
243
|
+
if (timer) clearTimeout(timer);
|
|
244
|
+
timer = setTimeout(() => {
|
|
245
|
+
ctrl.abort(
|
|
246
|
+
new Error(
|
|
247
|
+
`LLM 流式响应空闲超时:${timeoutMs}ms 内未收到任何新数据(总时长不限,只判空闲;可用 MINIMAL_AGENT_IDLE_TIMEOUT_MS 调整)`,
|
|
248
|
+
),
|
|
249
|
+
);
|
|
250
|
+
}, timeoutMs);
|
|
251
|
+
// 不阻止进程退出(Bun/Node 均支持 unref)
|
|
252
|
+
(timer as { unref?: () => void }).unref?.();
|
|
253
|
+
};
|
|
254
|
+
arm();
|
|
255
|
+
return {
|
|
256
|
+
signal: userSignal ? AbortSignal.any([userSignal, ctrl.signal]) : ctrl.signal,
|
|
257
|
+
kick: arm,
|
|
258
|
+
dispose: () => {
|
|
259
|
+
if (timer) clearTimeout(timer);
|
|
260
|
+
timer = null;
|
|
261
|
+
},
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* 调用 LLM 并以流的形式 yield 事件。协议格式由 `provider.apiFormat` 决定
|
|
267
|
+
* (缺省 'openai');两种格式共用本函数的重试/退避/看门狗骨架,请求体构造和
|
|
268
|
+
* SSE 解析分别委托给 providers/openaiFormat.ts / providers/anthropicFormat.ts。
|
|
269
|
+
*
|
|
270
|
+
* @param provider 选好的 provider(含 baseURL / apiKey / model / apiFormat)
|
|
271
|
+
* @param messages 完整的对话历史
|
|
272
|
+
* @param tools 工具列表(每个工具需要带 jsonSchema 才能给 LLM)
|
|
273
|
+
* @param signal AbortSignal,UI 按 Ctrl+C 时中断
|
|
274
|
+
*
|
|
275
|
+
* @yield LlmStreamEvent: text_delta / tool_call_delta / done
|
|
276
|
+
*
|
|
277
|
+
* 用法示例:
|
|
278
|
+
* ```
|
|
279
|
+
* for await (const ev of chat({ provider, messages, tools })) {
|
|
280
|
+
* if (ev.type === 'text_delta') process.stdout.write(ev.delta);
|
|
281
|
+
* }
|
|
282
|
+
* ```
|
|
283
|
+
*/
|
|
284
|
+
export async function* chat(args: {
|
|
285
|
+
provider: Provider;
|
|
286
|
+
messages: Message[];
|
|
287
|
+
tools: Tool[];
|
|
288
|
+
signal?: AbortSignal;
|
|
289
|
+
/** ADR-05: provider-native structured output。OpenAI / MiniMax / DeepSeek 兼容。
|
|
290
|
+
* 传入后 LLM 输出保证符合 schema;不兼容 provider 会报 400,runner 透传。
|
|
291
|
+
* Anthropic 格式的 provider 目前不支持(见 providers/anthropicFormat.ts 头注),
|
|
292
|
+
* 传了会在发请求前直接抛错,不浪费一次网络往返。*/
|
|
293
|
+
responseFormat?: {
|
|
294
|
+
type: 'json_schema';
|
|
295
|
+
json_schema: { name: string; schema: Record<string, unknown>; strict?: boolean };
|
|
296
|
+
};
|
|
297
|
+
promptCache?: import('../types.js').PromptCacheContext;
|
|
298
|
+
}): AsyncGenerator<LlmStreamEvent, void, void> {
|
|
299
|
+
const { provider, messages, tools, signal, responseFormat, promptCache } = args;
|
|
300
|
+
const format: 'openai' | 'anthropic' = provider.apiFormat === 'anthropic' ? 'anthropic' : 'openai';
|
|
301
|
+
|
|
302
|
+
if (format === 'anthropic' && responseFormat) {
|
|
303
|
+
throw new Error(
|
|
304
|
+
'output_schema 结构化输出目前不支持 Anthropic Messages 格式的 provider' +
|
|
305
|
+
'(Anthropic 的强制工具输出模式与 OpenAI 的 response_format.json_schema 形状不同,尚未实现映射)。' +
|
|
306
|
+
'请改用 OpenAI 格式的 provider,或去掉这一步的 output_schema。',
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// 过滤并清洁 messages 数组,剥离 timestamp, id, _ephemeral 等本地渲染元数据。
|
|
311
|
+
// 两种格式共用同一份清洗后的内部形状——Anthropic 分支的翻译(system 摘除/
|
|
312
|
+
// tool_calls→tool_use 等)在 buildAnthropicRequest 内部做,不在这里做。
|
|
313
|
+
let cleanedMessages: Message[] = messages
|
|
314
|
+
.filter((m) => !(m.role === 'system' && (m as { _ephemeral?: boolean })._ephemeral === true))
|
|
315
|
+
.map((m) => {
|
|
316
|
+
const { id, timestamp, attachments, browserReferences, display_metadata, ...rest } = m as any;
|
|
317
|
+
return rest;
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
const includeUsage = process.env.MINIMAL_AGENT_DISABLE_USAGE !== '1';
|
|
321
|
+
const rfKey = `${provider.baseURL}::${provider.model}`;
|
|
322
|
+
|
|
323
|
+
// OpenAI 分支专属的「就地退化」状态;Anthropic 无此概念,两个 flag 恒为 false,
|
|
324
|
+
// 下面的退化 if 分支天然不会进入(不需要额外的 format 判断散落各处)。
|
|
325
|
+
let wantResponseFormat =
|
|
326
|
+
format === 'openai' && responseFormat !== undefined && !rfUnsupported.has(rfKey);
|
|
327
|
+
let wantMaxTokens =
|
|
328
|
+
format === 'openai' &&
|
|
329
|
+
typeof provider.maxTokens === 'number' &&
|
|
330
|
+
provider.maxTokens > 0 &&
|
|
331
|
+
!maxTokensUnsupported.has(rfKey);
|
|
332
|
+
let imagesStripped = false;
|
|
333
|
+
let wantPromptCacheKey =
|
|
334
|
+
format === 'openai' &&
|
|
335
|
+
provider.promptCache?.mode === 'session-key' &&
|
|
336
|
+
!!promptCache?.scopeKey &&
|
|
337
|
+
!promptCacheKeyUnsupported.has(rfKey);
|
|
338
|
+
|
|
339
|
+
const stripImagesFromMessages = (msgs: Message[]): Message[] =>
|
|
340
|
+
msgs.map((m) => {
|
|
341
|
+
const c = (m as { content?: unknown }).content;
|
|
342
|
+
if (!Array.isArray(c)) return m;
|
|
343
|
+
const textOnly = c.filter((p: any) => p.type === 'text');
|
|
344
|
+
const newContent = textOnly.length > 0
|
|
345
|
+
? textOnly.map((p: any) => ({ type: 'text' as const, text: p.text }))
|
|
346
|
+
: [{ type: 'text' as const, text: '[图片内容未发送:当前模型不支持图片输入]' }];
|
|
347
|
+
return { ...m, content: newContent } as Message;
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
// 空闲 watchdog:每个 attempt 一只(重试前换新);fetch 等待期自动计时,
|
|
351
|
+
// 流式期间每个 chunk kick 一次。用户 signal 与 watchdog 任一触发即中断。
|
|
352
|
+
let watchdog = createIdleWatchdog(signal);
|
|
353
|
+
|
|
354
|
+
const postChat = (): Promise<Response> => {
|
|
355
|
+
const req =
|
|
356
|
+
format === 'anthropic'
|
|
357
|
+
? buildAnthropicRequest({ provider, cleanedMessages, tools })
|
|
358
|
+
: buildOpenAIRequest({
|
|
359
|
+
provider,
|
|
360
|
+
cleanedMessages,
|
|
361
|
+
tools,
|
|
362
|
+
withResponseFormat: wantResponseFormat,
|
|
363
|
+
withMaxTokens: wantMaxTokens,
|
|
364
|
+
includeUsage,
|
|
365
|
+
promptCache,
|
|
366
|
+
withPromptCacheKey: wantPromptCacheKey,
|
|
367
|
+
responseFormat,
|
|
368
|
+
});
|
|
369
|
+
return fetch(req.url, { method: 'POST', headers: req.headers, body: req.body, signal: watchdog.signal });
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
const reqId = ++reqCounter;
|
|
373
|
+
dlog('llm', `req#${reqId} POST →`, {
|
|
374
|
+
model: provider.model,
|
|
375
|
+
format,
|
|
376
|
+
rf: wantResponseFormat,
|
|
377
|
+
maxTokens: wantMaxTokens ? provider.maxTokens : undefined,
|
|
378
|
+
msgs: cleanedMessages.length,
|
|
379
|
+
tools: tools.length,
|
|
380
|
+
promptCacheMode: provider.promptCache?.mode ?? 'auto',
|
|
381
|
+
promptCacheScope: shortCacheScope(promptCache?.scopeKey),
|
|
382
|
+
systemPromptHash: promptMaterialHash(
|
|
383
|
+
cleanedMessages
|
|
384
|
+
.filter((message) => message.role === 'system')
|
|
385
|
+
.map((message) => message.content),
|
|
386
|
+
),
|
|
387
|
+
toolSchemaHash: promptMaterialHash(
|
|
388
|
+
tools.map((tool) => ({
|
|
389
|
+
name: tool.name,
|
|
390
|
+
description: typeof tool.description === 'function' ? tool.description() : tool.description,
|
|
391
|
+
parameters: tool.parameters,
|
|
392
|
+
})),
|
|
393
|
+
),
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
// 重试循环:每个 attempt 内部可能触发"response_format 不支持 → 去掉"的本地退化
|
|
397
|
+
// (本地退化不算重试,仍属同一 attempt 的"拿到 200"路径的一部分;只 openai 分支会真的
|
|
398
|
+
// 进入这两个退化 if)。只有"网络/超时/5xx/429"才跨 attempt 重试,4xx 业务错误直接抛
|
|
399
|
+
// (401/403 走 buildChatError 的专属文案,不浪费 5 次重试)
|
|
400
|
+
let resp: Response | undefined;
|
|
401
|
+
let lastErr: Error | undefined;
|
|
402
|
+
|
|
403
|
+
// 网络错误统一收尾(主路径 + 两条退化路径共用,防退避策略三处漂移):
|
|
404
|
+
// 用户 abort → 抛;置 lastErr + dlog;额度用尽 → 'break';否则退避后 'retry'。
|
|
405
|
+
const handleNetworkError = async (
|
|
406
|
+
e: unknown,
|
|
407
|
+
attempt: number,
|
|
408
|
+
label: string,
|
|
409
|
+
): Promise<'break' | 'retry'> => {
|
|
410
|
+
if (signal?.aborted) throw makeAbortError();
|
|
411
|
+
const err = e as Error;
|
|
412
|
+
lastErr = new Error(`LLM 网络错误 [${err.name ?? 'NetworkError'}]:${err.message}`);
|
|
413
|
+
dlog('llm', `req#${reqId} attempt ${attempt} ${label}:`, err.message);
|
|
414
|
+
if (attempt >= DEFAULT_RETRY.maxAttempts) return 'break';
|
|
415
|
+
const delay = nextRetryDelayMs();
|
|
416
|
+
dlog('llm', `req#${reqId} retry in ${delay}ms (attempt ${attempt + 1}/${DEFAULT_RETRY.maxAttempts}, tier=${backoffTier})`);
|
|
417
|
+
await sleep(delay, signal);
|
|
418
|
+
return 'retry';
|
|
419
|
+
};
|
|
420
|
+
|
|
421
|
+
for (let attempt = 1; attempt <= DEFAULT_RETRY.maxAttempts; attempt++) {
|
|
422
|
+
// 用户按 ESC/Ctrl+C 触发的 abort:立刻退出,不重试
|
|
423
|
+
if (signal?.aborted) {
|
|
424
|
+
throw makeAbortError();
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// 每个 attempt 换一只新 watchdog(上一只可能已超时 abort,不能复用)
|
|
428
|
+
watchdog.dispose();
|
|
429
|
+
watchdog = createIdleWatchdog(signal);
|
|
430
|
+
|
|
431
|
+
try {
|
|
432
|
+
resp = await postChat();
|
|
433
|
+
watchdog.kick(); // 响应头到达 = 有进展
|
|
434
|
+
} catch (e) {
|
|
435
|
+
// 1) 用户主动 abort → handleNetworkError 里立刻抛
|
|
436
|
+
// 2) AbortSignal.timeout 触发的超时(Fetch 空闲超时还没回)
|
|
437
|
+
// 3) 其它网络错误(ECONNRESET / fetch failed / DNS 失败)→ 全部按"可重试"处理
|
|
438
|
+
if ((await handleNetworkError(e, attempt, 'failed (network)')) === 'break') break;
|
|
439
|
+
continue;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// resp.ok = true 直接跳出循环
|
|
443
|
+
if (resp.ok) break;
|
|
444
|
+
|
|
445
|
+
// resp !ok:本地退化路径(response_format / max_tokens 不被支持 → 去掉重发,不算重试;
|
|
446
|
+
// 仅 openai 分支——wantResponseFormat/wantMaxTokens 在 anthropic 分支恒为 false)
|
|
447
|
+
let errText = await resp.text().catch(() => '');
|
|
448
|
+
|
|
449
|
+
if (wantPromptCacheKey && isPromptCacheKeyRejected(resp.status, errText)) {
|
|
450
|
+
promptCacheKeyUnsupported.add(rfKey);
|
|
451
|
+
wantPromptCacheKey = false;
|
|
452
|
+
dlog('llm', `req#${reqId} prompt_cache_key unsupported; downgraded`, {
|
|
453
|
+
model: provider.model,
|
|
454
|
+
});
|
|
455
|
+
try {
|
|
456
|
+
resp = await postChat();
|
|
457
|
+
watchdog.kick();
|
|
458
|
+
} catch (e) {
|
|
459
|
+
if ((await handleNetworkError(e, attempt, 'cache-key fallback fetch failed')) === 'break') break;
|
|
460
|
+
continue;
|
|
461
|
+
}
|
|
462
|
+
if (resp.ok) break;
|
|
463
|
+
errText = await resp.text().catch(() => '');
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// 退化①:response_format.json_schema 不支持
|
|
467
|
+
if (wantResponseFormat && isResponseFormatUnsupported(resp.status, errText)) {
|
|
468
|
+
// 记住该 model 不支持,单进程内后续结构化调用直接跳过 response_format(不再撞 400)。
|
|
469
|
+
rfUnsupported.add(rfKey);
|
|
470
|
+
console.warn(
|
|
471
|
+
`[minimal-agent] provider model "${provider.model}" 不支持 response_format.json_schema,` +
|
|
472
|
+
`已退化为 prompt 引导 + 容错解析(结构化输出的硬保证降级为尽力而为)。`,
|
|
473
|
+
);
|
|
474
|
+
wantResponseFormat = false; // 本 attempt 内继续,关闭 response_format
|
|
475
|
+
// 在同一 attempt 内再发一次(不计重试)
|
|
476
|
+
try {
|
|
477
|
+
resp = await postChat();
|
|
478
|
+
watchdog.kick();
|
|
479
|
+
} catch (e) {
|
|
480
|
+
// 退化二次 fetch 也网络错:把错误外提到外层重试循环
|
|
481
|
+
if ((await handleNetworkError(e, attempt, 'fallback fetch failed')) === 'break') break;
|
|
482
|
+
continue;
|
|
483
|
+
}
|
|
484
|
+
if (resp.ok) break;
|
|
485
|
+
// 仍 !ok:刷新 errText,让下面 max_tokens 退化看到最新报错(两种退化可叠加)
|
|
486
|
+
errText = await resp.text().catch(() => '');
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// 退化②:max_tokens 不被该 provider 接受(极少数;豆包实测接受到 ≥131072)→ 去掉重发
|
|
490
|
+
if (wantMaxTokens && isMaxTokensRejected(resp.status, errText)) {
|
|
491
|
+
maxTokensUnsupported.add(rfKey);
|
|
492
|
+
console.warn(
|
|
493
|
+
`[minimal-agent] provider model "${provider.model}" 拒绝 max_tokens=${provider.maxTokens},` +
|
|
494
|
+
`已去掉 max_tokens 重发(回落该 provider 默认输出上限,长输出可能被截断)。`,
|
|
495
|
+
);
|
|
496
|
+
wantMaxTokens = false; // 本 attempt 内继续,关闭 max_tokens
|
|
497
|
+
try {
|
|
498
|
+
resp = await postChat();
|
|
499
|
+
watchdog.kick();
|
|
500
|
+
} catch (e) {
|
|
501
|
+
if ((await handleNetworkError(e, attempt, 'fallback fetch failed')) === 'break') break;
|
|
502
|
+
continue;
|
|
503
|
+
}
|
|
504
|
+
if (resp.ok) break;
|
|
505
|
+
errText = await resp.text().catch(() => '');
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
// 退化③:模型不支持图片输入 → 剥离所有 image_url/video_url 重发(自动降级为纯文本)。
|
|
509
|
+
// 适用于新旧格式(openai/anthropic),仅触发一次,避免无限循环。
|
|
510
|
+
if (!imagesStripped && isImageInputRejected(resp.status, errText)) {
|
|
511
|
+
const hasImages = cleanedMessages.some((m) => {
|
|
512
|
+
const c = (m as { content?: unknown }).content;
|
|
513
|
+
return Array.isArray(c) && c.some((p: any) => p.type === 'image_url' || p.type === 'video_url');
|
|
514
|
+
});
|
|
515
|
+
if (hasImages) {
|
|
516
|
+
console.warn(
|
|
517
|
+
`[minimal-agent] provider model "${provider.model}" 不支持图片输入,` +
|
|
518
|
+
`已剥离图片重发(仅保留文本内容)。如该模型实际支持视觉,请在模型设置中确认。`,
|
|
519
|
+
);
|
|
520
|
+
cleanedMessages = stripImagesFromMessages(cleanedMessages);
|
|
521
|
+
imagesStripped = true;
|
|
522
|
+
try {
|
|
523
|
+
resp = await postChat();
|
|
524
|
+
watchdog.kick();
|
|
525
|
+
} catch (e) {
|
|
526
|
+
if ((await handleNetworkError(e, attempt, 'fallback fetch failed')) === 'break') break;
|
|
527
|
+
continue;
|
|
528
|
+
}
|
|
529
|
+
if (resp.ok) break;
|
|
530
|
+
errText = await resp.text().catch(() => '');
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
// 4xx 业务错误:不重试,直接抛(401/403 走 buildChatError 专属文案)
|
|
535
|
+
if (!isRetryableStatus(resp.status)) {
|
|
536
|
+
throw buildChatError(resp, errText);
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
// 5xx / 408 / 425 / 429:可重试
|
|
540
|
+
lastErr = buildChatError(resp, errText);
|
|
541
|
+
dlog('llm', `req#${reqId} attempt ${attempt} failed [${resp.status}]:`, errText.slice(0, 200));
|
|
542
|
+
if (attempt >= DEFAULT_RETRY.maxAttempts) break;
|
|
543
|
+
|
|
544
|
+
// 429 优先用 Retry-After 头,否则走自适应档位退避
|
|
545
|
+
const retryAfterMs = parseRetryAfter(resp);
|
|
546
|
+
const delay = nextRetryDelayMs(retryAfterMs);
|
|
547
|
+
dlog('llm', `req#${reqId} retry in ${delay}ms (attempt ${attempt + 1}/${DEFAULT_RETRY.maxAttempts}, tier=${backoffTier}, retryAfter=${retryAfterMs ?? 'tier'})`);
|
|
548
|
+
await sleep(delay, signal);
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// 重试预算用尽仍没拿到 200:把最后一次的 Error 抛出去(注意不重置档位——还在堵)
|
|
552
|
+
if (!resp || !resp.ok) {
|
|
553
|
+
throw lastErr ?? new Error('LLM 请求失败:超过最大重试次数');
|
|
554
|
+
}
|
|
555
|
+
// 拿到 200 = 端点可达、拥塞已缓解 → 档位立即回落到最小值(成功就恢复,不永久拉长)。
|
|
556
|
+
resetBackoff();
|
|
557
|
+
if (!resp.body) {
|
|
558
|
+
throw new Error('LLM 响应没有 body(provider 可能不支持流式?)');
|
|
559
|
+
}
|
|
560
|
+
// fetch 已返回且 ok:若日志卡在这条之前 = fetch 阶段挂起(无超时);卡在这条之后无 done = 流中途挂起。
|
|
561
|
+
dlog('llm', `req#${reqId} streaming begin (status ${resp.status})`);
|
|
562
|
+
|
|
563
|
+
// SSE 解析:把 ReadableStream 切成一行行 "data: ..." 交给 format 对应的 parser。
|
|
564
|
+
// 两种格式的 data 载荷都自解释(OpenAI 用字面量 "[DONE]";Anthropic 的 JSON payload
|
|
565
|
+
// 自带 type 字段),行拆分逻辑本身不需要为格式差异分叉。
|
|
566
|
+
const reader = resp.body.getReader();
|
|
567
|
+
const decoder = new TextDecoder();
|
|
568
|
+
/** 临时缓冲:HTTP chunk 不一定按行切,要自己拼 */
|
|
569
|
+
let buffer = '';
|
|
570
|
+
const parser = format === 'anthropic' ? createAnthropicSSEParser() : createOpenAISSEParser();
|
|
571
|
+
|
|
572
|
+
try {
|
|
573
|
+
while (true) {
|
|
574
|
+
// 每次 read 前检查 abort:用户按 ESC/Ctrl+C 时 signal.aborted 为 true。
|
|
575
|
+
// 这样即使 fetch 还没完成,loop 也能及时退出而不是一直等到网络超时。
|
|
576
|
+
if (signal?.aborted) {
|
|
577
|
+
yield { type: 'done', stopReason: 'aborted' };
|
|
578
|
+
return;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
const { value, done } = await reader.read();
|
|
582
|
+
watchdog.kick(); // 有新数据(reasoning/text/心跳都算进展)→ 重置空闲计时
|
|
583
|
+
if (done) break;
|
|
584
|
+
|
|
585
|
+
// 每个 chunk 处理完后也检查 abort,让快速连按能及时退出。
|
|
586
|
+
if (signal?.aborted) {
|
|
587
|
+
yield { type: 'done', stopReason: 'aborted' };
|
|
588
|
+
return;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
buffer += decoder.decode(value, { stream: true });
|
|
592
|
+
|
|
593
|
+
// SSE 用 "\n\n" 分事件,按行拆 buffer
|
|
594
|
+
let lineEnd: number;
|
|
595
|
+
while ((lineEnd = buffer.indexOf('\n')) !== -1) {
|
|
596
|
+
const line = buffer.slice(0, lineEnd).trim();
|
|
597
|
+
buffer = buffer.slice(lineEnd + 1);
|
|
598
|
+
if (!line || !line.startsWith('data:')) continue;
|
|
599
|
+
|
|
600
|
+
const dataStr = line.slice(5).trim(); // 去掉 "data:" 前缀
|
|
601
|
+
const events = parser.handleData(dataStr);
|
|
602
|
+
for (const ev of events) {
|
|
603
|
+
yield ev;
|
|
604
|
+
if (ev.type === 'done') {
|
|
605
|
+
dlog('llm', `req#${reqId} done`, ev);
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
// 流没有显式发终止事件(OpenAI [DONE] / Anthropic message_stop)就结束了——
|
|
613
|
+
// 用 parser 累积的兜底状态补一个 done。
|
|
614
|
+
const fallback = parser.buildFallbackDone();
|
|
615
|
+
dlog('llm', `req#${reqId} done (stream end, no terminal event)`, fallback);
|
|
616
|
+
yield fallback;
|
|
617
|
+
} finally {
|
|
618
|
+
watchdog.dispose();
|
|
619
|
+
reader.releaseLock?.();
|
|
620
|
+
}
|
|
621
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
const SCOPE_VERSION = 'minimal-agent:prompt-cache:v1';
|
|
3
|
+
/** Build a provider-safe cache routing key without exposing local identity. */
|
|
4
|
+
export function createPromptCacheContext(conversationKey, namespace = 'main') {
|
|
5
|
+
const scopeKey = createHash('sha256')
|
|
6
|
+
.update(SCOPE_VERSION)
|
|
7
|
+
.update('\0')
|
|
8
|
+
.update(namespace)
|
|
9
|
+
.update('\0')
|
|
10
|
+
.update(conversationKey)
|
|
11
|
+
.digest('hex');
|
|
12
|
+
return { scopeKey, conversationKey };
|
|
13
|
+
}
|
|
14
|
+
export function derivePromptCacheContext(parent, namespace) {
|
|
15
|
+
return parent ? createPromptCacheContext(parent.conversationKey, namespace) : undefined;
|
|
16
|
+
}
|
|
17
|
+
export function normalizePromptCacheMode(value) {
|
|
18
|
+
return value === 'session-key' || value === 'disabled' ? value : 'auto';
|
|
19
|
+
}
|
|
20
|
+
export function shortCacheScope(scopeKey) {
|
|
21
|
+
return scopeKey?.slice(0, 12);
|
|
22
|
+
}
|
|
23
|
+
/** Diagnostic fingerprint only; callers must never log the source material. */
|
|
24
|
+
export function promptMaterialHash(value) {
|
|
25
|
+
const serialized = typeof value === 'string' ? value : JSON.stringify(value);
|
|
26
|
+
return createHash('sha256').update(serialized ?? '').digest('hex').slice(0, 12);
|
|
27
|
+
}
|