@xiaobuyu/nodesign 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +661 -0
- package/README.md +140 -0
- package/bin/nodesign.js +176 -0
- package/package.json +104 -0
- package/server/api/README.md +45 -0
- package/server/api/_guard.js +75 -0
- package/server/api/admin.js +187 -0
- package/server/api/assets/docx-page.js +103 -0
- package/server/api/assets/helpers.js +22 -0
- package/server/api/assets/notes.js +144 -0
- package/server/api/assets.js +896 -0
- package/server/api/board.js +57 -0
- package/server/api/browse.js +138 -0
- package/server/api/canvas.js +444 -0
- package/server/api/chatai.js +324 -0
- package/server/api/exports/build-standalone.js +980 -0
- package/server/api/exports/cards.js +118 -0
- package/server/api/exports/docx-pdf.js +46 -0
- package/server/api/exports/export-page.js +142 -0
- package/server/api/exports/handoff.js +147 -0
- package/server/api/exports.js +690 -0
- package/server/api/instruction.js +75 -0
- package/server/api/local.js +115 -0
- package/server/api/me.js +116 -0
- package/server/api/memory.js +185 -0
- package/server/api/pending-changes.js +366 -0
- package/server/api/plugins.js +164 -0
- package/server/api/projects.js +220 -0
- package/server/api/publish.js +64 -0
- package/server/api/recent.js +73 -0
- package/server/api/sessions.js +589 -0
- package/server/api/skills.js +77 -0
- package/server/api/standalone-fit.js +168 -0
- package/server/api/turn-compose.js +180 -0
- package/server/api/turn-inflight.js +44 -0
- package/server/api/turn.js +669 -0
- package/server/auth/README.md +17 -0
- package/server/auth/middleware.js +138 -0
- package/server/auth/origin-guard.js +71 -0
- package/server/auth/session.js +108 -0
- package/server/auth/tier.js +113 -0
- package/server/auth/users-store.js +348 -0
- package/server/edit/README.md +29 -0
- package/server/engine/README.md +113 -0
- package/server/engine/agent/agent-shared.js +550 -0
- package/server/engine/agent/auto-mode-default-hard-deny.txt +4 -0
- package/server/engine/agent/auto-mode-rules.js +78 -0
- package/server/engine/agent/context.js +418 -0
- package/server/engine/agent/events.js +464 -0
- package/server/engine/agent/hooks/canvas-validate.js +199 -0
- package/server/engine/agent/hooks/failure.js +120 -0
- package/server/engine/agent/hooks/file-events.js +75 -0
- package/server/engine/agent/hooks/lifecycle.js +233 -0
- package/server/engine/agent/hooks/post-canvas-focus.js +80 -0
- package/server/engine/agent/hooks/post-guidance.js +206 -0
- package/server/engine/agent/hooks/post-subagent-report.js +104 -0
- package/server/engine/agent/hooks/post-trim.js +58 -0
- package/server/engine/agent/hooks/pre-board-neighborhood.js +34 -0
- package/server/engine/agent/hooks/pre-defaults.js +84 -0
- package/server/engine/agent/hooks/pre-injectors.js +277 -0
- package/server/engine/agent/hooks/pre-performance-log-guard.js +78 -0
- package/server/engine/agent/hooks/pre-starter-files.js +61 -0
- package/server/engine/agent/hooks/pre-workspace-scope-guard.js +78 -0
- package/server/engine/agent/hooks/site-validate.js +94 -0
- package/server/engine/agent/hooks/tool-prompts.js +31 -0
- package/server/engine/agent/hooks/turn-state-memory.js +56 -0
- package/server/engine/agent/hooks/user-prompt-submit.js +215 -0
- package/server/engine/agent/hooks.js +354 -0
- package/server/engine/agent/init-contract.js +69 -0
- package/server/engine/agent/isolation.js +159 -0
- package/server/engine/agent/model-context.js +359 -0
- package/server/engine/agent/model-table.js +292 -0
- package/server/engine/agent/plugin-loader.js +266 -0
- package/server/engine/agent/prompts/nodesign-prelude.md +336 -0
- package/server/engine/agent/prompts/tools/ask-user-question-protocol.md +71 -0
- package/server/engine/agent/prompts/tools/direct-edit-protocol.md +98 -0
- package/server/engine/agent/prompts/tools/generate-image-cookbook.gemini-gateway.md +583 -0
- package/server/engine/agent/prompts/tools/generate-image-cookbook.md +483 -0
- package/server/engine/agent/prompts/tools/hybrid-reference.md +100 -0
- package/server/engine/agent/prompts/tools/paint-still-cookbook.md +270 -0
- package/server/engine/agent/prompts/tools/roll-film-cookbook.md +70 -0
- package/server/engine/agent/prompts/tools/site-reference.md +106 -0
- package/server/engine/agent/prompts/tools/tweaks-syntax.md +125 -0
- package/server/engine/agent/prompts/tools/vision-checker-dispatch.md +48 -0
- package/server/engine/agent/session-loop.js +1024 -0
- package/server/engine/agent/session-model.js +164 -0
- package/server/engine/agent/skill.js +204 -0
- package/server/engine/agent/system-prompts.js +86 -0
- package/server/engine/agent/task-events.js +78 -0
- package/server/engine/agents/ds-extractor.md +132 -0
- package/server/engine/agents/explorer.md +190 -0
- package/server/engine/agents/index.js +230 -0
- package/server/engine/agents/schemas/design-system.json +156 -0
- package/server/engine/agents/schemas/tweak-schema.json +113 -0
- package/server/engine/agents/tweak-proposer.md +127 -0
- package/server/engine/agents/vision-checker.md +254 -0
- package/server/engine/browse/capture.js +422 -0
- package/server/engine/browse/card.js +137 -0
- package/server/engine/browse/handover.js +59 -0
- package/server/engine/browse/page-digest.js +119 -0
- package/server/engine/browse/refs.js +196 -0
- package/server/engine/browse/registry.js +307 -0
- package/server/engine/browse/screencast.js +170 -0
- package/server/engine/browse/state.js +121 -0
- package/server/engine/chatai/chat-log.js +79 -0
- package/server/engine/chatai/index.js +193 -0
- package/server/engine/chatai/openai-compat.js +152 -0
- package/server/engine/chatai/orchestrate.js +309 -0
- package/server/engine/chatai/perform.js +40 -0
- package/server/engine/chatai/summarize.js +79 -0
- package/server/engine/mcp/capability-gate.js +52 -0
- package/server/engine/mcp/index.js +339 -0
- package/server/engine/mcp/param-sanitizer.js +142 -0
- package/server/engine/mcp/tools/arrange-on-board.js +124 -0
- package/server/engine/mcp/tools/artifact-session.js +278 -0
- package/server/engine/mcp/tools/browse-computer.js +421 -0
- package/server/engine/mcp/tools/browse-find-batch.js +183 -0
- package/server/engine/mcp/tools/browse-screenshot.js +157 -0
- package/server/engine/mcp/tools/browse.js +480 -0
- package/server/engine/mcp/tools/build-docx.js +101 -0
- package/server/engine/mcp/tools/clear-pending-changes.js +99 -0
- package/server/engine/mcp/tools/create-on-board.js +155 -0
- package/server/engine/mcp/tools/crystallize-skill.js +168 -0
- package/server/engine/mcp/tools/deliver-files.js +0 -0
- package/server/engine/mcp/tools/explain-style.js +237 -0
- package/server/engine/mcp/tools/export-handoff.js +133 -0
- package/server/engine/mcp/tools/expose-tweaks.js +149 -0
- package/server/engine/mcp/tools/generate-image.js +842 -0
- package/server/engine/mcp/tools/get-computed-styles.js +164 -0
- package/server/engine/mcp/tools/get-pending-changes.js +205 -0
- package/server/engine/mcp/tools/h3box-ssh.js +84 -0
- package/server/engine/mcp/tools/helpers/acquire-page.js +82 -0
- package/server/engine/mcp/tools/helpers/motion-lab.js +554 -0
- package/server/engine/mcp/tools/helpers/motion-scroll.js +112 -0
- package/server/engine/mcp/tools/helpers/perception-page.js +215 -0
- package/server/engine/mcp/tools/helpers/reference-download.js +103 -0
- package/server/engine/mcp/tools/helpers/rembg-bridge.py +80 -0
- package/server/engine/mcp/tools/helpers/rembg.js +273 -0
- package/server/engine/mcp/tools/helpers/shot-pipeline.js +280 -0
- package/server/engine/mcp/tools/highlight.js +68 -0
- package/server/engine/mcp/tools/list-pages.js +222 -0
- package/server/engine/mcp/tools/lookup-tags.js +59 -0
- package/server/engine/mcp/tools/navigate-to-page.js +60 -0
- package/server/engine/mcp/tools/organize-board.js +64 -0
- package/server/engine/mcp/tools/paint-still.js +434 -0
- package/server/engine/mcp/tools/pin-to-board.js +130 -0
- package/server/engine/mcp/tools/preview-deck.js +130 -0
- package/server/engine/mcp/tools/profile-scroll.js +305 -0
- package/server/engine/mcp/tools/publish-site.js +102 -0
- package/server/engine/mcp/tools/query-elements.js +195 -0
- package/server/engine/mcp/tools/read-board.js +103 -0
- package/server/engine/mcp/tools/read-document.js +93 -0
- package/server/engine/mcp/tools/read-page.js +264 -0
- package/server/engine/mcp/tools/read-tavern-json.js +143 -0
- package/server/engine/mcp/tools/record-decision.js +140 -0
- package/server/engine/mcp/tools/relate-on-board.js +110 -0
- package/server/engine/mcp/tools/remove-background.js +391 -0
- package/server/engine/mcp/tools/report-issue.js +115 -0
- package/server/engine/mcp/tools/roll-film.js +266 -0
- package/server/engine/mcp/tools/screenshot-docx.js +138 -0
- package/server/engine/mcp/tools/screenshot-url.js +195 -0
- package/server/engine/mcp/tools/screenshot.js +583 -0
- package/server/engine/mcp/tools/tier-gate.js +96 -0
- package/server/engine/mcp/tools/trace-motion.js +215 -0
- package/server/engine/mcp/tools/web-search.js +548 -0
- package/server/engine/motion/inventory.js +349 -0
- package/server/engine/perception/session.js +235 -0
- package/server/engine/plugins/nodesign/.claude-plugin/plugin.json +5 -0
- package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/SKILL.md +192 -0
- package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/canvas.template.html +667 -0
- package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/hybrid-grid.md +22 -0
- package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/image-led-cover.md +25 -0
- package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/portrait.md +23 -0
- package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/quote-backdrop.md +21 -0
- package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/section-divider.md +23 -0
- package/server/engine/plugins/nodesign/skills/deskskill-engine-mini/patterns/text-led.md +21 -0
- package/server/engine/plugins/nodesign/skills/docx-craft/SKILL.md +162 -0
- package/server/engine/plugins/nodesign/skills/docx-craft/references/token-schema.md +407 -0
- package/server/engine/plugins/nodesign/skills/docx-craft//346/226/207/346/241/243.template.json +80 -0
- package/server/engine/plugins/nodesign/skills/rp-craft/SKILL.md +228 -0
- package/server/engine/plugins/nodesign/skills/rp-craft/patterns//346/274/224/345/207/272/351/241/265-/346/234/200/345/260/217/345/256/236/347/216/260.html +78 -0
- package/server/engine/plugins/nodesign/skills/rp-craft//346/274/224/345/207/272.template.js +268 -0
- package/server/engine/plugins/nodesign/skills/site-craft/SKILL.md +323 -0
- package/server/engine/plugins/nodesign/skills/site-craft/patterns/build-lane.md +134 -0
- package/server/engine/plugins/nodesign/skills/site-craft/patterns/cutout-collage.md +61 -0
- package/server/engine/plugins/nodesign/skills/site-craft/patterns/mock-app.md +163 -0
- package/server/engine/plugins/nodesign/skills/site-craft/patterns/page-transitions.md +86 -0
- package/server/engine/runs/active-runs.js +698 -0
- package/server/engine/runs/live-turn.js +252 -0
- package/server/engine/runs/store.js +364 -0
- package/server/engine/runs/turn-relay.js +217 -0
- package/server/engine/runtime/workspace.js +164 -0
- package/server/index.js +235 -0
- package/server/lib/artifact-file-path.js +80 -0
- package/server/lib/artifact-target.js +284 -0
- package/server/lib/asset-refs.js +239 -0
- package/server/lib/async-queue.js +104 -0
- package/server/lib/auto-relations.js +130 -0
- package/server/lib/binding-types.js +60 -0
- package/server/lib/board-kind-sizes.js +62 -0
- package/server/lib/board-relations.js +133 -0
- package/server/lib/browse-proxy.js +180 -0
- package/server/lib/canvas-id.js +36 -0
- package/server/lib/cover.js +227 -0
- package/server/lib/danbooru-tags.js +291 -0
- package/server/lib/doc-extract.js +156 -0
- package/server/lib/docx/build-from-source.js +260 -0
- package/server/lib/docx/build.js +490 -0
- package/server/lib/docx/dump-styles.js +287 -0
- package/server/lib/docx/fonts/nodesign-cjk.conf +164 -0
- package/server/lib/docx/merge-runs.js +129 -0
- package/server/lib/docx/numbering.js +218 -0
- package/server/lib/docx/order.js +152 -0
- package/server/lib/docx/rawzip.js +172 -0
- package/server/lib/docx/render.js +92 -0
- package/server/lib/docx/text-lint.js +225 -0
- package/server/lib/docx/tokens.js +335 -0
- package/server/lib/docx/units.js +29 -0
- package/server/lib/docx/xml.js +291 -0
- package/server/lib/docx-pages.js +187 -0
- package/server/lib/export-collect.js +289 -0
- package/server/lib/export-package.js +210 -0
- package/server/lib/html-srcset.js +145 -0
- package/server/lib/image-variant.js +460 -0
- package/server/lib/ingress/forward-openai-chat.js +305 -0
- package/server/lib/ingress/openai-chat.js +491 -0
- package/server/lib/ingress/session-notice.js +66 -0
- package/server/lib/ingress/session-routes.js +68 -0
- package/server/lib/ingress/slot-probe.js +130 -0
- package/server/lib/ingress/upstream-billing.js +52 -0
- package/server/lib/ingress/upstream-fail-streak.js +72 -0
- package/server/lib/ingress/upstream-truncation.js +48 -0
- package/server/lib/issues-store.js +182 -0
- package/server/lib/kinds/deck.js +72 -0
- package/server/lib/kinds/docx.js +283 -0
- package/server/lib/kinds/file-kinds.js +113 -0
- package/server/lib/kinds/index.js +202 -0
- package/server/lib/kinds/site.js +161 -0
- package/server/lib/model-ingress.js +543 -0
- package/server/lib/moderation.js +255 -0
- package/server/lib/notice-store.js +103 -0
- package/server/lib/plugin-install.js +104 -0
- package/server/lib/plugin-validator.js +721 -0
- package/server/lib/publish-store.js +115 -0
- package/server/lib/quick-summary.js +52 -0
- package/server/lib/quota.js +270 -0
- package/server/lib/rate-window.js +29 -0
- package/server/lib/reference-assets.js +92 -0
- package/server/lib/region-shot.js +135 -0
- package/server/lib/safe-path.js +73 -0
- package/server/lib/sdk-session.js +33 -0
- package/server/lib/showcase-store.js +92 -0
- package/server/lib/site-publish.js +465 -0
- package/server/lib/ssrf-guard.js +432 -0
- package/server/lib/task-scan.js +158 -0
- package/server/lib/tavern-json.js +154 -0
- package/server/lib/video-variant.js +237 -0
- package/server/lib/workspace-path.js +33 -0
- package/server/ops/fix-sdk-musl.mjs +40 -0
- package/server/ops/install-macos-fonts.sh +114 -0
- package/server/ops/macos-fonts.conf +336 -0
- package/server/ops/sandbox-shim/bwrap +57 -0
- package/server/projects/assets-summary.js +119 -0
- package/server/projects/auto-name.js +53 -0
- package/server/projects/board-store.js +640 -0
- package/server/projects/move-entry.js +103 -0
- package/server/projects/store.js +270 -0
- package/server/projects/ui-config.js +54 -0
- package/server/projects/workspace-templates.js +53 -0
- package/server/projects/workspace.js +1025 -0
- package/server/runtime/capabilities.js +153 -0
- package/server/runtime/local-config.js +177 -0
- package/server/runtime/local-env.js +88 -0
- package/server/runtime/platform.js +342 -0
- package/server/runtime/profile.js +72 -0
- package/server/services/rembg-launcher.js +313 -0
- package/server/services/rembg-service.py +334 -0
- package/server/shared/README.md +25 -0
- package/server/shared/deck.js +54 -0
- package/server/shared/time.js +49 -0
- package/server/style-pipeline/README.md +53 -0
- package/server/ws/broker.js +36 -0
- package/server/ws/browse-channel.js +177 -0
- package/server/ws/index.js +386 -0
- package/web/README.md +89 -0
- package/web/dist/assets/BrowserWindow-Dc7J7CYc.js +11 -0
- package/web/dist/assets/DeckWindow-4G0xX7P3.js +16 -0
- package/web/dist/assets/DocxWindow-BXQsoPkh.js +16 -0
- package/web/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- package/web/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- package/web/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- package/web/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- package/web/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- package/web/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- package/web/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- package/web/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- package/web/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- package/web/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- package/web/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- package/web/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- package/web/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- package/web/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- package/web/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- package/web/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- package/web/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- package/web/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- package/web/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- package/web/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- package/web/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- package/web/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- package/web/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- package/web/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- package/web/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- package/web/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- package/web/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- package/web/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- package/web/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- package/web/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- package/web/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- package/web/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- package/web/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- package/web/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- package/web/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- package/web/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- package/web/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- package/web/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- package/web/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- package/web/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- package/web/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- package/web/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- package/web/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- package/web/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- package/web/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- package/web/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- package/web/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- package/web/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- package/web/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- package/web/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- package/web/dist/assets/SiteWindow-D6eS-2gM.js +18 -0
- package/web/dist/assets/again-D8tfgKKO.webp +0 -0
- package/web/dist/assets/caveat-nd-CP6HlsNg.woff2 +0 -0
- package/web/dist/assets/clock-xJhvfYpS.webp +0 -0
- package/web/dist/assets/film-cut-BBKMwH7q.webp +0 -0
- package/web/dist/assets/film-still-BZ_DL9Tt.webp +0 -0
- package/web/dist/assets/index-C0KUf6Wd.css +1 -0
- package/web/dist/assets/index-Cbl3ATvp.js +2125 -0
- package/web/dist/assets/ink-desk-BRbxeDsR.webp +0 -0
- package/web/dist/assets/ink-night-Bz4BT3XH.webp +0 -0
- package/web/dist/assets/ink-portrait-C0DM4V8Q.webp +0 -0
- package/web/dist/assets/longcang-regular-C0uvTuKa.woff2 +0 -0
- package/web/dist/assets/lxgw-nd-bold-DbRfiTgo.woff2 +0 -0
- package/web/dist/assets/lxgw-nd-regular-CpMw9Ehg.woff2 +0 -0
- package/web/dist/assets/pending-edit-apply-DTjo2geW.js +31 -0
- package/web/dist/assets/question-8H4HSqxh.webp +0 -0
- package/web/dist/assets/reject-BaPJqElj.webp +0 -0
- package/web/dist/assets/rotate-cw-LMT3x1Zj.js +11 -0
- package/web/dist/assets/rp-portrait-CbrGpew2.webp +0 -0
- package/web/dist/assets/rp-street-C4jVKjxx.webp +0 -0
- package/web/dist/assets/square-dashed-mouse-pointer-CBoAkfhi.js +11 -0
- package/web/dist/assets/tangle-DIxHVNuT.webp +0 -0
- package/web/dist/assets/thisone-BylZKglV.webp +0 -0
- package/web/dist/assets/thumb-DO1wfK1I.webp +0 -0
- package/web/dist/index.html +18 -0
|
@@ -0,0 +1,548 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* mcp/tools/web-search.js — web_search MCP tool
|
|
3
|
+
*
|
|
4
|
+
* 多 provider 联网搜索(移植自 ~/.deskclaw/skills/deskclaw-search-pro/scripts/search.py)。
|
|
5
|
+
* Provider 优先级按 query 语言自动路由:CJK → baidu 优先;非 CJK → tavily 优先;
|
|
6
|
+
* zhipu 配额稀缺永远最后。zero 外部依赖(仅 Node 内置 fetch + URL)。
|
|
7
|
+
*
|
|
8
|
+
* key 配置(.env,至少配一个):
|
|
9
|
+
* NODESIGN_BAIDU_QIANFAN_KEY=bce-v3/...
|
|
10
|
+
* NODESIGN_TAVILY_KEY=tvly-...
|
|
11
|
+
* NODESIGN_EXA_KEY=...
|
|
12
|
+
* NODESIGN_ZHIPU_KEY=...
|
|
13
|
+
*
|
|
14
|
+
* agent 视角的工具名:mcp__nodesign__web_search
|
|
15
|
+
*
|
|
16
|
+
* 返回 markdown(top N 条 title + source + snippet + url),让 agent 直接消费,
|
|
17
|
+
* 不暴露 raw JSON 避免 context 灌满。
|
|
18
|
+
*
|
|
19
|
+
* # 调用上限(agent 应自律,SKILL.md 强约束)
|
|
20
|
+
* - baidu 中文:≤2 次 / turn(snippet 即正文,单次通常够)
|
|
21
|
+
* - tavily:≤3-4 次(摘要浅 100-200 字,需多 query 三角验证)
|
|
22
|
+
* - exa:≤1-2 次(content 字段 2000+ 字完整正文,3 次以上爆 context)
|
|
23
|
+
* - zhipu:≤1 次(包配额稀缺)
|
|
24
|
+
* - baidu 英文:禁用(实测严重跑题)
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import path from 'node:path';
|
|
28
|
+
import fs from 'node:fs/promises';
|
|
29
|
+
import crypto from 'node:crypto';
|
|
30
|
+
import { downloadReferenceImages } from './helpers/reference-download.js';
|
|
31
|
+
import { tool } from '@anthropic-ai/claude-agent-sdk';
|
|
32
|
+
import { z } from 'zod';
|
|
33
|
+
|
|
34
|
+
const PROVIDERS = {
|
|
35
|
+
tavily: { keyEnv: 'NODESIGN_TAVILY_KEY' },
|
|
36
|
+
exa: { keyEnv: 'NODESIGN_EXA_KEY' },
|
|
37
|
+
baidu: { keyEnv: 'NODESIGN_BAIDU_QIANFAN_KEY' },
|
|
38
|
+
zhipu: { keyEnv: 'NODESIGN_ZHIPU_KEY' },
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const PRIORITY_CJK = ['baidu', 'tavily', 'exa', 'zhipu'];
|
|
42
|
+
const PRIORITY_NON_CJK = ['tavily', 'exa', 'baidu', 'zhipu'];
|
|
43
|
+
|
|
44
|
+
function looksChinese(text) {
|
|
45
|
+
// U+4E00-U+9FFF Unified CJK
|
|
46
|
+
return /[一-鿿]/.test(text || '');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function getKey(providerId) {
|
|
50
|
+
const env = PROVIDERS[providerId]?.keyEnv;
|
|
51
|
+
return env ? (process.env[env] || '') : '';
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function autoSelectProvider(query) {
|
|
55
|
+
const order = looksChinese(query) ? PRIORITY_CJK : PRIORITY_NON_CJK;
|
|
56
|
+
for (const id of order) if (getKey(id)) return id;
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function domainOf(url) {
|
|
61
|
+
try { return new URL(url).hostname.replace(/^www\./, ''); } catch { return ''; }
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ── adapters ──
|
|
65
|
+
|
|
66
|
+
async function searchTavily(query, key, n, { includeImages = false } = {}) {
|
|
67
|
+
const body = {
|
|
68
|
+
query,
|
|
69
|
+
search_depth: includeImages ? 'advanced' : 'basic',
|
|
70
|
+
topic: 'general',
|
|
71
|
+
max_results: n,
|
|
72
|
+
include_answer: false,
|
|
73
|
+
include_raw_content: false,
|
|
74
|
+
};
|
|
75
|
+
if (includeImages) {
|
|
76
|
+
body.include_images = true;
|
|
77
|
+
body.include_image_descriptions = true;
|
|
78
|
+
body.include_favicon = true;
|
|
79
|
+
}
|
|
80
|
+
const res = await fetch('https://api.tavily.com/search', {
|
|
81
|
+
method: 'POST',
|
|
82
|
+
headers: { 'Authorization': `Bearer ${key}`, 'Content-Type': 'application/json' },
|
|
83
|
+
body: JSON.stringify(body),
|
|
84
|
+
});
|
|
85
|
+
if (!res.ok) throw new ProviderError('tavily', res.status, await res.text());
|
|
86
|
+
const raw = await res.json();
|
|
87
|
+
const hits = (raw.results || []).map(r => ({
|
|
88
|
+
title: r.title || '',
|
|
89
|
+
url: r.url || '',
|
|
90
|
+
snippet: r.content || '',
|
|
91
|
+
source: domainOf(r.url || ''),
|
|
92
|
+
publishedAt: r.published_date || '',
|
|
93
|
+
}));
|
|
94
|
+
const images = includeImages
|
|
95
|
+
? (raw.images || [])
|
|
96
|
+
.filter(i => i?.url)
|
|
97
|
+
.map(i => ({
|
|
98
|
+
url: i.url,
|
|
99
|
+
description: i.description || '',
|
|
100
|
+
title: i.title || '',
|
|
101
|
+
}))
|
|
102
|
+
: [];
|
|
103
|
+
return { hits, images };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async function searchExa(query, key, n, { includeImages = false } = {}) {
|
|
107
|
+
// Exa 顶层是 camelCase(numResults / maxCharacters / imageLinks),
|
|
108
|
+
// 旧字段 num_results 仍兼容,新功能用官方约定的 camelCase。
|
|
109
|
+
const contents = {
|
|
110
|
+
highlights: { maxCharacters: 4000 },
|
|
111
|
+
};
|
|
112
|
+
if (includeImages) {
|
|
113
|
+
contents.extras = { imageLinks: Math.max(3, n) };
|
|
114
|
+
}
|
|
115
|
+
const res = await fetch('https://api.exa.ai/search', {
|
|
116
|
+
method: 'POST',
|
|
117
|
+
headers: { 'x-api-key': key, 'Content-Type': 'application/json' },
|
|
118
|
+
body: JSON.stringify({
|
|
119
|
+
query,
|
|
120
|
+
type: 'auto',
|
|
121
|
+
numResults: n,
|
|
122
|
+
contents,
|
|
123
|
+
}),
|
|
124
|
+
});
|
|
125
|
+
if (!res.ok) throw new ProviderError('exa', res.status, await res.text());
|
|
126
|
+
const raw = await res.json();
|
|
127
|
+
const hits = (raw.results || []).map(r => {
|
|
128
|
+
const highlights = r.highlights || [];
|
|
129
|
+
return {
|
|
130
|
+
title: r.title || '',
|
|
131
|
+
url: r.url || '',
|
|
132
|
+
snippet: highlights[0] || r.text || '',
|
|
133
|
+
source: domainOf(r.url || ''),
|
|
134
|
+
publishedAt: r.publishedDate || '',
|
|
135
|
+
};
|
|
136
|
+
});
|
|
137
|
+
// images: results[].image(页面代表图)+ results[].extras.imageLinks(页面内抽图),dedupe by url
|
|
138
|
+
const images = [];
|
|
139
|
+
if (includeImages) {
|
|
140
|
+
const seen = new Set();
|
|
141
|
+
for (const r of (raw.results || [])) {
|
|
142
|
+
const parentTitle = r.title || '';
|
|
143
|
+
// 1) 页面代表图(每条最多 1 张)
|
|
144
|
+
if (r.image && !seen.has(r.image)) {
|
|
145
|
+
seen.add(r.image);
|
|
146
|
+
images.push({ url: r.image, description: parentTitle, title: parentTitle });
|
|
147
|
+
}
|
|
148
|
+
// 2) 页面内抽到的图片链接(无描述,用 parent title 兜底)
|
|
149
|
+
const links = r.extras?.imageLinks || [];
|
|
150
|
+
for (const link of links) {
|
|
151
|
+
if (!link || seen.has(link)) continue;
|
|
152
|
+
seen.add(link);
|
|
153
|
+
images.push({ url: link, description: parentTitle, title: parentTitle });
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return { hits, images };
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
async function searchBaidu(query, key, n, { includeImages = false } = {}) {
|
|
161
|
+
// Baidu Qianfan content 字段硬上限 72 字符
|
|
162
|
+
const truncated = query.length > 72 ? query.slice(0, 72) : query;
|
|
163
|
+
const body = {
|
|
164
|
+
messages: [{ role: 'user', content: truncated }],
|
|
165
|
+
};
|
|
166
|
+
if (includeImages) {
|
|
167
|
+
// 默认 image.top_k=0;必须显式声明才返图。同时保留 web 模态拿 hits。
|
|
168
|
+
body.search_source = 'baidu_search_v2';
|
|
169
|
+
body.resource_type_filter = [
|
|
170
|
+
{ type: 'web', top_k: n },
|
|
171
|
+
{ type: 'image', top_k: Math.min(30, Math.max(n, 10)) },
|
|
172
|
+
];
|
|
173
|
+
}
|
|
174
|
+
const res = await fetch('https://qianfan.baidubce.com/v2/ai_search/web_search', {
|
|
175
|
+
method: 'POST',
|
|
176
|
+
headers: { 'Authorization': `Bearer ${key}`, 'Content-Type': 'application/json' },
|
|
177
|
+
body: JSON.stringify(body),
|
|
178
|
+
});
|
|
179
|
+
if (!res.ok) throw new ProviderError('baidu', res.status, await res.text());
|
|
180
|
+
const raw = await res.json();
|
|
181
|
+
let results = raw.search_results || raw.results || [];
|
|
182
|
+
if (!Array.isArray(results) || results.length === 0) {
|
|
183
|
+
results = raw.references || [];
|
|
184
|
+
}
|
|
185
|
+
// 把 web 与 image 两类拆开:image 模态是 type==='image'
|
|
186
|
+
const webRefs = results.filter(r => (r?.type || 'web') !== 'image');
|
|
187
|
+
const imageRefs = results.filter(r => r?.type === 'image');
|
|
188
|
+
|
|
189
|
+
const hits = webRefs.slice(0, n).map(r => ({
|
|
190
|
+
title: r.title || '',
|
|
191
|
+
url: r.url || r.link || '',
|
|
192
|
+
snippet: r.content || r.abstract || r.segment_text || '',
|
|
193
|
+
source: r.source || domainOf(r.url || r.link || ''),
|
|
194
|
+
publishedAt: r.publish_time || '',
|
|
195
|
+
}));
|
|
196
|
+
|
|
197
|
+
const images = [];
|
|
198
|
+
if (includeImages) {
|
|
199
|
+
const seen = new Set();
|
|
200
|
+
// 1) 图片模态条目
|
|
201
|
+
for (const r of imageRefs) {
|
|
202
|
+
const u = r.image?.url || r.url;
|
|
203
|
+
if (!u || seen.has(u)) continue;
|
|
204
|
+
seen.add(u);
|
|
205
|
+
images.push({
|
|
206
|
+
url: u,
|
|
207
|
+
description: r.title || '',
|
|
208
|
+
title: r.title || '',
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
// 2) 网页条目里夹带的相关图(web_extensions.images)
|
|
212
|
+
for (const r of webRefs) {
|
|
213
|
+
const arr = r.web_extensions?.images || [];
|
|
214
|
+
for (const img of arr) {
|
|
215
|
+
const u = img?.url;
|
|
216
|
+
if (!u || seen.has(u)) continue;
|
|
217
|
+
seen.add(u);
|
|
218
|
+
images.push({
|
|
219
|
+
url: u,
|
|
220
|
+
description: r.title || '',
|
|
221
|
+
title: r.title || '',
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return { hits, images };
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
async function searchZhipu(query, key, n) {
|
|
230
|
+
// 用 dedicated tools/web_search endpoint(比 chat completions 路径更直接)
|
|
231
|
+
const res = await fetch('https://open.bigmodel.cn/api/paas/v4/tools/web_search', {
|
|
232
|
+
method: 'POST',
|
|
233
|
+
headers: { 'Authorization': `Bearer ${key}`, 'Content-Type': 'application/json' },
|
|
234
|
+
body: JSON.stringify({
|
|
235
|
+
search_engine: 'search_pro',
|
|
236
|
+
search_query: query,
|
|
237
|
+
count: n,
|
|
238
|
+
}),
|
|
239
|
+
});
|
|
240
|
+
if (!res.ok) throw new ProviderError('zhipu', res.status, await res.text());
|
|
241
|
+
const raw = await res.json();
|
|
242
|
+
const hits = (raw.search_result || []).slice(0, n).map(r => ({
|
|
243
|
+
title: r.title || '',
|
|
244
|
+
url: r.link || r.url || '',
|
|
245
|
+
snippet: r.content || '',
|
|
246
|
+
source: r.media || domainOf(r.link || r.url || ''),
|
|
247
|
+
publishedAt: '',
|
|
248
|
+
}));
|
|
249
|
+
return { hits, images: [] };
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const ADAPTERS = {
|
|
253
|
+
tavily: searchTavily,
|
|
254
|
+
exa: searchExa,
|
|
255
|
+
baidu: searchBaidu,
|
|
256
|
+
zhipu: searchZhipu,
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
class ProviderError extends Error {
|
|
260
|
+
constructor(provider, code, body) {
|
|
261
|
+
super(`[${provider}] HTTP ${code}: ${String(body || '').slice(0, 300)}`);
|
|
262
|
+
this.provider = provider;
|
|
263
|
+
this.code = code;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function formatMarkdown(query, provider, hits, { images = [] } = {}) {
|
|
268
|
+
const lines = [`## Search results (${provider}, ${hits.length} hits)`, '', `> Query: ${query}`, ''];
|
|
269
|
+
if (hits.length === 0) {
|
|
270
|
+
lines.push('No text results found.');
|
|
271
|
+
} else {
|
|
272
|
+
hits.forEach((h, i) => {
|
|
273
|
+
const src = h.source ? ` — ${h.source}` : '';
|
|
274
|
+
const pub = h.publishedAt ? ` (${h.publishedAt})` : '';
|
|
275
|
+
lines.push(`${i + 1}. **${h.title || 'Untitled'}**${src}${pub}`);
|
|
276
|
+
if (h.snippet) {
|
|
277
|
+
// 缩进显示,限单条 snippet 800 字符防爆 context
|
|
278
|
+
const snippet = h.snippet.length > 800 ? h.snippet.slice(0, 800) + '…' : h.snippet;
|
|
279
|
+
for (const line of snippet.split('\n')) lines.push(` ${line}`);
|
|
280
|
+
}
|
|
281
|
+
if (h.url) lines.push(` ${h.url}`);
|
|
282
|
+
lines.push('');
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
if (images.length > 0) {
|
|
287
|
+
lines.push(`## Reference images (downloaded, ${images.length})`, '');
|
|
288
|
+
lines.push(
|
|
289
|
+
'> The actual pixel content of each image follows this text as inline '
|
|
290
|
+
+ 'image content blocks (in the same numbered order). Vision-check them '
|
|
291
|
+
+ 'directly to pick the best 1-2 candidates.',
|
|
292
|
+
'> Then pass the chosen `local_path` value(s) as `referenceImages[]` to '
|
|
293
|
+
+ '`mcp__nodesign__generate_image` (paths are already inside the workspace).',
|
|
294
|
+
'',
|
|
295
|
+
);
|
|
296
|
+
images.forEach((img, i) => {
|
|
297
|
+
const desc = img.description || img.title || '(no description)';
|
|
298
|
+
const sizeKB = (img.sizeBytes / 1024).toFixed(0);
|
|
299
|
+
lines.push(`${i + 1}. ${desc}`);
|
|
300
|
+
lines.push(` local_path: ${img.relPath}`);
|
|
301
|
+
lines.push(` size: ${sizeKB} KB, mime: ${img.mimeType}`);
|
|
302
|
+
lines.push(` source: ${domainOf(img.url)}`);
|
|
303
|
+
lines.push(` url: ${img.url}`);
|
|
304
|
+
lines.push('');
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
return lines.join('\n');
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* @param {object} deps
|
|
312
|
+
* @param {string} [deps.workspaceRoot] agent cwd(include_images 下载落档需要)
|
|
313
|
+
* @param {string} [deps.sharedRoot] project shared/,存在时优先落档
|
|
314
|
+
* @param {import('../../agent/context.js').AgentContext} [deps.ctx]
|
|
315
|
+
*/
|
|
316
|
+
export function makeWebSearchTool({ workspaceRoot, sharedRoot, ctx } = {}) {
|
|
317
|
+
return tool(
|
|
318
|
+
'web_search',
|
|
319
|
+
`Search the web via tavily / exa / baidu (Qianfan) / zhipu providers. Auto-routes by query language: CJK
|
|
320
|
+
queries prefer baidu, English queries prefer tavily.
|
|
321
|
+
|
|
322
|
+
Use this tool when:
|
|
323
|
+
- You need current information (latest design trends, library docs, recent events)
|
|
324
|
+
- You need to verify a fact or find a citation
|
|
325
|
+
- You need to find a URL to then GO LOOK AT: for anything visual, follow up with browser_navigate and browse the site's inner pages — snippets carry no layout, type or colour. (Or web_fetch for text.)
|
|
326
|
+
- You need REFERENCE IMAGES for generate_image (set include_images=true; see below)
|
|
327
|
+
|
|
328
|
+
DO NOT:
|
|
329
|
+
- Run more than 2-3 queries per turn (context bloat)
|
|
330
|
+
- Use baidu for English queries (returns severely off-topic results)
|
|
331
|
+
- Re-issue the same query to retry — change the wording instead
|
|
332
|
+
|
|
333
|
+
For Chinese queries baidu's snippet field already contains 500-3000 chars of body text;
|
|
334
|
+
you usually don't need to web_fetch the URL afterwards. Tavily snippets are short (100-200
|
|
335
|
+
chars) and may need a follow-up fetch.
|
|
336
|
+
|
|
337
|
+
Add a year hint (e.g., "2025 2026") to the query — search engines especially Chinese ones
|
|
338
|
+
often return stale results without it.
|
|
339
|
+
|
|
340
|
+
# include_images mode (reference imagery for generate_image)
|
|
341
|
+
|
|
342
|
+
When you need real-world subject anchor before generate_image (product shots, scenes,
|
|
343
|
+
iconic landmarks, brand visuals, etc.), pass include_images=true. Behavior:
|
|
344
|
+
- Auto-routes by language (excluding zhipu, which has no image support):
|
|
345
|
+
CJK query → baidu (native CJK image search; no translation)
|
|
346
|
+
EN query → tavily (richest image descriptions, ~100% covered)
|
|
347
|
+
fallback → exa (page-representative image + page-internal imageLinks)
|
|
348
|
+
You can override with provider='tavily'|'exa'|'baidu'.
|
|
349
|
+
- When provider lands on tavily/exa AND query is CJK, the tool auto-translates
|
|
350
|
+
the query to English first (Tavily/Exa image descriptions are dramatically
|
|
351
|
+
richer in English; baidu doesn't need translation).
|
|
352
|
+
- Top-N image hits are downloaded into <workspace>/assets/references/ref-<hash>.<ext>
|
|
353
|
+
and listed in the markdown output as "Reference images (downloaded, N)".
|
|
354
|
+
- The CallToolResult also returns each downloaded image as an inline image
|
|
355
|
+
content block (in the same numbered order as the markdown), so you can
|
|
356
|
+
vision-check the candidates immediately without calling Read.
|
|
357
|
+
- Each entry has a 'local_path' field — pass that path directly into
|
|
358
|
+
mcp__nodesign__generate_image referenceImages[] (NOT the http url; the gen tool
|
|
359
|
+
only accepts workspace-relative paths).
|
|
360
|
+
- Filtered: 5KB ≤ size ≤ 8MB, only png/jpg/webp/gif content-types accepted.
|
|
361
|
+
- Per-provider quirks:
|
|
362
|
+
tavily: top-level images[] — clean, on-topic, every image has a description
|
|
363
|
+
exa: page-rep image + extras.imageLinks — many small/decorative URLs
|
|
364
|
+
survive the size filter, expect more variance
|
|
365
|
+
baidu: native CJK + image size/ratio/format filter via search_filter (we
|
|
366
|
+
don't expose those yet); image entries lack descriptions, we use
|
|
367
|
+
the parent reference title as fallback caption
|
|
368
|
+
|
|
369
|
+
Suggested flow for image-led pages:
|
|
370
|
+
1. user picks theme → 2. web_search(query, include_images=true) → 3. pick the
|
|
371
|
+
best-matching local_path → 4. generate_image(prompt, referenceImages=[that path])`,
|
|
372
|
+
{
|
|
373
|
+
query: z.string().min(2).max(500).describe('Search query. Add year hints (2025/2026) for time-sensitive results. Baidu truncates content to 72 chars — keep CJK queries short.'),
|
|
374
|
+
provider: z
|
|
375
|
+
.enum(['tavily', 'exa', 'baidu', 'zhipu', 'auto'])
|
|
376
|
+
.optional()
|
|
377
|
+
.describe('Force a provider; default "auto" routes by query language. include_images=true auto-routes among tavily/exa/baidu (zhipu rejected, no image support).'),
|
|
378
|
+
count: z
|
|
379
|
+
.number()
|
|
380
|
+
.int()
|
|
381
|
+
.min(1)
|
|
382
|
+
.max(10)
|
|
383
|
+
.optional()
|
|
384
|
+
.describe('Max text results to return (default 5). Image count follows the same value.'),
|
|
385
|
+
include_images: z
|
|
386
|
+
.boolean()
|
|
387
|
+
.optional()
|
|
388
|
+
.describe('Returns top-N reference images downloaded into assets/references/ for use as generate_image referenceImages. Auto-routes CJK→baidu / EN→tavily (exa fallback). CJK queries on tavily/exa are auto-translated to English for richer descriptions. zhipu rejected. Default false.'),
|
|
389
|
+
},
|
|
390
|
+
async ({ query, provider = 'auto', count = 5, include_images = false }) => {
|
|
391
|
+
try {
|
|
392
|
+
// 1) Provider 选择
|
|
393
|
+
// - 普通模式:tavily/exa/baidu/zhipu auto-route by language(CJK→baidu, EN→tavily)
|
|
394
|
+
// - include_images:zhipu 不支持图,从候选中剔除;其余按语言 auto-route
|
|
395
|
+
let providerId;
|
|
396
|
+
if (include_images) {
|
|
397
|
+
if (provider === 'zhipu') {
|
|
398
|
+
return {
|
|
399
|
+
content: [{
|
|
400
|
+
type: 'text',
|
|
401
|
+
text: 'web_search failed: provider "zhipu" does not support image search. Use tavily / exa / baidu, or omit provider for auto-routing.',
|
|
402
|
+
}],
|
|
403
|
+
isError: true,
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
if (provider === 'auto') {
|
|
407
|
+
const order = (looksChinese(query) ? PRIORITY_CJK : PRIORITY_NON_CJK)
|
|
408
|
+
.filter(id => id !== 'zhipu');
|
|
409
|
+
providerId = order.find(id => getKey(id)) || null;
|
|
410
|
+
if (!providerId) {
|
|
411
|
+
return {
|
|
412
|
+
content: [{
|
|
413
|
+
type: 'text',
|
|
414
|
+
text: 'web_search failed (include_images=true): no image-capable provider configured. Set at least one of '
|
|
415
|
+
+ ['tavily', 'exa', 'baidu'].map(id => PROVIDERS[id].keyEnv).join(' / '),
|
|
416
|
+
}],
|
|
417
|
+
isError: true,
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
} else {
|
|
421
|
+
providerId = provider;
|
|
422
|
+
if (!getKey(providerId)) {
|
|
423
|
+
return {
|
|
424
|
+
content: [{
|
|
425
|
+
type: 'text',
|
|
426
|
+
text: `web_search failed: ${PROVIDERS[providerId].keyEnv} not set in .env.`,
|
|
427
|
+
}],
|
|
428
|
+
isError: true,
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
} else {
|
|
433
|
+
providerId = provider === 'auto' ? autoSelectProvider(query) : provider;
|
|
434
|
+
if (!providerId) {
|
|
435
|
+
return {
|
|
436
|
+
content: [{
|
|
437
|
+
type: 'text',
|
|
438
|
+
text: 'web_search failed: no provider configured. Set at least one of '
|
|
439
|
+
+ Object.values(PROVIDERS).map(p => p.keyEnv).join(' / ')
|
|
440
|
+
+ ' in NoDesign .env.',
|
|
441
|
+
}],
|
|
442
|
+
isError: true,
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
if (!getKey(providerId)) {
|
|
446
|
+
return {
|
|
447
|
+
content: [{
|
|
448
|
+
type: 'text',
|
|
449
|
+
text: `web_search failed: ${PROVIDERS[providerId].keyEnv} not set in .env.`,
|
|
450
|
+
}],
|
|
451
|
+
isError: true,
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
const key = getKey(providerId);
|
|
457
|
+
|
|
458
|
+
// 2) CJK query 翻英文再搜图这一步已移除(2026-07-31)。
|
|
459
|
+
// 它唯一的实现走 NoDesk 网关,网关 07-27 永久退役后函数直接 return null,
|
|
460
|
+
// 于是这段代码一直在跑但从来没生效过 —— 中文 query 搜图拿到的
|
|
461
|
+
// images[].description 约一半是 null,而日志里什么都看不出来。
|
|
462
|
+
// 静默失效比不存在更坏:读代码的人会以为这个能力还在。
|
|
463
|
+
// 要恢复的话得先给它一个活着的 provider(见 lib/openai 那套 key),
|
|
464
|
+
// 不是把这几行搬回来就完事。
|
|
465
|
+
const effectiveQuery = query;
|
|
466
|
+
|
|
467
|
+
// 3) Provider call —— tavily/exa/baidu 都接 includeImages opts;zhipu 不传
|
|
468
|
+
const adapter = ADAPTERS[providerId];
|
|
469
|
+
const adapterOpts = providerId === 'zhipu' ? undefined : { includeImages: include_images };
|
|
470
|
+
const result = await adapter(effectiveQuery, key, count, adapterOpts);
|
|
471
|
+
const hits = result.hits || [];
|
|
472
|
+
const rawImages = result.images || [];
|
|
473
|
+
|
|
474
|
+
// 4) Image 下载(include_images=true 才走)
|
|
475
|
+
// Provider 之间数量差异大(tavily ≤5、exa 可达 count*5、baidu image
|
|
476
|
+
// 模态 + web_extensions 合起来可达 30+)。取 count*2 当下载头空间,
|
|
477
|
+
// 再把最终结果截到 count 张,让 markdown 输出可预期。
|
|
478
|
+
let downloadedImages = [];
|
|
479
|
+
if (include_images && rawImages.length > 0) {
|
|
480
|
+
if (!workspaceRoot) {
|
|
481
|
+
return {
|
|
482
|
+
content: [{
|
|
483
|
+
type: 'text',
|
|
484
|
+
text: 'web_search internal error: workspaceRoot not configured for include_images mode.',
|
|
485
|
+
}],
|
|
486
|
+
isError: true,
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
// ⚠️ **下多少就报多少**(2026-08-18 修)。以前按 `count*2` 下载却只
|
|
490
|
+
// `slice(0, count)` 上报 —— 磁盘上留着一半 agent 不知道存在的文件,
|
|
491
|
+
// 而它们照样占空间、照样进导出包。多下的那批本来是为了容错(有些 URL
|
|
492
|
+
// 会 404),现在改成:**下够 count 张就停**,成功几张报几张。
|
|
493
|
+
const dlCandidates = rawImages.slice(0, Math.max(count * 2, 5));
|
|
494
|
+
const downloaded = await downloadReferenceImages(dlCandidates, {
|
|
495
|
+
workspaceRoot, sharedRoot, stopAfter: count,
|
|
496
|
+
});
|
|
497
|
+
downloadedImages = downloaded;
|
|
498
|
+
// 落盘了就发 file_changed —— generate_image 一直在发,这里漏了,
|
|
499
|
+
// 于是参考图落进工作区之后前端毫无感知(素材抽屉要刷新才看得见)
|
|
500
|
+
for (const img of downloadedImages) {
|
|
501
|
+
try { ctx?.emit?.({ type: 'run.file_changed', path: img.relPath, change: 'add' }); } catch { /* */ }
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
try {
|
|
506
|
+
ctx?.emit?.({
|
|
507
|
+
type: 'run.web_search',
|
|
508
|
+
provider: providerId,
|
|
509
|
+
query: effectiveQuery,
|
|
510
|
+
hits: hits.length,
|
|
511
|
+
referenceImages: downloadedImages.length,
|
|
512
|
+
});
|
|
513
|
+
} catch { /* fail-safe */ }
|
|
514
|
+
|
|
515
|
+
// 文本 + 每张图一块 image content block(顺序与 markdown 编号一致)
|
|
516
|
+
// 让 agent 当 turn 直接 vision-check 候选 reference,省掉逐张 Read。
|
|
517
|
+
// 内联图片成本:~1568 token/张(Anthropic image block tokenization)×
|
|
518
|
+
// count 张 + base64 体积。已在上一步通过 count 截断控量。
|
|
519
|
+
const out = [{
|
|
520
|
+
type: 'text',
|
|
521
|
+
text: formatMarkdown(query, providerId, hits, { images: downloadedImages }),
|
|
522
|
+
}];
|
|
523
|
+
for (const img of downloadedImages) {
|
|
524
|
+
if (img.base64) {
|
|
525
|
+
out.push({
|
|
526
|
+
type: 'image',
|
|
527
|
+
data: img.base64,
|
|
528
|
+
mimeType: img.mimeType,
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
return { content: out };
|
|
533
|
+
} catch (err) {
|
|
534
|
+
const msg = err instanceof ProviderError
|
|
535
|
+
? err.message + (err.code === 401 || err.code === 403
|
|
536
|
+
? ` (auth failed — check ${PROVIDERS[err.provider].keyEnv})`
|
|
537
|
+
: err.code === 429
|
|
538
|
+
? ' (rate limit / quota exhausted — try another provider)'
|
|
539
|
+
: '')
|
|
540
|
+
: `web_search error: ${err?.message || String(err)}`;
|
|
541
|
+
return {
|
|
542
|
+
content: [{ type: 'text', text: msg }],
|
|
543
|
+
isError: true,
|
|
544
|
+
};
|
|
545
|
+
}
|
|
546
|
+
},
|
|
547
|
+
);
|
|
548
|
+
}
|