@astralyn/pi 1.0.4 → 1.1.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/CHANGELOG.md +53 -0
- package/dist/bundle/chunks/{anthropic-messages-YIOJIMVL.js → anthropic-messages-3K3DWVY6.js} +1 -1
- package/dist/bundle/chunks/anthropic.js +1 -1
- package/dist/bundle/chunks/{azure-openai-responses-LDW4FGAQ.js → azure-openai-responses-CH74HGP4.js} +1 -1
- package/dist/bundle/chunks/bedrock-converse-stream.js +2 -2
- package/dist/bundle/chunks/{chunk-UCXU7KLO.js → chunk-3MMB5I7J.js} +1 -1
- package/dist/bundle/chunks/{chunk-GIG5IHES.js → chunk-5KJH3GGH.js} +1 -1
- package/dist/bundle/chunks/chunk-6VYRLG5F.js +2 -0
- package/dist/bundle/chunks/{chunk-73WG7F7N.js → chunk-C2ARIVS3.js} +1 -1
- package/dist/bundle/chunks/{chunk-23OTKHCH.js → chunk-D3XW5X3Z.js} +490 -260
- package/dist/bundle/chunks/{chunk-IFELM7MW.js → chunk-EJEOEBSJ.js} +1 -1
- package/dist/bundle/chunks/chunk-H35KWBT5.js +11 -0
- package/dist/bundle/chunks/chunk-KAC2YPTW.js +2 -0
- package/dist/bundle/chunks/chunk-MAC4N3BW.js +2 -0
- package/dist/bundle/chunks/chunk-QLU3LESZ.js +2 -0
- package/dist/bundle/chunks/{chunk-A6ALEEY5.js → chunk-TEAAYBTS.js} +1 -1
- package/dist/bundle/chunks/cloudflare-workers-ai-system-one-XO45ZT3V.js +2 -0
- package/dist/bundle/chunks/codemode-worker.js +101 -5
- package/dist/bundle/chunks/github-copilot.js +1 -1
- package/dist/bundle/chunks/{google-generative-ai-S5A5IBU4.js → google-generative-ai-VVO64ZRQ.js} +1 -1
- package/dist/bundle/chunks/{google-vertex-UBRQFDB4.js → google-vertex-LKS5NUHA.js} +1 -1
- package/dist/bundle/chunks/image-resize-worker.js +1 -1
- package/dist/bundle/chunks/{llama-cpp-classify-R7DQLTQN.js → llama-cpp-classify-H3IZDR5O.js} +2 -2
- package/dist/bundle/chunks/{mistral-conversations-A2N5FSOV.js → mistral-conversations-NJUOG4ET.js} +2 -2
- package/dist/bundle/chunks/openai-chatgpt.js +1 -1
- package/dist/bundle/chunks/{openai-codex-responses-Q6JHCWLE.js → openai-codex-responses-TZWBTTYM.js} +2 -2
- package/dist/bundle/chunks/openai-codex.js +1 -1
- package/dist/bundle/chunks/{openai-completions-H4VMO7AU.js → openai-completions-7HTTU6FH.js} +1 -1
- package/dist/bundle/chunks/openai-decisions-776H4VDZ.js +5 -0
- package/dist/bundle/chunks/{openai-responses-G4PGNM2H.js → openai-responses-7A6BAQ5X.js} +1 -1
- package/dist/bundle/chunks/{openrouter-images-YP3YVB54.js → openrouter-images-TDLJV5N4.js} +1 -1
- package/dist/bundle/chunks/{pi-messages-HZZ7GEEW.js → pi-messages-ZXNERWJG.js} +1 -1
- package/dist/bundle/chunks/typesafe-system-one-2DQLG4R2.js +2 -0
- package/dist/bundle/chunks/{virtual-modules-IJTAY26N.js → virtual-modules-2NHGQRS6.js} +1 -1
- package/dist/bundle/cli.js +1 -1
- package/dist/bundle/index.js +1 -1
- package/dist/bundle/rpc-entry.js +1 -1
- package/dist/cli/args.d.ts +2 -1
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +24 -5
- package/dist/cli/args.js.map +1 -1
- package/dist/core/agent-session.d.ts +38 -10
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +190 -186
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/bash-executor.d.ts.map +1 -1
- package/dist/core/bash-executor.js +40 -45
- package/dist/core/bash-executor.js.map +1 -1
- package/dist/core/context-snapshot.d.ts +4 -1
- package/dist/core/context-snapshot.d.ts.map +1 -1
- package/dist/core/context-snapshot.js +45 -0
- package/dist/core/context-snapshot.js.map +1 -1
- package/dist/core/export-html/tool-renderer.d.ts.map +1 -1
- package/dist/core/export-html/tool-renderer.js +2 -0
- package/dist/core/export-html/tool-renderer.js.map +1 -1
- package/dist/core/extensions/types.d.ts +13 -0
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/mcp-servers.d.ts +8 -0
- package/dist/core/mcp-servers.d.ts.map +1 -1
- package/dist/core/mcp-servers.js +30 -7
- package/dist/core/mcp-servers.js.map +1 -1
- package/dist/core/model-runtime.d.ts.map +1 -1
- package/dist/core/model-runtime.js +2 -1
- package/dist/core/model-runtime.js.map +1 -1
- package/dist/core/nested-tool-calls.d.ts +1 -0
- package/dist/core/nested-tool-calls.d.ts.map +1 -1
- package/dist/core/nested-tool-calls.js +1 -0
- package/dist/core/nested-tool-calls.js.map +1 -1
- package/dist/core/resource-loader.d.ts +3 -0
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +3 -3
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/sdk.d.ts +13 -4
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +19 -6
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/settings-manager.d.ts +13 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +37 -13
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/skills.d.ts +4 -1
- package/dist/core/skills.d.ts.map +1 -1
- package/dist/core/skills.js +6 -1
- package/dist/core/skills.js.map +1 -1
- package/dist/core/system-prompt.d.ts +6 -0
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +9 -4
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/tasks/session.d.ts +9 -0
- package/dist/core/tasks/session.d.ts.map +1 -1
- package/dist/core/tasks/session.js +21 -0
- package/dist/core/tasks/session.js.map +1 -1
- package/dist/core/tools/read.d.ts +13 -0
- package/dist/core/tools/read.d.ts.map +1 -1
- package/dist/core/tools/read.js +18 -1
- package/dist/core/tools/read.js.map +1 -1
- package/dist/core/tools/renderers/bash.d.ts.map +1 -1
- package/dist/core/tools/renderers/bash.js +8 -3
- package/dist/core/tools/renderers/bash.js.map +1 -1
- package/dist/core/tools/renderers/edit.js +1 -1
- package/dist/core/tools/renderers/edit.js.map +1 -1
- package/dist/extensions/codemode/execute.d.ts.map +1 -1
- package/dist/extensions/codemode/execute.js +65 -8
- package/dist/extensions/codemode/execute.js.map +1 -1
- package/dist/extensions/codemode/index.d.ts.map +1 -1
- package/dist/extensions/codemode/index.js +1 -0
- package/dist/extensions/codemode/index.js.map +1 -1
- package/dist/extensions/codemode/tool.d.ts +9 -2
- package/dist/extensions/codemode/tool.d.ts.map +1 -1
- package/dist/extensions/codemode/tool.js +14 -6
- package/dist/extensions/codemode/tool.js.map +1 -1
- package/dist/extensions/index.js +2 -2
- package/dist/extensions/index.js.map +1 -1
- package/dist/extensions/llama/provider.d.ts.map +1 -1
- package/dist/extensions/llama/provider.js +34 -11
- package/dist/extensions/llama/provider.js.map +1 -1
- package/dist/extensions/mcp/cli.js +17 -23
- package/dist/extensions/mcp/cli.js.map +1 -1
- package/dist/extensions/mcp/index.d.ts.map +1 -1
- package/dist/extensions/mcp/index.js +165 -64
- package/dist/extensions/mcp/index.js.map +1 -1
- package/dist/extensions/mcp/oauth.d.ts +5 -3
- package/dist/extensions/mcp/oauth.d.ts.map +1 -1
- package/dist/extensions/mcp/oauth.js +36 -10
- package/dist/extensions/mcp/oauth.js.map +1 -1
- package/dist/extensions/mcp/resources.d.ts +3 -4
- package/dist/extensions/mcp/resources.d.ts.map +1 -1
- package/dist/extensions/mcp/resources.js +3 -4
- package/dist/extensions/mcp/resources.js.map +1 -1
- package/dist/extensions/mcp/runtime.d.ts +4 -1
- package/dist/extensions/mcp/runtime.d.ts.map +1 -1
- package/dist/extensions/mcp/runtime.js +22 -5
- package/dist/extensions/mcp/runtime.js.map +1 -1
- package/dist/extensions/mcp/tools.d.ts +2 -1
- package/dist/extensions/mcp/tools.d.ts.map +1 -1
- package/dist/extensions/mcp/tools.js +2 -1
- package/dist/extensions/mcp/tools.js.map +1 -1
- package/dist/extensions/mcp/ui.d.ts +3 -3
- package/dist/extensions/mcp/ui.d.ts.map +1 -1
- package/dist/extensions/mcp/ui.js +10 -2
- package/dist/extensions/mcp/ui.js.map +1 -1
- package/dist/extensions/search/code-search.d.ts +29 -0
- package/dist/extensions/search/code-search.d.ts.map +1 -0
- package/dist/extensions/search/code-search.js +120 -0
- package/dist/extensions/search/code-search.js.map +1 -0
- package/dist/extensions/search/command.d.ts +5 -0
- package/dist/extensions/search/command.d.ts.map +1 -0
- package/dist/extensions/search/command.js +69 -0
- package/dist/extensions/search/command.js.map +1 -0
- package/dist/extensions/search/credential.d.ts +25 -0
- package/dist/extensions/search/credential.d.ts.map +1 -0
- package/dist/extensions/search/credential.js +117 -0
- package/dist/extensions/search/credential.js.map +1 -0
- package/dist/extensions/search/devin.d.ts +34 -0
- package/dist/extensions/search/devin.d.ts.map +1 -0
- package/dist/extensions/search/devin.js +210 -0
- package/dist/extensions/search/devin.js.map +1 -0
- package/dist/extensions/search/index.d.ts +5 -0
- package/dist/extensions/search/index.d.ts.map +1 -0
- package/dist/extensions/search/index.js +110 -0
- package/dist/extensions/search/index.js.map +1 -0
- package/dist/extensions/search/panel.d.ts +53 -0
- package/dist/extensions/search/panel.d.ts.map +1 -0
- package/dist/extensions/search/panel.js +157 -0
- package/dist/extensions/search/panel.js.map +1 -0
- package/dist/extensions/search/prompt.d.ts +14 -0
- package/dist/extensions/search/prompt.d.ts.map +1 -0
- package/dist/extensions/search/prompt.js +221 -0
- package/dist/extensions/search/prompt.js.map +1 -0
- package/dist/extensions/search/render.d.ts +28 -0
- package/dist/extensions/search/render.d.ts.map +1 -0
- package/dist/extensions/search/render.js +86 -0
- package/dist/extensions/search/render.js.map +1 -0
- package/dist/extensions/search/workspace.d.ts +38 -0
- package/dist/extensions/search/workspace.d.ts.map +1 -0
- package/dist/extensions/search/workspace.js +245 -0
- package/dist/extensions/search/workspace.js.map +1 -0
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +1 -0
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/bash-execution.d.ts +5 -1
- package/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/bash-execution.js +14 -14
- package/dist/modes/interactive/components/bash-execution.js.map +1 -1
- package/dist/modes/interactive/components/branch-summary-message.d.ts +2 -1
- package/dist/modes/interactive/components/branch-summary-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/branch-summary-message.js +5 -2
- package/dist/modes/interactive/components/branch-summary-message.js.map +1 -1
- package/dist/modes/interactive/components/compaction-summary-message.d.ts +2 -1
- package/dist/modes/interactive/components/compaction-summary-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/compaction-summary-message.js +5 -2
- package/dist/modes/interactive/components/compaction-summary-message.js.map +1 -1
- package/dist/modes/interactive/components/custom-entry.d.ts +3 -1
- package/dist/modes/interactive/components/custom-entry.d.ts.map +1 -1
- package/dist/modes/interactive/components/custom-entry.js +7 -2
- package/dist/modes/interactive/components/custom-entry.js.map +1 -1
- package/dist/modes/interactive/components/custom-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/custom-message.js +1 -0
- package/dist/modes/interactive/components/custom-message.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector.js +1 -1
- package/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/dist/modes/interactive/components/skill-invocation-message.d.ts +2 -1
- package/dist/modes/interactive/components/skill-invocation-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/skill-invocation-message.js +5 -2
- package/dist/modes/interactive/components/skill-invocation-message.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +2 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +52 -59
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/program-status-reporter.d.ts +34 -0
- package/dist/modes/interactive/program-status-reporter.d.ts.map +1 -0
- package/dist/modes/interactive/program-status-reporter.js +107 -0
- package/dist/modes/interactive/program-status-reporter.js.map +1 -0
- package/dist/modes/interactive/tasks/host-hooks.d.ts +14 -0
- package/dist/modes/interactive/tasks/host-hooks.d.ts.map +1 -0
- package/dist/modes/interactive/tasks/host-hooks.js +31 -0
- package/dist/modes/interactive/tasks/host-hooks.js.map +1 -0
- package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme.js +1 -0
- package/dist/modes/interactive/theme/theme.js.map +1 -1
- package/dist/modes/interactive/tool-view/tool-execution.d.ts +5 -0
- package/dist/modes/interactive/tool-view/tool-execution.d.ts.map +1 -1
- package/dist/modes/interactive/tool-view/tool-execution.js +7 -0
- package/dist/modes/interactive/tool-view/tool-execution.js.map +1 -1
- package/dist/package-manager-cli.d.ts.map +1 -1
- package/dist/package-manager-cli.js +24 -0
- package/dist/package-manager-cli.js.map +1 -1
- package/dist/utils/ansi.d.ts +8 -0
- package/dist/utils/ansi.d.ts.map +1 -1
- package/dist/utils/ansi.js +30 -11
- package/dist/utils/ansi.js.map +1 -1
- package/dist/utils/clipboard.d.ts.map +1 -1
- package/dist/utils/clipboard.js +12 -11
- package/dist/utils/clipboard.js.map +1 -1
- package/dist/utils/image-resize-core.d.ts +12 -0
- package/dist/utils/image-resize-core.d.ts.map +1 -1
- package/dist/utils/image-resize-core.js +5 -0
- package/dist/utils/image-resize-core.js.map +1 -1
- package/dist/utils/image-resize-worker.js +3 -2
- package/dist/utils/image-resize-worker.js.map +1 -1
- package/dist/utils/image-resize.d.ts.map +1 -1
- package/dist/utils/image-resize.js +9 -10
- package/dist/utils/image-resize.js.map +1 -1
- package/dist/utils/syntax-highlight.d.ts.map +1 -1
- package/dist/utils/syntax-highlight.js +6 -1
- package/dist/utils/syntax-highlight.js.map +1 -1
- package/docs/bundled/README.md +1 -1
- package/docs/bundled/extensions/provider.md +1 -1
- package/docs/bundled/extensions/question.md +1 -1
- package/docs/bundled/extensions/search.md +50 -0
- package/docs/bundled/extensions/statusline.md +15 -41
- package/docs/codemode.md +26 -5
- package/docs/docs.json +0 -4
- package/docs/environment-variables.md +1 -0
- package/docs/extensions.md +6 -2
- package/docs/json.md +2 -2
- package/docs/llama-cpp.md +8 -1
- package/docs/mcp.md +3 -1
- package/docs/models.md +4 -1
- package/docs/providers.md +17 -0
- package/docs/rpc.md +6 -3
- package/docs/session-format.md +1 -0
- package/docs/settings.md +4 -4
- package/docs/terminal-setup.md +14 -0
- package/docs/usage.md +19 -5
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/gondolin/package-lock.json +2 -2
- package/examples/extensions/gondolin/package.json +1 -1
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/npm-shrinkwrap.json +54 -54
- package/package.json +13 -11
- package/dist/bundle/chunks/chunk-57XI7YJH.js +0 -2
- package/dist/bundle/chunks/chunk-JI4WGISU.js +0 -2
- package/dist/bundle/chunks/chunk-TCQ4HHQD.js +0 -2
- package/dist/bundle/chunks/chunk-TXJO4AJ7.js +0 -11
- package/dist/bundle/chunks/cloudflare-workers-ai-system-one-OWU5SM6J.js +0 -2
- package/dist/bundle/chunks/typesafe-system-one-KYKKNU3E.js +0 -2
- package/dist/extensions/web-search/auth.d.ts +0 -12
- package/dist/extensions/web-search/auth.d.ts.map +0 -1
- package/dist/extensions/web-search/auth.js +0 -90
- package/dist/extensions/web-search/auth.js.map +0 -1
- package/dist/extensions/web-search/constants.d.ts +0 -13
- package/dist/extensions/web-search/constants.d.ts.map +0 -1
- package/dist/extensions/web-search/constants.js +0 -26
- package/dist/extensions/web-search/constants.js.map +0 -1
- package/dist/extensions/web-search/execute.d.ts +0 -9
- package/dist/extensions/web-search/execute.d.ts.map +0 -1
- package/dist/extensions/web-search/execute.js +0 -96
- package/dist/extensions/web-search/execute.js.map +0 -1
- package/dist/extensions/web-search/format.d.ts +0 -21
- package/dist/extensions/web-search/format.d.ts.map +0 -1
- package/dist/extensions/web-search/format.js +0 -117
- package/dist/extensions/web-search/format.js.map +0 -1
- package/dist/extensions/web-search/index.d.ts +0 -6
- package/dist/extensions/web-search/index.d.ts.map +0 -1
- package/dist/extensions/web-search/index.js +0 -50
- package/dist/extensions/web-search/index.js.map +0 -1
- package/dist/extensions/web-search/providers/deepseek.d.ts +0 -13
- package/dist/extensions/web-search/providers/deepseek.d.ts.map +0 -1
- package/dist/extensions/web-search/providers/deepseek.js +0 -110
- package/dist/extensions/web-search/providers/deepseek.js.map +0 -1
- package/dist/extensions/web-search/providers/http.d.ts +0 -4
- package/dist/extensions/web-search/providers/http.d.ts.map +0 -1
- package/dist/extensions/web-search/providers/http.js +0 -42
- package/dist/extensions/web-search/providers/http.js.map +0 -1
- package/dist/extensions/web-search/providers/minimax.d.ts +0 -12
- package/dist/extensions/web-search/providers/minimax.d.ts.map +0 -1
- package/dist/extensions/web-search/providers/minimax.js +0 -61
- package/dist/extensions/web-search/providers/minimax.js.map +0 -1
- package/dist/extensions/web-search/render.d.ts +0 -15
- package/dist/extensions/web-search/render.d.ts.map +0 -1
- package/dist/extensions/web-search/render.js +0 -142
- package/dist/extensions/web-search/render.js.map +0 -1
- package/dist/extensions/web-search/results.d.ts +0 -35
- package/dist/extensions/web-search/results.d.ts.map +0 -1
- package/dist/extensions/web-search/results.js +0 -167
- package/dist/extensions/web-search/results.js.map +0 -1
- package/dist/extensions/web-search/schema.d.ts +0 -10
- package/dist/extensions/web-search/schema.d.ts.map +0 -1
- package/dist/extensions/web-search/schema.js +0 -36
- package/dist/extensions/web-search/schema.js.map +0 -1
- package/dist/extensions/web-search/types.d.ts +0 -51
- package/dist/extensions/web-search/types.d.ts.map +0 -1
- package/dist/extensions/web-search/types.js +0 -5
- package/dist/extensions/web-search/types.js.map +0 -1
- package/docs/bundled/extensions/web-search.md +0 -46
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* code_search: Devin's SWE-grep model plans, this process executes.
|
|
3
|
+
*
|
|
4
|
+
* Each round sends the transcript to Devin, which replies with one tool call: `restricted_exec`
|
|
5
|
+
* (up to eight rg/readfile/tree commands, run locally by `Workspace`) or `answer` (an `<ANSWER>`
|
|
6
|
+
* document of files and line ranges). After `MAX_TURNS` rounds the answer is demanded.
|
|
7
|
+
*/
|
|
8
|
+
import { randomUUID } from "node:crypto";
|
|
9
|
+
import { relative } from "node:path";
|
|
10
|
+
import { chat } from "./devin.js";
|
|
11
|
+
import { FORCE_ANSWER, MAX_COMMANDS, MAX_RESULTS, MAX_TURNS, RETRY_TOOL_CALL, SYSTEM_PROMPT, TOOL_DEFINITIONS, } from "./prompt.js";
|
|
12
|
+
import { VIRTUAL_ROOT, Workspace } from "./workspace.js";
|
|
13
|
+
export async function codeSearch(options) {
|
|
14
|
+
const { apiKey, query, signal } = options;
|
|
15
|
+
const progress = options.onProgress ?? (() => { });
|
|
16
|
+
const workspace = options.workspace ?? new Workspace(options.root);
|
|
17
|
+
const map = workspace.repoMap();
|
|
18
|
+
const messages = [
|
|
19
|
+
{ role: "system", content: SYSTEM_PROMPT },
|
|
20
|
+
{
|
|
21
|
+
role: "user",
|
|
22
|
+
content: `Problem Statement: ${query}\n\nRepo Map (tree -L ${map.depth} ${VIRTUAL_ROOT}):\n\`\`\`text\n${map.tree}\n\`\`\``,
|
|
23
|
+
},
|
|
24
|
+
];
|
|
25
|
+
for (let turn = 1; turn <= MAX_TURNS + 1; turn++) {
|
|
26
|
+
if (turn === MAX_TURNS + 1)
|
|
27
|
+
messages.push({ role: "user", content: FORCE_ANSWER });
|
|
28
|
+
progress(turn > MAX_TURNS ? "Collecting the answer…" : `Planning (round ${turn}/${MAX_TURNS})…`);
|
|
29
|
+
const reply = await chat(apiKey, messages, TOOL_DEFINITIONS, signal);
|
|
30
|
+
const call = parseToolCall(reply);
|
|
31
|
+
if (call?.name === "answer") {
|
|
32
|
+
return parseAnswer(String(call.args.answer ?? ""), workspace, options.cwd);
|
|
33
|
+
}
|
|
34
|
+
if (call?.name !== "restricted_exec") {
|
|
35
|
+
messages.push({ role: "assistant", content: reply }, { role: "user", content: RETRY_TOOL_CALL });
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
const keys = Object.keys(call.args)
|
|
39
|
+
.filter((k) => /^command\d+$/.test(k))
|
|
40
|
+
.sort((a, b) => Number(a.slice(7)) - Number(b.slice(7)))
|
|
41
|
+
.slice(0, MAX_COMMANDS);
|
|
42
|
+
progress(`Running ${keys.length} ${keys.length === 1 ? "command" : "commands"} (round ${turn}/${MAX_TURNS})…`);
|
|
43
|
+
const results = await Promise.all(keys.map(async (k) => `<${k}_result>\n${await workspace.run(call.args[k], signal)}\n</${k}_result>`));
|
|
44
|
+
const id = randomUUID();
|
|
45
|
+
messages.push({
|
|
46
|
+
role: "assistant",
|
|
47
|
+
content: call.thinking,
|
|
48
|
+
call: { id, name: call.name, arguments: JSON.stringify(call.args) },
|
|
49
|
+
}, { role: "tool", content: results.join(""), callId: id });
|
|
50
|
+
}
|
|
51
|
+
return [];
|
|
52
|
+
}
|
|
53
|
+
/** `thinking[TOOL_CALLS]name[ARGS]{json}`. The JSON is the first balanced object, repaired if needed. */
|
|
54
|
+
export function parseToolCall(text) {
|
|
55
|
+
const m = text.match(/\[TOOL_CALLS\]\s*(\w+)\s*\[ARGS\]\s*(\{[\s\S]*)/);
|
|
56
|
+
if (!m)
|
|
57
|
+
return null;
|
|
58
|
+
// Repair before scanning: a stray key quote would otherwise end the scan inside a "string".
|
|
59
|
+
for (const candidate of [firstObject(m[2]), firstObject(repairKeys(m[2]))]) {
|
|
60
|
+
try {
|
|
61
|
+
const args = JSON.parse(candidate);
|
|
62
|
+
if (args && typeof args === "object" && !Array.isArray(args)) {
|
|
63
|
+
return { thinking: text.slice(0, m.index).trim(), name: m[1], args: args };
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
catch { }
|
|
67
|
+
}
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
function firstObject(text) {
|
|
71
|
+
let depth = 0;
|
|
72
|
+
let inString = false;
|
|
73
|
+
for (let i = 0; i < text.length; i++) {
|
|
74
|
+
const c = text[i];
|
|
75
|
+
if (inString) {
|
|
76
|
+
if (c === "\\")
|
|
77
|
+
i++;
|
|
78
|
+
else if (c === '"')
|
|
79
|
+
inString = false;
|
|
80
|
+
}
|
|
81
|
+
else if (c === '"')
|
|
82
|
+
inString = true;
|
|
83
|
+
else if (c === "{")
|
|
84
|
+
depth++;
|
|
85
|
+
else if (c === "}" && --depth === 0)
|
|
86
|
+
return text.slice(0, i + 1);
|
|
87
|
+
}
|
|
88
|
+
return text;
|
|
89
|
+
}
|
|
90
|
+
/** The planner sometimes drops a key's quotes, as in `,exclude":[]` or `{path:"…"}`. */
|
|
91
|
+
function repairKeys(json) {
|
|
92
|
+
return json.replace(/([{,]\s*)"?([A-Za-z_]\w*)"?\s*:/g, '$1"$2":');
|
|
93
|
+
}
|
|
94
|
+
const FILE_RE = /<file\s+path=(["'])(.+?)\1\s*>([\s\S]*?)<\/file>/g;
|
|
95
|
+
const RANGE_RE = /<range>\s*(\d+)\s*-\s*(\d+)\s*<\/range>/g;
|
|
96
|
+
/** The files of an `<ANSWER>`, each re-checked against the workspace; anything outside it is dropped. */
|
|
97
|
+
export function parseAnswer(xml, workspace, cwd) {
|
|
98
|
+
const locations = [];
|
|
99
|
+
for (const [, , path, body] of xml.matchAll(FILE_RE)) {
|
|
100
|
+
const real = workspace.toReal(path);
|
|
101
|
+
if (!real)
|
|
102
|
+
continue;
|
|
103
|
+
locations.push({
|
|
104
|
+
path: relative(cwd, real).replace(/\\/g, "/") || ".",
|
|
105
|
+
ranges: [...body.matchAll(RANGE_RE)]
|
|
106
|
+
.map(([, start, end]) => [Number(start), Number(end)])
|
|
107
|
+
.sort((a, b) => a[0] - b[0]),
|
|
108
|
+
});
|
|
109
|
+
if (locations.length === MAX_RESULTS)
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
return locations;
|
|
113
|
+
}
|
|
114
|
+
export function formatLocations(locations) {
|
|
115
|
+
if (!locations.length)
|
|
116
|
+
return "No relevant code found. Try a narrower behavioral query, or grep for a known identifier.";
|
|
117
|
+
const lines = locations.map(({ path, ranges }) => `${path}${ranges.length ? ` (${ranges.map(([s, e]) => `L${s}-${e}`).join(", ")})` : ""}`);
|
|
118
|
+
return `${locations.length} candidate ${locations.length === 1 ? "location" : "locations"}; read them before relying on them:\n${lines.join("\n")}`;
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=code-search.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"code-search.js","sourceRoot":"","sources":["../../../src/extensions/search/code-search.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * code_search: Devin's SWE-grep model plans, this process executes.\n *\n * Each round sends the transcript to Devin, which replies with one tool call: `restricted_exec`\n * (up to eight rg/readfile/tree commands, run locally by `Workspace`) or `answer` (an `<ANSWER>`\n * document of files and line ranges). After `MAX_TURNS` rounds the answer is demanded.\n */\nimport { randomUUID } from \"node:crypto\";\nimport { relative } from \"node:path\";\nimport { type ChatMessage, chat } from \"./devin.ts\";\nimport {\n\tFORCE_ANSWER,\n\tMAX_COMMANDS,\n\tMAX_RESULTS,\n\tMAX_TURNS,\n\tRETRY_TOOL_CALL,\n\tSYSTEM_PROMPT,\n\tTOOL_DEFINITIONS,\n} from \"./prompt.ts\";\nimport { type Command, VIRTUAL_ROOT, Workspace } from \"./workspace.ts\";\n\nexport interface CodeLocation {\n\t/** Relative to the session's working directory. */\n\tpath: string;\n\tranges: Array<[number, number]>;\n}\n\nexport interface CodeSearchOptions {\n\tapiKey: string;\n\tquery: string;\n\t/** Directory to search; already confined to the working directory by the caller. */\n\troot: string;\n\tcwd: string;\n\tsignal?: AbortSignal;\n\tonProgress?: (message: string) => void;\n\t/** Test seam for the local `rg` backend. */\n\tworkspace?: Workspace;\n}\n\nexport async function codeSearch(options: CodeSearchOptions): Promise<CodeLocation[]> {\n\tconst { apiKey, query, signal } = options;\n\tconst progress = options.onProgress ?? (() => {});\n\tconst workspace = options.workspace ?? new Workspace(options.root);\n\tconst map = workspace.repoMap();\n\tconst messages: ChatMessage[] = [\n\t\t{ role: \"system\", content: SYSTEM_PROMPT },\n\t\t{\n\t\t\trole: \"user\",\n\t\t\tcontent: `Problem Statement: ${query}\\n\\nRepo Map (tree -L ${map.depth} ${VIRTUAL_ROOT}):\\n\\`\\`\\`text\\n${map.tree}\\n\\`\\`\\``,\n\t\t},\n\t];\n\n\tfor (let turn = 1; turn <= MAX_TURNS + 1; turn++) {\n\t\tif (turn === MAX_TURNS + 1) messages.push({ role: \"user\", content: FORCE_ANSWER });\n\t\tprogress(turn > MAX_TURNS ? \"Collecting the answer…\" : `Planning (round ${turn}/${MAX_TURNS})…`);\n\t\tconst reply = await chat(apiKey, messages, TOOL_DEFINITIONS, signal);\n\t\tconst call = parseToolCall(reply);\n\n\t\tif (call?.name === \"answer\") {\n\t\t\treturn parseAnswer(String(call.args.answer ?? \"\"), workspace, options.cwd);\n\t\t}\n\t\tif (call?.name !== \"restricted_exec\") {\n\t\t\tmessages.push({ role: \"assistant\", content: reply }, { role: \"user\", content: RETRY_TOOL_CALL });\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst keys = Object.keys(call.args)\n\t\t\t.filter((k) => /^command\\d+$/.test(k))\n\t\t\t.sort((a, b) => Number(a.slice(7)) - Number(b.slice(7)))\n\t\t\t.slice(0, MAX_COMMANDS);\n\t\tprogress(`Running ${keys.length} ${keys.length === 1 ? \"command\" : \"commands\"} (round ${turn}/${MAX_TURNS})…`);\n\t\tconst results = await Promise.all(\n\t\t\tkeys.map(\n\t\t\t\tasync (k) => `<${k}_result>\\n${await workspace.run(call.args[k] as Command, signal)}\\n</${k}_result>`,\n\t\t\t),\n\t\t);\n\t\tconst id = randomUUID();\n\t\tmessages.push(\n\t\t\t{\n\t\t\t\trole: \"assistant\",\n\t\t\t\tcontent: call.thinking,\n\t\t\t\tcall: { id, name: call.name, arguments: JSON.stringify(call.args) },\n\t\t\t},\n\t\t\t{ role: \"tool\", content: results.join(\"\"), callId: id },\n\t\t);\n\t}\n\treturn [];\n}\n\nexport interface ToolCall {\n\tthinking: string;\n\tname: string;\n\targs: Record<string, unknown>;\n}\n\n/** `thinking[TOOL_CALLS]name[ARGS]{json}`. The JSON is the first balanced object, repaired if needed. */\nexport function parseToolCall(text: string): ToolCall | null {\n\tconst m = text.match(/\\[TOOL_CALLS\\]\\s*(\\w+)\\s*\\[ARGS\\]\\s*(\\{[\\s\\S]*)/);\n\tif (!m) return null;\n\t// Repair before scanning: a stray key quote would otherwise end the scan inside a \"string\".\n\tfor (const candidate of [firstObject(m[2]!), firstObject(repairKeys(m[2]!))]) {\n\t\ttry {\n\t\t\tconst args = JSON.parse(candidate) as unknown;\n\t\t\tif (args && typeof args === \"object\" && !Array.isArray(args)) {\n\t\t\t\treturn { thinking: text.slice(0, m.index).trim(), name: m[1]!, args: args as Record<string, unknown> };\n\t\t\t}\n\t\t} catch {}\n\t}\n\treturn null;\n}\n\nfunction firstObject(text: string): string {\n\tlet depth = 0;\n\tlet inString = false;\n\tfor (let i = 0; i < text.length; i++) {\n\t\tconst c = text[i];\n\t\tif (inString) {\n\t\t\tif (c === \"\\\\\") i++;\n\t\t\telse if (c === '\"') inString = false;\n\t\t} else if (c === '\"') inString = true;\n\t\telse if (c === \"{\") depth++;\n\t\telse if (c === \"}\" && --depth === 0) return text.slice(0, i + 1);\n\t}\n\treturn text;\n}\n\n/** The planner sometimes drops a key's quotes, as in `,exclude\":[]` or `{path:\"…\"}`. */\nfunction repairKeys(json: string): string {\n\treturn json.replace(/([{,]\\s*)\"?([A-Za-z_]\\w*)\"?\\s*:/g, '$1\"$2\":');\n}\n\nconst FILE_RE = /<file\\s+path=([\"'])(.+?)\\1\\s*>([\\s\\S]*?)<\\/file>/g;\nconst RANGE_RE = /<range>\\s*(\\d+)\\s*-\\s*(\\d+)\\s*<\\/range>/g;\n\n/** The files of an `<ANSWER>`, each re-checked against the workspace; anything outside it is dropped. */\nexport function parseAnswer(xml: string, workspace: Workspace, cwd: string): CodeLocation[] {\n\tconst locations: CodeLocation[] = [];\n\tfor (const [, , path, body] of xml.matchAll(FILE_RE)) {\n\t\tconst real = workspace.toReal(path);\n\t\tif (!real) continue;\n\t\tlocations.push({\n\t\t\tpath: relative(cwd, real).replace(/\\\\/g, \"/\") || \".\",\n\t\t\tranges: [...body!.matchAll(RANGE_RE)]\n\t\t\t\t.map(([, start, end]): [number, number] => [Number(start), Number(end)])\n\t\t\t\t.sort((a, b) => a[0] - b[0]),\n\t\t});\n\t\tif (locations.length === MAX_RESULTS) break;\n\t}\n\treturn locations;\n}\n\nexport function formatLocations(locations: CodeLocation[]): string {\n\tif (!locations.length)\n\t\treturn \"No relevant code found. Try a narrower behavioral query, or grep for a known identifier.\";\n\tconst lines = locations.map(\n\t\t({ path, ranges }) => `${path}${ranges.length ? ` (${ranges.map(([s, e]) => `L${s}-${e}`).join(\", \")})` : \"\"}`,\n\t);\n\treturn `${locations.length} candidate ${locations.length === 1 ? \"location\" : \"locations\"}; read them before relying on them:\\n${lines.join(\"\\n\")}`;\n}\n"]}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { ExtensionAPI } from "../../core/extensions/types.ts";
|
|
2
|
+
/** Deactivate both tools when no credential exists; `activate` re-enables them after a sign-in. */
|
|
3
|
+
export declare function syncTools(pi: ExtensionAPI, activate?: boolean): void;
|
|
4
|
+
export declare function registerSearchCommand(pi: ExtensionAPI): void;
|
|
5
|
+
//# sourceMappingURL=command.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"command.d.ts","sourceRoot":"","sources":["../../../src/extensions/search/command.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AAwBnE,mGAAmG;AACnG,wBAAgB,SAAS,CAAC,EAAE,EAAE,YAAY,EAAE,QAAQ,UAAQ,GAAG,IAAI,CASlE;AA+BD,wBAAgB,qBAAqB,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CAoB5D"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/** `/search` and the tool activation that follows the account state. */
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { openBrowser } from "../../utils/open-browser.js";
|
|
4
|
+
import { createSignIn, credentialPath, deleteCredential, ENV_KEY, exchangeCode, getCredential, looksLikeToken, saveCredential, toApiKey, } from "./credential.js";
|
|
5
|
+
import { webSearch } from "./devin.js";
|
|
6
|
+
import { SearchPanel } from "./panel.js";
|
|
7
|
+
/** `~/.pi/agent/search-auth.json` rather than the full home path, so panel rows stay short. */
|
|
8
|
+
function displayPath(path) {
|
|
9
|
+
const home = homedir();
|
|
10
|
+
return path.startsWith(home) ? `~${path.slice(home.length).replace(/\\/g, "/")}` : path;
|
|
11
|
+
}
|
|
12
|
+
const TOOL_NAMES = ["code_search", "web_search"];
|
|
13
|
+
/** Deactivate both tools when no credential exists; `activate` re-enables them after a sign-in. */
|
|
14
|
+
export function syncTools(pi, activate = false) {
|
|
15
|
+
const active = pi.getActiveTools();
|
|
16
|
+
if (!getCredential()) {
|
|
17
|
+
const next = active.filter((name) => !TOOL_NAMES.includes(name));
|
|
18
|
+
if (next.length !== active.length)
|
|
19
|
+
pi.setActiveTools(next);
|
|
20
|
+
}
|
|
21
|
+
else if (activate) {
|
|
22
|
+
const missing = TOOL_NAMES.filter((name) => !active.includes(name));
|
|
23
|
+
if (missing.length)
|
|
24
|
+
pi.setActiveTools([...active, ...missing]);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function accountActions(pi) {
|
|
28
|
+
/** One cheap authenticated call, so a bad token fails here rather than at the model's first search. */
|
|
29
|
+
const verifyAndSave = async (token) => {
|
|
30
|
+
await webSearch(toApiKey(token), "Devin", 1);
|
|
31
|
+
saveCredential(token);
|
|
32
|
+
syncTools(pi, true);
|
|
33
|
+
};
|
|
34
|
+
return {
|
|
35
|
+
state() {
|
|
36
|
+
const source = getCredential()?.source;
|
|
37
|
+
return { source, location: source === "env" ? ENV_KEY : displayPath(credentialPath()) };
|
|
38
|
+
},
|
|
39
|
+
startSignIn() {
|
|
40
|
+
const attempt = createSignIn();
|
|
41
|
+
openBrowser(attempt.url);
|
|
42
|
+
return {
|
|
43
|
+
url: attempt.url,
|
|
44
|
+
complete: async (input) => verifyAndSave(looksLikeToken(input) ? input : await exchangeCode(attempt, input)),
|
|
45
|
+
};
|
|
46
|
+
},
|
|
47
|
+
saveToken: verifyAndSave,
|
|
48
|
+
signOut() {
|
|
49
|
+
deleteCredential();
|
|
50
|
+
syncTools(pi);
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
export function registerSearchCommand(pi) {
|
|
55
|
+
pi.registerCommand("search", {
|
|
56
|
+
description: "Sign in to Devin for code_search and web_search",
|
|
57
|
+
handler: async (_args, ctx) => {
|
|
58
|
+
if (!ctx.hasUI) {
|
|
59
|
+
const credential = getCredential();
|
|
60
|
+
ctx.ui.notify(credential
|
|
61
|
+
? `Devin Search: signed in ($credential.source === "env" ? ENV_KEY : credentialPath()).`
|
|
62
|
+
: `Devin Search: not signed in. Run /search interactively, or set $ENV_KEY.`, credential ? "info" : "warning");
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
await ctx.ui.custom((tui, theme, keybindings, done) => new SearchPanel(tui, theme, keybindings, accountActions(pi), () => done()));
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=command.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"command.js","sourceRoot":"","sources":["../../../src/extensions/search/command.ts"],"names":[],"mappings":"AAAA,wEAAwE;AACxE,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAElC,OAAO,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAC1D,OAAO,EACN,YAAY,EACZ,cAAc,EACd,gBAAgB,EAChB,OAAO,EACP,YAAY,EACZ,aAAa,EACb,cAAc,EACd,cAAc,EACd,QAAQ,GACR,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,EAAqB,WAAW,EAAE,MAAM,YAAY,CAAC;AAE5D,+FAA+F;AAC/F,SAAS,WAAW,CAAC,IAAY;IAChC,MAAM,IAAI,GAAG,OAAO,EAAE,CAAC;IACvB,OAAO,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;AACzF,CAAC;AAED,MAAM,UAAU,GAAG,CAAC,aAAa,EAAE,YAAY,CAAC,CAAC;AAEjD,mGAAmG;AACnG,MAAM,UAAU,SAAS,CAAC,EAAgB,EAAE,QAAQ,GAAG,KAAK;IAC3D,MAAM,MAAM,GAAG,EAAE,CAAC,cAAc,EAAE,CAAC;IACnC,IAAI,CAAC,aAAa,EAAE,EAAE,CAAC;QACtB,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,UAAU,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC;QACjE,IAAI,IAAI,CAAC,MAAM,KAAK,MAAM,CAAC,MAAM;YAAE,EAAE,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC;IAC5D,CAAC;SAAM,IAAI,QAAQ,EAAE,CAAC;QACrB,MAAM,OAAO,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC;QACpE,IAAI,OAAO,CAAC,MAAM;YAAE,EAAE,CAAC,cAAc,CAAC,CAAC,GAAG,MAAM,EAAE,GAAG,OAAO,CAAC,CAAC,CAAC;IAChE,CAAC;AACF,CAAC;AAED,SAAS,cAAc,CAAC,EAAgB;IACvC,uGAAuG;IACvG,MAAM,aAAa,GAAG,KAAK,EAAE,KAAa,EAAE,EAAE;QAC7C,MAAM,SAAS,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC;QAC7C,cAAc,CAAC,KAAK,CAAC,CAAC;QACtB,SAAS,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;IACrB,CAAC,CAAC;IACF,OAAO;QACN,KAAK;YACJ,MAAM,MAAM,GAAG,aAAa,EAAE,EAAE,MAAM,CAAC;YACvC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,WAAW,CAAC,cAAc,EAAE,CAAC,EAAE,CAAC;QACzF,CAAC;QACD,WAAW;YACV,MAAM,OAAO,GAAG,YAAY,EAAE,CAAC;YAC/B,WAAW,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;YACzB,OAAO;gBACN,GAAG,EAAE,OAAO,CAAC,GAAG;gBAChB,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,CACzB,aAAa,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,YAAY,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;aAClF,CAAC;QACH,CAAC;QACD,SAAS,EAAE,aAAa;QACxB,OAAO;YACN,gBAAgB,EAAE,CAAC;YACnB,SAAS,CAAC,EAAE,CAAC,CAAC;QACf,CAAC;KACD,CAAC;AACH,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,EAAgB;IACrD,EAAE,CAAC,eAAe,CAAC,QAAQ,EAAE;QAC5B,WAAW,EAAE,iDAAiD;QAC9D,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;YAC7B,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC;gBAChB,MAAM,UAAU,GAAG,aAAa,EAAE,CAAC;gBACnC,GAAG,CAAC,EAAE,CAAC,MAAM,CACZ,UAAU;oBACT,CAAC,CAAC,sFAAsF;oBACxF,CAAC,CAAC,0EAA0E,EAC7E,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAC/B,CAAC;gBACF,OAAO;YACR,CAAC;YACD,MAAM,GAAG,CAAC,EAAE,CAAC,MAAM,CAClB,CAAC,GAAG,EAAE,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,EAAE,CACjC,IAAI,WAAW,CAAC,GAAG,EAAE,KAAK,EAAE,WAAW,EAAE,cAAc,CAAC,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,IAAI,EAAE,CAAC,CAC3E,CAAC;QACH,CAAC;KACD,CAAC,CAAC;AACJ,CAAC","sourcesContent":["/** `/search` and the tool activation that follows the account state. */\nimport { homedir } from \"node:os\";\nimport type { ExtensionAPI } from \"../../core/extensions/types.ts\";\nimport { openBrowser } from \"../../utils/open-browser.ts\";\nimport {\n\tcreateSignIn,\n\tcredentialPath,\n\tdeleteCredential,\n\tENV_KEY,\n\texchangeCode,\n\tgetCredential,\n\tlooksLikeToken,\n\tsaveCredential,\n\ttoApiKey,\n} from \"./credential.ts\";\nimport { webSearch } from \"./devin.ts\";\nimport { type PanelActions, SearchPanel } from \"./panel.ts\";\n\n/** `~/.pi/agent/search-auth.json` rather than the full home path, so panel rows stay short. */\nfunction displayPath(path: string): string {\n\tconst home = homedir();\n\treturn path.startsWith(home) ? `~${path.slice(home.length).replace(/\\\\/g, \"/\")}` : path;\n}\n\nconst TOOL_NAMES = [\"code_search\", \"web_search\"];\n\n/** Deactivate both tools when no credential exists; `activate` re-enables them after a sign-in. */\nexport function syncTools(pi: ExtensionAPI, activate = false): void {\n\tconst active = pi.getActiveTools();\n\tif (!getCredential()) {\n\t\tconst next = active.filter((name) => !TOOL_NAMES.includes(name));\n\t\tif (next.length !== active.length) pi.setActiveTools(next);\n\t} else if (activate) {\n\t\tconst missing = TOOL_NAMES.filter((name) => !active.includes(name));\n\t\tif (missing.length) pi.setActiveTools([...active, ...missing]);\n\t}\n}\n\nfunction accountActions(pi: ExtensionAPI): PanelActions {\n\t/** One cheap authenticated call, so a bad token fails here rather than at the model's first search. */\n\tconst verifyAndSave = async (token: string) => {\n\t\tawait webSearch(toApiKey(token), \"Devin\", 1);\n\t\tsaveCredential(token);\n\t\tsyncTools(pi, true);\n\t};\n\treturn {\n\t\tstate() {\n\t\t\tconst source = getCredential()?.source;\n\t\t\treturn { source, location: source === \"env\" ? ENV_KEY : displayPath(credentialPath()) };\n\t\t},\n\t\tstartSignIn() {\n\t\t\tconst attempt = createSignIn();\n\t\t\topenBrowser(attempt.url);\n\t\t\treturn {\n\t\t\t\turl: attempt.url,\n\t\t\t\tcomplete: async (input) =>\n\t\t\t\t\tverifyAndSave(looksLikeToken(input) ? input : await exchangeCode(attempt, input)),\n\t\t\t};\n\t\t},\n\t\tsaveToken: verifyAndSave,\n\t\tsignOut() {\n\t\t\tdeleteCredential();\n\t\t\tsyncTools(pi);\n\t\t},\n\t};\n}\n\nexport function registerSearchCommand(pi: ExtensionAPI): void {\n\tpi.registerCommand(\"search\", {\n\t\tdescription: \"Sign in to Devin for code_search and web_search\",\n\t\thandler: async (_args, ctx) => {\n\t\t\tif (!ctx.hasUI) {\n\t\t\t\tconst credential = getCredential();\n\t\t\t\tctx.ui.notify(\n\t\t\t\t\tcredential\n\t\t\t\t\t\t? `Devin Search: signed in ($credential.source === \"env\" ? ENV_KEY : credentialPath()).`\n\t\t\t\t\t\t: `Devin Search: not signed in. Run /search interactively, or set $ENV_KEY.`,\n\t\t\t\t\tcredential ? \"info\" : \"warning\",\n\t\t\t\t);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tawait ctx.ui.custom<void>(\n\t\t\t\t(tui, theme, keybindings, done) =>\n\t\t\t\t\tnew SearchPanel(tui, theme, keybindings, accountActions(pi), () => done()),\n\t\t\t);\n\t\t},\n\t});\n}\n"]}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export declare const ENV_KEY = "SEARCH_KEY";
|
|
2
|
+
/** `{ "devin": { "token": "<as Devin issued it>" } }`, beside auth.json and mcp-auth.json. */
|
|
3
|
+
export declare function credentialPath(): string;
|
|
4
|
+
/** A Devin session JWT gains its prefix; anything else (an already prefixed key) passes through. */
|
|
5
|
+
export declare function toApiKey(token: string): string;
|
|
6
|
+
export type CredentialSource = "saved" | "env";
|
|
7
|
+
/** Read on every call, so a sign-in elsewhere or an edited file applies without a restart. */
|
|
8
|
+
export declare function getCredential(): {
|
|
9
|
+
apiKey: string;
|
|
10
|
+
source: CredentialSource;
|
|
11
|
+
} | undefined;
|
|
12
|
+
/** Saves the token exactly as Devin issued it; the request prefix is added at use. */
|
|
13
|
+
export declare function saveCredential(token: string): void;
|
|
14
|
+
export declare function deleteCredential(): void;
|
|
15
|
+
/** Whether pasted text is a token rather than a one-time authorization code. */
|
|
16
|
+
export declare function looksLikeToken(value: string): boolean;
|
|
17
|
+
export interface SignInAttempt {
|
|
18
|
+
url: string;
|
|
19
|
+
/** Never leaves this process. */
|
|
20
|
+
verifier: string;
|
|
21
|
+
}
|
|
22
|
+
/** PKCE S256 without a redirect: Devin shows the code in the browser and the user pastes it back. */
|
|
23
|
+
export declare function createSignIn(): SignInAttempt;
|
|
24
|
+
export declare function exchangeCode(attempt: SignInAttempt, input: string, fetcher?: typeof fetch): Promise<string>;
|
|
25
|
+
//# sourceMappingURL=credential.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credential.d.ts","sourceRoot":"","sources":["../../../src/extensions/search/credential.ts"],"names":[],"mappings":"AAYA,eAAO,MAAM,OAAO,eAAe,CAAC;AAOpC,8FAA8F;AAC9F,wBAAgB,cAAc,IAAI,MAAM,CAEvC;AAID,oGAAoG;AACpG,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAG9C;AA+BD,MAAM,MAAM,gBAAgB,GAAG,OAAO,GAAG,KAAK,CAAC;AAE/C,8FAA8F;AAC9F,wBAAgB,aAAa,IAAI;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,gBAAgB,CAAA;CAAE,GAAG,SAAS,CAKxF;AAED,sFAAsF;AACtF,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAIlD;AAED,wBAAgB,gBAAgB,IAAI,IAAI,CAKvC;AAED,gFAAgF;AAChF,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAErD;AAED,MAAM,WAAW,aAAa;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,iCAAiC;IACjC,QAAQ,EAAE,MAAM,CAAC;CACjB;AAED,qGAAqG;AACrG,wBAAgB,YAAY,IAAI,aAAa,CAU5C;AAED,wBAAsB,YAAY,CACjC,OAAO,EAAE,aAAa,EACtB,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,OAAO,KAAa,GAC3B,OAAO,CAAC,MAAM,CAAC,CAyBjB"}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one Devin credential both tools share, and the browser sign-in that obtains it.
|
|
3
|
+
*
|
|
4
|
+
* Devin's CLI sign-in returns a bare session JWT; the Windsurf backends accept it only as
|
|
5
|
+
* `devin-session-token$<JWT>`. `toApiKey` is the single place that rule lives.
|
|
6
|
+
*/
|
|
7
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
8
|
+
import { existsSync } from "node:fs";
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
import { getAgentDir } from "../../config.js";
|
|
11
|
+
import { FileAuthStorageBackend } from "../../core/auth-storage.js";
|
|
12
|
+
export const ENV_KEY = "SEARCH_KEY";
|
|
13
|
+
const SESSION_PREFIX = "devin-session-token$";
|
|
14
|
+
const AUTHORIZE_URL = "https://app.devin.ai/auth/cli/continue";
|
|
15
|
+
const TOKEN_URL = "https://api.devin.ai/auth/cli/token";
|
|
16
|
+
const MAX_INPUT_CHARS = 16_384;
|
|
17
|
+
/** `{ "devin": { "token": "<as Devin issued it>" } }`, beside auth.json and mcp-auth.json. */
|
|
18
|
+
export function credentialPath() {
|
|
19
|
+
return join(getAgentDir(), "search-auth.json");
|
|
20
|
+
}
|
|
21
|
+
const PROVIDER = "devin";
|
|
22
|
+
/** A Devin session JWT gains its prefix; anything else (an already prefixed key) passes through. */
|
|
23
|
+
export function toApiKey(token) {
|
|
24
|
+
const value = token.trim();
|
|
25
|
+
return /^eyJ[\w-]*\.[\w-]*\.[\w-]*$/.test(value) ? `${SESSION_PREFIX}${value}` : value;
|
|
26
|
+
}
|
|
27
|
+
function parseStored(current) {
|
|
28
|
+
try {
|
|
29
|
+
const data = JSON.parse(current ?? "{}");
|
|
30
|
+
return data && typeof data === "object" && !Array.isArray(data) ? data : {};
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return {};
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/** Read-modify-write under the file lock that auth.json and mcp-auth.json also use. */
|
|
37
|
+
function updateStored(change) {
|
|
38
|
+
new FileAuthStorageBackend(credentialPath()).withLock((current) => {
|
|
39
|
+
const stored = parseStored(current);
|
|
40
|
+
change(stored);
|
|
41
|
+
return { result: undefined, next: `${JSON.stringify(stored, null, 2)}\n` };
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
function readSaved() {
|
|
45
|
+
// Checked first so a session that never signs in does not create the file.
|
|
46
|
+
if (!existsSync(credentialPath()))
|
|
47
|
+
return undefined;
|
|
48
|
+
const token = new FileAuthStorageBackend(credentialPath()).withLock((current) => ({
|
|
49
|
+
result: parseStored(current)[PROVIDER]?.token,
|
|
50
|
+
}));
|
|
51
|
+
return typeof token === "string" && token.trim() ? token.trim() : undefined;
|
|
52
|
+
}
|
|
53
|
+
/** Read on every call, so a sign-in elsewhere or an edited file applies without a restart. */
|
|
54
|
+
export function getCredential() {
|
|
55
|
+
const saved = readSaved();
|
|
56
|
+
if (saved)
|
|
57
|
+
return { apiKey: toApiKey(saved), source: "saved" };
|
|
58
|
+
const env = process.env[ENV_KEY]?.trim();
|
|
59
|
+
return env ? { apiKey: toApiKey(env), source: "env" } : undefined;
|
|
60
|
+
}
|
|
61
|
+
/** Saves the token exactly as Devin issued it; the request prefix is added at use. */
|
|
62
|
+
export function saveCredential(token) {
|
|
63
|
+
updateStored((stored) => {
|
|
64
|
+
stored[PROVIDER] = { token: token.trim() };
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
export function deleteCredential() {
|
|
68
|
+
if (!existsSync(credentialPath()))
|
|
69
|
+
return;
|
|
70
|
+
updateStored((stored) => {
|
|
71
|
+
delete stored[PROVIDER];
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
/** Whether pasted text is a token rather than a one-time authorization code. */
|
|
75
|
+
export function looksLikeToken(value) {
|
|
76
|
+
return value.startsWith(SESSION_PREFIX) || toApiKey(value) !== value;
|
|
77
|
+
}
|
|
78
|
+
/** PKCE S256 without a redirect: Devin shows the code in the browser and the user pastes it back. */
|
|
79
|
+
export function createSignIn() {
|
|
80
|
+
const verifier = randomBytes(64).toString("base64url");
|
|
81
|
+
const url = new URL(AUTHORIZE_URL);
|
|
82
|
+
url.search = new URLSearchParams({
|
|
83
|
+
state: randomBytes(32).toString("base64url"),
|
|
84
|
+
prompt: "select_account",
|
|
85
|
+
code_challenge: createHash("sha256").update(verifier).digest("base64url"),
|
|
86
|
+
code_challenge_method: "S256",
|
|
87
|
+
}).toString();
|
|
88
|
+
return { url: url.toString(), verifier };
|
|
89
|
+
}
|
|
90
|
+
export async function exchangeCode(attempt, input, fetcher = fetch) {
|
|
91
|
+
const code = input.trim();
|
|
92
|
+
if (!code || code.length > MAX_INPUT_CHARS || /\s|[\x00-\x1f\x7f]/.test(code)) {
|
|
93
|
+
throw new Error("That does not look like a Devin authorization code.");
|
|
94
|
+
}
|
|
95
|
+
let response;
|
|
96
|
+
try {
|
|
97
|
+
response = await fetcher(TOKEN_URL, {
|
|
98
|
+
method: "POST",
|
|
99
|
+
headers: { "content-type": "application/json", accept: "application/json" },
|
|
100
|
+
body: JSON.stringify({ code, code_verifier: attempt.verifier }),
|
|
101
|
+
redirect: "error",
|
|
102
|
+
signal: AbortSignal.timeout(60_000),
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
throw new Error("Could not reach Devin. Check your network and run /search again.");
|
|
107
|
+
}
|
|
108
|
+
if (!response.ok) {
|
|
109
|
+
throw new Error(`Devin rejected the code (HTTP ${response.status}). Codes are one-time; run /search again.`);
|
|
110
|
+
}
|
|
111
|
+
const token = (await response.json().catch(() => undefined))?.token;
|
|
112
|
+
if (typeof token !== "string" || !token || token.length > MAX_INPUT_CHARS) {
|
|
113
|
+
throw new Error("Devin returned no usable session token.");
|
|
114
|
+
}
|
|
115
|
+
return token;
|
|
116
|
+
}
|
|
117
|
+
//# sourceMappingURL=credential.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credential.js","sourceRoot":"","sources":["../../../src/extensions/search/credential.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The one Devin credential both tools share, and the browser sign-in that obtains it.\n *\n * Devin's CLI sign-in returns a bare session JWT; the Windsurf backends accept it only as\n * `devin-session-token$<JWT>`. `toApiKey` is the single place that rule lives.\n */\nimport { createHash, randomBytes } from \"node:crypto\";\nimport { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { getAgentDir } from \"../../config.ts\";\nimport { FileAuthStorageBackend } from \"../../core/auth-storage.ts\";\n\nexport const ENV_KEY = \"SEARCH_KEY\";\n\nconst SESSION_PREFIX = \"devin-session-token$\";\nconst AUTHORIZE_URL = \"https://app.devin.ai/auth/cli/continue\";\nconst TOKEN_URL = \"https://api.devin.ai/auth/cli/token\";\nconst MAX_INPUT_CHARS = 16_384;\n\n/** `{ \"devin\": { \"token\": \"<as Devin issued it>\" } }`, beside auth.json and mcp-auth.json. */\nexport function credentialPath(): string {\n\treturn join(getAgentDir(), \"search-auth.json\");\n}\n\nconst PROVIDER = \"devin\";\n\n/** A Devin session JWT gains its prefix; anything else (an already prefixed key) passes through. */\nexport function toApiKey(token: string): string {\n\tconst value = token.trim();\n\treturn /^eyJ[\\w-]*\\.[\\w-]*\\.[\\w-]*$/.test(value) ? `${SESSION_PREFIX}${value}` : value;\n}\n\ntype StoredTokens = Record<string, { token?: unknown } | undefined>;\n\nfunction parseStored(current: string | undefined): StoredTokens {\n\ttry {\n\t\tconst data = JSON.parse(current ?? \"{}\") as unknown;\n\t\treturn data && typeof data === \"object\" && !Array.isArray(data) ? (data as StoredTokens) : {};\n\t} catch {\n\t\treturn {};\n\t}\n}\n\n/** Read-modify-write under the file lock that auth.json and mcp-auth.json also use. */\nfunction updateStored(change: (stored: StoredTokens) => void): void {\n\tnew FileAuthStorageBackend(credentialPath()).withLock((current) => {\n\t\tconst stored = parseStored(current);\n\t\tchange(stored);\n\t\treturn { result: undefined, next: `${JSON.stringify(stored, null, 2)}\\n` };\n\t});\n}\n\nfunction readSaved(): string | undefined {\n\t// Checked first so a session that never signs in does not create the file.\n\tif (!existsSync(credentialPath())) return undefined;\n\tconst token = new FileAuthStorageBackend(credentialPath()).withLock((current) => ({\n\t\tresult: parseStored(current)[PROVIDER]?.token,\n\t}));\n\treturn typeof token === \"string\" && token.trim() ? token.trim() : undefined;\n}\n\nexport type CredentialSource = \"saved\" | \"env\";\n\n/** Read on every call, so a sign-in elsewhere or an edited file applies without a restart. */\nexport function getCredential(): { apiKey: string; source: CredentialSource } | undefined {\n\tconst saved = readSaved();\n\tif (saved) return { apiKey: toApiKey(saved), source: \"saved\" };\n\tconst env = process.env[ENV_KEY]?.trim();\n\treturn env ? { apiKey: toApiKey(env), source: \"env\" } : undefined;\n}\n\n/** Saves the token exactly as Devin issued it; the request prefix is added at use. */\nexport function saveCredential(token: string): void {\n\tupdateStored((stored) => {\n\t\tstored[PROVIDER] = { token: token.trim() };\n\t});\n}\n\nexport function deleteCredential(): void {\n\tif (!existsSync(credentialPath())) return;\n\tupdateStored((stored) => {\n\t\tdelete stored[PROVIDER];\n\t});\n}\n\n/** Whether pasted text is a token rather than a one-time authorization code. */\nexport function looksLikeToken(value: string): boolean {\n\treturn value.startsWith(SESSION_PREFIX) || toApiKey(value) !== value;\n}\n\nexport interface SignInAttempt {\n\turl: string;\n\t/** Never leaves this process. */\n\tverifier: string;\n}\n\n/** PKCE S256 without a redirect: Devin shows the code in the browser and the user pastes it back. */\nexport function createSignIn(): SignInAttempt {\n\tconst verifier = randomBytes(64).toString(\"base64url\");\n\tconst url = new URL(AUTHORIZE_URL);\n\turl.search = new URLSearchParams({\n\t\tstate: randomBytes(32).toString(\"base64url\"),\n\t\tprompt: \"select_account\",\n\t\tcode_challenge: createHash(\"sha256\").update(verifier).digest(\"base64url\"),\n\t\tcode_challenge_method: \"S256\",\n\t}).toString();\n\treturn { url: url.toString(), verifier };\n}\n\nexport async function exchangeCode(\n\tattempt: SignInAttempt,\n\tinput: string,\n\tfetcher: typeof fetch = fetch,\n): Promise<string> {\n\tconst code = input.trim();\n\tif (!code || code.length > MAX_INPUT_CHARS || /\\s|[\\x00-\\x1f\\x7f]/.test(code)) {\n\t\tthrow new Error(\"That does not look like a Devin authorization code.\");\n\t}\n\tlet response: Response;\n\ttry {\n\t\tresponse = await fetcher(TOKEN_URL, {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: { \"content-type\": \"application/json\", accept: \"application/json\" },\n\t\t\tbody: JSON.stringify({ code, code_verifier: attempt.verifier }),\n\t\t\tredirect: \"error\",\n\t\t\tsignal: AbortSignal.timeout(60_000),\n\t\t});\n\t} catch {\n\t\tthrow new Error(\"Could not reach Devin. Check your network and run /search again.\");\n\t}\n\tif (!response.ok) {\n\t\tthrow new Error(`Devin rejected the code (HTTP ${response.status}). Codes are one-time; run /search again.`);\n\t}\n\tconst token = ((await response.json().catch(() => undefined)) as { token?: unknown } | undefined)?.token;\n\tif (typeof token !== \"string\" || !token || token.length > MAX_INPUT_CHARS) {\n\t\tthrow new Error(\"Devin returned no usable session token.\");\n\t}\n\treturn token;\n}\n"]}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export declare class DevinError extends Error {
|
|
2
|
+
name: string;
|
|
3
|
+
}
|
|
4
|
+
/** The credential is missing, revoked, or malformed; only signing in again fixes it. */
|
|
5
|
+
export declare class DevinAuthError extends DevinError {
|
|
6
|
+
name: string;
|
|
7
|
+
}
|
|
8
|
+
export interface ChatMessage {
|
|
9
|
+
role: "system" | "user" | "assistant" | "tool";
|
|
10
|
+
content: string;
|
|
11
|
+
/** On an assistant message: the tool call it made. */
|
|
12
|
+
call?: {
|
|
13
|
+
id: string;
|
|
14
|
+
name: string;
|
|
15
|
+
arguments: string;
|
|
16
|
+
};
|
|
17
|
+
/** On a tool message: the id of the call it answers. */
|
|
18
|
+
callId?: string;
|
|
19
|
+
}
|
|
20
|
+
export declare function encodeChatRequest(apiKey: string, messages: ChatMessage[], tools: string): Buffer;
|
|
21
|
+
/**
|
|
22
|
+
* Connect streaming envelopes: a flags byte (bit 0 gzip, bit 1 end-of-stream) and a length. Data
|
|
23
|
+
* frames carry text chunks in field 2; the end frame is JSON that names an error, if any.
|
|
24
|
+
*/
|
|
25
|
+
export declare function decodeChatResponse(body: Buffer): string;
|
|
26
|
+
export declare function chat(apiKey: string, messages: ChatMessage[], tools: string, signal?: AbortSignal): Promise<string>;
|
|
27
|
+
export interface WebResult {
|
|
28
|
+
url: string;
|
|
29
|
+
title?: string;
|
|
30
|
+
/** Query-relevant excerpts of the page, joined by `...`; up to about 4,000 characters. */
|
|
31
|
+
summary?: string;
|
|
32
|
+
}
|
|
33
|
+
export declare function webSearch(apiKey: string, query: string, limit: number, signal?: AbortSignal): Promise<WebResult[]>;
|
|
34
|
+
//# sourceMappingURL=devin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"devin.d.ts","sourceRoot":"","sources":["../../../src/extensions/search/devin.ts"],"names":[],"mappings":"AAoBA,qBAAa,UAAW,SAAQ,KAAK;IAC3B,IAAI,SAAgB;CAC7B;AAED,wFAAwF;AACxF,qBAAa,cAAe,SAAQ,UAAU;IACpC,IAAI,SAAoB;CACjC;AAiFD,MAAM,WAAW,WAAW;IAC3B,IAAI,EAAE,QAAQ,GAAG,MAAM,GAAG,WAAW,GAAG,MAAM,CAAC;IAC/C,OAAO,EAAE,MAAM,CAAC;IAChB,sDAAsD;IACtD,IAAI,CAAC,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;IACvD,wDAAwD;IACxD,MAAM,CAAC,EAAE,MAAM,CAAC;CAChB;AAID,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,WAAW,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAkBhG;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAiBvD;AAED,wBAAsB,IAAI,CACzB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,WAAW,EAAE,EACvB,KAAK,EAAE,MAAM,EACb,MAAM,CAAC,EAAE,WAAW,GAClB,OAAO,CAAC,MAAM,CAAC,CAoBjB;AAID,MAAM,WAAW,SAAS;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,0FAA0F;IAC1F,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wBAAsB,SAAS,CAC9B,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,EACb,MAAM,CAAC,EAAE,WAAW,GAClB,OAAO,CAAC,SAAS,EAAE,CAAC,CA0BtB"}
|