@iislee/opencodex 2.11.0 → 2.31.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/AGENTS_INSTALL.md +109 -0
- package/README.md +114 -19
- package/bin/ocx.mjs +164 -36
- package/bin/package-main.mjs +1 -1
- package/gui/dist/assets/index-BF38heuV.js +104 -0
- package/gui/dist/assets/index-DMiI18Kv.css +1 -0
- package/gui/dist/index.html +2 -2
- package/gui/dist/provider-icons/alibaba-color.svg +1 -1
- package/gui/dist/provider-icons/antigravity-color.svg +1 -1
- package/gui/dist/provider-icons/claude-color.svg +1 -1
- package/gui/dist/provider-icons/cline-color.svg +16 -0
- package/gui/dist/provider-icons/cloudflare-ai-gateway-color.svg +1 -1
- package/gui/dist/provider-icons/commandcode-color.svg +1 -0
- package/gui/dist/provider-icons/copilot-color.svg +1 -1
- package/gui/dist/provider-icons/cursor-color.svg +1 -1
- package/gui/dist/provider-icons/deepseek-color.svg +1 -1
- package/gui/dist/provider-icons/firepass-color.svg +1 -1
- package/gui/dist/provider-icons/fireworks-color.svg +1 -1
- package/gui/dist/provider-icons/gemini-color.svg +1 -1
- package/gui/dist/provider-icons/github-copilot-color.svg +1 -1
- package/gui/dist/provider-icons/gitlab-duo-color.svg +1 -1
- package/gui/dist/provider-icons/grok.svg +1 -1
- package/gui/dist/provider-icons/groq-color.svg +1 -1
- package/gui/dist/provider-icons/huggingface-color.svg +1 -1
- package/gui/dist/provider-icons/kimi-color.svg +1 -1
- package/gui/dist/provider-icons/kiro-color.svg +2 -2
- package/gui/dist/provider-icons/lm-studio-color.svg +1 -1
- package/gui/dist/provider-icons/mistral-color.svg +1 -1
- package/gui/dist/provider-icons/moonshot-color.svg +1 -1
- package/gui/dist/provider-icons/nvidia-color.svg +1 -1
- package/gui/dist/provider-icons/ollama-color.svg +1 -1
- package/gui/dist/provider-icons/openai.svg +1 -1
- package/gui/dist/provider-icons/opencode.svg +2 -1
- package/gui/dist/provider-icons/openrouter-color.svg +1 -1
- package/gui/dist/provider-icons/pi.svg +2 -2
- package/gui/dist/provider-icons/qianfan-color.svg +1 -1
- package/gui/dist/provider-icons/qwen-portal-color.svg +1 -1
- package/gui/dist/provider-icons/vercel-ai-gateway-color.svg +1 -1
- package/gui/dist/provider-icons/vllm-color.svg +1 -1
- package/gui/dist/provider-icons/xiaomi-color.svg +1 -1
- package/package.json +19 -10
- package/src/adapters/anthropic-output-schema.ts +137 -0
- package/src/adapters/anthropic.ts +376 -52
- package/src/adapters/base.ts +54 -7
- package/src/adapters/client-fingerprint.ts +18 -12
- package/src/adapters/cline-pass-deepseek-v4-tool-replay.ts +69 -0
- package/src/adapters/command-code.ts +601 -0
- package/src/adapters/cursor/checkpoint-store.ts +303 -0
- package/src/adapters/cursor/cursor-errors.ts +129 -6
- package/src/adapters/cursor/discovery.ts +61 -4
- package/src/adapters/cursor/effort-map.ts +27 -3
- package/src/adapters/cursor/framing.ts +39 -0
- package/src/adapters/cursor/h2-pool.ts +123 -0
- package/src/adapters/cursor/http1-bidi.ts +361 -0
- package/src/adapters/cursor/images.ts +704 -0
- package/src/adapters/cursor/live-models.ts +174 -58
- package/src/adapters/cursor/live-transport.ts +609 -170
- package/src/adapters/cursor/native-exec-common.ts +23 -2
- package/src/adapters/cursor/native-exec-fs.ts +10 -7
- package/src/adapters/cursor/native-exec-network.ts +1 -1
- package/src/adapters/cursor/native-exec-shell.ts +0 -1
- package/src/adapters/cursor/native-exec.ts +101 -14
- package/src/adapters/cursor/protobuf-events.ts +829 -11
- package/src/adapters/cursor/protobuf-request.ts +383 -65
- package/src/adapters/cursor/request-builder.ts +237 -31
- package/src/adapters/cursor/tool-definitions.ts +242 -12
- package/src/adapters/cursor/tool-result-normalize.ts +92 -0
- package/src/adapters/cursor/transport.ts +22 -0
- package/src/adapters/cursor/types.ts +28 -1
- package/src/adapters/cursor.ts +133 -7
- package/src/adapters/google-antigravity-replay.ts +558 -34
- package/src/adapters/google-antigravity-wire.ts +43 -10
- package/src/adapters/google-http.ts +38 -10
- package/src/adapters/google-tool-schema.ts +80 -15
- package/src/adapters/google-truncation.ts +11 -0
- package/src/adapters/google.ts +618 -74
- package/src/adapters/identity.ts +39 -6
- package/src/adapters/kiro-errors.ts +11 -0
- package/src/adapters/kiro-events.ts +19 -1
- package/src/adapters/kiro-thinking.ts +18 -2
- package/src/adapters/kiro-tools.ts +10 -1
- package/src/adapters/kiro.ts +92 -54
- package/src/adapters/mimo-free.ts +17 -0
- package/src/adapters/openai-chat-url.ts +11 -0
- package/src/adapters/openai-chat.ts +1311 -336
- package/src/adapters/openai-responses-url.ts +16 -0
- package/src/adapters/openai-responses.ts +830 -56
- package/src/adapters/registry.ts +175 -0
- package/src/adapters/responses-tool-schema.ts +67 -0
- package/src/adapters/tool-call-id.ts +119 -0
- package/src/adapters/tool-catalog-nudge.ts +104 -21
- package/src/adapters/xai-web-search.ts +185 -0
- package/src/bridge.ts +428 -61
- package/src/chat/inbound.ts +43 -19
- package/src/chat/outbound.ts +82 -26
- package/src/claude/agents-inject.ts +32 -9
- package/src/claude/context-windows.ts +21 -5
- package/src/claude/desktop-3p.ts +243 -9
- package/src/claude/gateway-cache.ts +41 -4
- package/src/claude/inbound.ts +72 -3
- package/src/claude/model-info.ts +38 -15
- package/src/claude/outbound.ts +70 -16
- package/src/cli/account-api.ts +35 -1
- package/src/cli/account-auth.ts +33 -6
- package/src/cli/account-catalog-refresh.ts +14 -0
- package/src/cli/account-extended.ts +389 -2
- package/src/cli/account-main.ts +317 -0
- package/src/cli/account.ts +30 -6
- package/src/cli/agent.ts +45 -1
- package/src/cli/claude-agent-startup-sync.ts +73 -0
- package/src/cli/claude-desktop.ts +29 -4
- package/src/cli/claude.ts +103 -34
- package/src/cli/codex-log-guard-doctor.ts +103 -0
- package/src/cli/codex-shim-autorestore.ts +2 -0
- package/src/cli/codex-shim-readiness.ts +76 -0
- package/src/cli/combo.ts +8 -0
- package/src/cli/config-command.ts +74 -10
- package/src/cli/dispatch.ts +593 -0
- package/src/cli/doctor.ts +315 -43
- package/src/cli/ensure-desired-integrations.ts +152 -0
- package/src/cli/export-command.ts +46 -20
- package/src/cli/help.ts +20 -271
- package/src/cli/index.ts +378 -519
- package/src/cli/init.ts +4 -17
- package/src/cli/integrations.ts +120 -2
- package/src/cli/lab.ts +607 -0
- package/src/cli/launcher-context.ts +77 -0
- package/src/cli/minimax.ts +497 -0
- package/src/cli/models-runtime.ts +35 -2
- package/src/cli/models.ts +100 -14
- package/src/cli/observe.ts +92 -3
- package/src/cli/opencode.ts +4 -2
- package/src/cli/provider-runtime.ts +18 -1
- package/src/cli/provider.ts +24 -3
- package/src/cli/ready.ts +301 -0
- package/src/cli/registry.ts +437 -0
- package/src/cli/root.ts +86 -0
- package/src/cli/route-policy.ts +92 -0
- package/src/cli/runtime-api.ts +6 -3
- package/src/cli/star-prompt.ts +71 -15
- package/src/cli/status.ts +10 -3
- package/src/cli/system-restart-client.ts +146 -0
- package/src/cli/tray-proxy.ts +153 -6
- package/src/cli/v2.ts +105 -10
- package/src/cli.ts +1 -1
- package/src/clients/config-export.ts +1358 -21
- package/src/codex/account-label.ts +14 -1
- package/src/codex/account-lifecycle.ts +130 -13
- package/src/codex/account-namespaces.ts +49 -3
- package/src/codex/account-priority.ts +83 -0
- package/src/codex/account-store.ts +29 -2
- package/src/codex/account-usability.ts +25 -2
- package/src/codex/admission.ts +256 -0
- package/src/codex/affinity-debug.ts +162 -0
- package/src/codex/app-server-processes.ts +493 -106
- package/src/codex/app-server-restart-service.ts +232 -0
- package/src/codex/auth-api.ts +849 -242
- package/src/codex/auth-collision.ts +5 -3
- package/src/codex/auth-context.ts +345 -32
- package/src/codex/autostart-health.ts +8 -1
- package/src/codex/catalog/account-models.ts +67 -0
- package/src/codex/catalog/aggregation.ts +68 -10
- package/src/codex/catalog/bundled.ts +331 -33
- package/src/codex/catalog/effort.ts +121 -30
- package/src/codex/catalog/filesystem-evidence.ts +302 -0
- package/src/codex/catalog/kinds.ts +2 -0
- package/src/codex/catalog/metadata.ts +529 -45
- package/src/codex/catalog/native-models.ts +72 -0
- package/src/codex/catalog/parsing.ts +224 -30
- package/src/codex/catalog/provider-fetch.ts +1460 -134
- package/src/codex/catalog/sync.ts +1449 -186
- package/src/codex/catalog-admission.ts +199 -0
- package/src/codex/catalog-refresh-status.ts +105 -0
- package/src/codex/catalog-write-serialization.ts +242 -0
- package/src/codex/catalog.ts +6 -3
- package/src/codex/codex-write-lock.ts +384 -0
- package/src/codex/convergence-types.ts +614 -0
- package/src/codex/convergence.ts +651 -0
- package/src/codex/coordinator-doctor.ts +332 -0
- package/src/codex/custom-model-catalog-migration.ts +176 -0
- package/src/codex/desired-state.ts +230 -0
- package/src/codex/features.ts +636 -39
- package/src/codex/generation.ts +202 -0
- package/src/codex/history-job.ts +407 -0
- package/src/codex/history-lock.ts +242 -0
- package/src/codex/history-migration-guardian.ts +26 -20
- package/src/codex/history-provider.ts +231 -28
- package/src/codex/history-transition.ts +105 -0
- package/src/codex/history-worker.ts +220 -0
- package/src/codex/inject-coordination.ts +290 -0
- package/src/codex/inject.ts +1073 -152
- package/src/codex/injected-marker.ts +37 -3
- package/src/codex/integration-record.ts +266 -0
- package/src/codex/internal/catalog-writer.ts +203 -0
- package/src/codex/internal/history-writer.ts +80 -0
- package/src/codex/journal.ts +66 -4
- package/src/codex/log-guard/inspect.ts +506 -0
- package/src/codex/log-guard/lock.ts +150 -0
- package/src/codex/log-guard/maintenance.ts +403 -0
- package/src/codex/log-guard/path-safety.ts +88 -0
- package/src/codex/log-guard/policy.ts +44 -0
- package/src/codex/log-guard/processes.ts +205 -0
- package/src/codex/log-guard/protection.ts +489 -0
- package/src/codex/log-guard/sqlite-errors.ts +9 -0
- package/src/codex/main-account-cache.ts +24 -0
- package/src/codex/main-account.ts +29 -1
- package/src/codex/management-convergence.ts +167 -0
- package/src/codex/model-cache.ts +56 -10
- package/src/codex/model-entitlements.ts +353 -0
- package/src/codex/native-main-admission.ts +47 -0
- package/src/codex/native-main-auth-temp.ts +187 -0
- package/src/codex/native-main-claim.ts +178 -0
- package/src/codex/native-main-lock-file.ts +162 -0
- package/src/codex/native-main-owner.ts +329 -0
- package/src/codex/native-profile-api.ts +247 -0
- package/src/codex/native-profile-manager.ts +1531 -0
- package/src/codex/native-profile-processes.ts +121 -0
- package/src/codex/native-profile-recovery.ts +99 -0
- package/src/codex/native-profile-stage-store.ts +387 -0
- package/src/codex/native-profile-startup.ts +492 -0
- package/src/codex/native-profile-store.ts +855 -0
- package/src/codex/native-profile-types.ts +120 -0
- package/src/codex/native-residue.ts +682 -0
- package/src/codex/paths.ts +80 -1
- package/src/codex/plan-from-token.ts +140 -0
- package/src/codex/plan.ts +40 -0
- package/src/codex/plugins-doctor.ts +1 -1
- package/src/codex/pool-rotation.ts +74 -4
- package/src/codex/project-config-warnings.ts +20 -6
- package/src/codex/prompt-journal.ts +352 -0
- package/src/codex/prompt-layers.ts +967 -0
- package/src/codex/prompt-lock.ts +143 -0
- package/src/codex/quota-rejection.ts +298 -0
- package/src/codex/quota.ts +175 -13
- package/src/codex/refresh.ts +11 -2
- package/src/codex/reset-credit-recovery.ts +1044 -0
- package/src/codex/routing.ts +505 -94
- package/src/codex/runtime.ts +159 -38
- package/src/codex/shim.ts +1009 -28
- package/src/codex/subagent-model-fallback.ts +350 -35
- package/src/codex/sync.ts +191 -2
- package/src/codex/transition-state.ts +612 -0
- package/src/codex/upstream-host-health.ts +368 -0
- package/src/codex/user-identity.ts +557 -0
- package/src/codex/warmup.ts +187 -81
- package/src/codex/write-coordination.ts +114 -0
- package/src/combos/failover.ts +20 -0
- package/src/combos/index.ts +4 -0
- package/src/combos/request.ts +32 -0
- package/src/combos/types.ts +81 -9
- package/src/config/provider-name.ts +24 -0
- package/src/config.ts +1762 -140
- package/src/generated/compatibility-version.json +3164 -0
- package/src/generated/{jawcode-model-metadata.ts → model-metadata.ts} +19 -17
- package/src/grok/inject.ts +16 -5
- package/src/grok/inspect.ts +45 -0
- package/src/grok/sync.ts +2 -2
- package/src/images/loop.ts +152 -29
- package/src/images/plan.ts +23 -13
- package/src/integrations/config-io.ts +269 -0
- package/src/integrations/journal.ts +315 -0
- package/src/integrations/merge.ts +135 -0
- package/src/integrations/mutation-flight.ts +71 -0
- package/src/integrations/native/ownership-preflight.ts +202 -0
- package/src/integrations/omp-yaml-source.ts +358 -0
- package/src/integrations/owned-refresh.ts +74 -0
- package/src/integrations/ownership.ts +111 -0
- package/src/integrations/registry.ts +159 -0
- package/src/integrations/serialize.ts +314 -0
- package/src/integrations/state.ts +361 -0
- package/src/integrations/store.ts +103 -0
- package/src/integrations/writer-lock.ts +98 -0
- package/src/integrations/writer.ts +691 -0
- package/src/lab/artifacts/sanitize.ts +586 -0
- package/src/lab/artifacts/secure-fs.ts +475 -0
- package/src/lab/artifacts/store.ts +310 -0
- package/src/lab/automation/budgets.ts +78 -0
- package/src/lab/automation/config-persistence.ts +256 -0
- package/src/lab/automation/constants.ts +39 -0
- package/src/lab/automation/cooldown.ts +103 -0
- package/src/lab/automation/dispatch.ts +211 -0
- package/src/lab/automation/index.ts +13 -0
- package/src/lab/automation/orchestrator.ts +499 -0
- package/src/lab/automation/persistence.ts +512 -0
- package/src/lab/automation/planner.ts +371 -0
- package/src/lab/automation/policy.ts +136 -0
- package/src/lab/automation/queue.ts +191 -0
- package/src/lab/automation/recovery.ts +24 -0
- package/src/lab/automation/route-context.ts +21 -0
- package/src/lab/automation/run-key.ts +44 -0
- package/src/lab/automation/runs-query.ts +34 -0
- package/src/lab/automation/types.ts +160 -0
- package/src/lab/conformance/assertion.ts +325 -0
- package/src/lab/conformance/digest.ts +22 -0
- package/src/lab/conformance/executor.ts +741 -0
- package/src/lab/conformance/fixture-provider.ts +27 -0
- package/src/lab/conformance/fixtures/live-v1-cases.json +175 -0
- package/src/lab/conformance/fixtures/protocol-v1-cases.json +461 -0
- package/src/lab/conformance/harness-budget.ts +47 -0
- package/src/lab/conformance/index.ts +5 -0
- package/src/lab/conformance/jcs.ts +64 -0
- package/src/lab/conformance/json-pointer.ts +39 -0
- package/src/lab/conformance/manifest.ts +180 -0
- package/src/lab/conformance/mcp-stub.ts +179 -0
- package/src/lab/conformance/negative-controls.ts +164 -0
- package/src/lab/conformance/observation.ts +355 -0
- package/src/lab/conformance/runner.ts +68 -0
- package/src/lab/conformance/sse-normalize.ts +59 -0
- package/src/lab/conformance/suite-manifest.ts +78 -0
- package/src/lab/conformance/types.ts +214 -0
- package/src/lab/constants.ts +126 -0
- package/src/lab/digest.ts +64 -0
- package/src/lab/events/errors.ts +9 -0
- package/src/lab/events/limits.ts +117 -0
- package/src/lab/events/types.ts +229 -0
- package/src/lab/events/validate.ts +781 -0
- package/src/lab/fabric/constants.ts +40 -0
- package/src/lab/fabric/executor.ts +492 -0
- package/src/lab/fabric/index.ts +80 -0
- package/src/lab/fabric/manifest.ts +222 -0
- package/src/lab/fabric/observe.ts +489 -0
- package/src/lab/fabric/patch.ts +79 -0
- package/src/lab/fabric/producer-child.ts +139 -0
- package/src/lab/fabric/producer-isolate.ts +276 -0
- package/src/lab/fabric/producer-protocol.ts +61 -0
- package/src/lab/fabric/scratch.ts +439 -0
- package/src/lab/fabric/subject.ts +106 -0
- package/src/lab/fabric/types.ts +134 -0
- package/src/lab/fabric/verifier.ts +98 -0
- package/src/lab/index.ts +54 -0
- package/src/lab/ledger/artifact-refs.ts +127 -0
- package/src/lab/ledger/invalidation.ts +136 -0
- package/src/lab/ledger/purge.ts +310 -0
- package/src/lab/ledger/store.ts +532 -0
- package/src/lab/live/credential-lease.ts +53 -0
- package/src/lab/live/destination.ts +155 -0
- package/src/lab/live/executor.ts +336 -0
- package/src/lab/live/inert-tools.ts +56 -0
- package/src/lab/live/manifest.ts +85 -0
- package/src/lab/live/mcp-loopback.ts +57 -0
- package/src/lab/live/runner.ts +19 -0
- package/src/lab/live/sandbox.ts +61 -0
- package/src/lab/live/suite-manifest.ts +41 -0
- package/src/lab/live/transport.ts +118 -0
- package/src/lab/live/types.ts +197 -0
- package/src/lab/observe/from-conformance.ts +301 -0
- package/src/lab/observe/from-live.ts +117 -0
- package/src/lab/paths.ts +153 -0
- package/src/lab/projection/rebuild.ts +495 -0
- package/src/lab/projection/schema.ts +135 -0
- package/src/lab/projection/verdicts.ts +474 -0
- package/src/lab/projection/verification.ts +412 -0
- package/src/lab/public/bundle.ts +217 -0
- package/src/lab/public/community-authority.ts +175 -0
- package/src/lab/public/community-files.ts +29 -0
- package/src/lab/public/community.ts +479 -0
- package/src/lab/public/file-safety.ts +155 -0
- package/src/lab/public/ids.ts +26 -0
- package/src/lab/public/index.ts +16 -0
- package/src/lab/public/mutation-lock.ts +424 -0
- package/src/lab/public/operator.ts +353 -0
- package/src/lab/public/origin-purge.ts +79 -0
- package/src/lab/public/origin.ts +203 -0
- package/src/lab/public/privacy.ts +143 -0
- package/src/lab/public/private-file.ts +261 -0
- package/src/lab/public/project.ts +124 -0
- package/src/lab/public/purge-test-fault.ts +21 -0
- package/src/lab/public/purge.ts +223 -0
- package/src/lab/public/registry.ts +44 -0
- package/src/lab/public/revocation.ts +252 -0
- package/src/lab/public/signature.ts +243 -0
- package/src/lab/public/storage.ts +105 -0
- package/src/lab/public/strict-json.ts +206 -0
- package/src/lab/public/time.ts +26 -0
- package/src/lab/public/types.ts +172 -0
- package/src/lab/public/validate.ts +391 -0
- package/src/lab/query/catalog.ts +101 -0
- package/src/lab/query/connection.ts +107 -0
- package/src/lab/query/constants.ts +4 -0
- package/src/lab/query/cursor.ts +132 -0
- package/src/lab/query/dto-map.ts +277 -0
- package/src/lab/query/errors.ts +22 -0
- package/src/lab/query/freshness.ts +53 -0
- package/src/lab/query/index.ts +45 -0
- package/src/lab/query/latest-observation.ts +59 -0
- package/src/lab/query/passive-production.ts +159 -0
- package/src/lab/query/queries.ts +444 -0
- package/src/lab/query/types.ts +266 -0
- package/src/lab/subject/behavior-fingerprint.ts +77 -0
- package/src/lab/subject/installation-salt.ts +112 -0
- package/src/lab/subject/protocol-subject.ts +80 -0
- package/src/lab/subject/route-subject.ts +74 -0
- package/src/lib/app-owned-memory-stores.ts +22 -0
- package/src/lib/bounded-body.ts +153 -9
- package/src/lib/bun-runtime.ts +125 -12
- package/src/lib/bun-stream-caps.ts +13 -9
- package/src/lib/codex-restart-contract.ts +120 -0
- package/src/lib/config-ownership.ts +6 -2
- package/src/lib/destination-policy.ts +65 -1
- package/src/lib/errors.ts +44 -2
- package/src/lib/fabric-task-execution-authority.ts +7 -0
- package/src/lib/fabric-task-host.ts +29 -0
- package/src/lib/lab-activation.ts +223 -0
- package/src/lib/lab-live-execution-authority.ts +13 -0
- package/src/lib/lab-live-host.ts +30 -0
- package/src/lib/lab-live-pinned-sender.ts +56 -0
- package/src/lib/lab-live-route-production.ts +130 -0
- package/src/lib/lab-passive-linker-registration.ts +26 -0
- package/src/lib/local-management-attestation.ts +51 -0
- package/src/lib/local-management-capability.ts +100 -0
- package/src/lib/local-provider-reload-contract.ts +100 -0
- package/src/lib/optional-shutdown-hooks.ts +57 -0
- package/src/lib/pinned-http.ts +145 -26
- package/src/lib/process-control.ts +4 -1
- package/src/lib/provider-outbound.ts +49 -9
- package/src/lib/redact.ts +419 -3
- package/src/lib/self-launch-argv.ts +15 -0
- package/src/lib/server-resource-ownership.ts +71 -0
- package/src/lib/shadow-call.ts +35 -4
- package/src/lib/sse-decoder.ts +41 -0
- package/src/lib/state-store-registrations.ts +10 -2
- package/src/lib/system-restart-contract.ts +73 -0
- package/src/lib/token-estimate.ts +19 -2
- package/src/lib/tool-argument-integers.ts +202 -0
- package/src/lib/translator-budget.ts +44 -0
- package/src/lib/upstream-http-version.ts +57 -0
- package/src/lib/upstream-reachability.ts +95 -0
- package/src/lib/upstream-retry.ts +156 -3
- package/src/lib/windows-atomic-replace.ts +155 -0
- package/src/lib/windows-elevation.ts +70 -2
- package/src/lib/windows-secret-acl.ts +409 -69
- package/src/lib/windows-service-wrappers.ts +72 -0
- package/src/lib/windows-text.ts +106 -0
- package/src/lib/windows-user-principal.ts +341 -0
- package/src/lib/winsw.ts +33 -5
- package/src/oauth/account-import/google-antigravity-adapter.ts +74 -0
- package/src/oauth/account-import/index.ts +15 -0
- package/src/oauth/account-import/parser.ts +83 -0
- package/src/oauth/account-import/registry.ts +18 -0
- package/src/oauth/account-import/service.ts +75 -0
- package/src/oauth/account-import/types.ts +91 -0
- package/src/oauth/anthropic.ts +12 -1
- package/src/oauth/callback-server.ts +8 -2
- package/src/oauth/chatgpt.ts +12 -1
- package/src/oauth/command-code.ts +239 -0
- package/src/oauth/cursor.ts +46 -5
- package/src/oauth/google-antigravity.ts +35 -3
- package/src/oauth/health.ts +20 -12
- package/src/oauth/index.ts +398 -66
- package/src/oauth/key-providers.ts +16 -0
- package/src/oauth/kimi.ts +16 -2
- package/src/oauth/kiro.ts +50 -6
- package/src/oauth/local-token-detect.ts +11 -2
- package/src/oauth/log.ts +3 -1
- package/src/oauth/login-cli.ts +88 -28
- package/src/oauth/nous.ts +798 -0
- package/src/oauth/store.ts +119 -21
- package/src/oauth/token-guardian.ts +9 -3
- package/src/pi/models.ts +2 -2
- package/src/providers/alibaba-region-migration.ts +1 -1
- package/src/providers/antigravity-models.ts +521 -31
- package/src/providers/base-url-choices.ts +10 -0
- package/src/providers/codex-capacity.ts +292 -0
- package/src/providers/command-code-efforts.ts +144 -0
- package/src/providers/context-cap.ts +22 -5
- package/src/providers/cursor-pool.ts +72 -0
- package/src/providers/derive.ts +253 -6
- package/src/providers/fastwire.ts +501 -0
- package/src/providers/free-directory.ts +10 -7
- package/src/providers/google-vertex-location.ts +14 -0
- package/src/providers/key-failover.ts +71 -3
- package/src/providers/label.ts +1 -1
- package/src/providers/model-discovery-limits.ts +16 -0
- package/src/providers/model-discovery.ts +115 -22
- package/src/providers/model-rename-migration.ts +255 -0
- package/src/providers/model-rename-startup.ts +28 -0
- package/src/providers/openai-sidecar.ts +72 -4
- package/src/providers/openai-tier-startup.ts +31 -2
- package/src/providers/openai-tiers.ts +119 -4
- package/src/providers/openai-virtual-models.ts +1 -0
- package/src/providers/opencode-zen-rate-limit.ts +102 -0
- package/src/providers/provider-id-rewrite.ts +29 -0
- package/src/providers/quota.ts +1319 -38
- package/src/providers/registry.ts +1429 -111
- package/src/providers/request-pacing.ts +310 -0
- package/src/providers/service-tier.ts +277 -0
- package/src/providers/slug-codec.ts +42 -6
- package/src/providers/static-model-discovery.ts +86 -0
- package/src/providers/xai-responses-opt-in.ts +15 -0
- package/src/providers/xai-transport.ts +11 -4
- package/src/reasoning-effort.ts +49 -1
- package/src/responses/compaction.ts +26 -1
- package/src/responses/custom-tool-compat.ts +266 -0
- package/src/responses/hosted-tool-policy.ts +9 -0
- package/src/responses/namespace-tool-compat.ts +355 -0
- package/src/responses/parser.ts +220 -38
- package/src/responses/provider-continuation.ts +98 -0
- package/src/responses/provider-opaque-metadata.ts +73 -0
- package/src/responses/reasoning-envelope.ts +9 -1
- package/src/responses/reasoning-replay-cache.ts +426 -0
- package/src/responses/schema.ts +7 -1
- package/src/responses/spill-store.ts +75 -10
- package/src/responses/state.ts +565 -27
- package/src/responses/thought-signature-replay.ts +347 -0
- package/src/responses/tool-search-compat.ts +301 -0
- package/src/responses/truncated-stop-reason.ts +60 -0
- package/src/router.ts +366 -30
- package/src/routing/analytics.ts +378 -0
- package/src/routing/capability.ts +244 -0
- package/src/routing/compatibility/assemble.ts +73 -0
- package/src/routing/compatibility/behavior.ts +278 -0
- package/src/routing/compatibility/catalog.ts +99 -0
- package/src/routing/compatibility/endpoint.ts +52 -0
- package/src/routing/compatibility/lab-evidence-provider.ts +130 -0
- package/src/routing/compatibility/policy.ts +181 -0
- package/src/routing/compatibility/provider-slot.ts +56 -0
- package/src/routing/compatibility/reader.ts +110 -0
- package/src/routing/compatibility/subject.ts +191 -0
- package/src/routing/compatibility/types.ts +64 -0
- package/src/routing/compatibility/version.ts +104 -0
- package/src/routing/cost.ts +77 -0
- package/src/routing/evaluator.ts +495 -0
- package/src/routing/health.ts +412 -0
- package/src/routing/history/cursor.ts +43 -0
- package/src/routing/history/indexer.ts +605 -0
- package/src/routing/history/schema.ts +72 -0
- package/src/routing/profile-namespace.ts +15 -0
- package/src/routing/profile.ts +547 -0
- package/src/routing/quota.ts +145 -0
- package/src/routing/request-evidence.ts +45 -0
- package/src/routing/trace.ts +776 -0
- package/src/server/adapter-resolve.ts +2 -29
- package/src/server/auth-cors.ts +267 -46
- package/src/server/background-lifecycle.ts +182 -0
- package/src/server/chat-completions.ts +130 -56
- package/src/server/chat-native-sse.ts +331 -0
- package/src/server/chat-native.ts +426 -0
- package/src/server/claude-messages.ts +159 -43
- package/src/server/direct-local-http.ts +347 -0
- package/src/server/effort-policy.ts +18 -0
- package/src/server/github-copilot-responses-repair.ts +338 -0
- package/src/server/gui-static.ts +39 -10
- package/src/server/images.ts +94 -12
- package/src/server/index.ts +865 -181
- package/src/server/lifecycle.ts +284 -13
- package/src/server/live.ts +136 -17
- package/src/server/local-management-read-client.ts +90 -0
- package/src/server/local-provider-reload-client.ts +137 -0
- package/src/server/management/agent-settings-routes.ts +398 -103
- package/src/server/management/api-key-usage.ts +31 -5
- package/src/server/management/body.ts +6 -0
- package/src/server/management/combo-routes.ts +62 -24
- package/src/server/management/config-routes.ts +464 -51
- package/src/server/management/context.ts +80 -2
- package/src/server/management/integration-routes.ts +498 -0
- package/src/server/management/lab-automation-routes.ts +206 -0
- package/src/server/management/lab-routes.ts +563 -0
- package/src/server/management/logs-usage-routes.ts +101 -32
- package/src/server/management/model-routes.ts +189 -131
- package/src/server/management/model-rows.ts +163 -0
- package/src/server/management/native-integration-routes.ts +769 -0
- package/src/server/management/oauth-account-routes.ts +80 -4
- package/src/server/management/provider-capability-config.ts +48 -0
- package/src/server/management/provider-routes.ts +764 -157
- package/src/server/management/request-history-routes.ts +191 -0
- package/src/server/management/routing-analytics-routes.ts +74 -0
- package/src/server/management/routing-profile-routes.ts +380 -0
- package/src/server/management/shared.ts +27 -11
- package/src/server/management/sidebar-routes.ts +47 -31
- package/src/server/management/storage-log-guard-routes.ts +186 -0
- package/src/server/management/sync-response.ts +69 -0
- package/src/server/management/system-restart.ts +288 -32
- package/src/server/management/system-routes.ts +77 -0
- package/src/server/management/usage-summary-cache.ts +9 -1
- package/src/server/management/vision-sidecar-options.ts +167 -0
- package/src/server/management/web-search-sidecar-options.ts +120 -0
- package/src/server/management-api.ts +115 -14
- package/src/server/management-auth.ts +220 -5
- package/src/server/passive-route-linker.ts +66 -0
- package/src/server/ports.ts +41 -1
- package/src/server/proxy-liveness.ts +132 -5
- package/src/server/readiness.ts +99 -0
- package/src/server/relay-eager.ts +82 -42
- package/src/server/relay.ts +236 -76
- package/src/server/request-decompress.ts +113 -6
- package/src/server/request-log.ts +235 -22
- package/src/server/responses/agent-task-recovery-cache.ts +143 -0
- package/src/server/responses/agent-task-recovery.ts +465 -0
- package/src/server/responses/collaboration.ts +204 -35
- package/src/server/responses/compact.ts +442 -55
- package/src/server/responses/core.ts +2872 -331
- package/src/server/responses/empty-completion-guard.ts +276 -0
- package/src/server/responses/encrypted-payload.ts +62 -39
- package/src/server/responses/fetch-helpers.ts +79 -4
- package/src/server/responses/input-admission.ts +185 -0
- package/src/server/responses/pacing-overload.ts +13 -0
- package/src/server/responses/policy-fallback.ts +178 -0
- package/src/server/responses/responses-field-backfill.ts +251 -0
- package/src/server/responses/terminal-guard.ts +26 -5
- package/src/server/responses/upstream-error.ts +5 -0
- package/src/server/responses/ws-upstream.ts +308 -0
- package/src/server/responses-custom-tool-repair.ts +282 -0
- package/src/server/responses-item-id-repair.ts +54 -6
- package/src/server/responses-json-events.ts +90 -0
- package/src/server/responses-model-rewrite.ts +29 -0
- package/src/server/responses-reasoning-summary-rewrite.ts +178 -0
- package/src/server/responses-snapshot-repair.ts +621 -0
- package/src/server/responses-terminal-repair.ts +342 -0
- package/src/server/responses-tool-search-repair.ts +267 -0
- package/src/server/responses-undeclared-tool-guard.ts +153 -0
- package/src/server/responses.ts +18 -2
- package/src/server/search.ts +78 -13
- package/src/server/sse-frame-buffer.ts +292 -0
- package/src/server/sse-payload-rewrite.ts +110 -22
- package/src/server/startup-action-control.ts +8 -1
- package/src/server/startup-health-cache.ts +19 -1
- package/src/server/system-env.ts +80 -9
- package/src/server/ws-bridge.ts +39 -38
- package/src/service-manager-probe.ts +892 -0
- package/src/service.ts +1111 -90
- package/src/sidecar/auth.ts +92 -0
- package/src/sidecar/candidates.ts +83 -0
- package/src/storage/cleanup.ts +2 -2
- package/src/storage/scanner.ts +1 -1
- package/src/storage/worker-lifecycle.ts +14 -14
- package/src/tray/windows-tray.ps1 +83 -9
- package/src/tray/windows.ts +43 -16
- package/src/types/accounts.ts +37 -0
- package/src/types/config.ts +845 -0
- package/src/types/provider.ts +545 -0
- package/src/types/request.ts +384 -0
- package/src/types/tools.ts +131 -0
- package/src/types/wire.ts +80 -0
- package/src/types.ts +104 -1236
- package/src/update/index.ts +32 -19
- package/src/update/job.ts +442 -67
- package/src/update/notify.ts +12 -6
- package/src/update/npm-cache-preflight.d.mts +47 -0
- package/src/update/npm-cache-preflight.mjs +201 -0
- package/src/update/transactional-install.d.mts +22 -0
- package/src/update/transactional-install.mjs +259 -0
- package/src/usage/cost.ts +0 -0
- package/src/usage/expected-prices.ts +268 -16
- package/src/usage/log.ts +606 -41
- package/src/usage/summary.ts +177 -9
- package/src/usage/user-cost-overlay-reconciler.ts +313 -0
- package/src/usage/user-cost-overlays.ts +314 -0
- package/src/vision/anthropic-describe.ts +10 -6
- package/src/vision/backends.ts +97 -0
- package/src/vision/describe.ts +9 -3
- package/src/vision/eligibility.ts +250 -0
- package/src/vision/index.ts +238 -24
- package/src/vision/reasoning.ts +55 -0
- package/src/vision/routed-describe.ts +175 -0
- package/src/vision/timeout-bounds.ts +9 -0
- package/src/web-search/anthropic-executor.ts +13 -7
- package/src/web-search/backends.ts +108 -0
- package/src/web-search/exa-executor.ts +88 -0
- package/src/web-search/executor.ts +11 -3
- package/src/web-search/gemini-executor.ts +141 -0
- package/src/web-search/index.ts +150 -15
- package/src/web-search/loop.ts +279 -50
- package/src/web-search/parse.ts +125 -30
- package/src/web-search/sources.ts +60 -0
- package/src/web-search/xai-executor.ts +219 -0
- package/gui/dist/assets/index-DTpMHS4F.js +0 -67
- package/gui/dist/assets/index-ZNVDE3C7.css +0 -1
- package/gui/dist/provider-icons/antigravity.svg +0 -1
- package/gui/dist/provider-icons/claude.svg +0 -1
- package/gui/dist/provider-icons/copilot.svg +0 -1
- package/gui/dist/provider-icons/cursor.svg +0 -2
- package/gui/dist/provider-icons/gemini.svg +0 -1
- package/gui/dist/provider-icons/grok-color.svg +0 -1
- package/gui/dist/provider-icons/kiro.svg +0 -14
- package/src/cli/internal-dispatch.ts +0 -20
package/src/web-search/parse.ts
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
|
+
import { sseFieldValue } from "../lib/sse-decoder";
|
|
2
|
+
import {
|
|
3
|
+
appendSafeWebSearchSource,
|
|
4
|
+
safeWebSearchSources,
|
|
5
|
+
type SafeWebSearchSource,
|
|
6
|
+
} from "./sources";
|
|
7
|
+
|
|
1
8
|
/** A single web source backing the sidecar's answer. */
|
|
2
|
-
export
|
|
3
|
-
url: string;
|
|
4
|
-
title?: string;
|
|
5
|
-
}
|
|
9
|
+
export type WebSearchSource = SafeWebSearchSource;
|
|
6
10
|
|
|
7
11
|
/** The sidecar's synthesized answer plus its sources (empty `sources` is fine). */
|
|
8
12
|
export interface WebSearchResult {
|
|
@@ -27,11 +31,17 @@ interface OutputItem {
|
|
|
27
31
|
content?: OutputTextBlock[];
|
|
28
32
|
}
|
|
29
33
|
|
|
34
|
+
// ChatGPT's Codex backend does not accept `max_output_tokens` on sidecar requests. Bound the raw
|
|
35
|
+
// streamed response here, before decoded text and authoritative/delta copies can accumulate.
|
|
36
|
+
export const MAX_SIDECAR_RESPONSE_BYTES = 64 * 1024;
|
|
37
|
+
|
|
30
38
|
/** Push a `url_citation` annotation as a source, de-duplicated by URL. */
|
|
31
39
|
function collectAnnotation(ann: AnnotationLike | undefined, sources: WebSearchSource[], seen: Set<string>): void {
|
|
32
40
|
if (!ann || ann.type !== "url_citation" || typeof ann.url !== "string" || seen.has(ann.url)) return;
|
|
33
|
-
|
|
34
|
-
|
|
41
|
+
if (appendSafeWebSearchSource(sources, {
|
|
42
|
+
url: ann.url,
|
|
43
|
+
...(ann.title !== undefined ? { title: ann.title } : {}),
|
|
44
|
+
})) seen.add(ann.url);
|
|
35
45
|
}
|
|
36
46
|
|
|
37
47
|
/**
|
|
@@ -45,9 +55,58 @@ function collectAnnotation(ann: AnnotationLike | undefined, sources: WebSearchSo
|
|
|
45
55
|
* (`### Sources:`, `**Sources**`), a title line whose URL sits on the FOLLOWING line, and trailing
|
|
46
56
|
* URL punctuation (`;`, `,`, `)`, `]`, `.`). Prose that follows the source list is preserved.
|
|
47
57
|
*/
|
|
48
|
-
const URL_RE = /https?:\/\/[^\s<>()\[\]]
|
|
58
|
+
const URL_RE = /https?:\/\/[^\s<>()\[\]]+/i;
|
|
59
|
+
// Recognize URI-like candidates separately from the HTTP(S)-only acceptance boundary. A rejected
|
|
60
|
+
// citation (for example `javascript:`) still belongs to the trailing Sources block and must not be
|
|
61
|
+
// left behind as ordinary assistant text.
|
|
62
|
+
const URI_LIKE_RE = /[a-z][a-z0-9+.-]*:[^\s<>()\[\]]+/i;
|
|
49
63
|
// A "Sources:" / "Source:" header, allowing markdown prefixes (#, *, -, >) and bold/italic wrappers.
|
|
50
|
-
const
|
|
64
|
+
const SOURCES_WORD_RE = /^sources?/i;
|
|
65
|
+
|
|
66
|
+
/** Match the legacy Sources-header grammar without overlapping regex quantifiers. */
|
|
67
|
+
function isSourcesHeader(line: string): boolean {
|
|
68
|
+
let cursor = 0;
|
|
69
|
+
/** Match one code unit using JavaScript's existing `\s` semantics. */
|
|
70
|
+
const isWhitespace = (char: string | undefined): boolean => char !== undefined && /\s/u.test(char);
|
|
71
|
+
/** Advance over the current contiguous whitespace run. */
|
|
72
|
+
const skipWhitespace = (): void => {
|
|
73
|
+
while (isWhitespace(line[cursor])) cursor += 1;
|
|
74
|
+
};
|
|
75
|
+
/** Advance over the current contiguous Markdown-star run. */
|
|
76
|
+
const skipStars = (): void => {
|
|
77
|
+
while (line[cursor] === "*") cursor += 1;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
skipWhitespace();
|
|
81
|
+
if (line[cursor] === "#") {
|
|
82
|
+
const start = cursor;
|
|
83
|
+
while (line[cursor] === "#") cursor += 1;
|
|
84
|
+
if (cursor - start > 6) return false;
|
|
85
|
+
skipWhitespace();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
while (isWhitespace(line[cursor]) || line[cursor] === "-" || line[cursor] === "*" || line[cursor] === ">") {
|
|
89
|
+
cursor += 1;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const word = SOURCES_WORD_RE.exec(line.slice(cursor, cursor + 7));
|
|
93
|
+
if (!word) return false;
|
|
94
|
+
cursor += word[0].length;
|
|
95
|
+
|
|
96
|
+
// Preserve `\s*\**\s*:?\s*\**\s*$`: at most two star runs, with the optional
|
|
97
|
+
// colon between them. Either run may be empty, so `Sources:*` is one trailing run; three
|
|
98
|
+
// separated runs and a colon after the second run remain invalid.
|
|
99
|
+
skipWhitespace();
|
|
100
|
+
skipStars();
|
|
101
|
+
skipWhitespace();
|
|
102
|
+
if (line[cursor] === ":") {
|
|
103
|
+
cursor += 1;
|
|
104
|
+
skipWhitespace();
|
|
105
|
+
}
|
|
106
|
+
skipStars();
|
|
107
|
+
skipWhitespace();
|
|
108
|
+
return cursor === line.length;
|
|
109
|
+
}
|
|
51
110
|
|
|
52
111
|
/** Trim wrapping/trailing noise from a captured URL: angle brackets, then trailing punctuation. */
|
|
53
112
|
function cleanUrl(url: string): string {
|
|
@@ -62,14 +121,14 @@ function cleanTitle(prefix: string): string {
|
|
|
62
121
|
return title;
|
|
63
122
|
}
|
|
64
123
|
|
|
65
|
-
function extractTrailingSources(text: string): { text: string; sources: WebSearchSource[] } {
|
|
124
|
+
function extractTrailingSources(text: string): { text: string; sources: WebSearchSource[]; stripped: boolean } {
|
|
66
125
|
const lines = text.split("\n");
|
|
67
126
|
// Find the LAST line that is a "Sources:" header (markdown prefixes allowed).
|
|
68
127
|
let headerIdx = -1;
|
|
69
128
|
for (let i = lines.length - 1; i >= 0; i--) {
|
|
70
|
-
if (
|
|
129
|
+
if (isSourcesHeader(lines[i])) { headerIdx = i; break; }
|
|
71
130
|
}
|
|
72
|
-
if (headerIdx === -1) return { text, sources: [] };
|
|
131
|
+
if (headerIdx === -1) return { text, sources: [], stripped: false };
|
|
73
132
|
const sources: WebSearchSource[] = [];
|
|
74
133
|
const seen = new Set<string>();
|
|
75
134
|
// Track the last line index actually consumed as part of the source list so trailing prose after
|
|
@@ -77,14 +136,16 @@ function extractTrailingSources(text: string): { text: string; sources: WebSearc
|
|
|
77
136
|
let lastConsumed = headerIdx;
|
|
78
137
|
// A title line whose URL is expected on a following line (multiline entry).
|
|
79
138
|
let pendingTitle: string | null = null;
|
|
139
|
+
let consumedSourceLine = false;
|
|
80
140
|
for (let i = headerIdx + 1; i < lines.length; i++) {
|
|
81
141
|
const raw = lines[i].trim();
|
|
82
142
|
if (raw === "") {
|
|
83
143
|
// Blank line between header and first entry is fine; a blank AFTER entries ends the list.
|
|
84
|
-
if (
|
|
144
|
+
if (consumedSourceLine || pendingTitle !== null) break;
|
|
85
145
|
continue;
|
|
86
146
|
}
|
|
87
|
-
const
|
|
147
|
+
const httpMatch = raw.match(URL_RE);
|
|
148
|
+
const m = httpMatch ?? raw.match(URI_LIKE_RE);
|
|
88
149
|
if (!m) {
|
|
89
150
|
// A list-ish line with no URL may be a title whose URL is on the next line. Only treat it as a
|
|
90
151
|
// pending title when it looks like a list item; otherwise it's prose → stop.
|
|
@@ -93,23 +154,27 @@ function extractTrailingSources(text: string): { text: string; sources: WebSearc
|
|
|
93
154
|
}
|
|
94
155
|
break;
|
|
95
156
|
}
|
|
157
|
+
// A non-HTTP URI embedded in prose is not sufficient to classify the line as a citation.
|
|
158
|
+
// Accept it as a consumed source line only when it is a list item or the whole line starts with
|
|
159
|
+
// the URI candidate, matching the existing bare-URL grammar.
|
|
160
|
+
if (!httpMatch && !/^[-*>\d.)]/.test(raw) && m.index !== 0) break;
|
|
96
161
|
const url = cleanUrl(m[0]);
|
|
97
162
|
if (!url) { break; }
|
|
163
|
+
consumedSourceLine = true;
|
|
98
164
|
lastConsumed = i;
|
|
99
165
|
// Title: text before the URL on this line, else a buffered title from a preceding line.
|
|
100
166
|
const inlinePrefix = raw.slice(0, m.index);
|
|
101
167
|
const title = cleanTitle(inlinePrefix) || (pendingTitle ? cleanTitle(pendingTitle) : "");
|
|
102
168
|
pendingTitle = null;
|
|
103
169
|
if (seen.has(url)) continue;
|
|
104
|
-
seen.add(url);
|
|
105
|
-
sources.push(title ? { url, title } : { url });
|
|
170
|
+
if (appendSafeWebSearchSource(sources, title ? { url, title } : { url })) seen.add(url);
|
|
106
171
|
}
|
|
107
|
-
if (
|
|
172
|
+
if (!consumedSourceLine) return { text, sources: [], stripped: false };
|
|
108
173
|
// Keep text before the header AND any prose after the consumed source lines.
|
|
109
174
|
const before = lines.slice(0, headerIdx).join("\n").replace(/\s+$/, "");
|
|
110
175
|
const after = lines.slice(lastConsumed + 1).join("\n").replace(/^\s+/, "");
|
|
111
176
|
const body = after ? (before ? `${before}\n\n${after}` : after) : before;
|
|
112
|
-
return { text: body, sources };
|
|
177
|
+
return { text: body, sources, stripped: true };
|
|
113
178
|
}
|
|
114
179
|
|
|
115
180
|
/** Pull final text + url_citation sources from a completed Responses `output[]` array. */
|
|
@@ -128,6 +193,15 @@ function fromOutputArray(output: OutputItem[], seen: Set<string>): WebSearchResu
|
|
|
128
193
|
return { text, sources };
|
|
129
194
|
}
|
|
130
195
|
|
|
196
|
+
function cancelReaderWithoutWaiting(
|
|
197
|
+
reader: ReadableStreamDefaultReader<Uint8Array>,
|
|
198
|
+
reason: string,
|
|
199
|
+
): void {
|
|
200
|
+
try {
|
|
201
|
+
void reader.cancel(reason).catch(() => undefined);
|
|
202
|
+
} catch { /* best-effort body teardown */ }
|
|
203
|
+
}
|
|
204
|
+
|
|
131
205
|
/**
|
|
132
206
|
* Parse the sidecar's streamed Responses SSE into a final answer + sources. Tolerant of the full set of
|
|
133
207
|
* Responses streaming events: prefers the authoritative `response.completed` output[], then the
|
|
@@ -141,6 +215,7 @@ export async function parseSidecarSSE(response: Response): Promise<WebSearchResu
|
|
|
141
215
|
const reader = response.body.getReader();
|
|
142
216
|
const decoder = new TextDecoder();
|
|
143
217
|
let buffer = "";
|
|
218
|
+
let responseBytes = 0;
|
|
144
219
|
const seen = new Set<string>();
|
|
145
220
|
// Holder object — fields are mutated inside the closure, so they can't live as narrowed locals.
|
|
146
221
|
const acc: {
|
|
@@ -153,11 +228,23 @@ export async function parseSidecarSSE(response: Response): Promise<WebSearchResu
|
|
|
153
228
|
|
|
154
229
|
const handle = (payload: string): void => {
|
|
155
230
|
if (!payload || payload === "[DONE]") return;
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
231
|
+
// Neither warning below copies the frame's content. An upstream SSE payload can carry model
|
|
232
|
+
// output or credential material, and a malformed frame is exactly the case where the content
|
|
233
|
+
// is least trustworthy. Length plus a classification separates the two failure modes in a log
|
|
234
|
+
// without reproducing anything from the wire.
|
|
235
|
+
let parsed: unknown;
|
|
236
|
+
try { parsed = JSON.parse(payload); } catch {
|
|
237
|
+
console.warn(`[web-search-parse] malformed SSE JSON (${payload.length} chars)`);
|
|
159
238
|
return;
|
|
160
239
|
}
|
|
240
|
+
// `JSON.parse("null")` returns null rather than throwing, so the catch above cannot cover it
|
|
241
|
+
// and the `data.type` read below threw out of parseSidecarSSE.
|
|
242
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
243
|
+
const shape = Array.isArray(parsed) ? "array" : parsed === null ? "null" : typeof parsed;
|
|
244
|
+
console.warn(`[web-search-parse] non-record SSE JSON frame (${payload.length} chars, ${shape})`);
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
const data = parsed as Record<string, unknown>;
|
|
161
248
|
const type = data.type as string | undefined;
|
|
162
249
|
if (type === "response.output_text.delta" && typeof data.delta === "string") {
|
|
163
250
|
acc.deltaText += data.delta;
|
|
@@ -183,11 +270,22 @@ export async function parseSidecarSSE(response: Response): Promise<WebSearchResu
|
|
|
183
270
|
while (true) {
|
|
184
271
|
const { done, value } = await reader.read();
|
|
185
272
|
if (done) break;
|
|
186
|
-
|
|
273
|
+
const remaining = MAX_SIDECAR_RESPONSE_BYTES - responseBytes;
|
|
274
|
+
const accepted = value.byteLength <= remaining ? value : value.subarray(0, remaining);
|
|
275
|
+
responseBytes += accepted.byteLength;
|
|
276
|
+
buffer += decoder.decode(accepted, { stream: true });
|
|
187
277
|
const lines = buffer.split("\n");
|
|
188
278
|
buffer = lines.pop() ?? "";
|
|
189
279
|
for (const line of lines) {
|
|
190
|
-
|
|
280
|
+
const data = sseFieldValue(line, "data");
|
|
281
|
+
if (data !== null) handle(data.trim());
|
|
282
|
+
}
|
|
283
|
+
if (responseBytes >= MAX_SIDECAR_RESPONSE_BYTES) {
|
|
284
|
+
// Preserve complete events accepted up to the cap, but discard any unterminated line and
|
|
285
|
+
// TextDecoder carry. Do not let a rejecting/hung cancel turn bounded partial output into
|
|
286
|
+
// an error or keep this parser waiting on upstream teardown.
|
|
287
|
+
cancelReaderWithoutWaiting(reader, "sidecar response byte limit reached");
|
|
288
|
+
break;
|
|
191
289
|
}
|
|
192
290
|
}
|
|
193
291
|
} finally {
|
|
@@ -199,22 +297,19 @@ export async function parseSidecarSSE(response: Response): Promise<WebSearchResu
|
|
|
199
297
|
|| acc.doneText.trim() && acc.doneText
|
|
200
298
|
|| acc.deltaText;
|
|
201
299
|
// Merge sources from the final output[] and the streaming annotation events.
|
|
202
|
-
const sources =
|
|
203
|
-
const seenMerge = new Set(sources.map(s => s.url));
|
|
300
|
+
const sources = safeWebSearchSources(acc.final?.sources ?? []);
|
|
204
301
|
for (const s of acc.streamSources) {
|
|
205
|
-
|
|
302
|
+
appendSafeWebSearchSource(sources, s);
|
|
206
303
|
}
|
|
207
304
|
// Hosted web_search usually omits url_citation annotations and lists sources in a trailing
|
|
208
305
|
// `Sources:` markdown block instead. Pull those out (and strip the block from the answer so the
|
|
209
306
|
// tool_result renderer doesn't print sources twice). Annotation titles win; text-block titles
|
|
210
307
|
// only fill a gap. URL-deduped against annotation sources.
|
|
211
|
-
const { text: body, sources: textSources } = extractTrailingSources(typeof text === "string" ? text : "");
|
|
308
|
+
const { text: body, sources: textSources, stripped } = extractTrailingSources(typeof text === "string" ? text : "");
|
|
212
309
|
for (const s of textSources) {
|
|
213
|
-
|
|
214
|
-
seenMerge.add(s.url);
|
|
215
|
-
sources.push(s);
|
|
310
|
+
appendSafeWebSearchSource(sources, s);
|
|
216
311
|
}
|
|
217
|
-
const finalText =
|
|
312
|
+
const finalText = stripped ? body : (typeof text === "string" ? text : "");
|
|
218
313
|
if (!finalText.trim() && acc.error) return { text: "", sources, error: acc.error };
|
|
219
314
|
return { text: finalText, sources };
|
|
220
315
|
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
export interface SafeWebSearchSource {
|
|
2
|
+
url: string;
|
|
3
|
+
title?: string;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export const MAX_WEB_SEARCH_SOURCES = 20;
|
|
7
|
+
export const MAX_WEB_SEARCH_URL_BYTES = 2_048;
|
|
8
|
+
export const MAX_WEB_SEARCH_TITLE_BYTES = 256;
|
|
9
|
+
export const MAX_WEB_SEARCH_SOURCE_BYTES = 16_384;
|
|
10
|
+
|
|
11
|
+
const CONTROL_CHARACTERS = /[\u0000-\u001f\u007f-\u009f]/u;
|
|
12
|
+
const encoder = new TextEncoder();
|
|
13
|
+
|
|
14
|
+
function byteLength(value: string): number {
|
|
15
|
+
return encoder.encode(value).byteLength;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function serializedSourceBytes(source: SafeWebSearchSource): number {
|
|
19
|
+
return byteLength(JSON.stringify(source));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function safeTitle(value: unknown): string | undefined {
|
|
23
|
+
if (typeof value !== "string"
|
|
24
|
+
|| value.trim().length === 0
|
|
25
|
+
|| CONTROL_CHARACTERS.test(value)
|
|
26
|
+
|| byteLength(value) > MAX_WEB_SEARCH_TITLE_BYTES) return undefined;
|
|
27
|
+
return value;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Add one client-safe citation while enforcing per-message count and byte budgets. */
|
|
31
|
+
export function appendSafeWebSearchSource(target: SafeWebSearchSource[], source: unknown): boolean {
|
|
32
|
+
if (target.length >= MAX_WEB_SEARCH_SOURCES || !source || typeof source !== "object") return false;
|
|
33
|
+
const candidate = source as { url?: unknown; title?: unknown };
|
|
34
|
+
if (typeof candidate.url !== "string"
|
|
35
|
+
|| candidate.url.length === 0
|
|
36
|
+
|| candidate.url.trim() !== candidate.url
|
|
37
|
+
|| CONTROL_CHARACTERS.test(candidate.url)
|
|
38
|
+
|| byteLength(candidate.url) > MAX_WEB_SEARCH_URL_BYTES
|
|
39
|
+
|| target.some(existing => existing.url === candidate.url)) return false;
|
|
40
|
+
|
|
41
|
+
let parsed: URL;
|
|
42
|
+
try { parsed = new URL(candidate.url); } catch { return false; }
|
|
43
|
+
if ((parsed.protocol !== "http:" && parsed.protocol !== "https:")
|
|
44
|
+
|| parsed.username.length > 0
|
|
45
|
+
|| parsed.password.length > 0) return false;
|
|
46
|
+
|
|
47
|
+
const title = safeTitle(candidate.title);
|
|
48
|
+
const next = title === undefined ? { url: candidate.url } : { url: candidate.url, title };
|
|
49
|
+
const usedBytes = target.reduce((sum, item) => sum + serializedSourceBytes(item), 0);
|
|
50
|
+
if (usedBytes + serializedSourceBytes(next) > MAX_WEB_SEARCH_SOURCE_BYTES) return false;
|
|
51
|
+
target.push(next);
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function safeWebSearchSources(sources: unknown): SafeWebSearchSource[] {
|
|
56
|
+
if (!Array.isArray(sources)) return [];
|
|
57
|
+
const safe: SafeWebSearchSource[] = [];
|
|
58
|
+
for (const source of sources) appendSafeWebSearchSource(safe, source);
|
|
59
|
+
return safe;
|
|
60
|
+
}
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Execute ONE web search via a Grok sidecar through the STORED xai OAuth credential (#2188 L7).
|
|
3
|
+
*
|
|
4
|
+
* POSTs the canonical api.x.ai Responses endpoint with the hosted web_search tool (plus the
|
|
5
|
+
* opt-in x_search tool) and reduces the SSE stream to a SidecarOutcome. Wire shapes are
|
|
6
|
+
* probe-verified (devlog 003 / 070): reasoning.effort is accepted alongside tools; both hosted
|
|
7
|
+
* tools may share one request; annotations arrive as url_citation events on message items;
|
|
8
|
+
* x_search activity surfaces as custom_tool_call items (ctc_) rather than the documented
|
|
9
|
+
* x_search_call — both are tolerated, neither is required. `action` may be absent on
|
|
10
|
+
* output_item.added (skeleton-first) and fills in later. Never throws — returns `{error}` so
|
|
11
|
+
* the caller injects a graceful tool result.
|
|
12
|
+
*/
|
|
13
|
+
import type { OcxProviderConfig } from "../types";
|
|
14
|
+
import { getValidAccessToken, publicOAuthAuthenticationErrorMessage } from "../oauth";
|
|
15
|
+
import { fetchWithResetRetry } from "../lib/upstream-retry";
|
|
16
|
+
import { cancelBodyOnAbort, signalWithTimeout } from "../lib/abort";
|
|
17
|
+
import { sidecarEnter } from "../lib/sidecar-tracker";
|
|
18
|
+
import { redactSecretString } from "../lib/redact";
|
|
19
|
+
import { MAX_SIDECAR_RESPONSE_BYTES, type WebSearchSource } from "./parse";
|
|
20
|
+
import { BASE_INSTRUCTION, IMAGE_INSTRUCTION, type SidecarOutcome, type SidecarSettings } from "./executor";
|
|
21
|
+
|
|
22
|
+
/** The one destination the OAuth credential may be sent to; provider.baseUrl is honored only on the same origin. */
|
|
23
|
+
const XAI_RESPONSES_ORIGIN = "https://api.x.ai";
|
|
24
|
+
|
|
25
|
+
export interface XaiSearchOptions {
|
|
26
|
+
/** Opt-in: add the hosted x_search tool next to web_search. */
|
|
27
|
+
xSearch?: boolean;
|
|
28
|
+
allowedXHandles?: string[];
|
|
29
|
+
excludedXHandles?: string[];
|
|
30
|
+
fromDate?: string;
|
|
31
|
+
toDate?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const ISO_DATE = /^\d{4}-\d{2}-\d{2}$/;
|
|
35
|
+
|
|
36
|
+
/** Doc-validated limits (docs.x.ai x-search): <=20 handles, allow XOR exclude, ISO dates. */
|
|
37
|
+
export function validateXaiSearchOptions(options: XaiSearchOptions): string | undefined {
|
|
38
|
+
const allowed = options.allowedXHandles ?? [];
|
|
39
|
+
const excluded = options.excludedXHandles ?? [];
|
|
40
|
+
if (allowed.length > 0 && excluded.length > 0) return "allowedXHandles and excludedXHandles are mutually exclusive";
|
|
41
|
+
if (allowed.length > 20) return "allowedXHandles admits at most 20 handles";
|
|
42
|
+
if (excluded.length > 20) return "excludedXHandles admits at most 20 handles";
|
|
43
|
+
for (const [field, value] of [["fromDate", options.fromDate], ["toDate", options.toDate]] as const) {
|
|
44
|
+
if (value !== undefined && !ISO_DATE.test(value)) return `${field} must be an ISO-8601 date (YYYY-MM-DD)`;
|
|
45
|
+
}
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function buildXSearchTool(options: XaiSearchOptions): Record<string, unknown> {
|
|
50
|
+
return {
|
|
51
|
+
type: "x_search",
|
|
52
|
+
...(options.allowedXHandles?.length ? { allowed_x_handles: options.allowedXHandles } : {}),
|
|
53
|
+
...(options.excludedXHandles?.length ? { excluded_x_handles: options.excludedXHandles } : {}),
|
|
54
|
+
...(options.fromDate ? { from_date: options.fromDate } : {}),
|
|
55
|
+
...(options.toDate ? { to_date: options.toDate } : {}),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function isRec(v: unknown): v is Record<string, unknown> {
|
|
60
|
+
return !!v && typeof v === "object" && !Array.isArray(v);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export async function runXaiWebSearch(
|
|
64
|
+
query: string,
|
|
65
|
+
providerName: string,
|
|
66
|
+
provider: OcxProviderConfig,
|
|
67
|
+
settings: SidecarSettings,
|
|
68
|
+
options: XaiSearchOptions = {},
|
|
69
|
+
abortSignal?: AbortSignal,
|
|
70
|
+
): Promise<SidecarOutcome> {
|
|
71
|
+
const invalid = validateXaiSearchOptions(options);
|
|
72
|
+
if (invalid) return { text: "", sources: [], error: `xai sidecar options invalid: ${invalid}` };
|
|
73
|
+
let token: string;
|
|
74
|
+
try {
|
|
75
|
+
token = await getValidAccessToken(providerName);
|
|
76
|
+
} catch (e) {
|
|
77
|
+
return { text: "", sources: [], error: `xai sidecar auth failed: ${publicOAuthAuthenticationErrorMessage(e)}` };
|
|
78
|
+
}
|
|
79
|
+
// Credential pinning: only the EXACT api.x.ai origin may carry the OAuth bearer.
|
|
80
|
+
// A prefix check would admit https://api.x.ai.evil/ (review Critical); parse and
|
|
81
|
+
// compare origins, falling back to the canonical endpoint on any mismatch.
|
|
82
|
+
let base = `${XAI_RESPONSES_ORIGIN}/v1`;
|
|
83
|
+
if (provider.baseUrl) {
|
|
84
|
+
try {
|
|
85
|
+
const parsed = new URL(provider.baseUrl);
|
|
86
|
+
if (parsed.origin === XAI_RESPONSES_ORIGIN) base = provider.baseUrl.replace(/\/+$/, "");
|
|
87
|
+
} catch { /* malformed baseUrl: keep the canonical endpoint */ }
|
|
88
|
+
}
|
|
89
|
+
const url = `${base}/responses`;
|
|
90
|
+
const instruction = settings.describeImages ? BASE_INSTRUCTION + IMAGE_INSTRUCTION : BASE_INSTRUCTION;
|
|
91
|
+
const body = {
|
|
92
|
+
model: settings.model,
|
|
93
|
+
instructions: instruction,
|
|
94
|
+
input: [{ role: "user", content: query }],
|
|
95
|
+
tools: [{ type: "web_search" }, ...(options.xSearch ? [buildXSearchTool(options)] : [])],
|
|
96
|
+
include: ["web_search_call.action.sources"],
|
|
97
|
+
reasoning: { effort: settings.reasoning },
|
|
98
|
+
stream: true,
|
|
99
|
+
};
|
|
100
|
+
const linkedSignal = signalWithTimeout(settings.timeoutMs, abortSignal);
|
|
101
|
+
const sidecarExit = sidecarEnter("web-search");
|
|
102
|
+
const t0 = Date.now();
|
|
103
|
+
try {
|
|
104
|
+
const res = await fetchWithResetRetry(
|
|
105
|
+
() => fetch(url, {
|
|
106
|
+
method: "POST",
|
|
107
|
+
headers: { "Content-Type": "application/json", "Authorization": `Bearer ${token}` },
|
|
108
|
+
body: JSON.stringify(body),
|
|
109
|
+
signal: linkedSignal.signal,
|
|
110
|
+
// Credential-bearing: never follow a redirect off the pinned origin.
|
|
111
|
+
redirect: "manual",
|
|
112
|
+
}),
|
|
113
|
+
{ abortSignal: linkedSignal.signal, label: "xai-web-search-sidecar" },
|
|
114
|
+
);
|
|
115
|
+
const detachBodyGuard = cancelBodyOnAbort(res.body, linkedSignal.signal);
|
|
116
|
+
if (!res.ok) {
|
|
117
|
+
const t = await res.text().catch(() => "");
|
|
118
|
+
detachBodyGuard();
|
|
119
|
+
const entitlement = res.status === 401 || res.status === 403 ? " (Grok OAuth entitlement — re-run ocx login xai?)" : "";
|
|
120
|
+
return { text: "", sources: [], error: `xai sidecar HTTP ${res.status}${entitlement}: ${redactSecretString(t.slice(0, 200))}` };
|
|
121
|
+
}
|
|
122
|
+
try {
|
|
123
|
+
return await parseXaiResponsesSSE(res);
|
|
124
|
+
} finally {
|
|
125
|
+
detachBodyGuard();
|
|
126
|
+
}
|
|
127
|
+
} catch (e) {
|
|
128
|
+
const kind = e instanceof Error && e.name === "TimeoutError" ? "timeout" : "connect_error";
|
|
129
|
+
console.warn(`[web-search] xai sidecar ${kind} (${Date.now() - t0}ms)`);
|
|
130
|
+
return { text: "", sources: [], error: redactSecretString(e instanceof Error ? e.message : String(e)) };
|
|
131
|
+
} finally {
|
|
132
|
+
sidecarExit();
|
|
133
|
+
linkedSignal.cleanup();
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Reduce a Grok Responses SSE stream to a SidecarOutcome (probe-shapes in devlog 003):
|
|
139
|
+
* text from output_text deltas on message items; sources from url_citation annotations
|
|
140
|
+
* unioned with web_search_call action.sources (deduped by url). Tolerates custom_tool_call
|
|
141
|
+
* and x_search_call items, absent `action`, and unknown event names. Bounds raw bytes.
|
|
142
|
+
*/
|
|
143
|
+
export async function parseXaiResponsesSSE(response: Response): Promise<SidecarOutcome> {
|
|
144
|
+
if (!response.body) return { text: "", sources: [] };
|
|
145
|
+
const reader = response.body.getReader();
|
|
146
|
+
const decoder = new TextDecoder();
|
|
147
|
+
let buffer = "";
|
|
148
|
+
let responseBytes = 0;
|
|
149
|
+
let text = "";
|
|
150
|
+
let doneText = "";
|
|
151
|
+
const sourceUrls = new Map<string, WebSearchSource>();
|
|
152
|
+
let error: string | null = null;
|
|
153
|
+
const addSource = (url: unknown, title?: unknown) => {
|
|
154
|
+
if (typeof url !== "string" || url.length === 0) return;
|
|
155
|
+
if (!sourceUrls.has(url)) sourceUrls.set(url, { url, ...(typeof title === "string" && title.length > 0 && title !== url ? { title } : {}) });
|
|
156
|
+
};
|
|
157
|
+
try {
|
|
158
|
+
while (true) {
|
|
159
|
+
const { done, value } = await reader.read();
|
|
160
|
+
if (done) break;
|
|
161
|
+
responseBytes += value.byteLength;
|
|
162
|
+
if (responseBytes > MAX_SIDECAR_RESPONSE_BYTES * 8) {
|
|
163
|
+
error = "xai sidecar stream exceeded byte bound";
|
|
164
|
+
await reader.cancel(error).catch(() => {});
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
167
|
+
buffer += decoder.decode(value, { stream: true });
|
|
168
|
+
let idx;
|
|
169
|
+
while ((idx = buffer.indexOf("\n\n")) >= 0) {
|
|
170
|
+
const frame = buffer.slice(0, idx);
|
|
171
|
+
buffer = buffer.slice(idx + 2);
|
|
172
|
+
const dataLine = frame.match(/^data: (.+)$/m)?.[1];
|
|
173
|
+
if (!dataLine || dataLine === "[DONE]") continue;
|
|
174
|
+
let payload: unknown;
|
|
175
|
+
try { payload = JSON.parse(dataLine); } catch { continue; }
|
|
176
|
+
if (!isRec(payload) || typeof payload.type !== "string") continue;
|
|
177
|
+
switch (payload.type) {
|
|
178
|
+
case "response.output_text.delta": {
|
|
179
|
+
if (typeof payload.delta === "string") text += payload.delta;
|
|
180
|
+
break;
|
|
181
|
+
}
|
|
182
|
+
case "response.output_text.done": {
|
|
183
|
+
if (typeof payload.text === "string") doneText = payload.text;
|
|
184
|
+
break;
|
|
185
|
+
}
|
|
186
|
+
case "response.output_text.annotation.added": {
|
|
187
|
+
const annotation = payload.annotation;
|
|
188
|
+
if (isRec(annotation) && annotation.type === "url_citation") addSource(annotation.url, annotation.title);
|
|
189
|
+
break;
|
|
190
|
+
}
|
|
191
|
+
case "response.output_item.done": {
|
|
192
|
+
const item = payload.item;
|
|
193
|
+
if (isRec(item) && item.type === "web_search_call" && isRec(item.action) && Array.isArray(item.action.sources)) {
|
|
194
|
+
for (const s of item.action.sources) if (isRec(s)) addSource(s.url);
|
|
195
|
+
}
|
|
196
|
+
break;
|
|
197
|
+
}
|
|
198
|
+
case "response.failed":
|
|
199
|
+
case "error": {
|
|
200
|
+
const message = isRec(payload.response) && isRec(payload.response.error) && typeof payload.response.error.message === "string"
|
|
201
|
+
? payload.response.error.message
|
|
202
|
+
: typeof payload.message === "string" ? payload.message : "upstream reported failure";
|
|
203
|
+
error = redactSecretString(String(message).slice(0, 200));
|
|
204
|
+
break;
|
|
205
|
+
}
|
|
206
|
+
default: break;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
} catch (e) {
|
|
211
|
+
error = redactSecretString(e instanceof Error ? e.message : String(e));
|
|
212
|
+
} finally {
|
|
213
|
+
reader.releaseLock();
|
|
214
|
+
}
|
|
215
|
+
const finalText = doneText.length >= text.length ? doneText : text;
|
|
216
|
+
const outcome: SidecarOutcome = { text: finalText, sources: [...sourceUrls.values()] };
|
|
217
|
+
if (error && finalText.length === 0) outcome.error = error;
|
|
218
|
+
return outcome;
|
|
219
|
+
}
|