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,41 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
|
|
3
|
+
import type { PromptCacheContext, PromptCacheMode } from '../types.js';
|
|
4
|
+
|
|
5
|
+
const SCOPE_VERSION = 'minimal-agent:prompt-cache:v1';
|
|
6
|
+
|
|
7
|
+
/** Build a provider-safe cache routing key without exposing local identity. */
|
|
8
|
+
export function createPromptCacheContext(
|
|
9
|
+
conversationKey: string,
|
|
10
|
+
namespace = 'main',
|
|
11
|
+
): PromptCacheContext {
|
|
12
|
+
const scopeKey = createHash('sha256')
|
|
13
|
+
.update(SCOPE_VERSION)
|
|
14
|
+
.update('\0')
|
|
15
|
+
.update(namespace)
|
|
16
|
+
.update('\0')
|
|
17
|
+
.update(conversationKey)
|
|
18
|
+
.digest('hex');
|
|
19
|
+
return { scopeKey, conversationKey };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function derivePromptCacheContext(
|
|
23
|
+
parent: PromptCacheContext | undefined,
|
|
24
|
+
namespace: string,
|
|
25
|
+
): PromptCacheContext | undefined {
|
|
26
|
+
return parent ? createPromptCacheContext(parent.conversationKey, namespace) : undefined;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function normalizePromptCacheMode(value: unknown): PromptCacheMode {
|
|
30
|
+
return value === 'session-key' || value === 'disabled' ? value : 'auto';
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function shortCacheScope(scopeKey: string | undefined): string | undefined {
|
|
34
|
+
return scopeKey?.slice(0, 12);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Diagnostic fingerprint only; callers must never log the source material. */
|
|
38
|
+
export function promptMaterialHash(value: unknown): string {
|
|
39
|
+
const serialized = typeof value === 'string' ? value : JSON.stringify(value);
|
|
40
|
+
return createHash('sha256').update(serialized ?? '').digest('hex').slice(0, 12);
|
|
41
|
+
}
|
|
@@ -171,9 +171,24 @@ export function buildAnthropicRequest(args) {
|
|
|
171
171
|
export function createAnthropicSSEParser() {
|
|
172
172
|
let stopReason = 'unknown';
|
|
173
173
|
let usagePromptTokens;
|
|
174
|
+
let usageCachedTokens;
|
|
175
|
+
let usageCacheWriteTokens;
|
|
176
|
+
let promptCacheUsage = { reported: false };
|
|
177
|
+
const buildDoneEvent = () => ({
|
|
178
|
+
type: 'done',
|
|
179
|
+
stopReason,
|
|
180
|
+
promptTokens: usagePromptTokens,
|
|
181
|
+
...(promptCacheUsage.reported
|
|
182
|
+
? {
|
|
183
|
+
...(usageCachedTokens !== undefined ? { cachedPromptTokens: usageCachedTokens } : {}),
|
|
184
|
+
...(usageCacheWriteTokens !== undefined ? { cacheWriteTokens: usageCacheWriteTokens } : {}),
|
|
185
|
+
promptCacheUsage,
|
|
186
|
+
}
|
|
187
|
+
: {}),
|
|
188
|
+
});
|
|
174
189
|
return {
|
|
175
190
|
buildFallbackDone() {
|
|
176
|
-
return
|
|
191
|
+
return buildDoneEvent();
|
|
177
192
|
},
|
|
178
193
|
handleData(dataStr) {
|
|
179
194
|
let data;
|
|
@@ -185,9 +200,30 @@ export function createAnthropicSSEParser() {
|
|
|
185
200
|
}
|
|
186
201
|
switch (data.type) {
|
|
187
202
|
case 'message_start': {
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
203
|
+
// ⚠️ Anthropic 的 input_tokens **不含**缓存部分(与 OpenAI 相反):
|
|
204
|
+
// 总 prompt = input_tokens + cache_read_input_tokens + cache_creation_input_tokens
|
|
205
|
+
// 旧 Anthropic 配置或兼容代理可能返回缓存字段。当前客户端不主动发送
|
|
206
|
+
// cache_control;这里仅兼容解析真实返回,不能据此宣称已开启显式缓存。
|
|
207
|
+
const usage = data.message?.usage;
|
|
208
|
+
const inputTokens = usage?.input_tokens;
|
|
209
|
+
const cacheRead = usage?.cache_read_input_tokens;
|
|
210
|
+
const cacheWrite = usage?.cache_creation_input_tokens;
|
|
211
|
+
if (typeof cacheRead === 'number')
|
|
212
|
+
usageCachedTokens = cacheRead;
|
|
213
|
+
if (typeof cacheWrite === 'number')
|
|
214
|
+
usageCacheWriteTokens = cacheWrite;
|
|
215
|
+
if (typeof inputTokens === 'number') {
|
|
216
|
+
usagePromptTokens =
|
|
217
|
+
inputTokens +
|
|
218
|
+
(typeof cacheRead === 'number' ? cacheRead : 0) +
|
|
219
|
+
(typeof cacheWrite === 'number' ? cacheWrite : 0);
|
|
220
|
+
}
|
|
221
|
+
promptCacheUsage = {
|
|
222
|
+
reported: typeof cacheRead === 'number' || typeof cacheWrite === 'number',
|
|
223
|
+
...(usagePromptTokens !== undefined ? { promptTokens: usagePromptTokens } : {}),
|
|
224
|
+
...(usageCachedTokens !== undefined ? { cacheReadTokens: usageCachedTokens } : {}),
|
|
225
|
+
...(usageCacheWriteTokens !== undefined ? { cacheWriteTokens: usageCacheWriteTokens } : {}),
|
|
226
|
+
};
|
|
191
227
|
return [];
|
|
192
228
|
}
|
|
193
229
|
case 'content_block_start': {
|
|
@@ -227,7 +263,7 @@ export function createAnthropicSSEParser() {
|
|
|
227
263
|
return [];
|
|
228
264
|
}
|
|
229
265
|
case 'message_stop':
|
|
230
|
-
return [
|
|
266
|
+
return [buildDoneEvent()];
|
|
231
267
|
case 'error':
|
|
232
268
|
// 中途错误事件(如 overloaded_error/529):不能静默结束流,必须让调用方看到失败——
|
|
233
269
|
// 抛出后由 client.ts 共享读取循环的 try/finally 传播给上层(与其它流中断失败同一路径)。
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ============================================================
|
|
3
|
+
* src/llm/providers/anthropicFormat.ts —— Anthropic Messages API 协议
|
|
4
|
+
* ------------------------------------------------------------
|
|
5
|
+
* ⚠️ 未经真实端点验证:本机没有任何 Anthropic 兼容的 key/代理,这里的请求体形状
|
|
6
|
+
* 和 SSE 事件字段完全照 Anthropic 官方文档(Messages API,2023-06-01)实现,配了
|
|
7
|
+
* 手写的 SSE 录像单测,但没有做过一次真实 round-trip。拿到真实 key 后务必先跑一次
|
|
8
|
+
* 真实对话冒烟测试,见 test/llm.anthropicFormat.test.ts 头注。
|
|
9
|
+
*
|
|
10
|
+
* 与 openaiFormat.ts 的分工一致:这里只做「内部 Message[]/Tool[] ⇄ Anthropic 协议
|
|
11
|
+
* 形状」的翻译 + SSE 逐行解析;重试/退避/空闲看门狗等协议无关骨架留在 client.ts。
|
|
12
|
+
*
|
|
13
|
+
* v1 明确不支持(写清楚,不装作支持):
|
|
14
|
+
* - output_schema 结构化输出(Anthropic 的强制工具输出模式跟 OpenAI 的
|
|
15
|
+
* response_format.json_schema 形状不同)——client.ts 在请求前直接抛错。
|
|
16
|
+
* - OpenAI 特有的「400 文本嗅探退化」(不支持 response_format/max_tokens 时自动
|
|
17
|
+
* 摘字段重发)无 Anthropic 对应逻辑;Anthropic 的 400 直接暴露。
|
|
18
|
+
* - 视频输入(ChatContentPart 的 video_url):Anthropic 无对应协议,退化成文本占位。
|
|
19
|
+
* ============================================================
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import type { ChatContentPart, LlmStreamEvent, Message, PromptCacheUsage, Provider, Tool } from '../../types.js';
|
|
23
|
+
|
|
24
|
+
const ANTHROPIC_VERSION = '2023-06-01';
|
|
25
|
+
|
|
26
|
+
interface AnthropicContentBlock {
|
|
27
|
+
type: string;
|
|
28
|
+
[key: string]: unknown;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface AnthropicMessage {
|
|
32
|
+
role: 'user' | 'assistant';
|
|
33
|
+
content: AnthropicContentBlock[];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function toAnthropicUserContent(content: string | ChatContentPart[]): AnthropicContentBlock[] {
|
|
37
|
+
if (typeof content === 'string') return [{ type: 'text', text: content }];
|
|
38
|
+
return content.map((part): AnthropicContentBlock => {
|
|
39
|
+
if (part.type === 'text') return { type: 'text', text: part.text };
|
|
40
|
+
if (part.type === 'image_url') {
|
|
41
|
+
const url = part.image_url.url;
|
|
42
|
+
if (url.startsWith('data:')) {
|
|
43
|
+
const match = /^data:([^;]+);base64,([\s\S]+)$/.exec(url);
|
|
44
|
+
if (match) return { type: 'image', source: { type: 'base64', media_type: match[1], data: match[2] } };
|
|
45
|
+
}
|
|
46
|
+
return { type: 'image', source: { type: 'url', url } };
|
|
47
|
+
}
|
|
48
|
+
// video_url:Anthropic 无对应协议,退化成文本占位(v1 范围限制)。
|
|
49
|
+
return { type: 'text', text: '[视频内容未发送:Anthropic 格式的 provider 暂不支持视频输入]' };
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* 内部 Message[](OpenAI 形状:role 含 tool、assistant.tool_calls、tool.tool_call_id)
|
|
55
|
+
* → Anthropic 形状:{system, messages}。
|
|
56
|
+
*
|
|
57
|
+
* 关键翻译规则:
|
|
58
|
+
* - 所有 system 角色内容拼成顶层 system 字符串,从 messages 数组里摘除。
|
|
59
|
+
* - tool 角色 → {role:'user', content:[{type:'tool_result', tool_use_id, content}]}。
|
|
60
|
+
* - assistant 的 tool_calls[i] → {type:'tool_use', id, name, input}(arguments 是内部的
|
|
61
|
+
* JSON 字符串,Anthropic 的 input 要解析后的对象;parse 失败兜底空对象)。
|
|
62
|
+
* - **必须合并连续同角色消息**:Anthropic 强制 user/assistant 严格交替,1:1 硬映射会因
|
|
63
|
+
* 「assistant 调工具 → tool 结果(→user) → 下一条也是 user 的正常对话」之类的连续同角色
|
|
64
|
+
* 序列而 400。
|
|
65
|
+
*/
|
|
66
|
+
export function toAnthropicMessages(messages: readonly Message[]): { system: string; messages: AnthropicMessage[] } {
|
|
67
|
+
const systemParts: string[] = [];
|
|
68
|
+
const pieces: AnthropicMessage[] = [];
|
|
69
|
+
|
|
70
|
+
for (const m of messages) {
|
|
71
|
+
if (m.role === 'system') {
|
|
72
|
+
systemParts.push(m.content);
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
if (m.role === 'user') {
|
|
76
|
+
pieces.push({ role: 'user', content: toAnthropicUserContent(m.content) });
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
if (m.role === 'assistant') {
|
|
80
|
+
const content: AnthropicContentBlock[] = [];
|
|
81
|
+
if (m.content) content.push({ type: 'text', text: m.content });
|
|
82
|
+
for (const call of m.tool_calls ?? []) {
|
|
83
|
+
let input: unknown = {};
|
|
84
|
+
try {
|
|
85
|
+
input = JSON.parse(call.function.arguments || '{}');
|
|
86
|
+
} catch {
|
|
87
|
+
input = {};
|
|
88
|
+
}
|
|
89
|
+
content.push({ type: 'tool_use', id: call.id, name: call.function.name, input });
|
|
90
|
+
}
|
|
91
|
+
// 退化 assistant 消息(content 为 null/空字符串且没有 tool_calls——如模型只吐了
|
|
92
|
+
// reasoning 就被中断)整条跳过,而不是塞一个空文本块:Anthropic 拒绝空 text block
|
|
93
|
+
// ("text content blocks must be non-empty"),塞了会让下一轮请求直接 400。
|
|
94
|
+
if (content.length > 0) pieces.push({ role: 'assistant', content });
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
// role === 'tool'
|
|
98
|
+
pieces.push({ role: 'user', content: [{ type: 'tool_result', tool_use_id: m.tool_call_id, content: m.content }] });
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const merged: AnthropicMessage[] = [];
|
|
102
|
+
for (const piece of pieces) {
|
|
103
|
+
const last = merged[merged.length - 1];
|
|
104
|
+
if (last && last.role === piece.role) last.content.push(...piece.content);
|
|
105
|
+
else merged.push({ role: piece.role, content: [...piece.content] });
|
|
106
|
+
}
|
|
107
|
+
return { system: systemParts.join('\n\n'), messages: merged };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function toAnthropicTools(tools: Tool[]): Array<{ name: string; description: string; input_schema: object }> {
|
|
111
|
+
return tools.map((t) => ({
|
|
112
|
+
name: t.name,
|
|
113
|
+
description: typeof t.description === 'function' ? t.description() : t.description,
|
|
114
|
+
input_schema: t.parameters,
|
|
115
|
+
}));
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function looksLikeVersionedBase(url: string): boolean {
|
|
119
|
+
try {
|
|
120
|
+
const u = new URL(url);
|
|
121
|
+
const path = u.pathname.replace(/\/+$/, '');
|
|
122
|
+
if (path === '' || path === '/') return true;
|
|
123
|
+
const segments = path.split('/');
|
|
124
|
+
const last = segments[segments.length - 1];
|
|
125
|
+
return /^v\d+/.test(last);
|
|
126
|
+
} catch {
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function buildAnthropicUrl(provider: Provider): string {
|
|
132
|
+
// baseURL 是完整端点 URL(以 /v1/messages 结尾)。
|
|
133
|
+
// 向后兼容:旧格式 baseURL(根域名或版本化基础路径)自动补全 /v1/messages 后缀。
|
|
134
|
+
const url = provider.baseURL.trim().replace(/\/+$/, '');
|
|
135
|
+
if (url.endsWith('/chat/completions') || url.endsWith('/v1/messages')) return url;
|
|
136
|
+
if (url.length === 0) return url;
|
|
137
|
+
if (looksLikeVersionedBase(url)) {
|
|
138
|
+
console.warn(
|
|
139
|
+
`[minimal-agent] 旧格式 baseURL 检测:"${provider.baseURL}" 未包含端点后缀,` +
|
|
140
|
+
`已自动补全为 "${url}/v1/messages"。请在设置页更新为完整端点地址。`,
|
|
141
|
+
);
|
|
142
|
+
return `${url}/v1/messages`;
|
|
143
|
+
}
|
|
144
|
+
return url;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export interface AnthropicRequestArgs {
|
|
148
|
+
provider: Provider;
|
|
149
|
+
cleanedMessages: readonly Message[];
|
|
150
|
+
tools: Tool[];
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export function buildAnthropicRequest(args: AnthropicRequestArgs): { url: string; headers: Record<string, string>; body: string } {
|
|
154
|
+
const { provider, cleanedMessages, tools } = args;
|
|
155
|
+
const { system, messages } = toAnthropicMessages(cleanedMessages);
|
|
156
|
+
const anthropicTools = toAnthropicTools(tools);
|
|
157
|
+
const body: Record<string, unknown> = {
|
|
158
|
+
model: provider.model,
|
|
159
|
+
...(system ? { system } : {}),
|
|
160
|
+
messages,
|
|
161
|
+
// Anthropic 的 max_tokens 是必填字段;解析层(configService.ts::resolveProvider)已保证
|
|
162
|
+
// provider.maxTokens 一定有值(defaultMaxTokens() 兜底),这里直接透传不做协商退化——
|
|
163
|
+
// OpenAI 那套「不支持就摘掉重发」的 400 文本嗅探对 Anthropic 无对应逻辑,见文件头注。
|
|
164
|
+
max_tokens: provider.maxTokens,
|
|
165
|
+
stream: true,
|
|
166
|
+
};
|
|
167
|
+
// tools: [] 会被部分 provider 拒绝或产生意外行为——空数组直接不发这个字段。
|
|
168
|
+
if (anthropicTools.length > 0) body.tools = anthropicTools;
|
|
169
|
+
return {
|
|
170
|
+
url: buildAnthropicUrl(provider),
|
|
171
|
+
headers: {
|
|
172
|
+
'content-type': 'application/json',
|
|
173
|
+
'x-api-key': provider.apiKey,
|
|
174
|
+
'anthropic-version': ANTHROPIC_VERSION,
|
|
175
|
+
},
|
|
176
|
+
body: JSON.stringify(body),
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* 每个请求一个新实例;把逐条 SSE `data:` 行的内容转换成 LlmStreamEvent(0..N 个)。
|
|
182
|
+
*
|
|
183
|
+
* Anthropic 的 SSE 每帧是 `event: <type>` + `data: <json>` 两行,但 data 的 JSON payload
|
|
184
|
+
* 自身也带 `type` 字段(与 event 行的值相同)——client.ts 的共享读取骨架只识别 `data:`
|
|
185
|
+
* 开头的行(继承自 OpenAI 分支的既有约定),所以这里直接从 JSON payload 的 `.type` 分支,
|
|
186
|
+
* 不需要读 `event:` 行,共享骨架的行拆分逻辑不用为 Anthropic 特殊改动。
|
|
187
|
+
*/
|
|
188
|
+
export function createAnthropicSSEParser(): {
|
|
189
|
+
handleData(dataStr: string): LlmStreamEvent[];
|
|
190
|
+
/** 流意外中断(没收到显式 message_stop)时,用累积状态兜底发一个 done。 */
|
|
191
|
+
buildFallbackDone(): LlmStreamEvent;
|
|
192
|
+
} {
|
|
193
|
+
let stopReason: 'stop' | 'tool_calls' | 'length' | 'unknown' = 'unknown';
|
|
194
|
+
let usagePromptTokens: number | undefined;
|
|
195
|
+
let usageCachedTokens: number | undefined;
|
|
196
|
+
let usageCacheWriteTokens: number | undefined;
|
|
197
|
+
let promptCacheUsage: PromptCacheUsage = { reported: false };
|
|
198
|
+
const buildDoneEvent = (): LlmStreamEvent => ({
|
|
199
|
+
type: 'done',
|
|
200
|
+
stopReason,
|
|
201
|
+
promptTokens: usagePromptTokens,
|
|
202
|
+
...(promptCacheUsage.reported
|
|
203
|
+
? {
|
|
204
|
+
...(usageCachedTokens !== undefined ? { cachedPromptTokens: usageCachedTokens } : {}),
|
|
205
|
+
...(usageCacheWriteTokens !== undefined ? { cacheWriteTokens: usageCacheWriteTokens } : {}),
|
|
206
|
+
promptCacheUsage,
|
|
207
|
+
}
|
|
208
|
+
: {}),
|
|
209
|
+
});
|
|
210
|
+
return {
|
|
211
|
+
buildFallbackDone(): LlmStreamEvent {
|
|
212
|
+
return buildDoneEvent();
|
|
213
|
+
},
|
|
214
|
+
handleData(dataStr: string): LlmStreamEvent[] {
|
|
215
|
+
let data: any;
|
|
216
|
+
try {
|
|
217
|
+
data = JSON.parse(dataStr);
|
|
218
|
+
} catch {
|
|
219
|
+
return [];
|
|
220
|
+
}
|
|
221
|
+
switch (data.type) {
|
|
222
|
+
case 'message_start': {
|
|
223
|
+
// ⚠️ Anthropic 的 input_tokens **不含**缓存部分(与 OpenAI 相反):
|
|
224
|
+
// 总 prompt = input_tokens + cache_read_input_tokens + cache_creation_input_tokens
|
|
225
|
+
// 旧 Anthropic 配置或兼容代理可能返回缓存字段。当前客户端不主动发送
|
|
226
|
+
// cache_control;这里仅兼容解析真实返回,不能据此宣称已开启显式缓存。
|
|
227
|
+
const usage = data.message?.usage;
|
|
228
|
+
const inputTokens = usage?.input_tokens;
|
|
229
|
+
const cacheRead = usage?.cache_read_input_tokens;
|
|
230
|
+
const cacheWrite = usage?.cache_creation_input_tokens;
|
|
231
|
+
if (typeof cacheRead === 'number') usageCachedTokens = cacheRead;
|
|
232
|
+
if (typeof cacheWrite === 'number') usageCacheWriteTokens = cacheWrite;
|
|
233
|
+
if (typeof inputTokens === 'number') {
|
|
234
|
+
usagePromptTokens =
|
|
235
|
+
inputTokens +
|
|
236
|
+
(typeof cacheRead === 'number' ? cacheRead : 0) +
|
|
237
|
+
(typeof cacheWrite === 'number' ? cacheWrite : 0);
|
|
238
|
+
}
|
|
239
|
+
promptCacheUsage = {
|
|
240
|
+
reported: typeof cacheRead === 'number' || typeof cacheWrite === 'number',
|
|
241
|
+
...(usagePromptTokens !== undefined ? { promptTokens: usagePromptTokens } : {}),
|
|
242
|
+
...(usageCachedTokens !== undefined ? { cacheReadTokens: usageCachedTokens } : {}),
|
|
243
|
+
...(usageCacheWriteTokens !== undefined ? { cacheWriteTokens: usageCacheWriteTokens } : {}),
|
|
244
|
+
};
|
|
245
|
+
return [];
|
|
246
|
+
}
|
|
247
|
+
case 'content_block_start': {
|
|
248
|
+
// tool_use 块的 id/name 在 start 事件里一次性给出(不像 OpenAI 分片在 delta 里),
|
|
249
|
+
// 这里补发一条只带 id/name、不带 argumentsDelta 的 tool_call_delta 把它们「种」进
|
|
250
|
+
// loop.ts::mergeToolCallDelta 的累积槽;后续同 index 的 input_json_delta 只带参数增量。
|
|
251
|
+
const block = data.content_block;
|
|
252
|
+
if (block?.type === 'tool_use') {
|
|
253
|
+
return [{ type: 'tool_call_delta', index: data.index, id: block.id, name: block.name }];
|
|
254
|
+
}
|
|
255
|
+
return [];
|
|
256
|
+
}
|
|
257
|
+
case 'content_block_delta': {
|
|
258
|
+
const delta = data.delta;
|
|
259
|
+
if (delta?.type === 'text_delta') return [{ type: 'text_delta', delta: delta.text }];
|
|
260
|
+
if (delta?.type === 'input_json_delta') {
|
|
261
|
+
return [{ type: 'tool_call_delta', index: data.index, argumentsDelta: delta.partial_json }];
|
|
262
|
+
}
|
|
263
|
+
if (delta?.type === 'thinking_delta') {
|
|
264
|
+
return [{ type: 'reasoning_delta', field: 'reasoning', delta: delta.thinking }];
|
|
265
|
+
}
|
|
266
|
+
// signature_delta(扩展思维签名)等其它 delta 类型:v1 不处理,安全忽略。
|
|
267
|
+
return [];
|
|
268
|
+
}
|
|
269
|
+
case 'message_delta': {
|
|
270
|
+
// ⚠️ stop_reason 嵌套在 delta.stop_reason 下,不是 message_delta 顶层。
|
|
271
|
+
const sr = data.delta?.stop_reason;
|
|
272
|
+
if (sr === 'end_turn' || sr === 'stop_sequence') stopReason = 'stop';
|
|
273
|
+
else if (sr === 'tool_use') stopReason = 'tool_calls';
|
|
274
|
+
else if (sr === 'max_tokens') stopReason = 'length';
|
|
275
|
+
else if (sr) stopReason = 'unknown';
|
|
276
|
+
return [];
|
|
277
|
+
}
|
|
278
|
+
case 'message_stop':
|
|
279
|
+
return [buildDoneEvent()];
|
|
280
|
+
case 'error':
|
|
281
|
+
// 中途错误事件(如 overloaded_error/529):不能静默结束流,必须让调用方看到失败——
|
|
282
|
+
// 抛出后由 client.ts 共享读取循环的 try/finally 传播给上层(与其它流中断失败同一路径)。
|
|
283
|
+
throw new Error(`Anthropic 流式响应报错:${JSON.stringify(data.error ?? data)}`);
|
|
284
|
+
default:
|
|
285
|
+
// 含 ping、content_block_stop 及未来可能新增的事件类型——已知安全的 no-op,不抛错。
|
|
286
|
+
return [];
|
|
287
|
+
}
|
|
288
|
+
},
|
|
289
|
+
};
|
|
290
|
+
}
|
|
@@ -45,22 +45,114 @@ export function isMaxTokensRejected(status, errText) {
|
|
|
45
45
|
/**
|
|
46
46
|
* 判断一个 4xx 是否在抱怨图片/image_url/多模态输入不被支持。
|
|
47
47
|
* 匹配关键词:image_url、image、vision、multimodal + not support/unsupported/invalid 等。
|
|
48
|
+
* 排除图片尺寸/大小/格式等临时性错误,以及参数值错误(如 detail 取值非法),这些不是模型不支持图片。
|
|
48
49
|
*/
|
|
49
50
|
export function isImageInputRejected(status, errText) {
|
|
50
51
|
if (status !== 400 && status !== 404)
|
|
51
52
|
return false;
|
|
52
53
|
const t = errText.toLowerCase();
|
|
54
|
+
// 先排除临时性图片错误(尺寸太小/太大、格式问题、参数值非法等):这些重试或调整参数后可能成功,不是模型不支持
|
|
55
|
+
if (t.includes('dimension') || t.includes('too small') || t.includes('too large')
|
|
56
|
+
|| t.includes('maximum') || t.includes('exceed') || t.includes('out of range')
|
|
57
|
+
|| t.includes('invalid format') || t.includes('decode') || t.includes('corrupt')
|
|
58
|
+
|| (t.includes('image_url') && t.includes('supported values'))) { // detail 等参数取值非法
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
53
61
|
const hasImageKw = t.includes('image_url') || t.includes('image') || t.includes('vision') || t.includes('multimodal') || t.includes('不支持图片') || t.includes('图片输入');
|
|
54
62
|
if (!hasImageKw)
|
|
55
63
|
return false;
|
|
56
64
|
return (t.includes('not support') ||
|
|
57
65
|
t.includes('unsupported') ||
|
|
58
66
|
t.includes('not valid') ||
|
|
59
|
-
t.includes('invalid') ||
|
|
60
67
|
t.includes('not allowed') ||
|
|
61
68
|
t.includes('does not support') ||
|
|
69
|
+
t.includes('cannot accept') ||
|
|
70
|
+
t.includes('no access') ||
|
|
71
|
+
t.includes('not found') ||
|
|
72
|
+
t.includes('不存在') ||
|
|
62
73
|
t.includes('不支持'));
|
|
63
74
|
}
|
|
75
|
+
export function isPromptCacheKeyRejected(status, errText) {
|
|
76
|
+
if (status < 400 || status >= 500)
|
|
77
|
+
return false;
|
|
78
|
+
const text = errText.toLowerCase();
|
|
79
|
+
return ((text.includes('prompt_cache_key') || text.includes('prompt cache key')) &&
|
|
80
|
+
(text.includes('unsupported') ||
|
|
81
|
+
text.includes('not support') ||
|
|
82
|
+
text.includes('unknown') ||
|
|
83
|
+
text.includes('invalid') ||
|
|
84
|
+
text.includes('extra')));
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* 递归遍历消息,把 image_url.detail: 'default' 转换成 'auto'。
|
|
88
|
+
* OpenAI 兼容 API(豆包/火山引擎等)不接受 'default',标准默认值是 'auto'。
|
|
89
|
+
*/
|
|
90
|
+
function normalizeImageDetailForOpenAI(messages) {
|
|
91
|
+
return messages.map((msg) => {
|
|
92
|
+
if (!msg || typeof msg !== 'object')
|
|
93
|
+
return msg;
|
|
94
|
+
const m = msg;
|
|
95
|
+
if (Array.isArray(m.content)) {
|
|
96
|
+
m.content = m.content.map((part) => {
|
|
97
|
+
if (!part || typeof part !== 'object')
|
|
98
|
+
return part;
|
|
99
|
+
const p = part;
|
|
100
|
+
if (p.type === 'image_url' && p.image_url && typeof p.image_url === 'object') {
|
|
101
|
+
const imgUrl = p.image_url;
|
|
102
|
+
if (imgUrl.detail === 'default') {
|
|
103
|
+
imgUrl.detail = 'auto';
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return p;
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
return m;
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* 图像滑动窗口限制(对齐UI-TARS):
|
|
114
|
+
* 最多保留最近N张图片,超出时从最旧的消息开始删除图片。
|
|
115
|
+
* 防止LLM对话历史中图像过多导致token爆炸。
|
|
116
|
+
*/
|
|
117
|
+
const MAX_IMAGES_IN_CONTEXT = 10;
|
|
118
|
+
/**
|
|
119
|
+
* 限制消息历史中的图片数量(滑动窗口)。
|
|
120
|
+
* 从最旧的消息开始,如果图片数量超过MAX_IMAGES_IN_CONTEXT,删除最旧的图片。
|
|
121
|
+
*/
|
|
122
|
+
function limitImageCount(messages) {
|
|
123
|
+
// 统计所有图片数量
|
|
124
|
+
let imageCount = 0;
|
|
125
|
+
const messagesWithImages = [];
|
|
126
|
+
messages.forEach((msg, msgIdx) => {
|
|
127
|
+
if (!msg || typeof msg !== 'object')
|
|
128
|
+
return;
|
|
129
|
+
const m = msg;
|
|
130
|
+
if (Array.isArray(m.content)) {
|
|
131
|
+
m.content.forEach((part, contentIdx) => {
|
|
132
|
+
if (!part || typeof part !== 'object')
|
|
133
|
+
return;
|
|
134
|
+
const p = part;
|
|
135
|
+
if (p.type === 'image_url') {
|
|
136
|
+
imageCount++;
|
|
137
|
+
messagesWithImages.push({ msgIdx, contentIdx });
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
// 如果图片数量超过限制,从最旧的开始删除
|
|
143
|
+
if (imageCount > MAX_IMAGES_IN_CONTEXT) {
|
|
144
|
+
const toDelete = messagesWithImages.slice(0, imageCount - MAX_IMAGES_IN_CONTEXT);
|
|
145
|
+
// 从后往前删除,避免索引偏移
|
|
146
|
+
for (let i = toDelete.length - 1; i >= 0; i--) {
|
|
147
|
+
const { msgIdx, contentIdx } = toDelete[i];
|
|
148
|
+
const msg = messages[msgIdx];
|
|
149
|
+
if (Array.isArray(msg.content)) {
|
|
150
|
+
msg.content.splice(contentIdx, 1);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return messages;
|
|
155
|
+
}
|
|
64
156
|
function looksLikeVersionedBase(url) {
|
|
65
157
|
try {
|
|
66
158
|
const u = new URL(url);
|
|
@@ -91,7 +183,11 @@ export function buildOpenAIUrl(provider) {
|
|
|
91
183
|
return url;
|
|
92
184
|
}
|
|
93
185
|
export function buildOpenAIRequest(args) {
|
|
94
|
-
const { provider, cleanedMessages, tools, withResponseFormat, withMaxTokens, includeUsage, responseFormat } = args;
|
|
186
|
+
const { provider, cleanedMessages, tools, withResponseFormat, withMaxTokens, includeUsage, responseFormat, promptCache, withPromptCacheKey, } = args;
|
|
187
|
+
// 转换 image_url.detail: 'default' → 'auto',兼容所有 OpenAI 兼容 provider
|
|
188
|
+
let messages = normalizeImageDetailForOpenAI(cleanedMessages);
|
|
189
|
+
// 限制图片数量(滑动窗口),防止token爆炸
|
|
190
|
+
messages = limitImageCount(messages);
|
|
95
191
|
const openaiTools = tools.map((t) => ({
|
|
96
192
|
type: 'function',
|
|
97
193
|
function: {
|
|
@@ -108,7 +204,7 @@ export function buildOpenAIRequest(args) {
|
|
|
108
204
|
},
|
|
109
205
|
body: JSON.stringify({
|
|
110
206
|
model: provider.model,
|
|
111
|
-
messages
|
|
207
|
+
messages,
|
|
112
208
|
tools: openaiTools.length > 0 ? openaiTools : undefined,
|
|
113
209
|
stream: true,
|
|
114
210
|
// 显式输出上限:兜住默认上限偏小的 provider(豆包默认 4096)。缺省 undefined 不出现在 body。
|
|
@@ -120,20 +216,95 @@ export function buildOpenAIRequest(args) {
|
|
|
120
216
|
tool_choice: openaiTools.length > 0 ? 'auto' : undefined,
|
|
121
217
|
// ADR-05: structured output;缺省 undefined 不出现在 body(JSON.stringify 自动剥离)
|
|
122
218
|
response_format: withResponseFormat ? responseFormat : undefined,
|
|
219
|
+
...(withPromptCacheKey && promptCache?.scopeKey
|
|
220
|
+
? { prompt_cache_key: promptCache.scopeKey }
|
|
221
|
+
: {}),
|
|
123
222
|
}),
|
|
124
223
|
};
|
|
125
224
|
}
|
|
225
|
+
/**
|
|
226
|
+
* 从 usage 帧里抽出「命中缓存的 prompt token 数」。
|
|
227
|
+
*
|
|
228
|
+
* 各家 OpenAI 兼容 provider 的字段形状不统一,且都属于自动前缀缓存(不需要我们在请求里
|
|
229
|
+
* 发任何标记),只在 usage 里回报命中量。已知三种形状,按优先级依次探测:
|
|
230
|
+
* 1. `prompt_tokens_details.cached_tokens` —— OpenAI 官方形状,Kimi/智谱等跟随
|
|
231
|
+
* 2. `prompt_cache_hit_tokens` —— DeepSeek 形状(配对 prompt_cache_miss_tokens)
|
|
232
|
+
* 3. `cached_tokens` —— 少数 provider 直接平铺在 usage 上
|
|
233
|
+
* 全都拿不到就返回 undefined(≠ 0)——「没报告」和「报告了 0 命中」必须可区分,
|
|
234
|
+
* 否则观测层会把不支持缓存的 provider 显示成「命中率 0%」,误导排障。
|
|
235
|
+
*
|
|
236
|
+
* ⚠️ OpenAI 协议里 prompt_tokens **已包含**缓存命中部分,cached_tokens 只是其子集明细,
|
|
237
|
+
* 所以这里不需要对 promptTokens 做任何加法(Anthropic 相反,见 anthropicFormat.ts)。
|
|
238
|
+
*/
|
|
239
|
+
export function extractPromptCacheUsage(usage) {
|
|
240
|
+
if (!usage || typeof usage !== 'object')
|
|
241
|
+
return { reported: false };
|
|
242
|
+
const u = usage;
|
|
243
|
+
let cacheReadTokens;
|
|
244
|
+
const details = u.prompt_tokens_details;
|
|
245
|
+
if (details && typeof details === 'object') {
|
|
246
|
+
const cached = details.cached_tokens;
|
|
247
|
+
if (typeof cached === 'number' && Number.isFinite(cached))
|
|
248
|
+
cacheReadTokens = cached;
|
|
249
|
+
}
|
|
250
|
+
if (cacheReadTokens === undefined && typeof u.prompt_cache_hit_tokens === 'number' && Number.isFinite(u.prompt_cache_hit_tokens)) {
|
|
251
|
+
cacheReadTokens = u.prompt_cache_hit_tokens;
|
|
252
|
+
}
|
|
253
|
+
if (cacheReadTokens === undefined && typeof u.cached_tokens === 'number' && Number.isFinite(u.cached_tokens)) {
|
|
254
|
+
cacheReadTokens = u.cached_tokens;
|
|
255
|
+
}
|
|
256
|
+
const cacheMissTokens = typeof u.prompt_cache_miss_tokens === 'number' && Number.isFinite(u.prompt_cache_miss_tokens)
|
|
257
|
+
? u.prompt_cache_miss_tokens
|
|
258
|
+
: undefined;
|
|
259
|
+
const directPrompt = typeof u.prompt_tokens === 'number' && Number.isFinite(u.prompt_tokens)
|
|
260
|
+
? u.prompt_tokens
|
|
261
|
+
: undefined;
|
|
262
|
+
const promptTokens = directPrompt ??
|
|
263
|
+
(cacheReadTokens !== undefined && cacheMissTokens !== undefined
|
|
264
|
+
? cacheReadTokens + cacheMissTokens
|
|
265
|
+
: undefined);
|
|
266
|
+
const reported = cacheReadTokens !== undefined ||
|
|
267
|
+
cacheMissTokens !== undefined;
|
|
268
|
+
return {
|
|
269
|
+
reported,
|
|
270
|
+
...(promptTokens !== undefined ? { promptTokens } : {}),
|
|
271
|
+
...(cacheReadTokens !== undefined ? { cacheReadTokens } : {}),
|
|
272
|
+
...(cacheMissTokens !== undefined ? { cacheMissTokens } : {}),
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
/** Compatibility helper retained for existing callers/tests. */
|
|
276
|
+
export function extractCachedPromptTokens(usage) {
|
|
277
|
+
return extractPromptCacheUsage(usage).cacheReadTokens;
|
|
278
|
+
}
|
|
126
279
|
/** 每个请求一个新实例;把逐条 SSE `data:` 行的内容转换成 LlmStreamEvent(0..N 个)。 */
|
|
127
280
|
export function createOpenAISSEParser() {
|
|
128
281
|
let stopReason = 'unknown';
|
|
129
282
|
let usagePromptTokens;
|
|
283
|
+
let usageCachedTokens;
|
|
284
|
+
let usageCacheMissTokens;
|
|
285
|
+
let promptCacheUsage = { reported: false };
|
|
130
286
|
return {
|
|
131
287
|
buildFallbackDone() {
|
|
132
|
-
return {
|
|
288
|
+
return {
|
|
289
|
+
type: 'done',
|
|
290
|
+
stopReason,
|
|
291
|
+
promptTokens: usagePromptTokens,
|
|
292
|
+
cachedPromptTokens: usageCachedTokens,
|
|
293
|
+
cacheMissTokens: usageCacheMissTokens,
|
|
294
|
+
promptCacheUsage,
|
|
295
|
+
};
|
|
133
296
|
},
|
|
134
297
|
handleData(dataStr) {
|
|
135
|
-
if (dataStr === '[DONE]')
|
|
136
|
-
return [{
|
|
298
|
+
if (dataStr === '[DONE]') {
|
|
299
|
+
return [{
|
|
300
|
+
type: 'done',
|
|
301
|
+
stopReason,
|
|
302
|
+
promptTokens: usagePromptTokens,
|
|
303
|
+
cachedPromptTokens: usageCachedTokens,
|
|
304
|
+
cacheMissTokens: usageCacheMissTokens,
|
|
305
|
+
promptCacheUsage,
|
|
306
|
+
}];
|
|
307
|
+
}
|
|
137
308
|
let chunk;
|
|
138
309
|
try {
|
|
139
310
|
chunk = JSON.parse(dataStr);
|
|
@@ -146,8 +317,15 @@ export function createOpenAISSEParser() {
|
|
|
146
317
|
// usage chunk:include_usage 开启时,provider 通常在流末单独发一帧,
|
|
147
318
|
// 此帧 choices 为空(没有 delta),必须在 `if (!delta) return` 之前读取,
|
|
148
319
|
// 否则会被跳过,永远拿不到真实 prompt_tokens。
|
|
149
|
-
if (chunk.usage
|
|
150
|
-
|
|
320
|
+
if (chunk.usage) {
|
|
321
|
+
const cacheUsage = extractPromptCacheUsage(chunk.usage);
|
|
322
|
+
promptCacheUsage = cacheUsage;
|
|
323
|
+
if (cacheUsage.promptTokens !== undefined)
|
|
324
|
+
usagePromptTokens = cacheUsage.promptTokens;
|
|
325
|
+
if (cacheUsage.cacheReadTokens !== undefined)
|
|
326
|
+
usageCachedTokens = cacheUsage.cacheReadTokens;
|
|
327
|
+
if (cacheUsage.cacheMissTokens !== undefined)
|
|
328
|
+
usageCacheMissTokens = cacheUsage.cacheMissTokens;
|
|
151
329
|
}
|
|
152
330
|
const delta = chunk.choices?.[0]?.delta;
|
|
153
331
|
const finish = chunk.choices?.[0]?.finish_reason;
|