one-code-extension 0.4.1 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -5
- package/agents/general-purpose.md +2 -2
- package/docs/guide/README.md +2 -1
- package/docs/guide/bring-your-claude-code-setup.md +60 -9
- package/docs/guide/configuration.md +48 -14
- package/docs/guide/differences-from-claude-code.md +32 -23
- package/docs/guide/doctor.md +20 -13
- package/docs/guide/hooks.md +18 -3
- package/docs/guide/installation.md +27 -4
- package/docs/guide/permissions-modes-and-auto-mode.md +225 -49
- package/docs/guide/providers-and-models.md +137 -54
- package/docs/guide/reference.md +27 -15
- package/docs/guide/sessions-and-context.md +69 -16
- package/docs/guide/skills-plugins-and-mcp.md +57 -20
- package/docs/guide/subagents-and-workflows.md +95 -29
- package/docs/guide/tasks-and-background-work.md +101 -26
- package/docs/guide/terminal-interface.md +24 -12
- package/docs/guide/tools.md +46 -5
- package/docs/guide/troubleshooting.md +13 -9
- package/docs/guide/windows.md +58 -31
- package/extensions/artifacts/gallery.ts +227 -0
- package/extensions/artifacts/index.ts +288 -0
- package/extensions/artifacts/store.ts +570 -0
- package/extensions/artifacts/viewer.ts +199 -0
- package/extensions/ask-user/description.ts +67 -0
- package/extensions/ask-user/dialogs.ts +80 -0
- package/extensions/ask-user/index.ts +37 -33
- package/extensions/ask-user/questions.ts +32 -7
- package/extensions/ask-user/widget.ts +45 -25
- package/extensions/auto-mode/actions.ts +6 -0
- package/extensions/auto-mode/cache.ts +88 -0
- package/extensions/auto-mode/classifier-prompt.ts +5 -4
- package/extensions/auto-mode/classifier.ts +290 -65
- package/extensions/auto-mode/config.ts +72 -20
- package/extensions/auto-mode/decision-log.ts +2 -0
- package/extensions/auto-mode/defaults.ts +1 -1
- package/extensions/auto-mode/denials.ts +123 -0
- package/extensions/auto-mode/git-checkout-programs.ts +187 -0
- package/extensions/auto-mode/git-status-meta.ts +159 -0
- package/extensions/auto-mode/history.ts +189 -0
- package/extensions/auto-mode/instructions.ts +58 -52
- package/extensions/auto-mode/model-select.ts +159 -134
- package/extensions/auto-mode/paths.ts +57 -5
- package/extensions/auto-mode/pause.ts +57 -2
- package/extensions/auto-mode/prompt.ts +37 -21
- package/extensions/auto-mode/read-only-options.ts +724 -0
- package/extensions/auto-mode/resolved-paths-meta.ts +133 -0
- package/extensions/auto-mode/rules.ts +18 -1
- package/extensions/auto-mode/safety-floor.ts +361 -18
- package/extensions/auto-mode/setup-run.ts +20 -14
- package/extensions/auto-mode/shell-analysis.ts +764 -490
- package/extensions/auto-mode/shell-parse.ts +885 -0
- package/extensions/auto-mode/system-note.ts +33 -0
- package/extensions/auto-mode/transcript.ts +80 -51
- package/extensions/background/cron.ts +515 -0
- package/extensions/background/index.ts +514 -236
- package/extensions/background/loop-fire.ts +136 -0
- package/extensions/background/loop-preamble.ts +59 -0
- package/extensions/background/loop-skill.ts +163 -0
- package/extensions/background/monitor-batch.ts +53 -0
- package/extensions/background/noop-fold.ts +128 -0
- package/extensions/background/switch-guard.ts +52 -0
- package/extensions/background/wakeup.ts +159 -55
- package/extensions/bash/background.ts +53 -17
- package/extensions/bash/child.ts +7 -0
- package/extensions/bash/description.ts +188 -0
- package/extensions/bash/guards.ts +15 -67
- package/extensions/bash/index.ts +88 -35
- package/extensions/branding/compaction-keep.ts +63 -0
- package/extensions/branding/declined.ts +22 -0
- package/extensions/branding/index.ts +133 -9
- package/extensions/branding/prompt-editor.ts +53 -6
- package/extensions/branding/queued-edit.ts +31 -0
- package/extensions/branding/replaced-builtins.ts +68 -0
- package/extensions/branding/startup.ts +33 -71
- package/extensions/btw/index.ts +229 -71
- package/extensions/btw/panel.ts +228 -100
- package/extensions/btw/prompt.ts +41 -4
- package/extensions/claude-compat/index.ts +7 -11
- package/extensions/claude-context/index.ts +446 -58
- package/extensions/compaction/cut.ts +157 -0
- package/extensions/compaction/index.ts +216 -75
- package/extensions/compaction/kept-thinking.ts +62 -0
- package/extensions/compaction/prompt.ts +15 -12
- package/extensions/context-budget/index.ts +24 -5
- package/extensions/context-management/index.ts +25 -0
- package/extensions/doctor/build.ts +59 -8
- package/extensions/doctor/builtins.ts +84 -0
- package/extensions/doctor/cli.ts +7 -1
- package/extensions/doctor/compat.ts +88 -23
- package/extensions/doctor/dependencies.ts +41 -5
- package/extensions/doctor/fix-prompt.ts +9 -2
- package/extensions/doctor/index.ts +102 -58
- package/extensions/doctor/models.ts +102 -63
- package/extensions/doctor/presets.ts +42 -27
- package/extensions/doctor/report.ts +19 -2
- package/extensions/doctor/viewer.ts +49 -27
- package/extensions/effort/index.ts +72 -19
- package/extensions/effort/settings.ts +21 -0
- package/extensions/effort/slider.ts +24 -20
- package/extensions/exit/index.ts +32 -1
- package/extensions/file-tracker/index.ts +337 -26
- package/extensions/file-tracker/replay.ts +96 -10
- package/extensions/file-tracker/restore.ts +63 -0
- package/extensions/file-tracker/results.ts +66 -0
- package/extensions/file-tracker/shell-reads.ts +211 -0
- package/extensions/file-tracker/tracker.ts +32 -15
- package/extensions/footer/footer-line.ts +45 -24
- package/extensions/footer/index.ts +47 -2
- package/extensions/footer/pr.ts +34 -5
- package/extensions/hooks/compaction.ts +7 -0
- package/extensions/hooks/executor.ts +32 -30
- package/extensions/hooks/index.ts +198 -31
- package/extensions/hooks/matcher.ts +3 -0
- package/extensions/hooks/protocol.ts +41 -3
- package/extensions/hooks/settings.ts +50 -19
- package/extensions/hooks/subagent-bridge.ts +2 -0
- package/extensions/hooks/trust.ts +13 -5
- package/extensions/idle-compact/index.ts +119 -0
- package/extensions/idle-compact/policy.ts +143 -0
- package/extensions/init/index.ts +11 -10
- package/extensions/init/prompt.ts +25 -32
- package/extensions/interrupted/index.ts +13 -1
- package/extensions/kept-thinking/index.ts +27 -0
- package/extensions/lib/agent-cron.ts +130 -0
- package/extensions/lib/agent-loader.ts +15 -1
- package/extensions/lib/agent-view.ts +85 -0
- package/extensions/lib/anthropic-payload.ts +82 -1
- package/extensions/lib/anthropic-server-call.ts +230 -0
- package/extensions/lib/anthropic-server-tools.ts +485 -0
- package/extensions/lib/app-launch.mjs +94 -0
- package/extensions/lib/argument-hints.ts +190 -0
- package/extensions/lib/atomic-write.ts +6 -2
- package/extensions/lib/auto-memory.ts +75 -0
- package/extensions/lib/bash-parser.ts +84 -0
- package/extensions/lib/btw-fork.ts +93 -0
- package/extensions/lib/child-extensions.ts +42 -0
- package/extensions/lib/child-writes.ts +45 -0
- package/extensions/lib/claude-context.ts +515 -100
- package/extensions/lib/claude-external-includes.ts +68 -0
- package/extensions/lib/claude-id.ts +22 -0
- package/extensions/lib/claude-rules.ts +323 -0
- package/extensions/lib/claude-settings.ts +76 -4
- package/extensions/lib/compaction-boundary.ts +36 -0
- package/extensions/lib/compaction-keep.mjs +83 -0
- package/extensions/lib/config-mode.ts +112 -0
- package/extensions/lib/consent-dialogs.ts +63 -0
- package/extensions/lib/consent-preview.ts +5 -1
- package/extensions/lib/consent-stores.ts +48 -0
- package/extensions/lib/context-facts.ts +78 -0
- package/extensions/lib/context-stack.ts +81 -0
- package/extensions/lib/deferred.ts +219 -25
- package/extensions/lib/dock-rows.ts +60 -0
- package/extensions/lib/empty-tool-result.ts +27 -0
- package/extensions/lib/environment-block.ts +76 -0
- package/extensions/lib/fetch-timeout.ts +24 -6
- package/extensions/{system-prompt → lib}/git-status.ts +51 -13
- package/extensions/lib/git.ts +75 -13
- package/extensions/lib/grammars/LICENSE-tree-sitter-bash +21 -0
- package/extensions/lib/grammars/README.md +2 -0
- package/extensions/lib/grammars/tree-sitter-bash.wasm +0 -0
- package/extensions/lib/guide-docs.ts +45 -0
- package/extensions/lib/headless-output.ts +46 -0
- package/extensions/lib/hook-gate.ts +7 -7
- package/extensions/lib/interrupt.ts +20 -4
- package/extensions/lib/key-input.ts +455 -0
- package/extensions/lib/keys.ts +14 -1
- package/extensions/lib/local-command.ts +47 -13
- package/extensions/lib/mcp-overrides.ts +1 -1
- package/extensions/lib/mcp-share.ts +46 -0
- package/extensions/lib/memory-content.ts +146 -0
- package/extensions/lib/memory-index.ts +18 -0
- package/extensions/lib/memory.ts +134 -123
- package/extensions/lib/model-catalog/huggingface.json +174 -0
- package/extensions/lib/model-catalog/models-dev.json +18297 -0
- package/extensions/lib/model-catalog/openrouter.json +5252 -0
- package/extensions/lib/model-catalog-data.ts +513 -0
- package/extensions/lib/model-catalog.ts +462 -0
- package/extensions/lib/model-choice-warnings.ts +137 -0
- package/extensions/lib/model-pairings.ts +152 -0
- package/extensions/{auto-mode → lib}/model-picker.ts +26 -43
- package/extensions/lib/model-policy.ts +112 -15
- package/extensions/lib/model-tier.ts +283 -355
- package/extensions/lib/model-unusable.ts +1 -1
- package/extensions/lib/newer-model.ts +100 -0
- package/extensions/lib/notices.ts +87 -0
- package/extensions/lib/notifications.ts +308 -64
- package/extensions/lib/one-code-settings.ts +74 -8
- package/extensions/lib/open-browser.ts +63 -0
- package/extensions/lib/openrouter-generation.ts +94 -0
- package/extensions/lib/operation-deadline.ts +30 -0
- package/extensions/lib/original-command.ts +4 -4
- package/extensions/lib/package-version.ts +1 -1
- package/extensions/lib/panel-focus.ts +134 -0
- package/extensions/lib/paths.ts +17 -2
- package/extensions/lib/permission-gate.ts +72 -6
- package/extensions/lib/persisted-output.ts +60 -8
- package/extensions/lib/pi-ai-estimate.ts +19 -3
- package/extensions/lib/pi-version.ts +21 -2
- package/extensions/lib/plan-mode-channels.ts +20 -5
- package/extensions/lib/plugins.ts +26 -17
- package/extensions/lib/powershell-parse-script.ts +208 -0
- package/extensions/lib/powershell-parser.ts +344 -0
- package/extensions/lib/process-tree.ts +131 -8
- package/extensions/lib/prompt-options.ts +82 -0
- package/extensions/lib/queued-delivery.ts +44 -0
- package/extensions/lib/reminders.ts +364 -58
- package/extensions/lib/replaced-builtins.mjs +199 -0
- package/extensions/lib/replay-call.ts +70 -0
- package/extensions/lib/request-replay.ts +439 -0
- package/extensions/lib/scratchpad.ts +81 -14
- package/extensions/lib/send-now-channel.ts +7 -0
- package/extensions/lib/session-model-tier.ts +51 -0
- package/extensions/lib/session-work.ts +64 -0
- package/extensions/lib/settings-channels.ts +4 -4
- package/extensions/lib/shell-exit-semantics.ts +170 -0
- package/extensions/lib/shell-quote.ts +11 -0
- package/extensions/lib/shell-result.ts +70 -0
- package/extensions/lib/shell-spawn.ts +70 -6
- package/extensions/lib/shell-tool.ts +75 -25
- package/extensions/lib/side-call-cache.ts +124 -0
- package/extensions/lib/side-call-run.ts +96 -0
- package/extensions/lib/side-call-usage.ts +38 -0
- package/extensions/lib/side-call.ts +13 -1
- package/extensions/lib/single-flight.ts +18 -0
- package/extensions/lib/skill-body.ts +42 -0
- package/extensions/lib/skill-scan.ts +63 -24
- package/extensions/lib/spill-file.ts +140 -0
- package/extensions/lib/subagent-handback.ts +113 -0
- package/extensions/lib/system-role.ts +156 -0
- package/extensions/lib/terminal-text.ts +114 -0
- package/extensions/lib/text-width.ts +47 -16
- package/extensions/lib/timer-ops.ts +20 -0
- package/extensions/lib/tool-additions.ts +149 -0
- package/extensions/lib/tool-call-corruption.ts +134 -0
- package/extensions/lib/tool-path.ts +66 -0
- package/extensions/lib/tool-variants.ts +69 -0
- package/extensions/lib/tui-render.ts +255 -27
- package/extensions/lib/turn-budget-layout.ts +143 -0
- package/extensions/lib/update-check.mjs +15 -2
- package/extensions/lib/usage-bus.ts +1 -1
- package/extensions/lib/user-denial.ts +38 -0
- package/extensions/lib/workspace-channel.ts +16 -0
- package/extensions/lib/worktree-channel.ts +31 -3
- package/extensions/lib/worktree-isolation.ts +40 -10
- package/extensions/lsp/client.ts +8 -9
- package/extensions/lsp/index.ts +70 -4
- package/extensions/lsp/keep-alive.ts +1 -1
- package/extensions/lsp/trust.ts +119 -0
- package/extensions/lsp/watcher.ts +1 -1
- package/extensions/mcp/announce.ts +87 -5
- package/extensions/mcp/client.ts +161 -48
- package/extensions/mcp/config.ts +132 -8
- package/extensions/mcp/discovery.ts +19 -0
- package/extensions/mcp/index.ts +209 -71
- package/extensions/mcp/oauth/browser.ts +38 -17
- package/extensions/mcp/oauth/flow.ts +25 -17
- package/extensions/mcp/oauth/provider.ts +14 -8
- package/extensions/mcp/oauth/store.ts +34 -16
- package/extensions/mcp/panel/keys.ts +12 -11
- package/extensions/mcp/panel/render.ts +9 -6
- package/extensions/mcp/schema.ts +104 -5
- package/extensions/mcp/trust.ts +151 -67
- package/extensions/memory/entries.ts +23 -14
- package/extensions/memory/index.ts +134 -28
- package/extensions/memory/open-external.ts +33 -18
- package/extensions/memory/panel.ts +135 -26
- package/extensions/newer-model/index.ts +31 -0
- package/extensions/notebook/description.ts +18 -0
- package/extensions/notebook/index.ts +6 -5
- package/extensions/notebook/notebook.ts +1 -1
- package/extensions/permissions/index.ts +1300 -381
- package/extensions/permissions/matcher.ts +425 -74
- package/extensions/permissions/modes.ts +6 -4
- package/extensions/permissions/panel/host.ts +130 -0
- package/extensions/permissions/panel/keys.ts +48 -0
- package/extensions/permissions/panel/render.ts +450 -0
- package/extensions/permissions/panel/state.ts +550 -0
- package/extensions/permissions/powershell-cmdlets.ts +198 -0
- package/extensions/permissions/powershell-paths.ts +197 -0
- package/extensions/permissions/powershell-rules.ts +207 -280
- package/extensions/permissions/powershell-tree.ts +557 -0
- package/extensions/permissions/project-trust.ts +27 -11
- package/extensions/permissions/protected-paths.ts +14 -2
- package/extensions/permissions/session-grant.ts +6 -0
- package/extensions/permissions/settings.ts +166 -31
- package/extensions/permissions/subagent-gate.ts +25 -0
- package/extensions/permissions/workspace.ts +78 -0
- package/extensions/plan-mode/index.ts +156 -69
- package/extensions/plan-mode/reminder.ts +89 -21
- package/extensions/plan-mode/texts.ts +177 -0
- package/extensions/plan-mode/viewer.ts +92 -27
- package/extensions/plugins/counts.ts +3 -3
- package/extensions/plugins/index.ts +61 -46
- package/extensions/plugins/install/install.ts +16 -0
- package/extensions/plugins/marketplace/sync.ts +9 -3
- package/extensions/plugins/marketplace/types.ts +3 -1
- package/extensions/plugins/panel/keys.ts +21 -24
- package/extensions/plugins/panel/render.ts +12 -6
- package/extensions/plugins/shell-expand.ts +107 -0
- package/extensions/plugins/template.ts +32 -15
- package/extensions/powershell/child.ts +7 -0
- package/extensions/powershell/description.ts +38 -6
- package/extensions/powershell/guards.ts +1 -1
- package/extensions/powershell/index.ts +12 -7
- package/extensions/recap/index.ts +65 -60
- package/extensions/recap/prompt.ts +2 -2
- package/extensions/recap/scheduler.ts +3 -5
- package/extensions/search-tools/grep-merge.ts +39 -0
- package/extensions/search-tools/index.ts +0 -0
- package/extensions/send-now/chord.ts +62 -0
- package/extensions/send-now/index.ts +133 -0
- package/extensions/session-title/index.ts +3 -0
- package/extensions/session-title/title.ts +4 -1
- package/extensions/skill/code-review.ts +97 -0
- package/extensions/skill/index.ts +421 -101
- package/extensions/skill/invoke.ts +128 -18
- package/extensions/skill/listing.ts +133 -9
- package/extensions/skill/panel/keys.ts +15 -16
- package/extensions/skill/panel/render.ts +5 -2
- package/extensions/skill/panel/state.ts +12 -1
- package/extensions/subagents/agent-args.ts +39 -0
- package/extensions/subagents/agent-description.ts +132 -0
- package/extensions/subagents/agents.ts +116 -23
- package/extensions/subagents/announce.ts +69 -0
- package/extensions/subagents/default-model.ts +2 -3
- package/extensions/subagents/delegation-steer.ts +4 -4
- package/extensions/subagents/fork-cache.ts +76 -0
- package/extensions/subagents/guide-agent.ts +189 -0
- package/extensions/subagents/hand-back-review.ts +3 -0
- package/extensions/subagents/index.ts +1493 -824
- package/extensions/subagents/live-runs.ts +26 -6
- package/extensions/subagents/model-select.ts +38 -25
- package/extensions/subagents/outcome.ts +2 -2
- package/extensions/subagents/panel-keys.ts +73 -31
- package/extensions/subagents/panel-render.ts +120 -96
- package/extensions/subagents/panel-widget.ts +60 -89
- package/extensions/subagents/runner.ts +108 -80
- package/extensions/subagents/runs.ts +39 -5
- package/extensions/subagents/send-to-main-tool.ts +1 -1
- package/extensions/subagents/session-turns.ts +32 -8
- package/extensions/subagents/shell-panel.ts +36 -178
- package/extensions/subagents/tasks-dialog.ts +170 -0
- package/extensions/subagents/viewport-mirror.ts +95 -0
- package/extensions/subagents/worktree.ts +57 -13
- package/extensions/system-prompt/environment.ts +19 -19
- package/extensions/system-prompt/idle-turn.ts +65 -0
- package/extensions/system-prompt/index.ts +131 -47
- package/extensions/system-prompt/template.ts +24 -50
- package/extensions/system-prompt/tiers/common.ts +23 -64
- package/extensions/system-prompt/tiers/frontier.ts +32 -11
- package/extensions/system-prompt/tiers/low.ts +67 -45
- package/extensions/system-prompt/tiers/mid.ts +96 -38
- package/extensions/system-reminder/index.ts +175 -13
- package/extensions/tasks/descriptions.ts +190 -0
- package/extensions/tasks/index.ts +120 -41
- package/extensions/tasks/store.ts +83 -16
- package/extensions/tool-call-corruption/child.ts +7 -0
- package/extensions/tool-call-corruption/index.ts +237 -0
- package/extensions/tool-search/announce.ts +16 -0
- package/extensions/tool-search/index.ts +189 -41
- package/extensions/tool-style/index.ts +68 -10
- package/extensions/tool-style/write-description.ts +40 -0
- package/extensions/tool-style/write-render.ts +237 -0
- package/extensions/turn-duration/index.ts +7 -3
- package/extensions/turn-duration/line.ts +47 -4
- package/extensions/web/backends.ts +6 -9
- package/extensions/web/description.ts +50 -0
- package/extensions/web/index.ts +118 -53
- package/extensions/web/native-call.ts +15 -0
- package/extensions/web/native-results.ts +41 -0
- package/extensions/web-fetch/description.ts +42 -0
- package/extensions/web-fetch/extract.ts +53 -6
- package/extensions/web-fetch/index.ts +125 -76
- package/extensions/web-fetch/page-content.ts +19 -0
- package/extensions/web-fetch/response.ts +49 -0
- package/extensions/web-fetch/summarize.ts +9 -10
- package/extensions/workflow/agent-session.ts +105 -18
- package/extensions/workflow/args.ts +24 -0
- package/extensions/workflow/description.ts +67 -0
- package/extensions/workflow/globals.ts +17 -3
- package/extensions/workflow/index.ts +145 -162
- package/extensions/workflow/records.ts +1 -0
- package/extensions/workflow/run-manager.ts +29 -6
- package/extensions/workflow/saved-workflows.ts +3 -2
- package/extensions/workflow/types.ts +22 -1
- package/extensions/workflow/viewer.ts +54 -24
- package/extensions/workflow/widget.ts +20 -0
- package/extensions/worktree/descriptions.ts +91 -0
- package/extensions/worktree/guards.ts +316 -54
- package/extensions/worktree/index.ts +121 -39
- package/extensions/worktree/powershell-guards.ts +167 -0
- package/extensions/worktree/rewrite.ts +14 -16
- package/package.json +18 -7
- package/skills/artifact-design/SKILL.md +85 -0
- package/skills/code-review/SKILL.md +20 -20
- package/skills/code-review/cells/arguments-and-flags.md +19 -0
- package/skills/code-review/cells/high-3x5-recall-verify.md +137 -0
- package/skills/code-review/cells/high-8-inline.md +122 -0
- package/skills/code-review/cells/low-1-pass-max4.md +27 -0
- package/skills/code-review/cells/low-1-pass-min-files-4.md +28 -0
- package/skills/code-review/cells/max-5x5-verify-sweep.md +162 -0
- package/skills/code-review/cells/medium-3x5-verify.md +132 -0
- package/skills/code-review/cells/minimal-prompt.md +32 -0
- package/skills/code-review/cells/xhigh-10-inline-sweep.md +149 -0
- package/skills/code-review/cells/xhigh-5x5-verify-sweep.md +162 -0
- package/skills/fewer-permission-prompts/SKILL.md +1 -1
- package/skills/loop/SKILL.md +11 -0
- package/skills/security-review/SKILL.md +2 -5
- package/skills/simplify/SKILL.md +7 -5
- package/skills/workflow-authoring/SKILL.md +143 -0
- package/themes/onecode-light.json +1 -1
- package/themes/onecode.json +1 -1
- package/types/app.d.ts +58 -0
- package/types/community-packages.d.ts +26 -1
- package/extensions/auto-mode/recoverability.ts +0 -229
- package/extensions/lib/capability-index.ts +0 -466
- package/extensions/lib/model-facts.generated.json +0 -2675
- package/extensions/lib/model-facts.ts +0 -162
package/extensions/web/index.ts
CHANGED
|
@@ -4,6 +4,11 @@
|
|
|
4
4
|
* `web_search` is registered here with Claude Code's schema (`query`,
|
|
5
5
|
* `allowed_domains`, `blocked_domains`) and routes by what the session can do:
|
|
6
6
|
*
|
|
7
|
+
* 0. Anthropic's server-side `web_search` with dynamic filtering, in our own
|
|
8
|
+
* nested call, on a first-party Anthropic API-key session with a Claude
|
|
9
|
+
* 4.6+ model available (lib/anthropic-server-tools.ts). pi-web-search's
|
|
10
|
+
* Anthropic call uses the older `web_search_20250305`, so it is the next
|
|
11
|
+
* step down, not this one.
|
|
7
12
|
* 1. Provider-native search through the community `pi-web-search` package
|
|
8
13
|
* when the current model's provider has one (Anthropic, OpenAI/Codex,
|
|
9
14
|
* Gemini, xAI) — no third-party key, and as close to Claude Code's
|
|
@@ -23,18 +28,24 @@
|
|
|
23
28
|
* WebFetch lives in `extensions/web-fetch` (our own); this file only owns search.
|
|
24
29
|
*/
|
|
25
30
|
|
|
26
|
-
import type
|
|
31
|
+
import { defineTool, type ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
27
32
|
import { homedir } from "node:os";
|
|
28
33
|
import { Type } from "typebox";
|
|
29
34
|
import webSearchPackage from "pi-web-search/src/index.ts";
|
|
30
|
-
import { getProviderKind } from "pi-web-search/src/api.ts";
|
|
31
|
-
import { webSearch as nativeWebSearch } from "pi-web-search/src/web_search.ts";
|
|
35
|
+
import { callApiStream, getConfig, getProviderKind } from "pi-web-search/src/api.ts";
|
|
32
36
|
import { getWebSearchModel } from "pi-web-search/src/utils.ts";
|
|
37
|
+
import { tryNativeWeb } from "../lib/anthropic-server-call.ts";
|
|
38
|
+
import { CUT_OFF_NOTE, nativeSearchBody, searchOutcome, searchSources, sourceLine, type ThinkingFields } from "../lib/anthropic-server-tools.ts";
|
|
33
39
|
import { readJsonFile } from "../lib/atomic-write.ts";
|
|
34
|
-
import {
|
|
40
|
+
import { recordUsage } from "../lib/usage-bus.ts";
|
|
41
|
+
import { DEFER_CHANNEL, WITHHOLD_CHANNEL } from "../lib/deferred.ts";
|
|
35
42
|
import { oneCodeSettingsPath } from "../lib/one-code-settings.ts";
|
|
36
43
|
import { persistIfLarge, sessionResultsDir } from "../lib/persisted-output.ts";
|
|
37
44
|
import { ccToolRenderers } from "../lib/tui-render.ts";
|
|
45
|
+
import { registerFormTool } from "../lib/tool-variants.ts";
|
|
46
|
+
import { webSearchDescription } from "./description.ts";
|
|
47
|
+
import { formatNativeSearchResult } from "./native-results.ts";
|
|
48
|
+
import { runNativeSearch } from "./native-call.ts";
|
|
38
49
|
import {
|
|
39
50
|
DEFAULT_MAX_RESULTS,
|
|
40
51
|
formatSearchResults,
|
|
@@ -68,16 +79,15 @@ export default function webExtension(pi: ExtensionAPI) {
|
|
|
68
79
|
|
|
69
80
|
// Once per session: the user is told the first time a search goes keyless.
|
|
70
81
|
let keylessNoticeShown = false;
|
|
82
|
+
// Per-session: the thinking-off fields a native-call model turned out to need.
|
|
83
|
+
const learnedNativeThinking = new Map<string, ThinkingFields>();
|
|
71
84
|
|
|
72
|
-
|
|
85
|
+
// Short or long text by the model's tier (description.ts, lib/tool-variants.ts).
|
|
86
|
+
const tool = defineTool({
|
|
73
87
|
name: "web_search",
|
|
74
88
|
label: "Web Search",
|
|
75
89
|
...ccToolRenderers<{ query?: string }>("Web Search", { title: (args) => args?.query }),
|
|
76
|
-
description:
|
|
77
|
-
"Search the web. Returns result blocks with titles and URLs.\n\n" +
|
|
78
|
-
"- `allowed_domains` / `blocked_domains` filter results (enforced on Brave/Tavily/Exa; with provider-native search they become `site:` operators in the query, best-effort).\n" +
|
|
79
|
-
'- After answering from results, end with a "Sources:" list of the URLs you used as markdown links.\n' +
|
|
80
|
-
"- Uses the model provider's own search when it has one; otherwise a configured search API (Brave, Tavily) or, with no key, a free rate-limited endpoint — the result names which.",
|
|
90
|
+
description: webSearchDescription("short"),
|
|
81
91
|
parameters: Type.Object({
|
|
82
92
|
query: Type.String({ minLength: 2, description: "The search query to use" }),
|
|
83
93
|
allowed_domains: Type.Optional(Type.Array(Type.String(), { description: "Only include search results from these domains" })),
|
|
@@ -86,37 +96,90 @@ export default function webExtension(pi: ExtensionAPI) {
|
|
|
86
96
|
async execute(toolCallId, params, signal, onUpdate, ctx) {
|
|
87
97
|
const filters = { allowed: params.allowed_domains, blocked: params.blocked_domains };
|
|
88
98
|
|
|
89
|
-
//
|
|
90
|
-
//
|
|
91
|
-
// it
|
|
92
|
-
//
|
|
93
|
-
//
|
|
99
|
+
// 0. Anthropic's server-side search with dynamic filtering, on a
|
|
100
|
+
// first-party Anthropic API-key session. The domain filters are enforced
|
|
101
|
+
// by the search itself; it takes one list or the other, so a call with
|
|
102
|
+
// both goes down the paths below. Anything short of an answer from real
|
|
103
|
+
// results falls through, with a note saying why.
|
|
104
|
+
let nativeNote: string | undefined;
|
|
105
|
+
const bothFilters = Boolean(params.allowed_domains?.length && params.blocked_domains?.length);
|
|
106
|
+
if (!bothFilters) {
|
|
107
|
+
const native = await tryNativeWeb(ctx, {
|
|
108
|
+
body: (model) =>
|
|
109
|
+
nativeSearchBody({ model, query: params.query, allowedDomains: params.allowed_domains, blockedDomains: params.blocked_domains }),
|
|
110
|
+
outcome: searchOutcome,
|
|
111
|
+
onUsage: (usage) => recordUsage(pi, "web-search", usage),
|
|
112
|
+
learned: learnedNativeThinking,
|
|
113
|
+
onStart: () =>
|
|
114
|
+
onUpdate?.({
|
|
115
|
+
content: [{ type: "text", text: `Searching with Anthropic's web search for "${params.query}"...` }],
|
|
116
|
+
details: { query: params.query } as SearchDetails,
|
|
117
|
+
}),
|
|
118
|
+
signal,
|
|
119
|
+
});
|
|
120
|
+
if (native.kind === "cancelled") {
|
|
121
|
+
return {
|
|
122
|
+
content: [{ type: "text", text: `Search for "${params.query}" was cancelled.` }],
|
|
123
|
+
details: { query: params.query } as SearchDetails,
|
|
124
|
+
isError: true,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
if (native.kind === "answered") {
|
|
128
|
+
const sources = searchSources(native.result);
|
|
129
|
+
const filtered = Boolean(params.allowed_domains?.length || params.blocked_domains?.length);
|
|
130
|
+
const text = [
|
|
131
|
+
native.text,
|
|
132
|
+
...(native.cutOff ? ["", CUT_OFF_NOTE] : []),
|
|
133
|
+
"",
|
|
134
|
+
"Sources:",
|
|
135
|
+
...sources.map(sourceLine),
|
|
136
|
+
"",
|
|
137
|
+
`(Searched with Anthropic's server-side web search with dynamic filtering, on ${native.spec}${filtered ? "; the domain filters were enforced by the search" : ""}.)`,
|
|
138
|
+
].join("\n");
|
|
139
|
+
return {
|
|
140
|
+
content: [{ type: "text", text: persistIfLarge(text, { dir: sessionResultsDir(ctx), id: toolCallId }) }],
|
|
141
|
+
details: { query: params.query, native: true, backend: `anthropic web_search via ${native.spec}`, resultCount: sources.length } as SearchDetails,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
if (native.kind === "fell-back") {
|
|
145
|
+
nativeNote = `(Anthropic's server-side web search did not answer (${native.reason}); the results below come from the next search route.)`;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
const withNativeNote = (text: string) => nativeNote ? `${text}\n\n${nativeNote}` : text;
|
|
149
|
+
|
|
150
|
+
// 1. Reuse pi-web-search's transport, not its tool wrapper: that wrapper
|
|
151
|
+
// truncates before we can persist the full answer and its sources.
|
|
94
152
|
const nativeModel = await getWebSearchModel(ctx);
|
|
95
153
|
if (nativeModel) {
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
? [
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
: result.
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
154
|
+
const backend = `${nativeModel.provider}/${nativeModel.id}`;
|
|
155
|
+
try {
|
|
156
|
+
const config = getConfig(nativeModel);
|
|
157
|
+
const response = await runNativeSearch((nativeSignal) => callApiStream(ctx, nativeModel, {
|
|
158
|
+
contents: [{ role: "user", parts: [{ text: withSiteOperators(params.query, filters) }] }],
|
|
159
|
+
...(config.kind === "google" ? { tools: [{ [config.searchTool!]: {} }] } : {}),
|
|
160
|
+
}, (update) => { if (!nativeSignal.aborted) onUpdate?.(update); }, nativeSignal), signal);
|
|
161
|
+
const result = formatNativeSearchResult(response);
|
|
162
|
+
// This route returns a synthesized answer, not rows we can filter.
|
|
163
|
+
const hasFilters = Boolean(params.allowed_domains?.length || params.blocked_domains?.length);
|
|
164
|
+
const text = withNativeNote([
|
|
165
|
+
result.text,
|
|
166
|
+
`(Searched with ${backend}.)`,
|
|
167
|
+
...(hasFilters ? ["(Domain filters were applied as `site:` operators in the query — best-effort with provider-native search; verify the sources' hosts.)"] : []),
|
|
168
|
+
].join("\n\n"));
|
|
169
|
+
return {
|
|
170
|
+
content: [{ type: "text", text: persistIfLarge(text, { dir: sessionResultsDir(ctx), id: toolCallId }) }],
|
|
171
|
+
details: { ...result.details, query: params.query, native: true, backend } as SearchDetails,
|
|
172
|
+
};
|
|
173
|
+
} catch (error) {
|
|
174
|
+
if (signal?.aborted) {
|
|
175
|
+
return {
|
|
176
|
+
content: [{ type: "text", text: `Search for "${params.query}" was cancelled.` }],
|
|
177
|
+
details: { query: params.query } as SearchDetails,
|
|
178
|
+
isError: true,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
nativeNote = withNativeNote(`(Provider-native search on ${backend} failed (${error instanceof Error ? error.message : String(error)}); the next search route was tried.)`);
|
|
182
|
+
}
|
|
120
183
|
}
|
|
121
184
|
|
|
122
185
|
// 2. Third-party chain.
|
|
@@ -133,7 +196,7 @@ export default function webExtension(pi: ExtensionAPI) {
|
|
|
133
196
|
}
|
|
134
197
|
// Bounded by result count, but a provider's error body (quoted in
|
|
135
198
|
// "Fell back after") or long snippets can still be large: persist, never slice.
|
|
136
|
-
const text = persistIfLarge(formatSearchResults(params.query, outcome), { dir: sessionResultsDir(ctx), id: toolCallId });
|
|
199
|
+
const text = persistIfLarge(withNativeNote(formatSearchResults(params.query, outcome)), { dir: sessionResultsDir(ctx), id: toolCallId });
|
|
137
200
|
return {
|
|
138
201
|
content: [{ type: "text", text }],
|
|
139
202
|
details: {
|
|
@@ -147,7 +210,7 @@ export default function webExtension(pi: ExtensionAPI) {
|
|
|
147
210
|
} catch (error) {
|
|
148
211
|
const message = (error as Error).message;
|
|
149
212
|
const text = persistIfLarge(
|
|
150
|
-
`web_search failed on ${ctx.model?.provider}/${ctx.model?.id}
|
|
213
|
+
withNativeNote(`web_search failed on ${ctx.model?.provider}/${ctx.model?.id}.\n${message}`),
|
|
151
214
|
{ dir: sessionResultsDir(ctx), id: toolCallId },
|
|
152
215
|
);
|
|
153
216
|
return {
|
|
@@ -158,6 +221,7 @@ export default function webExtension(pi: ExtensionAPI) {
|
|
|
158
221
|
}
|
|
159
222
|
},
|
|
160
223
|
});
|
|
224
|
+
registerFormTool(pi, tool, webSearchDescription);
|
|
161
225
|
|
|
162
226
|
// url_context reports failures ("Failed: …" text, details.error set) without
|
|
163
227
|
// isError. Stamp it here rather than patching the vendor package.
|
|
@@ -179,20 +243,21 @@ export default function webExtension(pi: ExtensionAPI) {
|
|
|
179
243
|
// still advertised it in the names-only reminder (and as a defer_loading
|
|
180
244
|
// definition) on every provider, so a non-Gemini model would load it and burn
|
|
181
245
|
// two rounds discovering it cannot run (TOOL-FIDELITY-REVIEW-2026-09-07 M5).
|
|
182
|
-
// Mirror
|
|
183
|
-
//
|
|
184
|
-
//
|
|
185
|
-
//
|
|
186
|
-
// no-op once url_context is already registered).
|
|
246
|
+
// Mirror that gate in the deferred registry too: withdraw on leaving Gemini
|
|
247
|
+
// and re-defer on returning. Emit only when availability changes, since a
|
|
248
|
+
// repeated defer would deactivate a tool already loaded through tool_search.
|
|
249
|
+
// The registry owns announcements and keeps the cached listing frozen.
|
|
187
250
|
let urlContextDeferred = false;
|
|
188
251
|
const deferUrlContextIfGemini = (model: { provider?: string; api?: string } | undefined) => {
|
|
189
|
-
//
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
if (
|
|
194
|
-
|
|
195
|
-
|
|
252
|
+
// session_start can run before a model resolves; getProviderKind requires one.
|
|
253
|
+
const available = !!model && getProviderKind(model) === "google";
|
|
254
|
+
if (available === urlContextDeferred) return;
|
|
255
|
+
urlContextDeferred = available;
|
|
256
|
+
if (available) {
|
|
257
|
+
pi.events.emit(DEFER_CHANNEL, { name: "url_context", keywords: ["url", "analyze page", "gemini"] });
|
|
258
|
+
} else {
|
|
259
|
+
pi.events.emit(WITHHOLD_CHANNEL, { name: "url_context" });
|
|
260
|
+
}
|
|
196
261
|
};
|
|
197
262
|
pi.on("session_start", (_event, ctx) => deferUrlContextIfGemini(ctx.model));
|
|
198
263
|
pi.on("session_tree", (_event, ctx) => deferUrlContextIfGemini(ctx.model));
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { runWithDeadline } from "../lib/operation-deadline.ts";
|
|
2
|
+
|
|
3
|
+
/** Bound the provider-native transport, including auth, streamed bodies and source resolution. */
|
|
4
|
+
export const NATIVE_SEARCH_TIMEOUT_MS = 120_000;
|
|
5
|
+
|
|
6
|
+
export async function runNativeSearch<T>(
|
|
7
|
+
call: (signal: AbortSignal) => Promise<T>,
|
|
8
|
+
signal: AbortSignal | undefined,
|
|
9
|
+
): Promise<T> {
|
|
10
|
+
return runWithDeadline(call, {
|
|
11
|
+
signal,
|
|
12
|
+
timeoutMs: NATIVE_SEARCH_TIMEOUT_MS,
|
|
13
|
+
message: "Provider-native search did not answer within 120 seconds.",
|
|
14
|
+
});
|
|
15
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/** Provider-native search formatting without pi-web-search's lossy truncateHead wrapper. */
|
|
2
|
+
import { applyCitations, type StreamResult } from "pi-web-search/src/api.ts";
|
|
3
|
+
|
|
4
|
+
export function formatNativeSearchResult(result: StreamResult) {
|
|
5
|
+
if (result.nativeSearchUsed === false) throw new Error("The provider returned a response without performing a web search.");
|
|
6
|
+
// The vendor substitutes this placeholder when the stream contains no answer text.
|
|
7
|
+
if (!result.text.trim() || result.text.trim() === "No answer available.") throw new Error("The provider returned an empty web-search answer.");
|
|
8
|
+
const cited = applyCitations(result.text, result.groundingMetadata);
|
|
9
|
+
const sources = result.sources?.length ? result.sources : cited.sources;
|
|
10
|
+
const failedSearch = result.searchResults?.find((item) => item.type === "web_search_tool_result_error");
|
|
11
|
+
if (failedSearch) throw new Error(`The provider's search returned ${failedSearch.status ?? "an unspecified tool error"}.`);
|
|
12
|
+
if (!sources.length && !result.searchResults?.some((item) => item.url)) throw new Error("The provider returned no search results.");
|
|
13
|
+
const seen = new Set<string>();
|
|
14
|
+
const extraResults = (result.searchResults ?? []).filter((item) => {
|
|
15
|
+
if (!item.url || sources.some((source) => source.url === item.url)) return false;
|
|
16
|
+
const key = `${item.title ?? ""}\t${item.url}`;
|
|
17
|
+
if (seen.has(key)) return false;
|
|
18
|
+
seen.add(key);
|
|
19
|
+
return true;
|
|
20
|
+
});
|
|
21
|
+
const parts = [cited.text];
|
|
22
|
+
if (sources.length) parts.push(`## Sources\n${sources.map((source, i) => `${i + 1}. [${source.title}](${source.url})`).join("\n")}`);
|
|
23
|
+
if (extraResults.length) {
|
|
24
|
+
parts.push(`## Additional Search Results\n${extraResults.map((item, i) => `${i + 1}. ${item.title || item.url} - ${item.url}`).join("\n")}`);
|
|
25
|
+
}
|
|
26
|
+
return {
|
|
27
|
+
text: parts.join("\n\n"),
|
|
28
|
+
details: {
|
|
29
|
+
sources,
|
|
30
|
+
providerKind: result.providerKind,
|
|
31
|
+
nativeSearchUsed: result.nativeSearchUsed,
|
|
32
|
+
nativeSearchEvents: result.nativeSearchEvents,
|
|
33
|
+
nativeSearchCalls: result.nativeSearchCalls,
|
|
34
|
+
searchQueries: result.searchQueries ?? result.groundingMetadata?.webSearchQueries,
|
|
35
|
+
searchResults: result.searchResults,
|
|
36
|
+
citations: result.citations,
|
|
37
|
+
grounded: sources.length > 0 || (result.searchResults?.length ?? 0) > 0,
|
|
38
|
+
resultCount: result.searchResults?.length || sources.length,
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* web_fetch's model-facing description (pure).
|
|
3
|
+
*
|
|
4
|
+
* The short form, on frontier and workhorse models, is One Code's own: it
|
|
5
|
+
* describes what this fetch does that Claude Code's does not (an optional
|
|
6
|
+
* `prompt`, windowed pages, Anthropic's server-side fetch). The long form, on
|
|
7
|
+
* cheap and tiny models (lib/tool-variants.ts), is the text Claude Code writes
|
|
8
|
+
* for its weakest models, with One Code's tool names, without the claude.ai
|
|
9
|
+
* artifact line, with `prompt` optional and with the paging note in place of
|
|
10
|
+
* "Results may be summarized".
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type { DescriptionForm } from "../lib/tool-variants.ts";
|
|
14
|
+
|
|
15
|
+
export const WEB_FETCH_SHORT_DESCRIPTION =
|
|
16
|
+
"Fetch a URL and return its readable content as markdown. Navigation and boilerplate are stripped. Pass `prompt` to have a small fast model answer it from the full page instead of returning the page itself — prefer that for long pages (on an Anthropic API-key session, Anthropic's server-side fetch answers it). Without `prompt`, long pages are windowed; pass `offset` to continue reading. Responses are cached for 15 minutes. Cross-host redirects are reported instead of followed; call again with the new URL to follow one.";
|
|
17
|
+
|
|
18
|
+
export const WEB_FETCH_LONG_DESCRIPTION = `IMPORTANT: web_fetch WILL FAIL for authenticated or private URLs. Before using this tool, check if the URL points to an authenticated service (e.g. Google Docs, Confluence, Jira, GitHub). If so, look for a specialized MCP tool that provides authenticated access.
|
|
19
|
+
|
|
20
|
+
- Fetches content from a specified URL and processes it using an AI model
|
|
21
|
+
- Takes a URL and an optional prompt as input
|
|
22
|
+
- Fetches the URL content, converts HTML to markdown
|
|
23
|
+
- Processes the content with the prompt using a small, fast model
|
|
24
|
+
- Returns the model's response about the content
|
|
25
|
+
- Use this tool when you need to retrieve and analyze web content
|
|
26
|
+
|
|
27
|
+
Usage notes:
|
|
28
|
+
- IMPORTANT: If an MCP-provided web fetch tool is available, prefer using that tool instead of this one, as it may have fewer restrictions.
|
|
29
|
+
- The URL must be a fully-formed valid URL
|
|
30
|
+
- HTTP URLs will be automatically upgraded to HTTPS
|
|
31
|
+
- localhost and other hostnames without a dot are not supported; for a local server, use curl via bash
|
|
32
|
+
- The prompt should describe what information you want to extract from the page
|
|
33
|
+
- This tool is read-only and does not modify any files
|
|
34
|
+
- Without a prompt, a long page is returned in windows; pass \`offset\` to continue reading
|
|
35
|
+
- Includes a self-cleaning cache (entries expire after 15 minutes) for faster responses when repeatedly accessing the same URL
|
|
36
|
+
- When a URL redirects to a different host, the tool will inform you and provide the redirect URL in a special format. You should then make a new web_fetch request with the redirect URL to fetch the content.
|
|
37
|
+
- For GitHub URLs, prefer using the gh CLI via bash instead (e.g., gh pr view, gh issue view, gh api).
|
|
38
|
+
`;
|
|
39
|
+
|
|
40
|
+
export function webFetchDescription(form: DescriptionForm): string {
|
|
41
|
+
return form === "long" ? WEB_FETCH_LONG_DESCRIPTION : WEB_FETCH_SHORT_DESCRIPTION;
|
|
42
|
+
}
|
|
@@ -47,19 +47,25 @@ export function normalizeUrl(input: string): NormalizedUrl {
|
|
|
47
47
|
throw new Error(`Not a valid URL: ${input}`);
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
51
|
+
throw new Error(`Unsupported URL scheme "${parsed.protocol}" — only http and https are fetched.`);
|
|
52
|
+
}
|
|
53
|
+
if (parsed.username || parsed.password) throw new Error("URLs with embedded credentials are not supported.");
|
|
54
|
+
if (!parsed.hostname.replace(/\.+$/, "").includes(".")) {
|
|
55
|
+
throw new Error("web_fetch cannot fetch localhost or other hostnames without a dot. To reach a local server, use bash with curl instead.");
|
|
56
|
+
}
|
|
50
57
|
if (parsed.protocol === "http:") {
|
|
51
58
|
parsed.protocol = "https:";
|
|
52
59
|
return { url: parsed.toString(), note: "Upgraded http to https." };
|
|
53
60
|
}
|
|
54
|
-
if (parsed.protocol !== "https:") {
|
|
55
|
-
throw new Error(`Unsupported URL scheme "${parsed.protocol}" — only http and https are fetched.`);
|
|
56
|
-
}
|
|
57
61
|
return { url: parsed.toString() };
|
|
58
62
|
}
|
|
59
63
|
|
|
60
64
|
export function isSameHost(a: string, b: string): boolean {
|
|
61
65
|
try {
|
|
62
|
-
|
|
66
|
+
const target = new URL(a);
|
|
67
|
+
const source = new URL(b);
|
|
68
|
+
return target.host === source.host && target.protocol === source.protocol && !target.username && !target.password;
|
|
63
69
|
} catch {
|
|
64
70
|
return false;
|
|
65
71
|
}
|
|
@@ -88,11 +94,22 @@ export interface ExtractResult {
|
|
|
88
94
|
* first (it strips navigation and boilerplate); if it finds no article — common
|
|
89
95
|
* for API references and landing pages — the whole body is converted instead.
|
|
90
96
|
*/
|
|
91
|
-
export async function htmlToMarkdown(html: string,
|
|
97
|
+
export async function htmlToMarkdown(html: string, url: string): Promise<ExtractResult> {
|
|
92
98
|
const { Readability, parseHTML, Turndown } = await loadLibs();
|
|
93
99
|
const { document } = parseHTML(html);
|
|
94
100
|
const turndown = createTurndown(Turndown);
|
|
101
|
+
// linkedom does not resolve relative attributes against the fetched URL.
|
|
102
|
+
let base = url;
|
|
103
|
+
try { base = new URL(document.querySelector("base[href]")?.getAttribute("href") ?? url, url).href; } catch { /* use the fetched URL */ }
|
|
104
|
+
for (const [selector, attribute] of [["a[href]", "href"], ["img[src]", "src"]]) {
|
|
105
|
+
for (const element of document.querySelectorAll(selector)) {
|
|
106
|
+
try { element.setAttribute(attribute, new URL(element.getAttribute(attribute)!, base).href); } catch { /* keep malformed links as written */ }
|
|
107
|
+
}
|
|
108
|
+
}
|
|
95
109
|
|
|
110
|
+
// Readability mutates the document, and linkedom can create an empty body
|
|
111
|
+
// for a fragment. Keep the original content for the whole-page fallback.
|
|
112
|
+
const body = document.querySelector("body")?.innerHTML ?? document.toString();
|
|
96
113
|
// linkedom's Document is structurally compatible with what Readability needs
|
|
97
114
|
// but is not the DOM lib's Document type, hence the cast.
|
|
98
115
|
let article: { title?: string | null; content?: string | null } | null = null;
|
|
@@ -110,7 +127,6 @@ export async function htmlToMarkdown(html: string, _url: string): Promise<Extrac
|
|
|
110
127
|
};
|
|
111
128
|
}
|
|
112
129
|
|
|
113
|
-
const body = document.body?.innerHTML ?? html;
|
|
114
130
|
return {
|
|
115
131
|
title: document.title || undefined,
|
|
116
132
|
markdown: turndown.turndown(body).trim(),
|
|
@@ -137,3 +153,34 @@ export function paginate(text: string, offset: number, maxChars: number): Page {
|
|
|
137
153
|
totalChars: text.length,
|
|
138
154
|
};
|
|
139
155
|
}
|
|
156
|
+
|
|
157
|
+
/** A redirect to another host: reported, not followed (Claude Code's rule). Not a failure. */
|
|
158
|
+
export class CrossHostRedirect extends Error {
|
|
159
|
+
constructor(
|
|
160
|
+
readonly redirectUrl: string,
|
|
161
|
+
readonly status: number,
|
|
162
|
+
) {
|
|
163
|
+
super(`Redirects to a different host: ${redirectUrl}`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Claude Code's WebFetch redirect result, with our tool name, and the prompt
|
|
169
|
+
* line only when the call had a prompt (ours is optional). Before it, the
|
|
170
|
+
* redirect read as a fetch failure ("Verify the URL and host, retry, or use
|
|
171
|
+
* web_search instead"), advice that does not apply.
|
|
172
|
+
*/
|
|
173
|
+
export function redirectMessage(originalUrl: string, redirect: CrossHostRedirect, prompt: string | undefined): string {
|
|
174
|
+
const statusText = redirect.status === 301 ? "Moved Permanently" : redirect.status === 308 ? "Permanent Redirect" : redirect.status === 307 ? "Temporary Redirect" : "Found";
|
|
175
|
+
return [
|
|
176
|
+
"REDIRECT DETECTED: The URL redirects to a different host.",
|
|
177
|
+
"",
|
|
178
|
+
`Original URL: ${originalUrl}`,
|
|
179
|
+
`Redirect URL: ${redirect.redirectUrl}`,
|
|
180
|
+
`Status: ${redirect.status} ${statusText}`,
|
|
181
|
+
"",
|
|
182
|
+
"To complete your request, I need to fetch content from the redirected URL. Please use web_fetch again with these parameters:",
|
|
183
|
+
`- url: "${redirect.redirectUrl}"`,
|
|
184
|
+
...(prompt ? [`- prompt: "${prompt}"`] : []),
|
|
185
|
+
].join("\n");
|
|
186
|
+
}
|