@xopcai/xopc 0.0.385 → 0.0.386
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser-ext/manifest.json +1 -1
- package/dist/extensions/telegram/xopc.extension.json +1 -1
- package/dist/gateway/static/root/assets/{agent-avatar-dicebear-Cb9eaXxR.js → agent-avatar-dicebear-D-86VOlb.js} +1 -1
- package/dist/gateway/static/root/assets/{agent-defaults-settings-panel-DEV-xtgm.js → agent-defaults-settings-panel-SmeUVHY8.js} +1 -1
- package/dist/gateway/static/root/assets/{agent-plugin-dialog-DXKc3Luj.js → agent-plugin-dialog-BVT3wXq5.js} +1 -1
- package/dist/gateway/static/root/assets/{agents-DcNxwpJQ.js → agents-D7rLQFhX.js} +3 -3
- package/dist/gateway/static/root/assets/{app-management-settings-panel-X5i0Tz7_.js → app-management-settings-panel-CUR7n5iP.js} +1 -1
- package/dist/gateway/static/root/assets/{appearance-settings-CmHHzquT.js → appearance-settings-BCotSGep.js} +1 -1
- package/dist/gateway/static/root/assets/{apps-page-ChFA4Zp0.js → apps-page-hk0bo9LT.js} +1 -1
- package/dist/gateway/static/root/assets/{automations-page-BFqsIqOI.js → automations-page-CqxuUwox.js} +1 -1
- package/dist/gateway/static/root/assets/{automations-workspace-Blfak-fT.js → automations-workspace-11_Z69pr.js} +1 -1
- package/dist/gateway/static/root/assets/{block-editor-CpOLG3Co.js → block-editor-eldB84Mj.js} +1 -1
- package/dist/gateway/static/root/assets/{browser-automations-page-BSbTJ9j2.js → browser-automations-page-D4TaUPYT.js} +1 -1
- package/dist/gateway/static/root/assets/{browser-settings-page-BUlILJrB.js → browser-settings-page-BXRlxeGV.js} +1 -1
- package/dist/gateway/static/root/assets/{capabilities-XFwwUNU3.js → capabilities-BkKgImWi.js} +1 -1
- package/dist/gateway/static/root/assets/{capabilities-2Do3eKMM.js → capabilities-DVj9n65C.js} +1 -1
- package/dist/gateway/static/root/assets/{capabilities-page-CeRdkwRf.js → capabilities-page-CyYlueEd.js} +2 -2
- package/dist/gateway/static/root/assets/{capabilities-settings-panel-S-cKsGja.js → capabilities-settings-panel-uVEgfvo0.js} +1 -1
- package/dist/gateway/static/root/assets/{capability-header-actions-6w8pQhnn.js → capability-header-actions-BXv0xu56.js} +1 -1
- package/dist/gateway/static/root/assets/{channels-DfNTGBmp.js → channels-fNb3SOzm.js} +1 -1
- package/dist/gateway/static/root/assets/{chat-terminal-dock-DnoQHpDc.js → chat-terminal-dock-CnLtparQ.js} +1 -1
- package/dist/gateway/static/root/assets/{computer-settings-page-7yliE7sG.js → computer-settings-page-xM0AnOlP.js} +1 -1
- package/dist/gateway/static/root/assets/{connector-service-page-DOLAUFSW.js → connector-service-page-CdG2WUb6.js} +1 -1
- package/dist/gateway/static/root/assets/{connectors-page-DAsn97XG.js → connectors-page-BRd08GH6.js} +1 -1
- package/dist/gateway/static/root/assets/constants-CpWu0oHS.js +1 -0
- package/dist/gateway/static/root/assets/{date-picker-DNDqr4Fk.js → date-picker-DIBbE-O8.js} +1 -1
- package/dist/gateway/static/root/assets/{dependency-picker-B5oG7RgE.js → dependency-picker-DDD8zokD.js} +1 -1
- package/dist/gateway/static/root/assets/{desktop-pet-K7kUCX1O.js → desktop-pet-KlF390Xn.js} +1 -1
- package/dist/gateway/static/root/assets/{desktop-pet-settings-KgOAuqSh.js → desktop-pet-settings-CgqQMKDC.js} +1 -1
- package/dist/gateway/static/root/assets/{device-pairing-wizard-DvqniZDm.js → device-pairing-wizard-BR6isfG2.js} +1 -1
- package/dist/gateway/static/root/assets/{directory-picker-path-field-BmOguLDl.js → directory-picker-path-field-vPQsW05P.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-debug-page-DtHNy4TE.js → extension-debug-page-ZHLPrLbH.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-page-DSSuMQxp.js → extension-page-DJ42KFRN.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-provider-nbgU7xBS.js → extension-provider-6IcsyNaV.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-settings-page-DSd__mNd.js → extension-settings-page-D7-5v0V4.js} +1 -1
- package/dist/gateway/static/root/assets/{fallback-models-D7bjbiwZ.js → fallback-models-DnYn7qOV.js} +1 -1
- package/dist/gateway/static/root/assets/{gateway-settings-hSXGWumL.js → gateway-settings-Bwe3JWX-.js} +1 -1
- package/dist/gateway/static/root/assets/{highlighted-body-KQOG7T2V-C7xJwE61.js → highlighted-body-KQOG7T2V-DefTxF3q.js} +1 -1
- package/dist/gateway/static/root/assets/{home-page-DJNmlMfH.js → home-page-BtSy2hRV.js} +1 -1
- package/dist/gateway/static/root/assets/{imports-page-BufZPzGE.js → imports-page-CTdBYZib.js} +1 -1
- package/dist/gateway/static/root/assets/{index-K8SXDtxr.js → index-B08iR7fT.js} +9 -9
- package/dist/gateway/static/root/assets/{keyboard-shortcuts-settings-B8pPP5RF.js → keyboard-shortcuts-settings-CXKp57O_.js} +1 -1
- package/dist/gateway/static/root/assets/{local-app-workbench-page-zEtvE7pJ.js → local-app-workbench-page-DzD_OzGY.js} +1 -1
- package/dist/gateway/static/root/assets/{local-apps-page-Bli7e_eg.js → local-apps-page-B0E0vttg.js} +1 -1
- package/dist/gateway/static/root/assets/{logs-page-BimZk3R4.js → logs-page-eL4fYcMl.js} +1 -1
- package/dist/gateway/static/root/assets/{management-settings-5Y4COyU9.js → management-settings-DSpjodMC.js} +1 -1
- package/dist/gateway/static/root/assets/mermaid-MCJ5UELQ-BTrk2wWv.js +1 -0
- package/dist/gateway/static/root/assets/{note-detail-panel-KLDPLVg7.js → note-detail-panel-CsY5tQp5.js} +3 -3
- package/dist/gateway/static/root/assets/{notes-page-DST0oPXD.js → notes-page-D09LjNoj.js} +1 -1
- package/dist/gateway/static/root/assets/{page-context-capture-button-Bmed83-6.js → page-context-capture-button-DO-aP3D9.js} +1 -1
- package/dist/gateway/static/root/assets/{personal-page-BG4Ystrm.js → personal-page-E8F2aGNB.js} +1 -1
- package/dist/gateway/static/root/assets/{product-open-page-CXkYP9xE.js → product-open-page-aUoA-uPw.js} +1 -1
- package/dist/gateway/static/root/assets/{product-preview-content-DjmupQSq.js → product-preview-content-B-cz-pIV.js} +1 -1
- package/dist/gateway/static/root/assets/{project-detail-page-CzBmV0P6.js → project-detail-page-WhefAh8E.js} +1 -1
- package/dist/gateway/static/root/assets/{projects-page-DByqe5Em.js → projects-page-CyqAs0tO.js} +1 -1
- package/dist/gateway/static/root/assets/{remote-access-hub-DTjj-odF.js → remote-access-hub-BwhRGL6m.js} +1 -1
- package/dist/gateway/static/root/assets/{runtime-tools-settings-panel-B_ptn3T8.js → runtime-tools-settings-panel-o26dmPNq.js} +1 -1
- package/dist/gateway/static/root/assets/{scenes-page-wkua4l5y.js → scenes-page-D_e19kv_.js} +1 -1
- package/dist/gateway/static/root/assets/{schema-form-VvOSM84Y.js → schema-form-DLLdqUC3.js} +1 -1
- package/dist/gateway/static/root/assets/{send-session-input-BCAF-Ikt.js → send-session-input-2I-nFJcE.js} +1 -1
- package/dist/gateway/static/root/assets/session-manager-Co5ozD0l.js +16 -0
- package/dist/gateway/static/root/assets/{sessions-page-D06XwYAT.js → sessions-page-DX6INsiS.js} +1 -1
- package/dist/gateway/static/root/assets/settings-advanced-gate-C4_gW1vw.js +1 -0
- package/dist/gateway/static/root/assets/{settings-page-C-6BJdb1.js → settings-page-Bc_zxxRu.js} +1 -1
- package/dist/gateway/static/root/assets/{setup-status-panel-L0JSG0K-.js → setup-status-panel-FpF_cPPr.js} +1 -1
- package/dist/gateway/static/root/assets/{share-preview-page-nJ2FWAEP.js → share-preview-page-BOCcPBqQ.js} +1 -1
- package/dist/gateway/static/root/assets/{shares-settings-BbgAUM3v.js → shares-settings-CInxjxgJ.js} +1 -1
- package/dist/gateway/static/root/assets/{skill-api-BFU1tPyw.js → skill-api-Bjd8WAA4.js} +1 -1
- package/dist/gateway/static/root/assets/{skills-page-DZw6el4o.js → skills-page-8CKS3Oo2.js} +1 -1
- package/dist/gateway/static/root/assets/{system-settings-panel-DshSGWNg.js → system-settings-panel-BgQdsrNw.js} +1 -1
- package/dist/gateway/static/root/assets/task-detail-page-BnG5ehZ6.js +2 -0
- package/dist/gateway/static/root/assets/task-expandable-content-BCuc70Vc.js +1 -0
- package/dist/gateway/static/root/assets/{tasks-DJIeIk9C.js → tasks-PuIUgF9L.js} +1 -1
- package/dist/gateway/static/root/assets/{tunnel-api-DFDXwWnU.js → tunnel-api-CY7MoDGf.js} +1 -1
- package/dist/gateway/static/root/assets/{usage-page-2RJLg54s.js → usage-page-s8Sv_PIG.js} +1 -1
- package/dist/gateway/static/root/assets/{use-composer-attachments-BFP3k6oC.js → use-composer-attachments-D0S2iZF4.js} +1 -1
- package/dist/gateway/static/root/assets/{use-file-preview-expanded-B1BiTQNS.js → use-file-preview-expanded-BElqELib.js} +1 -1
- package/dist/gateway/static/root/assets/{user-model-page-y1gjGYPI.js → user-model-page-CyWrEtHY.js} +1 -1
- package/dist/gateway/static/root/assets/{work-discovery-overlay-Ch-KfpCF.js → work-discovery-overlay-DmZXLs1v.js} +1 -1
- package/dist/gateway/static/root/assets/{workflow-run-setup-panel-CTu297Hc.js → workflow-run-setup-panel-BdLOzHm4.js} +1 -1
- package/dist/gateway/static/root/assets/{workflows-page-CRZXG_rm.js → workflows-page-Cx3I3AV5.js} +1 -1
- package/dist/gateway/static/root/assets/{workspace-api-Xr3wz8T3.js → workspace-api-Du7-xJ0q.js} +2 -2
- package/dist/gateway/static/root/assets/{xopc-cloud-account-card-hEfjVjnc.js → xopc-cloud-account-card-D8B136vW.js} +1 -1
- package/dist/gateway/static/root/index.html +11 -11
- package/dist/package.js +1 -1
- package/dist/packages/gateway-contract/src/index.js +2 -0
- package/dist/packages/gateway-contract/src/task-research-result.js +89 -0
- package/dist/packages/gateway-contract/src/tasks.js +7 -1
- package/dist/packages/gateway-contract/src/turn-outcome.js +3 -0
- package/dist/src/agent/child-agent-factory.d.ts +4 -1
- package/dist/src/agent/child-agent-factory.js +86 -12
- package/dist/src/agent/context/connection-objective-context.d.ts +1 -0
- package/dist/src/agent/context/connection-objective-context.js +33 -0
- package/dist/src/agent/data-acquisition/service.js +41 -6
- package/dist/src/agent/data-acquisition/types.d.ts +2 -0
- package/dist/src/agent/embedded/agent-profile-context.d.ts +7 -0
- package/dist/src/agent/embedded/agent-profile-context.js +92 -0
- package/dist/src/agent/embedded/run-for-session.js +5 -2
- package/dist/src/agent/embedded/run-turn.js +19 -6
- package/dist/src/agent/embedded/runs.d.ts +9 -2
- package/dist/src/agent/embedded/runs.js +30 -7
- package/dist/src/agent/embedded/session-runner.js +5 -2
- package/dist/src/agent/embedded/types.d.ts +4 -0
- package/dist/src/agent/external-tools/gateway-tools.js +7 -3
- package/dist/src/agent/orchestration/agent-run-supervisor.js +2 -1
- package/dist/src/agent/orchestration/agent-turn-policy.js +5 -1
- package/dist/src/agent/orchestration/delegation-budget.d.ts +7 -1
- package/dist/src/agent/orchestration/delegation-budget.js +17 -9
- package/dist/src/agent/orchestration/research-checkpoint.d.ts +56 -0
- package/dist/src/agent/orchestration/research-checkpoint.js +64 -0
- package/dist/src/agent/orchestration/run-agent-turn-with-timeout.d.ts +3 -1
- package/dist/src/agent/orchestration/run-agent-turn-with-timeout.js +15 -2
- package/dist/src/agent/orchestration/run-termination.d.ts +12 -0
- package/dist/src/agent/orchestration/run-termination.js +24 -0
- package/dist/src/agent/orchestration/turn-recovery.d.ts +4 -1
- package/dist/src/agent/orchestration/turn-recovery.js +19 -3
- package/dist/src/agent/prompt/sections/agent-identity.js +12 -7
- package/dist/src/agent/prompt/sections/messaging-runtime.js +1 -1
- package/dist/src/agent/prompt/sections/project-context.js +1 -1
- package/dist/src/agent/prompt/sections/response-language.d.ts +1 -1
- package/dist/src/agent/prompt/sections/response-language.js +6 -2
- package/dist/src/agent/prompt/subagent-context.js +1 -1
- package/dist/src/agent/prompt/system-prompt.js +4 -6
- package/dist/src/agent/tasks/task-judge-service.js +3 -1
- package/dist/src/agent/tools/apply-patch.js +1 -1
- package/dist/src/agent/tools/delegate-tool.js +216 -121
- package/dist/src/agent/tools/delegation-policy.js +13 -1
- package/dist/src/agent/tools/factory.js +2 -0
- package/dist/src/agent/tools/index.d.ts +1 -0
- package/dist/src/agent/tools/index.js +2 -1
- package/dist/src/agent/tools/language-diagnostics.js +1 -1
- package/dist/src/agent/tools/personal-request-tool.js +8 -13
- package/dist/src/agent/tools/personal-task-tool.d.ts +1 -2
- package/dist/src/agent/tools/personal-task-tool.js +9 -8
- package/dist/src/agent/tools/publish-artifacts.js +1 -1
- package/dist/src/agent/tools/research-checkpoint-tool.d.ts +3 -0
- package/dist/src/agent/tools/research-checkpoint-tool.js +28 -0
- package/dist/src/agent/tools/review-workspace.js +1 -1
- package/dist/src/agent/tools/search/cache.d.ts +20 -0
- package/dist/src/agent/tools/search/cache.js +19 -0
- package/dist/src/agent/tools/search/errors.d.ts +19 -0
- package/dist/src/agent/tools/search/errors.js +69 -0
- package/dist/src/agent/tools/search/providers/bing-api.js +2 -1
- package/dist/src/agent/tools/search/providers/bing-html.js +2 -1
- package/dist/src/agent/tools/search/providers/brave.js +2 -1
- package/dist/src/agent/tools/search/providers/duckduckgo-html.js +2 -1
- package/dist/src/agent/tools/search/providers/searxng.js +2 -1
- package/dist/src/agent/tools/search/providers/tavily.js +2 -1
- package/dist/src/agent/tools/search/providers/xopc-cloud.d.ts +1 -0
- package/dist/src/agent/tools/search/providers/xopc-cloud.js +7 -2
- package/dist/src/agent/tools/search/registry.d.ts +21 -5
- package/dist/src/agent/tools/search/registry.js +252 -25
- package/dist/src/agent/tools/search/types.d.ts +25 -0
- package/dist/src/agent/tools/task-result-tool.d.ts +2 -0
- package/dist/src/agent/tools/task-result-tool.js +73 -0
- package/dist/src/agent/tools/web.d.ts +16 -2
- package/dist/src/agent/tools/web.js +74 -29
- package/dist/src/agent/tools/xopc-use-tool.js +2 -2
- package/dist/src/automations/capabilities/write.d.ts +4 -4
- package/dist/src/automations/domain/validation.d.ts +8 -8
- package/dist/src/automations/draft/automation-draft-validator.d.ts +2 -2
- package/dist/src/browser/automations/schema.d.ts +2 -2
- package/dist/src/browser/automations/service.d.ts +2 -2
- package/dist/src/cli/commands/doctor/checks/database-schema.js +1 -1
- package/dist/src/config/index.js +2 -2
- package/dist/src/config/schema.d.ts +1 -0
- package/dist/src/config/schema.js +4 -1
- package/dist/src/connectors/connection-recovery-service.d.ts +3 -1
- package/dist/src/connectors/connection-recovery-service.js +66 -5
- package/dist/src/discussions/action-tasks.d.ts +2 -2
- package/dist/src/gateway/agent-builtin-tools.d.ts +1 -1
- package/dist/src/gateway/hono/routes/projects.js +1 -1
- package/dist/src/gateway/hono/routes/session-input-handler.d.ts +16 -0
- package/dist/src/gateway/hono/routes/tasks.js +1 -1
- package/dist/src/gateway/service/agent-runner.d.ts +2 -0
- package/dist/src/gateway/service/agent-runner.js +30 -17
- package/dist/src/gateway/service/run-gateway-agent.js +12 -1
- package/dist/src/gateway/service/session-input-coordinator.d.ts +10 -1
- package/dist/src/gateway/service/session-input-coordinator.js +213 -26
- package/dist/src/gateway/service.d.ts +1 -0
- package/dist/src/gateway/service.js +9 -7
- package/dist/src/gateway/session-context-summary.js +1 -1
- package/dist/src/personal-agent/prompts.js +4 -1
- package/dist/src/personal-agent/reply-composer.d.ts +1 -0
- package/dist/src/personal-agent/reply-composer.js +70 -12
- package/dist/src/personal-agent/reply-style.d.ts +1 -1
- package/dist/src/personal-agent/reply-style.js +1 -0
- package/dist/src/personal-agent/repository.d.ts +1 -1
- package/dist/src/personal-agent/repository.js +1 -1
- package/dist/src/personal-agent/request-repository.d.ts +2 -0
- package/dist/src/personal-agent/request-service.d.ts +4 -0
- package/dist/src/personal-agent/request-service.js +90 -19
- package/dist/src/personal-agent/result-file-links.d.ts +3 -0
- package/dist/src/personal-agent/result-file-links.js +21 -0
- package/dist/src/personal-agent/task-cancel.js +85 -35
- package/dist/src/scenes/taskFollowUp/service.d.ts +15 -5
- package/dist/src/session/session-context-for-llm.js +2 -2
- package/dist/src/session/store.d.ts +3 -1
- package/dist/src/session/store.js +13 -3
- package/dist/src/storage/sqlite/connection-wait-repository.d.ts +5 -2
- package/dist/src/storage/sqlite/connection-wait-repository.js +42 -17
- package/dist/src/storage/sqlite/index.d.ts +1 -0
- package/dist/src/storage/sqlite/index.js +3 -1
- package/dist/src/storage/sqlite/migrations/250_research_checkpoints.sql +15 -0
- package/dist/src/storage/sqlite/migrations/runner.d.ts +1 -1
- package/dist/src/storage/sqlite/migrations/runner.js +3 -3
- package/dist/src/storage/sqlite/research-checkpoint-repository.d.ts +24 -0
- package/dist/src/storage/sqlite/research-checkpoint-repository.js +91 -0
- package/dist/src/storage/sqlite/schema.js +1 -1
- package/dist/src/storage/sqlite/session-creation-repository.js +3 -2
- package/dist/src/storage/sqlite/session-input-repository.d.ts +16 -0
- package/dist/src/storage/sqlite/session-input-repository.js +41 -7
- package/dist/src/tasks/capabilities/management.d.ts +2 -2
- package/dist/src/tasks/capabilities/relations.d.ts +7 -7
- package/dist/src/tasks/capabilities/write.d.ts +2 -2
- package/dist/src/tasks/index.js +1 -1
- package/dist/src/tasks/task-allocation-service.d.ts +2 -0
- package/dist/src/tasks/task-allocation-service.js +43 -2
- package/dist/src/tasks/task-application-service.js +2 -2
- package/dist/src/tasks/task-context-assembler.js +6 -1
- package/dist/src/tasks/task-continuation.d.ts +1 -0
- package/dist/src/tasks/task-continuation.js +1 -0
- package/dist/src/tasks/task-contract-planner.d.ts +1 -0
- package/dist/src/tasks/task-contract-planner.js +22 -15
- package/dist/src/tasks/task-model-call.d.ts +7 -0
- package/dist/src/tasks/task-model-call.js +35 -0
- package/dist/src/tasks/task-research-repair.d.ts +19 -0
- package/dist/src/tasks/task-research-repair.js +75 -0
- package/dist/src/tasks/task-research-result.d.ts +85 -0
- package/dist/src/tasks/task-research-result.js +394 -0
- package/dist/src/tasks/task-result-delivery-repository.js +13 -2
- package/dist/src/tasks/task-result-delivery-service.js +1 -1
- package/dist/src/tasks/task-run-coordinator.js +69 -15
- package/dist/src/tasks/task-run-dispatcher.js +1 -0
- package/dist/src/tasks/task-run-repository.d.ts +5 -0
- package/dist/src/tasks/task-run-repository.js +88 -1
- package/package.json +9 -5
- package/dist/gateway/static/root/assets/constants-Dy8Gw0sF.js +0 -1
- package/dist/gateway/static/root/assets/mermaid-MCJ5UELQ-DBRj4-1-.js +0 -1
- package/dist/gateway/static/root/assets/session-manager-_yy4bxhT.js +0 -16
- package/dist/gateway/static/root/assets/settings-advanced-gate-DoAHy1wj.js +0 -1
- package/dist/gateway/static/root/assets/task-detail-page-CfZhq4XZ.js +0 -2
- package/dist/gateway/static/root/assets/task-expandable-content-CF6Bj-Qa.js +0 -1
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { ResearchCheckpointUpdateSchema, init_research_checkpoint } from "../orchestration/research-checkpoint.js";
|
|
2
|
+
//#region src/agent/tools/research-checkpoint-tool.ts
|
|
3
|
+
init_research_checkpoint();
|
|
4
|
+
function createResearchCheckpointTool(sink) {
|
|
5
|
+
return {
|
|
6
|
+
name: "research_checkpoint",
|
|
7
|
+
label: "Save research checkpoint",
|
|
8
|
+
description: "Persist findings immediately after verifying each source. Use stable finding ids to update fields; omitted findings are retained. Include public source URLs for confirmed claims. not_disclosed is a valid result; never invent missing prices or limits. nextSteps replaces the unfinished-work list; set it to [] only when all requested fields are addressed, including explicitly undisclosed fields. This writes only this delegated research checkpoint.",
|
|
9
|
+
parameters: ResearchCheckpointUpdateSchema,
|
|
10
|
+
async execute(_id, update, signal) {
|
|
11
|
+
signal?.throwIfAborted();
|
|
12
|
+
const checkpoint = sink.save(update);
|
|
13
|
+
return {
|
|
14
|
+
content: [{
|
|
15
|
+
type: "text",
|
|
16
|
+
text: JSON.stringify({
|
|
17
|
+
saved: true,
|
|
18
|
+
findingCount: checkpoint.findings.length,
|
|
19
|
+
nextSteps: checkpoint.nextSteps
|
|
20
|
+
})
|
|
21
|
+
}],
|
|
22
|
+
details: {}
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
//#endregion
|
|
28
|
+
export { createResearchCheckpointTool };
|
|
@@ -5,8 +5,8 @@ import { checkedFilePath, readWorkspaceFile } from "../sandbox/fileAccess.js";
|
|
|
5
5
|
import { resolve } from "node:path";
|
|
6
6
|
import { execFile } from "node:child_process";
|
|
7
7
|
import { promisify } from "node:util";
|
|
8
|
-
import { lstat, readlink, realpath } from "node:fs/promises";
|
|
9
8
|
import { Type } from "@sinclair/typebox";
|
|
9
|
+
import { lstat, readlink, realpath } from "node:fs/promises";
|
|
10
10
|
//#region src/agent/tools/review-workspace.ts
|
|
11
11
|
const exec = promisify(execFile);
|
|
12
12
|
function createReviewWorkspaceTool(workspace) {
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { SearchResult } from './types.js';
|
|
2
|
+
export declare const SEARCH_CACHE_TTL_MS: number;
|
|
3
|
+
export interface SearchCacheEntry {
|
|
4
|
+
key: string;
|
|
5
|
+
query: string;
|
|
6
|
+
count: number;
|
|
7
|
+
results: SearchResult[];
|
|
8
|
+
provider: string;
|
|
9
|
+
capturedAt: number;
|
|
10
|
+
}
|
|
11
|
+
export interface SearchCache {
|
|
12
|
+
get(key: string): SearchCacheEntry | undefined;
|
|
13
|
+
set(entry: SearchCacheEntry): void;
|
|
14
|
+
}
|
|
15
|
+
/** Research owns this scope. No account-wide or cross-conversation cache exists. */
|
|
16
|
+
export declare function withSearchCache<T>(cache: SearchCache, run: () => T): T;
|
|
17
|
+
export declare function currentSearchCache(): SearchCache | undefined;
|
|
18
|
+
export declare class SearchCacheWriteError extends Error {
|
|
19
|
+
constructor(cause: unknown);
|
|
20
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
+
//#region src/agent/tools/search/cache.ts
|
|
3
|
+
const SEARCH_CACHE_TTL_MS = 10 * 6e4;
|
|
4
|
+
const searchCache = new AsyncLocalStorage();
|
|
5
|
+
/** Research owns this scope. No account-wide or cross-conversation cache exists. */
|
|
6
|
+
function withSearchCache(cache, run) {
|
|
7
|
+
return searchCache.run(cache, run);
|
|
8
|
+
}
|
|
9
|
+
function currentSearchCache() {
|
|
10
|
+
return searchCache.getStore();
|
|
11
|
+
}
|
|
12
|
+
var SearchCacheWriteError = class extends Error {
|
|
13
|
+
constructor(cause) {
|
|
14
|
+
super("Search completed but its checkpoint could not be saved", { cause });
|
|
15
|
+
this.name = "SearchCacheWriteError";
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
//#endregion
|
|
19
|
+
export { SEARCH_CACHE_TTL_MS, SearchCacheWriteError, currentSearchCache, withSearchCache };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export type SearchFailureKind = 'permission_denied' | 'rate_limited' | 'timeout' | 'cancelled' | 'unsupported_batch' | 'provider_error';
|
|
2
|
+
export interface SearchFailureInfo {
|
|
3
|
+
kind: SearchFailureKind;
|
|
4
|
+
scope: 'request' | 'batch' | 'provider';
|
|
5
|
+
message: string;
|
|
6
|
+
status?: number;
|
|
7
|
+
retryAfterMs?: number;
|
|
8
|
+
}
|
|
9
|
+
/** Only a host adapter may explicitly identify a refusal as batch-only. */
|
|
10
|
+
export declare class SearchError extends Error implements SearchFailureInfo {
|
|
11
|
+
readonly kind: SearchFailureKind;
|
|
12
|
+
readonly scope: 'request' | 'batch' | 'provider';
|
|
13
|
+
readonly status?: number;
|
|
14
|
+
readonly retryAfterMs?: number;
|
|
15
|
+
constructor(info: SearchFailureInfo);
|
|
16
|
+
}
|
|
17
|
+
export declare function classifySearchError(error: unknown, signal?: AbortSignal): SearchError;
|
|
18
|
+
export declare function searchFailureInfo(error: SearchError): SearchFailureInfo;
|
|
19
|
+
export declare function searchHttpError(response: Response, message?: string): SearchError;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
//#region src/agent/tools/search/errors.ts
|
|
2
|
+
/** Only a host adapter may explicitly identify a refusal as batch-only. */
|
|
3
|
+
var SearchError = class extends Error {
|
|
4
|
+
kind;
|
|
5
|
+
scope;
|
|
6
|
+
status;
|
|
7
|
+
retryAfterMs;
|
|
8
|
+
constructor(info) {
|
|
9
|
+
super(info.message);
|
|
10
|
+
this.name = "SearchError";
|
|
11
|
+
this.kind = info.kind;
|
|
12
|
+
this.scope = info.scope;
|
|
13
|
+
this.status = info.status;
|
|
14
|
+
this.retryAfterMs = info.retryAfterMs;
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
function classifySearchError(error, signal) {
|
|
18
|
+
if (signal?.aborted) {
|
|
19
|
+
const reason = signal.reason;
|
|
20
|
+
const timedOut = reason?.name === "TimeoutError" || reason?.code === "deadline_exceeded";
|
|
21
|
+
return new SearchError({
|
|
22
|
+
kind: timedOut ? "timeout" : "cancelled",
|
|
23
|
+
scope: "request",
|
|
24
|
+
message: timedOut ? "Search caller deadline exceeded" : "Search cancelled by its caller"
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
if (error instanceof SearchError) return error;
|
|
28
|
+
const value = error;
|
|
29
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
30
|
+
const legacyStatus = /^HTTP (\d{3})\b/.exec(message)?.[1];
|
|
31
|
+
const status = typeof value?.status === "number" ? value.status : typeof value?.statusCode === "number" ? value.statusCode : legacyStatus ? Number(legacyStatus) : void 0;
|
|
32
|
+
const denied = [
|
|
33
|
+
"EACCES",
|
|
34
|
+
"EPERM",
|
|
35
|
+
"PERMISSION_DENIED",
|
|
36
|
+
"permission_denied",
|
|
37
|
+
"ACCESS_DENIED"
|
|
38
|
+
].includes(String(value?.code)) || /\b(permission denied|access denied|forbidden|unauthorized|not authorized)\b/i.test(message);
|
|
39
|
+
const kind = status === 401 || status === 403 || denied ? "permission_denied" : status === 429 ? "rate_limited" : status === 408 || status === 504 || value?.name === "TimeoutError" || value?.code === "ETIMEDOUT" ? "timeout" : value?.name === "AbortError" ? "cancelled" : "provider_error";
|
|
40
|
+
return new SearchError({
|
|
41
|
+
kind,
|
|
42
|
+
scope: kind === "permission_denied" ? "provider" : "request",
|
|
43
|
+
status,
|
|
44
|
+
message: message.slice(0, 500)
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
function searchFailureInfo(error) {
|
|
48
|
+
return {
|
|
49
|
+
kind: error.kind,
|
|
50
|
+
scope: error.scope,
|
|
51
|
+
message: error.message.slice(0, 500),
|
|
52
|
+
status: error.status,
|
|
53
|
+
retryAfterMs: error.retryAfterMs
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
function searchHttpError(response, message = `Search request failed (${response.status})`) {
|
|
57
|
+
const retryAfter = response.headers.get("retry-after");
|
|
58
|
+
const seconds = retryAfter === null ? NaN : Number(retryAfter);
|
|
59
|
+
const retryAfterMs = retryAfter === null ? void 0 : Number.isFinite(seconds) ? Math.max(0, seconds * 1e3) : Math.max(0, Date.parse(retryAfter) - Date.now());
|
|
60
|
+
return new SearchError({
|
|
61
|
+
kind: response.status === 401 || response.status === 403 ? "permission_denied" : response.status === 429 ? "rate_limited" : response.status === 408 || response.status === 504 ? "timeout" : "provider_error",
|
|
62
|
+
scope: response.status === 401 || response.status === 403 ? "provider" : "request",
|
|
63
|
+
message: message.slice(0, 500),
|
|
64
|
+
status: response.status,
|
|
65
|
+
retryAfterMs: retryAfterMs !== void 0 && Number.isFinite(retryAfterMs) ? retryAfterMs : void 0
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
//#endregion
|
|
69
|
+
export { SearchError, classifySearchError, searchFailureInfo, searchHttpError };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { searchHttpError } from "../errors.js";
|
|
1
2
|
//#region src/agent/tools/search/providers/bing-api.ts
|
|
2
3
|
/** Azure Bing Web Search API (global endpoint; works without a proxy from many regions). */
|
|
3
4
|
var BingApiProvider = class {
|
|
@@ -17,7 +18,7 @@ var BingApiProvider = class {
|
|
|
17
18
|
headers: { "Ocp-Apim-Subscription-Key": this.apiKey },
|
|
18
19
|
signal
|
|
19
20
|
});
|
|
20
|
-
if (!response.ok) throw
|
|
21
|
+
if (!response.ok) throw searchHttpError(response);
|
|
21
22
|
return ((await response.json()).webPages?.value ?? []).map((r) => ({
|
|
22
23
|
title: r.name ?? "",
|
|
23
24
|
url: r.url ?? "",
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { searchHttpError } from "../errors.js";
|
|
1
2
|
import { DEFAULT_HTML_SEARCH_TIMEOUT_MS, HTML_SEARCH_USER_AGENT, mergeAbortWithTimeout } from "../http.js";
|
|
2
3
|
//#region src/agent/tools/search/providers/bing-html.ts
|
|
3
4
|
const BING_CN_SEARCH = "https://cn.bing.com/search";
|
|
@@ -22,7 +23,7 @@ var BingHtmlProvider = class {
|
|
|
22
23
|
"Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8"
|
|
23
24
|
}
|
|
24
25
|
});
|
|
25
|
-
if (!response.ok) throw
|
|
26
|
+
if (!response.ok) throw searchHttpError(response);
|
|
26
27
|
return parseBingCnHtml(await response.text(), count);
|
|
27
28
|
}
|
|
28
29
|
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { searchHttpError } from "../errors.js";
|
|
1
2
|
//#region src/agent/tools/search/providers/brave.ts
|
|
2
3
|
var BraveProvider = class {
|
|
3
4
|
name = "brave";
|
|
@@ -17,7 +18,7 @@ var BraveProvider = class {
|
|
|
17
18
|
},
|
|
18
19
|
signal
|
|
19
20
|
});
|
|
20
|
-
if (!response.ok) throw
|
|
21
|
+
if (!response.ok) throw searchHttpError(response);
|
|
21
22
|
return ((await response.json()).web?.results ?? []).map((r) => ({
|
|
22
23
|
title: r.title ?? "",
|
|
23
24
|
url: r.url ?? "",
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { searchHttpError } from "../errors.js";
|
|
1
2
|
import { DEFAULT_HTML_SEARCH_TIMEOUT_MS, HTML_SEARCH_USER_AGENT, mergeAbortWithTimeout } from "../http.js";
|
|
2
3
|
//#region src/agent/tools/search/providers/duckduckgo-html.ts
|
|
3
4
|
const DDG_HTML = "https://html.duckduckgo.com/html/";
|
|
@@ -23,7 +24,7 @@ var DuckDuckGoHtmlProvider = class {
|
|
|
23
24
|
},
|
|
24
25
|
body: body.toString()
|
|
25
26
|
});
|
|
26
|
-
if (!response.ok) throw
|
|
27
|
+
if (!response.ok) throw searchHttpError(response);
|
|
27
28
|
return parseDdgHtml(await response.text(), count);
|
|
28
29
|
}
|
|
29
30
|
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { searchHttpError } from "../errors.js";
|
|
1
2
|
//#region src/agent/tools/search/providers/searxng.ts
|
|
2
3
|
var SearXNGProvider = class {
|
|
3
4
|
name = "searxng";
|
|
@@ -14,7 +15,7 @@ var SearXNGProvider = class {
|
|
|
14
15
|
url.searchParams.set("format", "json");
|
|
15
16
|
url.searchParams.set("categories", "general");
|
|
16
17
|
const response = await fetch(url, { signal });
|
|
17
|
-
if (!response.ok) throw
|
|
18
|
+
if (!response.ok) throw searchHttpError(response);
|
|
18
19
|
return ((await response.json()).results ?? []).slice(0, count).map((r) => ({
|
|
19
20
|
title: r.title ?? "",
|
|
20
21
|
url: r.url ?? "",
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { searchHttpError } from "../errors.js";
|
|
1
2
|
//#region src/agent/tools/search/providers/tavily.ts
|
|
2
3
|
var TavilyProvider = class {
|
|
3
4
|
name = "tavily";
|
|
@@ -20,7 +21,7 @@ var TavilyProvider = class {
|
|
|
20
21
|
}),
|
|
21
22
|
signal
|
|
22
23
|
});
|
|
23
|
-
if (!response.ok) throw
|
|
24
|
+
if (!response.ok) throw searchHttpError(response);
|
|
24
25
|
return ((await response.json()).results ?? []).map((r) => ({
|
|
25
26
|
title: r.title ?? "",
|
|
26
27
|
url: r.url ?? "",
|
|
@@ -14,6 +14,7 @@ export declare class XopcCloudSearchProvider implements SearchProvider {
|
|
|
14
14
|
private readonly catalogStore;
|
|
15
15
|
private readonly credentials;
|
|
16
16
|
constructor(options: XopcCloudSearchProviderOptions);
|
|
17
|
+
cacheIdentity(): string;
|
|
17
18
|
isAvailable(): boolean;
|
|
18
19
|
search(query: string, count: number, signal?: AbortSignal): Promise<SearchResult[]>;
|
|
19
20
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { getModelCatalogStore, init_model_catalog_store } from "../../../../providers/model-catalog-store.js";
|
|
2
2
|
import { getProviderAuthService, init_provider_auth_service } from "../../../../providers/provider-auth-service.js";
|
|
3
|
+
import { searchHttpError } from "../errors.js";
|
|
3
4
|
//#region src/agent/tools/search/providers/xopc-cloud.ts
|
|
4
5
|
init_model_catalog_store();
|
|
5
6
|
init_provider_auth_service();
|
|
@@ -14,6 +15,10 @@ var XopcCloudSearchProvider = class {
|
|
|
14
15
|
this.catalogStore = options.catalogStore ?? getModelCatalogStore();
|
|
15
16
|
this.credentials = options.credentials ?? getProviderAuthService();
|
|
16
17
|
}
|
|
18
|
+
cacheIdentity() {
|
|
19
|
+
const source = this.catalogStore.getSource("xopc-cloud");
|
|
20
|
+
return JSON.stringify([source?.baseUrl, source?.search]);
|
|
21
|
+
}
|
|
17
22
|
isAvailable() {
|
|
18
23
|
return Boolean(this.catalogStore.getSource("xopc-cloud")?.search);
|
|
19
24
|
}
|
|
@@ -22,7 +27,7 @@ var XopcCloudSearchProvider = class {
|
|
|
22
27
|
if (!source?.search) throw new Error("XOPC Cloud search is not available");
|
|
23
28
|
const requestSignal = signal ? AbortSignal.any([signal, AbortSignal.timeout(3e4)]) : AbortSignal.timeout(3e4);
|
|
24
29
|
const accessToken = await this.credentials.resolveApiKey("xopc-cloud", requestSignal);
|
|
25
|
-
if (!accessToken) throw new
|
|
30
|
+
if (!accessToken) throw searchHttpError(new Response(null, { status: 401 }), "XOPC Cloud is not connected");
|
|
26
31
|
const endpoint = `${source.baseUrl.replace(/\/+$/, "")}/${source.search.endpoint.replace(/^\/+/, "")}`;
|
|
27
32
|
const response = await this.fetchImpl(endpoint, {
|
|
28
33
|
method: "POST",
|
|
@@ -39,7 +44,7 @@ var XopcCloudSearchProvider = class {
|
|
|
39
44
|
signal: requestSignal
|
|
40
45
|
});
|
|
41
46
|
const body = await response.json().catch(() => null);
|
|
42
|
-
if (!response.ok) throw
|
|
47
|
+
if (!response.ok) throw searchHttpError(response, typeof body?.error?.message === "string" ? body.error.message : `XOPC Cloud search failed (${response.status})`);
|
|
43
48
|
if (!Array.isArray(body?.results)) throw new Error("XOPC Cloud search returned an invalid response");
|
|
44
49
|
return body.results.flatMap((result) => {
|
|
45
50
|
if (typeof result.title !== "string" || typeof result.url !== "string" || typeof result.description !== "string") return [];
|
|
@@ -1,14 +1,30 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { type SearchCache } from './cache.js';
|
|
2
|
+
import type { ResolvedWebSearchConfig, SearchProvider, SearchResponse, SearchRequest, SearchBatchResponse } from './types.js';
|
|
2
3
|
export declare class SearchProviderRegistry {
|
|
3
4
|
private readonly providers;
|
|
4
5
|
private readonly fallbackProvider;
|
|
6
|
+
private readonly cache;
|
|
7
|
+
private readonly cachePrefix;
|
|
8
|
+
private readonly requestTimeoutMs;
|
|
9
|
+
private readonly retryDelayMs;
|
|
10
|
+
sourceRequests: number;
|
|
11
|
+
private readonly cooldowns;
|
|
5
12
|
constructor(config: ResolvedWebSearchConfig, options?: {
|
|
6
13
|
cloudProvider?: SearchProvider;
|
|
14
|
+
providers?: SearchProvider[];
|
|
15
|
+
fallbackProvider?: SearchProvider | null;
|
|
16
|
+
cache?: SearchCache;
|
|
17
|
+
requestTimeoutMs?: number;
|
|
18
|
+
retryDelayMs?: number;
|
|
7
19
|
});
|
|
8
20
|
private buildProviders;
|
|
9
21
|
hasConfiguredApiProvider(): boolean;
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
22
|
+
private key;
|
|
23
|
+
private cached;
|
|
24
|
+
private remember;
|
|
25
|
+
private attempt;
|
|
26
|
+
private backoff;
|
|
27
|
+
search(query: string, count: number, signal?: AbortSignal, authorize?: () => void): Promise<SearchResponse>;
|
|
28
|
+
/** Only unfinished, non-denied items degrade to sequential authorized searches. */
|
|
29
|
+
searchBatch(requests: SearchRequest[], signal?: AbortSignal, authorize?: () => void): Promise<SearchBatchResponse[]>;
|
|
14
30
|
}
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { createLogger } from "../../../utils/logger/index.js";
|
|
2
2
|
import { init_logger } from "../../../utils/logger.js";
|
|
3
|
+
import { executeWithTimeout } from "../../lifecycle/timeout-wrapper.js";
|
|
4
|
+
import { SearchError, classifySearchError, searchFailureInfo } from "./errors.js";
|
|
5
|
+
import { SearchCacheWriteError, currentSearchCache } from "./cache.js";
|
|
3
6
|
import { BraveProvider } from "./providers/brave.js";
|
|
4
7
|
import { TavilyProvider } from "./providers/tavily.js";
|
|
5
8
|
import { BingApiProvider } from "./providers/bing-api.js";
|
|
@@ -7,15 +10,40 @@ import { BingHtmlProvider } from "./providers/bing-html.js";
|
|
|
7
10
|
import { DuckDuckGoHtmlProvider } from "./providers/duckduckgo-html.js";
|
|
8
11
|
import { SearXNGProvider } from "./providers/searxng.js";
|
|
9
12
|
import { XopcCloudSearchProvider } from "./providers/xopc-cloud.js";
|
|
13
|
+
import { createHash } from "node:crypto";
|
|
14
|
+
import { setTimeout } from "node:timers/promises";
|
|
10
15
|
//#region src/agent/tools/search/registry.ts
|
|
11
16
|
init_logger();
|
|
12
17
|
const log = createLogger("Agent:WebSearch");
|
|
13
18
|
var SearchProviderRegistry = class {
|
|
14
19
|
providers;
|
|
15
20
|
fallbackProvider;
|
|
21
|
+
cache;
|
|
22
|
+
cachePrefix;
|
|
23
|
+
requestTimeoutMs;
|
|
24
|
+
retryDelayMs;
|
|
25
|
+
sourceRequests = 0;
|
|
26
|
+
cooldowns = /* @__PURE__ */ new Map();
|
|
16
27
|
constructor(config, options = {}) {
|
|
17
|
-
this.providers = this.buildProviders(config, options.cloudProvider);
|
|
18
|
-
this.fallbackProvider = config.region === "cn" ? new BingHtmlProvider() : new DuckDuckGoHtmlProvider();
|
|
28
|
+
this.providers = options.providers ?? this.buildProviders(config, options.cloudProvider);
|
|
29
|
+
this.fallbackProvider = options.fallbackProvider === void 0 ? config.region === "cn" ? new BingHtmlProvider() : new DuckDuckGoHtmlProvider() : options.fallbackProvider;
|
|
30
|
+
const cache = /* @__PURE__ */ new Map();
|
|
31
|
+
this.cache = options.cache ?? currentSearchCache() ?? {
|
|
32
|
+
get: (key) => cache.get(key),
|
|
33
|
+
set: (entry) => {
|
|
34
|
+
if (JSON.stringify(entry).length > 2e5) return;
|
|
35
|
+
cache.delete(entry.key);
|
|
36
|
+
cache.set(entry.key, entry);
|
|
37
|
+
while (cache.size > 20) cache.delete(cache.keys().next().value);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
this.cachePrefix = createHash("sha256").update(JSON.stringify([
|
|
41
|
+
config,
|
|
42
|
+
this.providers.map((provider) => [provider.name, provider.cacheIdentity?.()]),
|
|
43
|
+
this.fallbackProvider?.name
|
|
44
|
+
])).digest("hex");
|
|
45
|
+
this.requestTimeoutMs = options.requestTimeoutMs ?? 1e4;
|
|
46
|
+
this.retryDelayMs = options.retryDelayMs ?? 300;
|
|
19
47
|
log.debug({
|
|
20
48
|
region: config.region,
|
|
21
49
|
configuredCount: this.providers.filter((p) => p.isAvailable()).length
|
|
@@ -50,33 +78,232 @@ var SearchProviderRegistry = class {
|
|
|
50
78
|
hasConfiguredApiProvider() {
|
|
51
79
|
return this.providers.some((p) => p.isAvailable());
|
|
52
80
|
}
|
|
53
|
-
|
|
54
|
-
|
|
81
|
+
key(query, count) {
|
|
82
|
+
return createHash("sha256").update(JSON.stringify([
|
|
83
|
+
this.cachePrefix,
|
|
84
|
+
query.trim(),
|
|
85
|
+
count
|
|
86
|
+
])).digest("hex");
|
|
87
|
+
}
|
|
88
|
+
cached(query, count) {
|
|
89
|
+
const cached = this.cache.get(this.key(query, count));
|
|
90
|
+
return cached && Date.now() >= cached.capturedAt && Date.now() - cached.capturedAt < 6e5 ? {
|
|
91
|
+
results: structuredClone(cached.results),
|
|
92
|
+
provider: cached.provider,
|
|
93
|
+
cached: true,
|
|
94
|
+
capturedAt: cached.capturedAt
|
|
95
|
+
} : void 0;
|
|
96
|
+
}
|
|
97
|
+
remember(query, count, response) {
|
|
98
|
+
try {
|
|
99
|
+
this.cache.set({
|
|
100
|
+
key: this.key(query, count),
|
|
101
|
+
query: query.trim(),
|
|
102
|
+
count,
|
|
103
|
+
results: structuredClone(response.results),
|
|
104
|
+
provider: response.provider,
|
|
105
|
+
capturedAt: response.capturedAt ?? Date.now()
|
|
106
|
+
});
|
|
107
|
+
} catch (error) {
|
|
108
|
+
throw new SearchCacheWriteError(error);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
async attempt(name, signal, execute) {
|
|
112
|
+
signal?.throwIfAborted();
|
|
113
|
+
this.sourceRequests++;
|
|
114
|
+
const controller = new AbortController();
|
|
115
|
+
const abort = () => controller.abort(signal?.reason);
|
|
116
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
117
|
+
try {
|
|
118
|
+
return await executeWithTimeout(() => execute(controller.signal), {
|
|
119
|
+
toolName: `web_search:${name}`,
|
|
120
|
+
timeoutMs: this.requestTimeoutMs,
|
|
121
|
+
signal,
|
|
122
|
+
onTimeout: (error) => controller.abort(error)
|
|
123
|
+
});
|
|
124
|
+
} catch (error) {
|
|
125
|
+
throw classifySearchError(error, signal);
|
|
126
|
+
} finally {
|
|
127
|
+
signal?.removeEventListener("abort", abort);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
async backoff(error, signal) {
|
|
131
|
+
const waitMs = error.retryAfterMs ?? this.retryDelayMs;
|
|
132
|
+
if (waitMs > 2e3) return false;
|
|
133
|
+
await setTimeout(Math.max(0, waitMs), void 0, { signal });
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
async search(query, count, signal, authorize = () => {}) {
|
|
137
|
+
signal?.throwIfAborted();
|
|
138
|
+
authorize();
|
|
139
|
+
const hit = this.cached(query, count);
|
|
140
|
+
if (hit) return hit;
|
|
141
|
+
let lastError;
|
|
142
|
+
for (const provider of [...this.providers, ...this.fallbackProvider ? [this.fallbackProvider] : []]) {
|
|
143
|
+
signal?.throwIfAborted();
|
|
144
|
+
authorize();
|
|
55
145
|
if (!provider.isAvailable()) continue;
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
146
|
+
const cooldown = this.cooldowns.get(provider.name);
|
|
147
|
+
if (cooldown && cooldown.until > Date.now()) {
|
|
148
|
+
lastError = cooldown.error;
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
152
|
+
signal?.throwIfAborted();
|
|
153
|
+
authorize();
|
|
154
|
+
try {
|
|
155
|
+
const results = await this.attempt(provider.name, signal, (requestSignal) => provider.search(query, count, requestSignal));
|
|
156
|
+
signal?.throwIfAborted();
|
|
157
|
+
authorize();
|
|
158
|
+
const response = {
|
|
159
|
+
results,
|
|
160
|
+
provider: provider.name,
|
|
161
|
+
capturedAt: Date.now()
|
|
162
|
+
};
|
|
163
|
+
this.remember(query, count, response);
|
|
164
|
+
return response;
|
|
165
|
+
} catch (error) {
|
|
166
|
+
if (error instanceof SearchCacheWriteError) throw error;
|
|
167
|
+
const failure = classifySearchError(error, signal);
|
|
168
|
+
if (signal?.aborted || failure.kind === "permission_denied" || failure.kind === "cancelled") throw failure;
|
|
169
|
+
lastError = failure;
|
|
170
|
+
if (failure.kind === "rate_limited") this.cooldowns.set(provider.name, {
|
|
171
|
+
until: Date.now() + (failure.retryAfterMs ?? this.retryDelayMs),
|
|
172
|
+
error: failure
|
|
173
|
+
});
|
|
174
|
+
log.debug({
|
|
175
|
+
provider: provider.name,
|
|
176
|
+
kind: failure.kind,
|
|
177
|
+
attempt
|
|
178
|
+
}, "Search provider attempt failed");
|
|
179
|
+
if (attempt || !["rate_limited", "timeout"].includes(failure.kind) || !await this.backoff(failure, signal)) break;
|
|
180
|
+
}
|
|
66
181
|
}
|
|
67
182
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
183
|
+
throw lastError ?? new SearchError({
|
|
184
|
+
kind: "provider_error",
|
|
185
|
+
scope: "request",
|
|
186
|
+
message: "No search provider is available"
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
/** Only unfinished, non-denied items degrade to sequential authorized searches. */
|
|
190
|
+
async searchBatch(requests, signal, authorize = () => {}) {
|
|
191
|
+
if (!requests.length || requests.length > 8 || new Set(requests.map((request) => request.id)).size !== requests.length) throw new Error("Invalid search batch");
|
|
192
|
+
signal?.throwIfAborted();
|
|
193
|
+
authorize();
|
|
194
|
+
const outputs = /* @__PURE__ */ new Map();
|
|
195
|
+
const unique = /* @__PURE__ */ new Map();
|
|
196
|
+
for (const request of requests) {
|
|
197
|
+
const hit = this.cached(request.query, request.count);
|
|
198
|
+
if (hit) outputs.set(this.key(request.query, request.count), {
|
|
199
|
+
...request,
|
|
200
|
+
response: hit
|
|
201
|
+
});
|
|
202
|
+
else unique.set(this.key(request.query, request.count), request);
|
|
203
|
+
}
|
|
204
|
+
const pending = [...unique.values()];
|
|
205
|
+
const provider = this.providers.find((provider) => provider.isAvailable() && (this.cooldowns.get(provider.name)?.until ?? 0) <= Date.now());
|
|
206
|
+
const itemFailures = /* @__PURE__ */ new Map();
|
|
207
|
+
if (pending.length > 1 && provider?.searchBatch) try {
|
|
208
|
+
authorize();
|
|
209
|
+
const items = await this.attempt(provider.name, signal, (requestSignal) => provider.searchBatch(pending, requestSignal));
|
|
210
|
+
const ids = new Set(pending.map((request) => request.id));
|
|
211
|
+
if (items.some((item) => !ids.has(item.id)) || new Set(items.map((item) => item.id)).size !== items.length) throw new Error("Invalid batch search response ids");
|
|
212
|
+
signal?.throwIfAborted();
|
|
213
|
+
authorize();
|
|
214
|
+
let providerRefusal;
|
|
215
|
+
for (const request of pending) {
|
|
216
|
+
const item = items.find((item) => item.id === request.id);
|
|
217
|
+
if (item?.results && !item.error) {
|
|
218
|
+
const response = {
|
|
219
|
+
results: item.results,
|
|
220
|
+
provider: provider.name,
|
|
221
|
+
capturedAt: Date.now()
|
|
222
|
+
};
|
|
223
|
+
this.remember(request.query, request.count, response);
|
|
224
|
+
outputs.set(this.key(request.query, request.count), {
|
|
225
|
+
...request,
|
|
226
|
+
response
|
|
227
|
+
});
|
|
228
|
+
} else if (item?.error) {
|
|
229
|
+
if (item.error.kind === "permission_denied" && item.error.scope === "provider") providerRefusal = new SearchError(item.error);
|
|
230
|
+
if (["permission_denied", "cancelled"].includes(item.error.kind) && item.error.scope !== "batch") outputs.set(this.key(request.query, request.count), {
|
|
231
|
+
...request,
|
|
232
|
+
error: item.error
|
|
233
|
+
});
|
|
234
|
+
else {
|
|
235
|
+
const failure = new SearchError(item.error);
|
|
236
|
+
itemFailures.set(this.key(request.query, request.count), failure);
|
|
237
|
+
if (failure.kind === "rate_limited") this.cooldowns.set(provider.name, {
|
|
238
|
+
until: Date.now() + (failure.retryAfterMs ?? this.retryDelayMs),
|
|
239
|
+
error: failure
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
if (providerRefusal) for (const request of pending) {
|
|
245
|
+
const key = this.key(request.query, request.count);
|
|
246
|
+
if (!outputs.has(key)) outputs.set(key, {
|
|
247
|
+
...request,
|
|
248
|
+
error: searchFailureInfo(providerRefusal)
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
} catch (error) {
|
|
252
|
+
if (error instanceof SearchCacheWriteError) throw error;
|
|
253
|
+
const batchFailure = classifySearchError(error, signal);
|
|
254
|
+
if (signal?.aborted || batchFailure.kind === "permission_denied" && batchFailure.scope !== "batch" || batchFailure.kind === "cancelled") for (const request of pending) outputs.set(this.key(request.query, request.count), {
|
|
255
|
+
...request,
|
|
256
|
+
error: searchFailureInfo(batchFailure)
|
|
257
|
+
});
|
|
258
|
+
else if (batchFailure.kind === "rate_limited") {
|
|
259
|
+
this.cooldowns.set(provider.name, {
|
|
260
|
+
until: Date.now() + (batchFailure.retryAfterMs ?? this.retryDelayMs),
|
|
261
|
+
error: batchFailure
|
|
262
|
+
});
|
|
263
|
+
if (!await this.backoff(batchFailure, signal)) for (const request of pending) outputs.set(this.key(request.query, request.count), {
|
|
264
|
+
...request,
|
|
265
|
+
error: searchFailureInfo(batchFailure)
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
for (const request of pending) {
|
|
270
|
+
const key = this.key(request.query, request.count);
|
|
271
|
+
if (outputs.has(key)) continue;
|
|
272
|
+
try {
|
|
273
|
+
const itemFailure = itemFailures.get(key);
|
|
274
|
+
if (itemFailure?.kind === "rate_limited" && !await this.backoff(itemFailure, signal)) {
|
|
275
|
+
outputs.set(key, {
|
|
276
|
+
...request,
|
|
277
|
+
error: searchFailureInfo(itemFailure)
|
|
278
|
+
});
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
281
|
+
const response = await this.search(request.query, request.count, signal, authorize);
|
|
282
|
+
outputs.set(key, {
|
|
283
|
+
...request,
|
|
284
|
+
response,
|
|
285
|
+
...provider?.searchBatch ? { fallback: "individual" } : {}
|
|
286
|
+
});
|
|
287
|
+
} catch (error) {
|
|
288
|
+
const failure = classifySearchError(error, signal);
|
|
289
|
+
outputs.set(key, {
|
|
290
|
+
...request,
|
|
291
|
+
error: searchFailureInfo(failure),
|
|
292
|
+
...provider?.searchBatch ? { fallback: "individual" } : {}
|
|
293
|
+
});
|
|
294
|
+
if (failure.kind === "permission_denied" && failure.scope === "provider") for (const remaining of pending) {
|
|
295
|
+
const remainingKey = this.key(remaining.query, remaining.count);
|
|
296
|
+
if (!outputs.has(remainingKey)) outputs.set(remainingKey, {
|
|
297
|
+
...remaining,
|
|
298
|
+
error: searchFailureInfo(failure)
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
}
|
|
79
302
|
}
|
|
303
|
+
return requests.map((request) => ({
|
|
304
|
+
...outputs.get(this.key(request.query, request.count)),
|
|
305
|
+
...request
|
|
306
|
+
}));
|
|
80
307
|
}
|
|
81
308
|
};
|
|
82
309
|
//#endregion
|