@burtson-labs/stealth-core-runtime 1.4.7
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/LICENSE +201 -0
- package/README.md +46 -0
- package/dist/banditEngineProvider.d.ts +48 -0
- package/dist/banditEngineProvider.d.ts.map +1 -0
- package/dist/banditEngineProvider.js +1021 -0
- package/dist/banditEngineProvider.js.map +1 -0
- package/dist/embeddingCache.d.ts +23 -0
- package/dist/embeddingCache.d.ts.map +1 -0
- package/dist/embeddingCache.js +196 -0
- package/dist/embeddingCache.js.map +1 -0
- package/dist/embeddingClient.d.ts +35 -0
- package/dist/embeddingClient.d.ts.map +1 -0
- package/dist/embeddingClient.js +162 -0
- package/dist/embeddingClient.js.map +1 -0
- package/dist/executorAgent.d.ts +7 -0
- package/dist/executorAgent.d.ts.map +1 -0
- package/dist/executorAgent.js +95 -0
- package/dist/executorAgent.js.map +1 -0
- package/dist/extensionSystemPrompt.d.ts +80 -0
- package/dist/extensionSystemPrompt.d.ts.map +1 -0
- package/dist/extensionSystemPrompt.js +208 -0
- package/dist/extensionSystemPrompt.js.map +1 -0
- package/dist/gatewaySearchAdapter.d.ts +69 -0
- package/dist/gatewaySearchAdapter.d.ts.map +1 -0
- package/dist/gatewaySearchAdapter.js +131 -0
- package/dist/gatewaySearchAdapter.js.map +1 -0
- package/dist/goalInference.d.ts +26 -0
- package/dist/goalInference.d.ts.map +1 -0
- package/dist/goalInference.js +605 -0
- package/dist/goalInference.js.map +1 -0
- package/dist/hostTypes.d.ts +86 -0
- package/dist/hostTypes.d.ts.map +1 -0
- package/dist/hostTypes.js +3 -0
- package/dist/hostTypes.js.map +1 -0
- package/dist/index.d.ts +37 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +83 -0
- package/dist/index.js.map +1 -0
- package/dist/internalTypes.d.ts +16 -0
- package/dist/internalTypes.d.ts.map +1 -0
- package/dist/internalTypes.js +20 -0
- package/dist/internalTypes.js.map +1 -0
- package/dist/ollamaEmbeddingClient.d.ts +45 -0
- package/dist/ollamaEmbeddingClient.d.ts.map +1 -0
- package/dist/ollamaEmbeddingClient.js +143 -0
- package/dist/ollamaEmbeddingClient.js.map +1 -0
- package/dist/pdfjsShim.d.ts +2 -0
- package/dist/pdfjsShim.d.ts.map +1 -0
- package/dist/pdfjsShim.js +80 -0
- package/dist/pdfjsShim.js.map +1 -0
- package/dist/runtime/actionRuntime.d.ts +13 -0
- package/dist/runtime/actionRuntime.d.ts.map +1 -0
- package/dist/runtime/actionRuntime.js +15 -0
- package/dist/runtime/actionRuntime.js.map +1 -0
- package/dist/runtime/actionServices.d.ts +72 -0
- package/dist/runtime/actionServices.d.ts.map +1 -0
- package/dist/runtime/actionServices.js +53 -0
- package/dist/runtime/actionServices.js.map +1 -0
- package/dist/runtime/adapters/connectorBus.d.ts +9 -0
- package/dist/runtime/adapters/connectorBus.d.ts.map +1 -0
- package/dist/runtime/adapters/connectorBus.js +20 -0
- package/dist/runtime/adapters/connectorBus.js.map +1 -0
- package/dist/runtime/adapters/fsAdapter.d.ts +6 -0
- package/dist/runtime/adapters/fsAdapter.d.ts.map +1 -0
- package/dist/runtime/adapters/fsAdapter.js +144 -0
- package/dist/runtime/adapters/fsAdapter.js.map +1 -0
- package/dist/runtime/adapters/llmAdapter.d.ts +4 -0
- package/dist/runtime/adapters/llmAdapter.d.ts.map +1 -0
- package/dist/runtime/adapters/llmAdapter.js +12 -0
- package/dist/runtime/adapters/llmAdapter.js.map +1 -0
- package/dist/runtime/adapters/shellAdapter.d.ts +6 -0
- package/dist/runtime/adapters/shellAdapter.d.ts.map +1 -0
- package/dist/runtime/adapters/shellAdapter.js +118 -0
- package/dist/runtime/adapters/shellAdapter.js.map +1 -0
- package/dist/runtime/additionalWrites.d.ts +22 -0
- package/dist/runtime/additionalWrites.d.ts.map +1 -0
- package/dist/runtime/additionalWrites.js +148 -0
- package/dist/runtime/additionalWrites.js.map +1 -0
- package/dist/runtime/artifactManager.d.ts +32 -0
- package/dist/runtime/artifactManager.d.ts.map +1 -0
- package/dist/runtime/artifactManager.js +154 -0
- package/dist/runtime/artifactManager.js.map +1 -0
- package/dist/runtime/autoHealer.d.ts +27 -0
- package/dist/runtime/autoHealer.d.ts.map +1 -0
- package/dist/runtime/autoHealer.js +583 -0
- package/dist/runtime/autoHealer.js.map +1 -0
- package/dist/runtime/changeTracker.d.ts +20 -0
- package/dist/runtime/changeTracker.d.ts.map +1 -0
- package/dist/runtime/changeTracker.js +147 -0
- package/dist/runtime/changeTracker.js.map +1 -0
- package/dist/runtime/contextBuilder.d.ts +85 -0
- package/dist/runtime/contextBuilder.d.ts.map +1 -0
- package/dist/runtime/contextBuilder.js +159 -0
- package/dist/runtime/contextBuilder.js.map +1 -0
- package/dist/runtime/coreRuntime.d.ts +7 -0
- package/dist/runtime/coreRuntime.d.ts.map +1 -0
- package/dist/runtime/coreRuntime.js +173 -0
- package/dist/runtime/coreRuntime.js.map +1 -0
- package/dist/runtime/createStealthRuntime.d.ts +4 -0
- package/dist/runtime/createStealthRuntime.d.ts.map +1 -0
- package/dist/runtime/createStealthRuntime.js +514 -0
- package/dist/runtime/createStealthRuntime.js.map +1 -0
- package/dist/runtime/diagnostics.d.ts +53 -0
- package/dist/runtime/diagnostics.d.ts.map +1 -0
- package/dist/runtime/diagnostics.js +396 -0
- package/dist/runtime/diagnostics.js.map +1 -0
- package/dist/runtime/diagnosticsServices.d.ts +51 -0
- package/dist/runtime/diagnosticsServices.d.ts.map +1 -0
- package/dist/runtime/diagnosticsServices.js +46 -0
- package/dist/runtime/diagnosticsServices.js.map +1 -0
- package/dist/runtime/diffManager.d.ts +20 -0
- package/dist/runtime/diffManager.d.ts.map +1 -0
- package/dist/runtime/diffManager.js +172 -0
- package/dist/runtime/diffManager.js.map +1 -0
- package/dist/runtime/diffPresenter.d.ts +8 -0
- package/dist/runtime/diffPresenter.d.ts.map +1 -0
- package/dist/runtime/diffPresenter.js +57 -0
- package/dist/runtime/diffPresenter.js.map +1 -0
- package/dist/runtime/embeddingClientResolver.d.ts +14 -0
- package/dist/runtime/embeddingClientResolver.d.ts.map +1 -0
- package/dist/runtime/embeddingClientResolver.js +54 -0
- package/dist/runtime/embeddingClientResolver.js.map +1 -0
- package/dist/runtime/embeddingManager.d.ts +22 -0
- package/dist/runtime/embeddingManager.d.ts.map +1 -0
- package/dist/runtime/embeddingManager.js +224 -0
- package/dist/runtime/embeddingManager.js.map +1 -0
- package/dist/runtime/eventBus.d.ts +7 -0
- package/dist/runtime/eventBus.d.ts.map +1 -0
- package/dist/runtime/eventBus.js +28 -0
- package/dist/runtime/eventBus.js.map +1 -0
- package/dist/runtime/executorServices.d.ts +46 -0
- package/dist/runtime/executorServices.d.ts.map +1 -0
- package/dist/runtime/executorServices.js +47 -0
- package/dist/runtime/executorServices.js.map +1 -0
- package/dist/runtime/extractionService.d.ts +11 -0
- package/dist/runtime/extractionService.d.ts.map +1 -0
- package/dist/runtime/extractionService.js +282 -0
- package/dist/runtime/extractionService.js.map +1 -0
- package/dist/runtime/feedbackService.d.ts +12 -0
- package/dist/runtime/feedbackService.d.ts.map +1 -0
- package/dist/runtime/feedbackService.js +111 -0
- package/dist/runtime/feedbackService.js.map +1 -0
- package/dist/runtime/goalEngine.d.ts +10 -0
- package/dist/runtime/goalEngine.d.ts.map +1 -0
- package/dist/runtime/goalEngine.js +429 -0
- package/dist/runtime/goalEngine.js.map +1 -0
- package/dist/runtime/goalFlowServices.d.ts +49 -0
- package/dist/runtime/goalFlowServices.d.ts.map +1 -0
- package/dist/runtime/goalFlowServices.js +45 -0
- package/dist/runtime/goalFlowServices.js.map +1 -0
- package/dist/runtime/goalReplay.d.ts +33 -0
- package/dist/runtime/goalReplay.d.ts.map +1 -0
- package/dist/runtime/goalReplay.js +58 -0
- package/dist/runtime/goalReplay.js.map +1 -0
- package/dist/runtime/goalRunner.d.ts +35 -0
- package/dist/runtime/goalRunner.d.ts.map +1 -0
- package/dist/runtime/goalRunner.js +42 -0
- package/dist/runtime/goalRunner.js.map +1 -0
- package/dist/runtime/healingEngine.d.ts +72 -0
- package/dist/runtime/healingEngine.d.ts.map +1 -0
- package/dist/runtime/healingEngine.js +969 -0
- package/dist/runtime/healingEngine.js.map +1 -0
- package/dist/runtime/healingServices.d.ts +62 -0
- package/dist/runtime/healingServices.d.ts.map +1 -0
- package/dist/runtime/healingServices.js +45 -0
- package/dist/runtime/healingServices.js.map +1 -0
- package/dist/runtime/helpers.d.ts +22 -0
- package/dist/runtime/helpers.d.ts.map +1 -0
- package/dist/runtime/helpers.js +694 -0
- package/dist/runtime/helpers.js.map +1 -0
- package/dist/runtime/hosts/actionHost.d.ts +82 -0
- package/dist/runtime/hosts/actionHost.d.ts.map +1 -0
- package/dist/runtime/hosts/actionHost.js +61 -0
- package/dist/runtime/hosts/actionHost.js.map +1 -0
- package/dist/runtime/hosts/baseServices.d.ts +51 -0
- package/dist/runtime/hosts/baseServices.d.ts.map +1 -0
- package/dist/runtime/hosts/baseServices.js +73 -0
- package/dist/runtime/hosts/baseServices.js.map +1 -0
- package/dist/runtime/hosts/embeddingServices.d.ts +25 -0
- package/dist/runtime/hosts/embeddingServices.d.ts.map +1 -0
- package/dist/runtime/hosts/embeddingServices.js +34 -0
- package/dist/runtime/hosts/embeddingServices.js.map +1 -0
- package/dist/runtime/hosts/goalFlowHost.d.ts +34 -0
- package/dist/runtime/hosts/goalFlowHost.d.ts.map +1 -0
- package/dist/runtime/hosts/goalFlowHost.js +35 -0
- package/dist/runtime/hosts/goalFlowHost.js.map +1 -0
- package/dist/runtime/hosts/goalFlowRuntime.d.ts +41 -0
- package/dist/runtime/hosts/goalFlowRuntime.d.ts.map +1 -0
- package/dist/runtime/hosts/goalFlowRuntime.js +31 -0
- package/dist/runtime/hosts/goalFlowRuntime.js.map +1 -0
- package/dist/runtime/hosts/providerHost.d.ts +19 -0
- package/dist/runtime/hosts/providerHost.d.ts.map +1 -0
- package/dist/runtime/hosts/providerHost.js +18 -0
- package/dist/runtime/hosts/providerHost.js.map +1 -0
- package/dist/runtime/hosts/rewriteHost.d.ts +84 -0
- package/dist/runtime/hosts/rewriteHost.d.ts.map +1 -0
- package/dist/runtime/hosts/rewriteHost.js +64 -0
- package/dist/runtime/hosts/rewriteHost.js.map +1 -0
- package/dist/runtime/hosts/workspaceHost.d.ts +113 -0
- package/dist/runtime/hosts/workspaceHost.d.ts.map +1 -0
- package/dist/runtime/hosts/workspaceHost.js +76 -0
- package/dist/runtime/hosts/workspaceHost.js.map +1 -0
- package/dist/runtime/index.d.ts +78 -0
- package/dist/runtime/index.d.ts.map +1 -0
- package/dist/runtime/index.js +95 -0
- package/dist/runtime/index.js.map +1 -0
- package/dist/runtime/insight.d.ts +16 -0
- package/dist/runtime/insight.d.ts.map +1 -0
- package/dist/runtime/insight.js +11 -0
- package/dist/runtime/insight.js.map +1 -0
- package/dist/runtime/internalActions.d.ts +16 -0
- package/dist/runtime/internalActions.d.ts.map +1 -0
- package/dist/runtime/internalActions.js +302 -0
- package/dist/runtime/internalActions.js.map +1 -0
- package/dist/runtime/modelBehavior.d.ts +69 -0
- package/dist/runtime/modelBehavior.d.ts.map +1 -0
- package/dist/runtime/modelBehavior.js +514 -0
- package/dist/runtime/modelBehavior.js.map +1 -0
- package/dist/runtime/modelCapabilities.d.ts +139 -0
- package/dist/runtime/modelCapabilities.d.ts.map +1 -0
- package/dist/runtime/modelCapabilities.js +458 -0
- package/dist/runtime/modelCapabilities.js.map +1 -0
- package/dist/runtime/modelsDevCatalog.d.ts +34 -0
- package/dist/runtime/modelsDevCatalog.d.ts.map +1 -0
- package/dist/runtime/modelsDevCatalog.js +263 -0
- package/dist/runtime/modelsDevCatalog.js.map +1 -0
- package/dist/runtime/pendingInference.d.ts +18 -0
- package/dist/runtime/pendingInference.d.ts.map +1 -0
- package/dist/runtime/pendingInference.js +121 -0
- package/dist/runtime/pendingInference.js.map +1 -0
- package/dist/runtime/persistence.d.ts +21 -0
- package/dist/runtime/persistence.d.ts.map +1 -0
- package/dist/runtime/persistence.js +73 -0
- package/dist/runtime/persistence.js.map +1 -0
- package/dist/runtime/planContext.d.ts +42 -0
- package/dist/runtime/planContext.d.ts.map +1 -0
- package/dist/runtime/planContext.js +246 -0
- package/dist/runtime/planContext.js.map +1 -0
- package/dist/runtime/planGenerator.d.ts +21 -0
- package/dist/runtime/planGenerator.d.ts.map +1 -0
- package/dist/runtime/planGenerator.js +89 -0
- package/dist/runtime/planGenerator.js.map +1 -0
- package/dist/runtime/planPreparation.d.ts +64 -0
- package/dist/runtime/planPreparation.d.ts.map +1 -0
- package/dist/runtime/planPreparation.js +173 -0
- package/dist/runtime/planPreparation.js.map +1 -0
- package/dist/runtime/projectSummary.d.ts +3 -0
- package/dist/runtime/projectSummary.d.ts.map +1 -0
- package/dist/runtime/projectSummary.js +42 -0
- package/dist/runtime/projectSummary.js.map +1 -0
- package/dist/runtime/providerSettings.d.ts +28 -0
- package/dist/runtime/providerSettings.d.ts.map +1 -0
- package/dist/runtime/providerSettings.js +93 -0
- package/dist/runtime/providerSettings.js.map +1 -0
- package/dist/runtime/pythonActions.d.ts +78 -0
- package/dist/runtime/pythonActions.d.ts.map +1 -0
- package/dist/runtime/pythonActions.js +392 -0
- package/dist/runtime/pythonActions.js.map +1 -0
- package/dist/runtime/pythonBridge.d.ts +13 -0
- package/dist/runtime/pythonBridge.d.ts.map +1 -0
- package/dist/runtime/pythonBridge.js +117 -0
- package/dist/runtime/pythonBridge.js.map +1 -0
- package/dist/runtime/rewriteEngine.d.ts +46 -0
- package/dist/runtime/rewriteEngine.d.ts.map +1 -0
- package/dist/runtime/rewriteEngine.js +259 -0
- package/dist/runtime/rewriteEngine.js.map +1 -0
- package/dist/runtime/rewriteGenerator.d.ts +29 -0
- package/dist/runtime/rewriteGenerator.d.ts.map +1 -0
- package/dist/runtime/rewriteGenerator.js +1527 -0
- package/dist/runtime/rewriteGenerator.js.map +1 -0
- package/dist/runtime/rewriteHydration.d.ts +22 -0
- package/dist/runtime/rewriteHydration.d.ts.map +1 -0
- package/dist/runtime/rewriteHydration.js +265 -0
- package/dist/runtime/rewriteHydration.js.map +1 -0
- package/dist/runtime/rewriteOrchestration.d.ts +105 -0
- package/dist/runtime/rewriteOrchestration.d.ts.map +1 -0
- package/dist/runtime/rewriteOrchestration.js +130 -0
- package/dist/runtime/rewriteOrchestration.js.map +1 -0
- package/dist/runtime/rewritePayload.d.ts +18 -0
- package/dist/runtime/rewritePayload.d.ts.map +1 -0
- package/dist/runtime/rewritePayload.js +166 -0
- package/dist/runtime/rewritePayload.js.map +1 -0
- package/dist/runtime/rewriteRuntime.d.ts +13 -0
- package/dist/runtime/rewriteRuntime.d.ts.map +1 -0
- package/dist/runtime/rewriteRuntime.js +21 -0
- package/dist/runtime/rewriteRuntime.js.map +1 -0
- package/dist/runtime/rewriteServices.d.ts +70 -0
- package/dist/runtime/rewriteServices.d.ts.map +1 -0
- package/dist/runtime/rewriteServices.js +50 -0
- package/dist/runtime/rewriteServices.js.map +1 -0
- package/dist/runtime/runtimeHelpers.d.ts +9 -0
- package/dist/runtime/runtimeHelpers.d.ts.map +1 -0
- package/dist/runtime/runtimeHelpers.js +86 -0
- package/dist/runtime/runtimeHelpers.js.map +1 -0
- package/dist/runtime/sessionData.d.ts +4 -0
- package/dist/runtime/sessionData.d.ts.map +1 -0
- package/dist/runtime/sessionData.js +45 -0
- package/dist/runtime/sessionData.js.map +1 -0
- package/dist/runtime/sessionRuntime.d.ts +47 -0
- package/dist/runtime/sessionRuntime.d.ts.map +1 -0
- package/dist/runtime/sessionRuntime.js +128 -0
- package/dist/runtime/sessionRuntime.js.map +1 -0
- package/dist/runtime/stealthRuntimeTypes.d.ts +12 -0
- package/dist/runtime/stealthRuntimeTypes.d.ts.map +1 -0
- package/dist/runtime/stealthRuntimeTypes.js +3 -0
- package/dist/runtime/stealthRuntimeTypes.js.map +1 -0
- package/dist/runtime/stepExecutor.d.ts +22 -0
- package/dist/runtime/stepExecutor.d.ts.map +1 -0
- package/dist/runtime/stepExecutor.js +83 -0
- package/dist/runtime/stepExecutor.js.map +1 -0
- package/dist/runtime/stepLifecycle.d.ts +29 -0
- package/dist/runtime/stepLifecycle.d.ts.map +1 -0
- package/dist/runtime/stepLifecycle.js +122 -0
- package/dist/runtime/stepLifecycle.js.map +1 -0
- package/dist/runtime/stepMetadata.d.ts +6 -0
- package/dist/runtime/stepMetadata.d.ts.map +1 -0
- package/dist/runtime/stepMetadata.js +87 -0
- package/dist/runtime/stepMetadata.js.map +1 -0
- package/dist/runtime/taskQueue.d.ts +16 -0
- package/dist/runtime/taskQueue.d.ts.map +1 -0
- package/dist/runtime/taskQueue.js +103 -0
- package/dist/runtime/taskQueue.js.map +1 -0
- package/dist/runtime/telemetry.d.ts +6 -0
- package/dist/runtime/telemetry.d.ts.map +1 -0
- package/dist/runtime/telemetry.js +42 -0
- package/dist/runtime/telemetry.js.map +1 -0
- package/dist/runtime/telemetryHub.d.ts +61 -0
- package/dist/runtime/telemetryHub.d.ts.map +1 -0
- package/dist/runtime/telemetryHub.js +190 -0
- package/dist/runtime/telemetryHub.js.map +1 -0
- package/dist/runtime/textSanitizer.d.ts +4 -0
- package/dist/runtime/textSanitizer.d.ts.map +1 -0
- package/dist/runtime/textSanitizer.js +86 -0
- package/dist/runtime/textSanitizer.js.map +1 -0
- package/dist/runtime/typeCheckRunner.d.ts +33 -0
- package/dist/runtime/typeCheckRunner.d.ts.map +1 -0
- package/dist/runtime/typeCheckRunner.js +357 -0
- package/dist/runtime/typeCheckRunner.js.map +1 -0
- package/dist/runtime/types.d.ts +334 -0
- package/dist/runtime/types.d.ts.map +1 -0
- package/dist/runtime/types.js +3 -0
- package/dist/runtime/types.js.map +1 -0
- package/dist/runtime/typescriptValidator.d.ts +18 -0
- package/dist/runtime/typescriptValidator.d.ts.map +1 -0
- package/dist/runtime/typescriptValidator.js +363 -0
- package/dist/runtime/typescriptValidator.js.map +1 -0
- package/dist/runtime/undoManager.d.ts +7 -0
- package/dist/runtime/undoManager.d.ts.map +1 -0
- package/dist/runtime/undoManager.js +56 -0
- package/dist/runtime/undoManager.js.map +1 -0
- package/dist/runtime/validationController.d.ts +11 -0
- package/dist/runtime/validationController.d.ts.map +1 -0
- package/dist/runtime/validationController.js +29 -0
- package/dist/runtime/validationController.js.map +1 -0
- package/dist/runtime/validationUtils.d.ts +17 -0
- package/dist/runtime/validationUtils.d.ts.map +1 -0
- package/dist/runtime/validationUtils.js +121 -0
- package/dist/runtime/validationUtils.js.map +1 -0
- package/dist/runtime/workspaceAssertions.d.ts +21 -0
- package/dist/runtime/workspaceAssertions.d.ts.map +1 -0
- package/dist/runtime/workspaceAssertions.js +183 -0
- package/dist/runtime/workspaceAssertions.js.map +1 -0
- package/dist/runtime/workspaceIndexService.d.ts +24 -0
- package/dist/runtime/workspaceIndexService.d.ts.map +1 -0
- package/dist/runtime/workspaceIndexService.js +133 -0
- package/dist/runtime/workspaceIndexService.js.map +1 -0
- package/dist/runtime/workspaceIndexer.d.ts +21 -0
- package/dist/runtime/workspaceIndexer.d.ts.map +1 -0
- package/dist/runtime/workspaceIndexer.js +95 -0
- package/dist/runtime/workspaceIndexer.js.map +1 -0
- package/dist/runtime/workspacePackages.d.ts +22 -0
- package/dist/runtime/workspacePackages.d.ts.map +1 -0
- package/dist/runtime/workspacePackages.js +198 -0
- package/dist/runtime/workspacePackages.js.map +1 -0
- package/dist/runtime/workspaceRuntime.d.ts +58 -0
- package/dist/runtime/workspaceRuntime.d.ts.map +1 -0
- package/dist/runtime/workspaceRuntime.js +86 -0
- package/dist/runtime/workspaceRuntime.js.map +1 -0
- package/dist/runtime/workspaceService.d.ts +14 -0
- package/dist/runtime/workspaceService.d.ts.map +1 -0
- package/dist/runtime/workspaceService.js +88 -0
- package/dist/runtime/workspaceService.js.map +1 -0
- package/dist/runtime/workspaceServices.d.ts +114 -0
- package/dist/runtime/workspaceServices.d.ts.map +1 -0
- package/dist/runtime/workspaceServices.js +114 -0
- package/dist/runtime/workspaceServices.js.map +1 -0
- package/dist/runtime/writeServices.d.ts +34 -0
- package/dist/runtime/writeServices.d.ts.map +1 -0
- package/dist/runtime/writeServices.js +32 -0
- package/dist/runtime/writeServices.js.map +1 -0
- package/dist/sharedPromptSections.d.ts +58 -0
- package/dist/sharedPromptSections.d.ts.map +1 -0
- package/dist/sharedPromptSections.js +94 -0
- package/dist/sharedPromptSections.js.map +1 -0
- package/dist/statusTypes.d.ts +13 -0
- package/dist/statusTypes.d.ts.map +1 -0
- package/dist/statusTypes.js +3 -0
- package/dist/statusTypes.js.map +1 -0
- package/dist/streamIdleTimeout.d.ts +38 -0
- package/dist/streamIdleTimeout.d.ts.map +1 -0
- package/dist/streamIdleTimeout.js +44 -0
- package/dist/streamIdleTimeout.js.map +1 -0
- package/dist/types/bandit.d.ts +113 -0
- package/dist/types/bandit.d.ts.map +1 -0
- package/dist/types/bandit.js +3 -0
- package/dist/types/bandit.js.map +1 -0
- package/dist/types.d.ts +152 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/workspaceIndex.d.ts +44 -0
- package/dist/workspaceIndex.d.ts.map +1 -0
- package/dist/workspaceIndex.js +258 -0
- package/dist/workspaceIndex.js.map +1 -0
- package/package.json +36 -0
|
@@ -0,0 +1,1021 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.normalizeOllamaMessages = normalizeOllamaMessages;
|
|
4
|
+
exports.createProvider = createProvider;
|
|
5
|
+
exports.serializeBanditPayload = serializeBanditPayload;
|
|
6
|
+
const modelCapabilities_1 = require("./runtime/modelCapabilities");
|
|
7
|
+
const streamIdleTimeout_1 = require("./streamIdleTimeout");
|
|
8
|
+
const DEFAULT_BANDIT_COMPLETIONS_URL = 'https://api.burtson.ai/completions';
|
|
9
|
+
const DEFAULT_OLLAMA_URL = 'http://localhost:11434';
|
|
10
|
+
const DEFAULT_OLLAMA_MODEL = 'gemma3:12b';
|
|
11
|
+
/** Control tokens emitted by Gemma3, Llama, and other local models that must be stripped. */
|
|
12
|
+
const CONTROL_TOKEN_PATTERNS = [
|
|
13
|
+
/<\/?end_of_turn>/g,
|
|
14
|
+
/<\/?start_of_turn>/g,
|
|
15
|
+
/<\|eot_id\|>/g,
|
|
16
|
+
/<\|start_header_id\|>/g,
|
|
17
|
+
/<\|end_header_id\|>/g,
|
|
18
|
+
/<\|begin_of_text\|>/g,
|
|
19
|
+
/\[INST\]/g,
|
|
20
|
+
/\[\/INST\]/g,
|
|
21
|
+
/<<SYS>>/g,
|
|
22
|
+
/<\/SYS>>/g,
|
|
23
|
+
];
|
|
24
|
+
function sanitizeOllamaOutput(text) {
|
|
25
|
+
// NOTE: called per streamed chunk — do NOT trim here. Word-break whitespace
|
|
26
|
+
// lives on chunk boundaries ("Hello", " world"); trimming each chunk
|
|
27
|
+
// collapses them into "Helloworld". Only control tokens are removed.
|
|
28
|
+
let result = text;
|
|
29
|
+
for (const pattern of CONTROL_TOKEN_PATTERNS) {
|
|
30
|
+
result = result.replace(pattern, '');
|
|
31
|
+
}
|
|
32
|
+
result = stripBase64BlobsInline(result);
|
|
33
|
+
return result;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Stream-safe base64 blob stripper. A single streamed chunk may contain
|
|
37
|
+
* the start of a base64 blob; replacing the partial contents would make
|
|
38
|
+
* the blob detectable again when the next chunk arrives. We only strip
|
|
39
|
+
* when we can prove the blob is fully contained in the current chunk
|
|
40
|
+
* (bounded by whitespace or string edges). Partial blobs pass through
|
|
41
|
+
* and the next chunk's sanitizer catches them when the whitespace hits.
|
|
42
|
+
*
|
|
43
|
+
* In practice, multimodal echos from the gateway arrive in a small
|
|
44
|
+
* number of large chunks so the full-blob case is the common one.
|
|
45
|
+
*/
|
|
46
|
+
function stripBase64BlobsInline(text) {
|
|
47
|
+
const BLOB = /(?:data:[\w/.+-]+;base64,)?[A-Za-z0-9+/]{120,}={0,2}/g;
|
|
48
|
+
return text.replace(BLOB, (match) => `[base64 stripped: ${match.length} chars]`);
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Returns true if the request looks like a plan-generation call that needs
|
|
52
|
+
* structured JSON output. This is intentionally conservative to avoid forcing
|
|
53
|
+
* JSON mode for normal conversational replies.
|
|
54
|
+
*/
|
|
55
|
+
function detectJsonRequest(request) {
|
|
56
|
+
const getLowerText = (content) => {
|
|
57
|
+
const text = typeof content === 'string'
|
|
58
|
+
? content
|
|
59
|
+
: content.map(p => p.type === 'text' ? p.text : '').join(' ');
|
|
60
|
+
return text.toLowerCase();
|
|
61
|
+
};
|
|
62
|
+
const systemText = request.messages
|
|
63
|
+
.filter(message => message.role === 'system')
|
|
64
|
+
.map(message => getLowerText(message.content))
|
|
65
|
+
.join('\n');
|
|
66
|
+
const lastUserText = [...request.messages]
|
|
67
|
+
.reverse()
|
|
68
|
+
.find(message => message.role === 'user');
|
|
69
|
+
const combined = `${systemText}\n${lastUserText ? getLowerText(lastUserText.content) : ''}`;
|
|
70
|
+
const hasJsonDirective = /\b(respond with json(?: only)?|return (?:valid )?json|output (?:valid )?json|json only|format\s*[:=]\s*"?json"?|strictly json)\b/.test(combined);
|
|
71
|
+
if (!hasJsonDirective) {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
const hasPlanSignal = /\b(plan|planning|steps?)\b|execution plan|agent plan|schema|"steps"|"id"|"title"|"description"/.test(combined);
|
|
75
|
+
return hasPlanSignal;
|
|
76
|
+
}
|
|
77
|
+
function extractTextContent(content) {
|
|
78
|
+
return splitMessageContent(content).text;
|
|
79
|
+
}
|
|
80
|
+
function splitMessageContent(content) {
|
|
81
|
+
if (typeof content === 'string') {
|
|
82
|
+
return { text: content, imageUrls: [] };
|
|
83
|
+
}
|
|
84
|
+
if (!Array.isArray(content)) {
|
|
85
|
+
return { text: '', imageUrls: [] };
|
|
86
|
+
}
|
|
87
|
+
const textParts = [];
|
|
88
|
+
const imageUrls = [];
|
|
89
|
+
for (const part of content) {
|
|
90
|
+
if (!part) {
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
if (part.type === 'text') {
|
|
94
|
+
const text = typeof part.text === 'string' ? part.text : part.text != null ? String(part.text) : '';
|
|
95
|
+
if (text.length > 0) {
|
|
96
|
+
textParts.push(text);
|
|
97
|
+
}
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
if (part.type === 'image_url') {
|
|
101
|
+
const url = extractImageUrl(part);
|
|
102
|
+
if (url) {
|
|
103
|
+
imageUrls.push(url);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return {
|
|
108
|
+
text: textParts.join('\n'),
|
|
109
|
+
imageUrls: dedupeStrings(imageUrls)
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
function dedupeStrings(values) {
|
|
113
|
+
const seen = new Set();
|
|
114
|
+
const deduped = [];
|
|
115
|
+
for (const value of values) {
|
|
116
|
+
const normalized = value.trim();
|
|
117
|
+
if (!normalized || seen.has(normalized)) {
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
seen.add(normalized);
|
|
121
|
+
deduped.push(normalized);
|
|
122
|
+
}
|
|
123
|
+
return deduped;
|
|
124
|
+
}
|
|
125
|
+
function normalizeOllamaImage(candidate) {
|
|
126
|
+
const normalized = normalizeImageUrl(candidate);
|
|
127
|
+
if (!normalized) {
|
|
128
|
+
return undefined;
|
|
129
|
+
}
|
|
130
|
+
if (/^https?:/i.test(normalized)) {
|
|
131
|
+
return undefined;
|
|
132
|
+
}
|
|
133
|
+
if (!/^data:/i.test(normalized)) {
|
|
134
|
+
return normalized;
|
|
135
|
+
}
|
|
136
|
+
const commaIndex = normalized.indexOf(',');
|
|
137
|
+
if (commaIndex < 0 || commaIndex === normalized.length - 1) {
|
|
138
|
+
return undefined;
|
|
139
|
+
}
|
|
140
|
+
return normalized.slice(commaIndex + 1).trim();
|
|
141
|
+
}
|
|
142
|
+
function normalizeOllamaMessages(request) {
|
|
143
|
+
const normalizedMessages = request.messages.map((message) => {
|
|
144
|
+
const { text, imageUrls } = splitMessageContent(message.content);
|
|
145
|
+
const images = imageUrls
|
|
146
|
+
.map((url) => normalizeOllamaImage(url))
|
|
147
|
+
.filter((image) => Boolean(image));
|
|
148
|
+
if (images.length > 0) {
|
|
149
|
+
return { role: message.role, content: text, images };
|
|
150
|
+
}
|
|
151
|
+
return { role: message.role, content: text };
|
|
152
|
+
});
|
|
153
|
+
const requestImages = (Array.isArray(request.images) ? request.images : [])
|
|
154
|
+
.map((image) => normalizeOllamaImage(image))
|
|
155
|
+
.filter((image) => Boolean(image));
|
|
156
|
+
if (requestImages.length === 0) {
|
|
157
|
+
return normalizedMessages;
|
|
158
|
+
}
|
|
159
|
+
for (let index = normalizedMessages.length - 1; index >= 0; index -= 1) {
|
|
160
|
+
if (normalizedMessages[index]?.role !== 'user') {
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
const existing = normalizedMessages[index].images ?? [];
|
|
164
|
+
normalizedMessages[index].images = dedupeStrings([...existing, ...requestImages]);
|
|
165
|
+
return normalizedMessages;
|
|
166
|
+
}
|
|
167
|
+
normalizedMessages.push({
|
|
168
|
+
role: 'user',
|
|
169
|
+
content: '',
|
|
170
|
+
images: requestImages
|
|
171
|
+
});
|
|
172
|
+
return normalizedMessages;
|
|
173
|
+
}
|
|
174
|
+
function collectBanditPayloadImages(request) {
|
|
175
|
+
const requestImages = (Array.isArray(request.images) ? request.images : [])
|
|
176
|
+
.map((image) => normalizeImageUrl(image))
|
|
177
|
+
.filter((image) => Boolean(image));
|
|
178
|
+
const lastUserMessage = [...request.messages]
|
|
179
|
+
.reverse()
|
|
180
|
+
.find((message) => message.role === 'user');
|
|
181
|
+
const messageImages = lastUserMessage
|
|
182
|
+
? splitMessageContent(lastUserMessage.content).imageUrls
|
|
183
|
+
: [];
|
|
184
|
+
return dedupeStrings([...requestImages, ...messageImages]);
|
|
185
|
+
}
|
|
186
|
+
async function* streamOllamaResponse(response) {
|
|
187
|
+
const body = response.body;
|
|
188
|
+
if (!body)
|
|
189
|
+
throw new Error('Ollama response has no body.');
|
|
190
|
+
const reader = body.getReader();
|
|
191
|
+
const decoder = new TextDecoder();
|
|
192
|
+
let buffer = '';
|
|
193
|
+
let stallWarned = false;
|
|
194
|
+
for (;;) {
|
|
195
|
+
const { value, done } = await (0, streamIdleTimeout_1.readWithIdleTimeout)(reader, {
|
|
196
|
+
idleMs: streamIdleTimeout_1.DEFAULT_STREAM_IDLE_MS,
|
|
197
|
+
warnAfterMs: streamIdleTimeout_1.DEFAULT_STREAM_WARN_MS,
|
|
198
|
+
abortLabel: 'Ollama stream',
|
|
199
|
+
onWarn: (elapsedMs) => {
|
|
200
|
+
if (stallWarned)
|
|
201
|
+
return;
|
|
202
|
+
stallWarned = true;
|
|
203
|
+
console.warn(`[banditEngineProvider] Ollama stream went quiet at ${elapsedMs}ms — still waiting…`);
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
if (done)
|
|
207
|
+
break;
|
|
208
|
+
buffer += decoder.decode(value, { stream: true });
|
|
209
|
+
const lines = buffer.split('\n');
|
|
210
|
+
buffer = lines.pop() ?? '';
|
|
211
|
+
for (const line of lines) {
|
|
212
|
+
const trimmed = line.trim();
|
|
213
|
+
if (!trimmed)
|
|
214
|
+
continue;
|
|
215
|
+
try {
|
|
216
|
+
const chunk = JSON.parse(trimmed);
|
|
217
|
+
const content = sanitizeOllamaOutput(chunk.message?.content ?? '');
|
|
218
|
+
const thinking = chunk.message?.thinking;
|
|
219
|
+
if (content || thinking) {
|
|
220
|
+
yield {
|
|
221
|
+
message: {
|
|
222
|
+
content,
|
|
223
|
+
role: 'assistant',
|
|
224
|
+
...(thinking ? { thinking } : {})
|
|
225
|
+
},
|
|
226
|
+
done: false
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
if (chunk.done) {
|
|
230
|
+
yield { message: { content: '', role: 'assistant' }, done: true };
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
catch {
|
|
235
|
+
// skip malformed lines
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
yield { message: { content: '', role: 'assistant' }, done: true };
|
|
240
|
+
}
|
|
241
|
+
function createDirectOllamaProvider(baseUrl, defaultModel, extraHeaders) {
|
|
242
|
+
// Caller-supplied headers (Authorization, Cloudflare Access, etc.) are
|
|
243
|
+
// merged once here; Content-Type always wins to avoid breaking the API.
|
|
244
|
+
const mergedHeaders = {
|
|
245
|
+
...(extraHeaders ?? {}),
|
|
246
|
+
'Content-Type': 'application/json'
|
|
247
|
+
};
|
|
248
|
+
return {
|
|
249
|
+
chat(request) {
|
|
250
|
+
const iterator = async function* () {
|
|
251
|
+
const model = request.model || defaultModel;
|
|
252
|
+
const wantsJson = detectJsonRequest(request);
|
|
253
|
+
// Tier-derived defaults for num_ctx + keep_alive. Without these
|
|
254
|
+
// Ollama falls back to a 2048-token chat window — too small for
|
|
255
|
+
// our agent system prompt (~3-4k tokens) plus tool results, so
|
|
256
|
+
// the framework framing gets sheared off and the model replies
|
|
257
|
+
// from its raw conversational persona. Caller-supplied
|
|
258
|
+
// request.options override these per-request (so tests / power
|
|
259
|
+
// users can still opt out or up the window).
|
|
260
|
+
const runtimeDefaults = (0, modelCapabilities_1.resolveOllamaRuntimeOptions)(model);
|
|
261
|
+
const payload = {
|
|
262
|
+
model,
|
|
263
|
+
messages: normalizeOllamaMessages(request),
|
|
264
|
+
stream: request.stream !== false,
|
|
265
|
+
keep_alive: runtimeDefaults.keep_alive,
|
|
266
|
+
options: {
|
|
267
|
+
temperature: request.temperature ?? 0.2,
|
|
268
|
+
num_ctx: runtimeDefaults.num_ctx,
|
|
269
|
+
...(request.options ?? {})
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
// `think` is a top-level request field in Ollama, not nested
|
|
273
|
+
// under `options` (Ollama rejects `PARAMETER think` in Modelfiles
|
|
274
|
+
// — it's a per-request toggle only). Reasoning-capable models
|
|
275
|
+
// like Qwen 3.6 ship thinking ON by default; for agent tool-use
|
|
276
|
+
// we disable it via resolveOllamaRuntimeOptions to save the
|
|
277
|
+
// 8-30s thinking preamble per turn. Per-request `request.think`
|
|
278
|
+
// (explicitly passed by the host for `/think on` / extension
|
|
279
|
+
// setting overrides) wins over the runtime default.
|
|
280
|
+
if (request.think !== undefined) {
|
|
281
|
+
payload.think = request.think;
|
|
282
|
+
}
|
|
283
|
+
else if (runtimeDefaults.think !== undefined) {
|
|
284
|
+
payload.think = runtimeDefaults.think;
|
|
285
|
+
}
|
|
286
|
+
if (wantsJson) {
|
|
287
|
+
payload.format = 'json';
|
|
288
|
+
}
|
|
289
|
+
// Native tool calling: when the caller provides `tools`, pass
|
|
290
|
+
// them through to Ollama so the model's chat template serializes
|
|
291
|
+
// the schemas efficiently (30-50% fewer tokens than our text
|
|
292
|
+
// XML block). Streaming is DISABLED for native-tools requests
|
|
293
|
+
// because Ollama's streaming path currently emits tool_calls
|
|
294
|
+
// only on the terminal chunk and interleaving that with partial
|
|
295
|
+
// content makes the downstream translator brittle. Non-native
|
|
296
|
+
// requests keep streaming as before.
|
|
297
|
+
const hasNativeTools = Array.isArray(request.tools) && request.tools.length > 0;
|
|
298
|
+
if (hasNativeTools) {
|
|
299
|
+
payload.tools = request.tools;
|
|
300
|
+
payload.stream = false;
|
|
301
|
+
}
|
|
302
|
+
const response = await fetchWithRetry(`${baseUrl}/api/chat`, {
|
|
303
|
+
method: 'POST',
|
|
304
|
+
headers: mergedHeaders,
|
|
305
|
+
body: JSON.stringify(payload)
|
|
306
|
+
});
|
|
307
|
+
if (!response.ok) {
|
|
308
|
+
const detail = await safeReadText(response);
|
|
309
|
+
throw new Error(`Ollama request failed: ${response.status} ${response.statusText}${detail ? ` – ${detail}` : ''}`);
|
|
310
|
+
}
|
|
311
|
+
if (payload.stream !== false) {
|
|
312
|
+
yield* streamOllamaResponse(response);
|
|
313
|
+
}
|
|
314
|
+
else {
|
|
315
|
+
const data = await response.json();
|
|
316
|
+
let content = sanitizeOllamaOutput(data.message?.content ?? '');
|
|
317
|
+
// Translate Ollama's native tool_calls into inline text
|
|
318
|
+
// markup so the ToolUseLoop's existing parseToolCalls()
|
|
319
|
+
// picks them up without any downstream change. Each call
|
|
320
|
+
// becomes one `<tool_call>{...}</tool_call>` block appended
|
|
321
|
+
// to the response text.
|
|
322
|
+
const toolCalls = data.message?.tool_calls;
|
|
323
|
+
if (Array.isArray(toolCalls) && toolCalls.length > 0) {
|
|
324
|
+
const markers = toolCalls.map(tc => {
|
|
325
|
+
const name = tc.function?.name ?? '';
|
|
326
|
+
const args = tc.function?.arguments ?? {};
|
|
327
|
+
// Ollama returns arguments as an already-parsed object;
|
|
328
|
+
// stringify to match the wire format our parser expects.
|
|
329
|
+
// Stringify values to strings — ToolRegistry schemas
|
|
330
|
+
// declare every param as type: 'string' and our tools
|
|
331
|
+
// read String(params.path), etc.
|
|
332
|
+
const params = {};
|
|
333
|
+
if (args && typeof args === 'object') {
|
|
334
|
+
for (const [k, v] of Object.entries(args)) {
|
|
335
|
+
params[k] = typeof v === 'string' ? v : JSON.stringify(v);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
return `<tool_call>${JSON.stringify({ name, params })}</tool_call>`;
|
|
339
|
+
}).join('\n');
|
|
340
|
+
content = content ? `${content}\n${markers}` : markers;
|
|
341
|
+
}
|
|
342
|
+
yield { message: { content, role: 'assistant' }, done: true };
|
|
343
|
+
}
|
|
344
|
+
};
|
|
345
|
+
return iterator();
|
|
346
|
+
}
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
async function createProvider(settings) {
|
|
350
|
+
if (settings.kind === 'bandit') {
|
|
351
|
+
const apiUrl = normalizeBanditApiUrl(settings.apiUrl);
|
|
352
|
+
const apiKey = settings.apiKey?.trim();
|
|
353
|
+
return createDirectBanditProvider(apiUrl, apiKey);
|
|
354
|
+
}
|
|
355
|
+
if (settings.kind === 'ollama') {
|
|
356
|
+
// Use node URL (RTX 5090) if configured, otherwise fall back to local Ollama
|
|
357
|
+
const rawUrl = settings.ollamaNodeUrl?.trim()
|
|
358
|
+
? settings.ollamaNodeUrl.trim()
|
|
359
|
+
: settings.ollamaUrl;
|
|
360
|
+
const baseUrl = normalizeUrl(rawUrl, DEFAULT_OLLAMA_URL);
|
|
361
|
+
const model = settings.ollamaModel?.trim() || DEFAULT_OLLAMA_MODEL;
|
|
362
|
+
return createDirectOllamaProvider(baseUrl, model, settings.ollamaHeaders);
|
|
363
|
+
}
|
|
364
|
+
if (settings.kind === 'openai-compatible') {
|
|
365
|
+
const rawBase = settings.openaiBaseUrl?.trim();
|
|
366
|
+
if (!rawBase) {
|
|
367
|
+
throw new Error('openai-compatible provider requires `openaiBaseUrl` (e.g. http://localhost:1234/v1, https://api.together.xyz/v1).');
|
|
368
|
+
}
|
|
369
|
+
const baseUrl = rawBase.replace(/\/+$/, '');
|
|
370
|
+
const apiUrl = `${baseUrl}/chat/completions`;
|
|
371
|
+
const apiKey = settings.openaiApiKey?.trim();
|
|
372
|
+
return createDirectOpenAICompatibleProvider(apiUrl, apiKey, settings.openaiHeaders);
|
|
373
|
+
}
|
|
374
|
+
throw new Error(`Unsupported provider kind: ${settings.kind}`);
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* Generic OpenAI-compatible chat provider.
|
|
378
|
+
*
|
|
379
|
+
* Reuses the same payload shape and SSE streaming helpers as the Bandit
|
|
380
|
+
* gateway path — every endpoint we care about (LM Studio, llama.cpp,
|
|
381
|
+
* vLLM, OpenAI, OpenRouter, Together, Groq, DeepSeek, xAI) speaks the
|
|
382
|
+
* `POST /v1/chat/completions` shape with `data: {…}` SSE chunks. The
|
|
383
|
+
* Bandit-specific bits (`X-Bandit-Source`/`X-Skip-Seed-Pack` headers,
|
|
384
|
+
* 429 rate-limit special-case JSON parsing) are intentionally NOT
|
|
385
|
+
* forwarded here — those are gateway-specific. Errors fall through to a
|
|
386
|
+
* generic message so the host shows the upstream provider's actual
|
|
387
|
+
* status text.
|
|
388
|
+
*/
|
|
389
|
+
function createDirectOpenAICompatibleProvider(apiUrl, apiKey, extraHeaders) {
|
|
390
|
+
return {
|
|
391
|
+
chat(request) {
|
|
392
|
+
const controller = new AbortController();
|
|
393
|
+
const iterator = async function* () {
|
|
394
|
+
try {
|
|
395
|
+
const payload = serializeBanditPayload(request);
|
|
396
|
+
const response = await fetchWithRetry(apiUrl, {
|
|
397
|
+
method: 'POST',
|
|
398
|
+
headers: buildOpenAICompatibleHeaders(apiKey, extraHeaders),
|
|
399
|
+
body: JSON.stringify(payload),
|
|
400
|
+
signal: controller.signal
|
|
401
|
+
});
|
|
402
|
+
if (!response.ok) {
|
|
403
|
+
const detail = await safeReadText(response);
|
|
404
|
+
throw new Error(`openai-compatible request failed: ${response.status} ${response.statusText}${detail ? ` – ${detail}` : ''}`);
|
|
405
|
+
}
|
|
406
|
+
if (payload.stream) {
|
|
407
|
+
yield* streamBanditResponse(response);
|
|
408
|
+
}
|
|
409
|
+
else {
|
|
410
|
+
const data = (await response.json());
|
|
411
|
+
if (data.error?.message) {
|
|
412
|
+
throw new Error(data.error.message);
|
|
413
|
+
}
|
|
414
|
+
const text = extractTextFromBanditResponse(data);
|
|
415
|
+
const thinking = typeof data.choices?.[0]?.message?.thinking === 'string'
|
|
416
|
+
? data.choices[0].message.thinking
|
|
417
|
+
: undefined;
|
|
418
|
+
yield {
|
|
419
|
+
message: {
|
|
420
|
+
content: text,
|
|
421
|
+
role: 'assistant',
|
|
422
|
+
...(thinking ? { thinking } : {})
|
|
423
|
+
},
|
|
424
|
+
done: true
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
finally {
|
|
429
|
+
controller.abort();
|
|
430
|
+
}
|
|
431
|
+
};
|
|
432
|
+
return iterator();
|
|
433
|
+
}
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
function buildOpenAICompatibleHeaders(apiKey, extra) {
|
|
437
|
+
const headers = { 'Content-Type': 'application/json' };
|
|
438
|
+
if (apiKey)
|
|
439
|
+
headers.Authorization = `Bearer ${apiKey}`;
|
|
440
|
+
if (extra) {
|
|
441
|
+
for (const [key, value] of Object.entries(extra)) {
|
|
442
|
+
if (!key || typeof key !== 'string' || typeof value !== 'string')
|
|
443
|
+
continue;
|
|
444
|
+
if (key.toLowerCase() === 'content-type')
|
|
445
|
+
continue;
|
|
446
|
+
headers[key] = value;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
return headers;
|
|
450
|
+
}
|
|
451
|
+
function createDirectBanditProvider(apiUrl, apiKey) {
|
|
452
|
+
return {
|
|
453
|
+
chat(request) {
|
|
454
|
+
const controller = new AbortController();
|
|
455
|
+
const iterator = async function* () {
|
|
456
|
+
try {
|
|
457
|
+
const payload = serializeBanditPayload(request);
|
|
458
|
+
// (Multimodal debug dump removed — it was writing the full
|
|
459
|
+
// base64 image payload to stderr on every image turn, which
|
|
460
|
+
// leaked into users' terminals and looked like 100KB of
|
|
461
|
+
// gibberish before the actual response arrived.)
|
|
462
|
+
const response = await fetchWithRetry(apiUrl, {
|
|
463
|
+
method: 'POST',
|
|
464
|
+
headers: buildHeaders(apiKey),
|
|
465
|
+
body: JSON.stringify(payload),
|
|
466
|
+
signal: controller.signal
|
|
467
|
+
});
|
|
468
|
+
if (!response.ok) {
|
|
469
|
+
const detail = await safeReadText(response);
|
|
470
|
+
// Special-case 429 (rate limit). Parse the JSON body so the
|
|
471
|
+
// host can relay the window/resetsAt details to the user
|
|
472
|
+
// instead of just showing a generic "request failed." The
|
|
473
|
+
// thrown Error name is inspected by the host to toast
|
|
474
|
+
// differently for rate limits vs generic failures.
|
|
475
|
+
if (response.status === 429) {
|
|
476
|
+
let rateMessage = 'Rate limit reached. Email team@burtson.ai to upgrade.';
|
|
477
|
+
let window = 'session';
|
|
478
|
+
let resetsAtUnix;
|
|
479
|
+
try {
|
|
480
|
+
const parsed = JSON.parse(detail);
|
|
481
|
+
if (parsed?.message)
|
|
482
|
+
rateMessage = parsed.message;
|
|
483
|
+
if (parsed?.window)
|
|
484
|
+
window = parsed.window;
|
|
485
|
+
if (typeof parsed?.resetsAtUnix === 'number')
|
|
486
|
+
resetsAtUnix = parsed.resetsAtUnix;
|
|
487
|
+
}
|
|
488
|
+
catch {
|
|
489
|
+
// Non-JSON 429 body — fall through with defaults.
|
|
490
|
+
}
|
|
491
|
+
const err = new Error(rateMessage);
|
|
492
|
+
err.isRateLimit = true;
|
|
493
|
+
err.window = window;
|
|
494
|
+
err.resetsAtUnix = resetsAtUnix;
|
|
495
|
+
throw err;
|
|
496
|
+
}
|
|
497
|
+
throw new Error(`Bandit request failed: ${response.status} ${response.statusText}${detail ? ` – ${detail}` : ''}`);
|
|
498
|
+
}
|
|
499
|
+
// Gateway-side workaround signal — when the Ollama 0.24.0 qwen3.5
|
|
500
|
+
// parser 500s on bandit-logic, the gateway retries upstream with
|
|
501
|
+
// tools[] stripped and sets this header. The retry succeeded if we
|
|
502
|
+
// got here; just surface the fact so traces show how often the
|
|
503
|
+
// upstream parser bug is firing. Remove this log once the upstream
|
|
504
|
+
// qwen3.6 parser ships and the gateway workaround is reverted.
|
|
505
|
+
if (response.headers.get('x-upstream-retry-without-tools') === 'true') {
|
|
506
|
+
console.warn('[banditEngineProvider] gateway stripped tools[] this turn (qwen3.6 parser workaround).');
|
|
507
|
+
}
|
|
508
|
+
if (payload.stream) {
|
|
509
|
+
yield* streamBanditResponse(response);
|
|
510
|
+
}
|
|
511
|
+
else {
|
|
512
|
+
const data = (await response.json());
|
|
513
|
+
if (data.error?.message) {
|
|
514
|
+
throw new Error(data.error.message);
|
|
515
|
+
}
|
|
516
|
+
const text = extractTextFromBanditResponse(data);
|
|
517
|
+
const thinking = typeof data.choices?.[0]?.message?.thinking === 'string'
|
|
518
|
+
? data.choices[0].message.thinking
|
|
519
|
+
: undefined;
|
|
520
|
+
yield {
|
|
521
|
+
message: {
|
|
522
|
+
content: text,
|
|
523
|
+
role: 'assistant',
|
|
524
|
+
...(thinking ? { thinking } : {})
|
|
525
|
+
},
|
|
526
|
+
done: true
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
finally {
|
|
531
|
+
controller.abort();
|
|
532
|
+
}
|
|
533
|
+
};
|
|
534
|
+
return iterator();
|
|
535
|
+
}
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
function buildHeaders(apiKey) {
|
|
539
|
+
const headers = {
|
|
540
|
+
'Content-Type': 'application/json',
|
|
541
|
+
// Agent framework requests carry structured tool-call escape rules in
|
|
542
|
+
// the system prompt. Injecting an additional RAG system message at the
|
|
543
|
+
// gateway dilutes those instructions and causes malformed JSON on long
|
|
544
|
+
// content strings (verified root cause — see v1.5.24 changelog).
|
|
545
|
+
// The gateway's SeedPackContextService should short-circuit when this
|
|
546
|
+
// header is present.
|
|
547
|
+
'X-Bandit-Source': 'agent-framework',
|
|
548
|
+
'X-Skip-Seed-Pack': 'true'
|
|
549
|
+
};
|
|
550
|
+
if (apiKey) {
|
|
551
|
+
headers.Authorization = `Bearer ${apiKey}`;
|
|
552
|
+
}
|
|
553
|
+
return headers;
|
|
554
|
+
}
|
|
555
|
+
function serializeBanditPayload(request) {
|
|
556
|
+
const payloadImages = collectBanditPayloadImages(request);
|
|
557
|
+
const messages = request.messages.map((message) => ({
|
|
558
|
+
role: message.role,
|
|
559
|
+
content: normalizeBanditMessageContent(message.content)
|
|
560
|
+
}));
|
|
561
|
+
// Promote top-level `request.images` onto the last user message as
|
|
562
|
+
// content parts. The tool-use adapter (extension.ts) attaches
|
|
563
|
+
// images via `request.images` since ToolLoopMessage is a plain
|
|
564
|
+
// { role, content: string } — it has no notion of content parts.
|
|
565
|
+
// Without this splice, the Bandit gateway (OpenAI-compatible)
|
|
566
|
+
// reads message.content only and the image silently never reaches
|
|
567
|
+
// the model. Ollama's normalizeOllamaMessages does the equivalent
|
|
568
|
+
// splice onto message.images; this is the symmetric fix for the
|
|
569
|
+
// hosted path.
|
|
570
|
+
if (payloadImages.length > 0) {
|
|
571
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
572
|
+
if (messages[i].role !== 'user')
|
|
573
|
+
continue;
|
|
574
|
+
const existingUrls = new Set(messages[i].content
|
|
575
|
+
.filter((part) => part.type === 'image_url')
|
|
576
|
+
.map((part) => part.image_url.url));
|
|
577
|
+
for (const url of payloadImages) {
|
|
578
|
+
if (!existingUrls.has(url)) {
|
|
579
|
+
messages[i].content.push({ type: 'image_url', image_url: { url } });
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
break;
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
const payload = {
|
|
586
|
+
model: request.model,
|
|
587
|
+
messages,
|
|
588
|
+
temperature: request.temperature,
|
|
589
|
+
top_p: typeof request.options?.top_p === 'number' ? request.options.top_p : undefined,
|
|
590
|
+
stream: request.stream !== false
|
|
591
|
+
};
|
|
592
|
+
// Forward native tool-calling schemas on the cloud path. The
|
|
593
|
+
// Gateway's BuildOllamaRequestPayloadAsync lets unknown top-level
|
|
594
|
+
// keys flow through via AdditionalProperties, so `tools` reaches
|
|
595
|
+
// the upstream Ollama /api/chat and the model gets structured tool
|
|
596
|
+
// calling. Without this, bandit-logic (Qwen 2.5 Coder) had neither
|
|
597
|
+
// the text-prompt tool block (skipped when nativeTools=true) NOR
|
|
598
|
+
// the native tools field, and emitted bare JSON tool-call prose
|
|
599
|
+
// that the downstream parser couldn't extract.
|
|
600
|
+
if (Array.isArray(request.tools) && request.tools.length > 0) {
|
|
601
|
+
payload.tools = request.tools;
|
|
602
|
+
// Ollama streams tool_calls only on the terminal chunk for native
|
|
603
|
+
// tool calling — disable streaming for these requests so the
|
|
604
|
+
// provider's non-streaming translator (see ollama path at ~line
|
|
605
|
+
// 334) can pair tool_calls back to inline <tool_call> markup.
|
|
606
|
+
payload.stream = false;
|
|
607
|
+
}
|
|
608
|
+
// Keep the top-level images field too for backward compat with any
|
|
609
|
+
// consumer that has been reading them there historically.
|
|
610
|
+
if (payloadImages.length > 0) {
|
|
611
|
+
payload.images = payloadImages;
|
|
612
|
+
}
|
|
613
|
+
// Forward the thinking override to the gateway. Symmetric to the
|
|
614
|
+
// direct Ollama path — per-request `think` overrides runtime
|
|
615
|
+
// defaults, and AdditionalProperties pass-through on the gateway
|
|
616
|
+
// side means it reaches upstream Ollama as a top-level field.
|
|
617
|
+
if (request.think !== undefined) {
|
|
618
|
+
payload.think = request.think;
|
|
619
|
+
}
|
|
620
|
+
else {
|
|
621
|
+
const runtimeDefaults = (0, modelCapabilities_1.resolveOllamaRuntimeOptions)(request.model);
|
|
622
|
+
if (runtimeDefaults.think !== undefined) {
|
|
623
|
+
payload.think = runtimeDefaults.think;
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
return payload;
|
|
627
|
+
}
|
|
628
|
+
function normalizeBanditMessageContent(content) {
|
|
629
|
+
if (Array.isArray(content)) {
|
|
630
|
+
const normalized = [];
|
|
631
|
+
for (const part of content) {
|
|
632
|
+
if (!part) {
|
|
633
|
+
continue;
|
|
634
|
+
}
|
|
635
|
+
if (part.type === 'text') {
|
|
636
|
+
const text = typeof part.text === 'string' ? part.text : part.text != null ? String(part.text) : '';
|
|
637
|
+
if (text.length > 0) {
|
|
638
|
+
normalized.push({ type: 'text', text });
|
|
639
|
+
}
|
|
640
|
+
continue;
|
|
641
|
+
}
|
|
642
|
+
if (part.type === 'image_url') {
|
|
643
|
+
const url = extractImageUrl(part);
|
|
644
|
+
if (url) {
|
|
645
|
+
normalized.push({ type: 'image_url', image_url: { url } });
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
if (normalized.length > 0) {
|
|
650
|
+
return normalized;
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
const text = typeof content === 'string' ? content : content != null ? String(content) : '';
|
|
654
|
+
return [{ type: 'text', text }];
|
|
655
|
+
}
|
|
656
|
+
function extractImageUrl(part) {
|
|
657
|
+
if (!part || part.type !== 'image_url') {
|
|
658
|
+
return undefined;
|
|
659
|
+
}
|
|
660
|
+
const imageField = part.image_url;
|
|
661
|
+
if (typeof imageField === 'string') {
|
|
662
|
+
return normalizeImageUrl(imageField);
|
|
663
|
+
}
|
|
664
|
+
if (imageField && typeof imageField.url === 'string') {
|
|
665
|
+
return normalizeImageUrl(imageField.url);
|
|
666
|
+
}
|
|
667
|
+
return undefined;
|
|
668
|
+
}
|
|
669
|
+
function normalizeImageUrl(candidate) {
|
|
670
|
+
if (typeof candidate !== 'string') {
|
|
671
|
+
return undefined;
|
|
672
|
+
}
|
|
673
|
+
const trimmed = candidate.trim();
|
|
674
|
+
if (!trimmed) {
|
|
675
|
+
return undefined;
|
|
676
|
+
}
|
|
677
|
+
if (/^data:/i.test(trimmed) || /^https?:/i.test(trimmed)) {
|
|
678
|
+
return trimmed;
|
|
679
|
+
}
|
|
680
|
+
return `data:image/png;base64,${trimmed}`;
|
|
681
|
+
}
|
|
682
|
+
function extractTextFromBanditResponse(response) {
|
|
683
|
+
const [firstChoice] = response.choices ?? [];
|
|
684
|
+
if (!firstChoice) {
|
|
685
|
+
throw new Error('Bandit response missing choices.');
|
|
686
|
+
}
|
|
687
|
+
if (firstChoice.text) {
|
|
688
|
+
return firstChoice.text;
|
|
689
|
+
}
|
|
690
|
+
const message = firstChoice.message;
|
|
691
|
+
if (!message) {
|
|
692
|
+
throw new Error('Bandit response missing message content.');
|
|
693
|
+
}
|
|
694
|
+
// Native tool-call translation for the cloud path. When the gateway
|
|
695
|
+
// forwarded a `tools` field, Ollama upstream returns `tool_calls` on
|
|
696
|
+
// the terminal message. We translate each call into inline
|
|
697
|
+
// `<tool_call>{"name":...,"params":{...}}</tool_call>` markup so the
|
|
698
|
+
// ToolUseLoop's existing parser picks them up unchanged — same
|
|
699
|
+
// contract the direct-Ollama path uses at line ~358.
|
|
700
|
+
//
|
|
701
|
+
// `arguments` arrives as either:
|
|
702
|
+
// - a JSON STRING (OpenAI-compat convention — what our gateway
|
|
703
|
+
// emits, what any OpenAI SDK client expects)
|
|
704
|
+
// - an object (Ollama's native shape, passed through if the
|
|
705
|
+
// gateway wasn't the translator)
|
|
706
|
+
// Handle both so the cloud and direct-Ollama paths stay symmetric.
|
|
707
|
+
let toolCallMarkup = '';
|
|
708
|
+
const toolCalls = message.tool_calls;
|
|
709
|
+
if (Array.isArray(toolCalls) && toolCalls.length > 0) {
|
|
710
|
+
const markers = toolCalls.map(tc => {
|
|
711
|
+
const name = tc.function?.name ?? '';
|
|
712
|
+
let args = tc.function?.arguments ?? {};
|
|
713
|
+
if (typeof args === 'string') {
|
|
714
|
+
try {
|
|
715
|
+
args = JSON.parse(args);
|
|
716
|
+
}
|
|
717
|
+
catch {
|
|
718
|
+
args = {};
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
const params = {};
|
|
722
|
+
if (args && typeof args === 'object') {
|
|
723
|
+
for (const [k, v] of Object.entries(args)) {
|
|
724
|
+
params[k] = typeof v === 'string' ? v : JSON.stringify(v);
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
return `<tool_call>${JSON.stringify({ name, params })}</tool_call>`;
|
|
728
|
+
});
|
|
729
|
+
toolCallMarkup = markers.join('\n');
|
|
730
|
+
}
|
|
731
|
+
let baseText = '';
|
|
732
|
+
if (typeof message.content === 'string') {
|
|
733
|
+
baseText = message.content;
|
|
734
|
+
}
|
|
735
|
+
else if (Array.isArray(message.content)) {
|
|
736
|
+
const parts = message.content
|
|
737
|
+
.filter((block) => block.type === 'text' && typeof block.text === 'string')
|
|
738
|
+
.map((block) => block.text ?? '');
|
|
739
|
+
baseText = parts.join('\n\n');
|
|
740
|
+
}
|
|
741
|
+
if (!baseText && !toolCallMarkup) {
|
|
742
|
+
// Soft-fail with forensics. The previous behaviour (throw) bubbled
|
|
743
|
+
// up to the user as a hard error mid-conversation when the gateway
|
|
744
|
+
// emitted an unusual response shape — a `tool_calls`-only response
|
|
745
|
+
// where the gateway dropped the array, a `thinking`-only response,
|
|
746
|
+
// a content-parts array with no text blocks, etc. The tool-use loop
|
|
747
|
+
// already has an empty-response retry path that handles "" cleanly,
|
|
748
|
+
// so returning empty here lets the model recover on its own turn
|
|
749
|
+
// instead of crashing the whole run.
|
|
750
|
+
//
|
|
751
|
+
// The console warn captures the full message shape for the next
|
|
752
|
+
// diagnostic pass — without this we have no idea what the gateway
|
|
753
|
+
// actually sent. Truncated to 500 chars so a runaway response
|
|
754
|
+
// doesn't flood the log.
|
|
755
|
+
try {
|
|
756
|
+
const shape = JSON.stringify(message).slice(0, 500);
|
|
757
|
+
console.warn(`[banditEngineProvider] empty response from gateway, soft-recovering. shape=${shape}`);
|
|
758
|
+
}
|
|
759
|
+
catch {
|
|
760
|
+
console.warn('[banditEngineProvider] empty response from gateway, soft-recovering. (shape unserializable)');
|
|
761
|
+
}
|
|
762
|
+
return '';
|
|
763
|
+
}
|
|
764
|
+
return toolCallMarkup
|
|
765
|
+
? (baseText ? `${baseText}\n${toolCallMarkup}` : toolCallMarkup)
|
|
766
|
+
: baseText;
|
|
767
|
+
}
|
|
768
|
+
function normalizeBanditApiUrl(value) {
|
|
769
|
+
const trimmed = value?.trim();
|
|
770
|
+
if (!trimmed) {
|
|
771
|
+
return DEFAULT_BANDIT_COMPLETIONS_URL;
|
|
772
|
+
}
|
|
773
|
+
const stripped = trimmed.replace(/\/+$/, '');
|
|
774
|
+
// If the user gave only a base URL (no path beyond "/"), auto-append the
|
|
775
|
+
// standard `/completions` endpoint. Anyone with a real custom path keeps
|
|
776
|
+
// it untouched. Covers the common "they set apiUrl to their host" case.
|
|
777
|
+
try {
|
|
778
|
+
const parsed = new URL(stripped);
|
|
779
|
+
if (parsed.pathname === '' || parsed.pathname === '/') {
|
|
780
|
+
return `${stripped}/completions`;
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
catch {
|
|
784
|
+
// Not a valid URL — let the fetch call surface the error itself.
|
|
785
|
+
}
|
|
786
|
+
return stripped;
|
|
787
|
+
}
|
|
788
|
+
function normalizeUrl(value, fallback) {
|
|
789
|
+
const trimmed = value?.trim();
|
|
790
|
+
return trimmed && trimmed.length > 0 ? trimmed.replace(/\/+$/, '') : fallback;
|
|
791
|
+
}
|
|
792
|
+
async function* streamBanditResponse(response) {
|
|
793
|
+
const body = response.body;
|
|
794
|
+
if (!body) {
|
|
795
|
+
throw new Error('Bandit response did not include a readable stream.');
|
|
796
|
+
}
|
|
797
|
+
const reader = body.getReader();
|
|
798
|
+
const decoder = new TextDecoder();
|
|
799
|
+
let buffer = '';
|
|
800
|
+
let emittedDone = false;
|
|
801
|
+
const readChunkText = (chunk) => {
|
|
802
|
+
const readContent = (content) => {
|
|
803
|
+
if (!content) {
|
|
804
|
+
return '';
|
|
805
|
+
}
|
|
806
|
+
if (typeof content === 'string') {
|
|
807
|
+
return content;
|
|
808
|
+
}
|
|
809
|
+
if (!Array.isArray(content)) {
|
|
810
|
+
return '';
|
|
811
|
+
}
|
|
812
|
+
return content
|
|
813
|
+
.map((part) => (part && typeof part.text === 'string' ? part.text : ''))
|
|
814
|
+
.filter((part) => part.length > 0)
|
|
815
|
+
.join('');
|
|
816
|
+
};
|
|
817
|
+
const choice = chunk.choices?.[0];
|
|
818
|
+
const fromDelta = readContent(choice?.delta?.content);
|
|
819
|
+
if (fromDelta) {
|
|
820
|
+
return fromDelta;
|
|
821
|
+
}
|
|
822
|
+
const fromChoiceMessage = readContent(choice?.message?.content);
|
|
823
|
+
if (fromChoiceMessage) {
|
|
824
|
+
return fromChoiceMessage;
|
|
825
|
+
}
|
|
826
|
+
const fromChoiceText = typeof choice?.text === 'string' ? choice.text : '';
|
|
827
|
+
if (fromChoiceText) {
|
|
828
|
+
return fromChoiceText;
|
|
829
|
+
}
|
|
830
|
+
const fromMessage = readContent(chunk.message?.content);
|
|
831
|
+
if (fromMessage) {
|
|
832
|
+
return fromMessage;
|
|
833
|
+
}
|
|
834
|
+
return typeof chunk.response === 'string' ? chunk.response : '';
|
|
835
|
+
};
|
|
836
|
+
/** Extract chain-of-thought reasoning from any of the standard
|
|
837
|
+
* positions the gateway might emit it — delta.thinking (streaming
|
|
838
|
+
* OpenAI-compat), message.thinking (non-streaming fallback), or the
|
|
839
|
+
* top-level message.thinking (direct Ollama pass-through). */
|
|
840
|
+
const readChunkThinking = (chunk) => {
|
|
841
|
+
const choice = chunk.choices?.[0];
|
|
842
|
+
const fromDelta = typeof choice?.delta?.thinking === 'string' ? choice.delta.thinking : '';
|
|
843
|
+
if (fromDelta) {
|
|
844
|
+
return fromDelta;
|
|
845
|
+
}
|
|
846
|
+
const fromChoiceMessage = typeof choice?.message?.thinking === 'string' ? choice.message.thinking : '';
|
|
847
|
+
if (fromChoiceMessage) {
|
|
848
|
+
return fromChoiceMessage;
|
|
849
|
+
}
|
|
850
|
+
return typeof chunk.message?.thinking === 'string' ? chunk.message.thinking : '';
|
|
851
|
+
};
|
|
852
|
+
const isDoneChunk = (chunk) => {
|
|
853
|
+
if (chunk.done === true) {
|
|
854
|
+
return true;
|
|
855
|
+
}
|
|
856
|
+
const finishReason = chunk.choices?.[0]?.finish_reason;
|
|
857
|
+
return typeof finishReason === 'string' && finishReason.length > 0;
|
|
858
|
+
};
|
|
859
|
+
const parseLine = (rawLine) => {
|
|
860
|
+
const line = rawLine.trim();
|
|
861
|
+
if (!line || line.startsWith('event:')) {
|
|
862
|
+
return { done: false, responses: [] };
|
|
863
|
+
}
|
|
864
|
+
const payload = line.startsWith('data:') ? line.slice(5).trim() : line;
|
|
865
|
+
if (!payload) {
|
|
866
|
+
return { done: false, responses: [] };
|
|
867
|
+
}
|
|
868
|
+
if (payload === '[DONE]') {
|
|
869
|
+
const responses = [];
|
|
870
|
+
if (!emittedDone) {
|
|
871
|
+
emittedDone = true;
|
|
872
|
+
responses.push({ message: { content: '', role: 'assistant' }, done: true });
|
|
873
|
+
}
|
|
874
|
+
return { done: true, responses };
|
|
875
|
+
}
|
|
876
|
+
try {
|
|
877
|
+
const chunk = JSON.parse(payload);
|
|
878
|
+
const responses = [];
|
|
879
|
+
const errorMessage = typeof chunk.error === 'string'
|
|
880
|
+
? chunk.error
|
|
881
|
+
: chunk.error?.message;
|
|
882
|
+
if (errorMessage) {
|
|
883
|
+
return {
|
|
884
|
+
done: true,
|
|
885
|
+
responses,
|
|
886
|
+
error: new Error(errorMessage)
|
|
887
|
+
};
|
|
888
|
+
}
|
|
889
|
+
const text = stripBase64BlobsInline(readChunkText(chunk));
|
|
890
|
+
const thinking = readChunkThinking(chunk);
|
|
891
|
+
if (text || thinking) {
|
|
892
|
+
responses.push({
|
|
893
|
+
message: {
|
|
894
|
+
content: text,
|
|
895
|
+
role: 'assistant',
|
|
896
|
+
...(thinking ? { thinking } : {})
|
|
897
|
+
},
|
|
898
|
+
done: false
|
|
899
|
+
});
|
|
900
|
+
}
|
|
901
|
+
if (isDoneChunk(chunk)) {
|
|
902
|
+
if (!emittedDone) {
|
|
903
|
+
emittedDone = true;
|
|
904
|
+
responses.push({ message: { content: '', role: 'assistant' }, done: true });
|
|
905
|
+
}
|
|
906
|
+
return { done: true, responses };
|
|
907
|
+
}
|
|
908
|
+
return { done: false, responses };
|
|
909
|
+
}
|
|
910
|
+
catch {
|
|
911
|
+
// Ignore malformed keep-alive or non-JSON lines and continue streaming.
|
|
912
|
+
return { done: false, responses: [] };
|
|
913
|
+
}
|
|
914
|
+
};
|
|
915
|
+
// Stream until the reader indicates completion.
|
|
916
|
+
let stallWarned = false;
|
|
917
|
+
for (;;) {
|
|
918
|
+
const { value, done } = await (0, streamIdleTimeout_1.readWithIdleTimeout)(reader, {
|
|
919
|
+
idleMs: streamIdleTimeout_1.DEFAULT_STREAM_IDLE_MS,
|
|
920
|
+
warnAfterMs: streamIdleTimeout_1.DEFAULT_STREAM_WARN_MS,
|
|
921
|
+
abortLabel: 'Bandit stream',
|
|
922
|
+
onWarn: (elapsedMs) => {
|
|
923
|
+
if (stallWarned)
|
|
924
|
+
return;
|
|
925
|
+
stallWarned = true;
|
|
926
|
+
console.warn(`[banditEngineProvider] Bandit stream went quiet at ${elapsedMs}ms — still waiting…`);
|
|
927
|
+
}
|
|
928
|
+
});
|
|
929
|
+
if (value) {
|
|
930
|
+
buffer += decoder.decode(value, { stream: true });
|
|
931
|
+
}
|
|
932
|
+
const lines = buffer.split('\n');
|
|
933
|
+
if (done) {
|
|
934
|
+
buffer = '';
|
|
935
|
+
}
|
|
936
|
+
else {
|
|
937
|
+
buffer = lines.pop() ?? '';
|
|
938
|
+
}
|
|
939
|
+
for (const rawLine of lines) {
|
|
940
|
+
const parsed = parseLine(rawLine);
|
|
941
|
+
if (parsed.error) {
|
|
942
|
+
throw parsed.error;
|
|
943
|
+
}
|
|
944
|
+
for (const responseChunk of parsed.responses) {
|
|
945
|
+
yield responseChunk;
|
|
946
|
+
}
|
|
947
|
+
if (parsed.done) {
|
|
948
|
+
return;
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
if (done) {
|
|
952
|
+
break;
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
if (!emittedDone) {
|
|
956
|
+
yield { message: { content: '', role: 'assistant' }, done: true };
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
async function safeReadText(response) {
|
|
960
|
+
try {
|
|
961
|
+
return await response.text();
|
|
962
|
+
}
|
|
963
|
+
catch {
|
|
964
|
+
return '';
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
/**
|
|
968
|
+
* Fetch with retry on transient gateway/network failures. Retries on 5xx
|
|
969
|
+
* status (502/503/504 are the gateway's typical "upstream had a hiccup"
|
|
970
|
+
* codes; 500 from the bandit cloud has been observed once in a session
|
|
971
|
+
* after a long compaction-heavy turn) and on transient network errors
|
|
972
|
+
* (ECONNREFUSED, socket hang up, fetch-failed). Does NOT retry on:
|
|
973
|
+
* - AbortError — the caller cancelled (Esc / signal); honour that.
|
|
974
|
+
* - 429 — rate limited; the caller has its own special-case handling.
|
|
975
|
+
* - 4xx — request-shape problems won't get better by replaying.
|
|
976
|
+
*
|
|
977
|
+
* Backoff is exponential (500ms, 1s, 2s) to a max of 3 retries. The
|
|
978
|
+
* bandit auto-evaluation turn that died with `Bandit request failed: 500
|
|
979
|
+
* Internal Server Error` after compacting 19 messages on iteration 7
|
|
980
|
+
* (2026-05-06 22:24Z) would have survived a single retry — same applies
|
|
981
|
+
* to the "1 failed" subagent, which loses its LLM call to the same kind
|
|
982
|
+
* of transient gateway 5xx.
|
|
983
|
+
*/
|
|
984
|
+
async function fetchWithRetry(apiUrl, init, opts) {
|
|
985
|
+
const retries = opts?.retries ?? 3;
|
|
986
|
+
const baseMs = opts?.baseMs ?? 500;
|
|
987
|
+
const transientNetworkRe = /fetch failed|ECONNREFUSED|ETIMEDOUT|EAI_AGAIN|socket hang up|network error/i;
|
|
988
|
+
let lastError;
|
|
989
|
+
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
990
|
+
try {
|
|
991
|
+
const response = await fetch(apiUrl, init);
|
|
992
|
+
if (response.status >= 500 && response.status <= 599) {
|
|
993
|
+
if (attempt < retries) {
|
|
994
|
+
// Drain and discard the body so the connection can be reused
|
|
995
|
+
// by the next attempt; otherwise fetch may keep it pinned.
|
|
996
|
+
try {
|
|
997
|
+
await response.body?.cancel();
|
|
998
|
+
}
|
|
999
|
+
catch { /* ignore */ }
|
|
1000
|
+
await new Promise(r => setTimeout(r, baseMs * Math.pow(2, attempt)));
|
|
1001
|
+
continue;
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
return response;
|
|
1005
|
+
}
|
|
1006
|
+
catch (err) {
|
|
1007
|
+
// AbortError = caller pulled the rip-cord. Don't retry.
|
|
1008
|
+
if (err instanceof Error && err.name === 'AbortError')
|
|
1009
|
+
throw err;
|
|
1010
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1011
|
+
if (!transientNetworkRe.test(msg) || attempt >= retries)
|
|
1012
|
+
throw err;
|
|
1013
|
+
lastError = err;
|
|
1014
|
+
await new Promise(r => setTimeout(r, baseMs * Math.pow(2, attempt)));
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
// Loop guarantees one of the branches above returns or throws; this
|
|
1018
|
+
// line is purely for the type-checker's benefit.
|
|
1019
|
+
throw lastError ?? new Error('fetchWithRetry: exhausted attempts');
|
|
1020
|
+
}
|
|
1021
|
+
//# sourceMappingURL=banditEngineProvider.js.map
|