@astralyn/pi 1.0.5 → 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 +37 -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-HDHRZ7GT.js → chunk-C2ARIVS3.js} +1 -1
- package/dist/bundle/chunks/{chunk-IE3Z7ESB.js → chunk-D3XW5X3Z.js} +371 -246
- 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 +2 -2
- 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-46MSJGKO.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 +1 -1
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +13 -1
- package/dist/cli/args.js.map +1 -1
- package/dist/core/agent-session.d.ts +12 -6
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +146 -174
- 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 +11 -0
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.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/sdk.d.ts +4 -0
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +17 -5
- 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/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/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 +63 -7
- package/dist/extensions/codemode/execute.js.map +1 -1
- package/dist/extensions/codemode/tool.js +2 -2
- 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/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/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/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/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 +23 -3
- package/docs/docs.json +0 -4
- package/docs/environment-variables.md +1 -0
- package/docs/extensions.md +5 -1
- package/docs/json.md +2 -2
- package/docs/llama-cpp.md +8 -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 +3 -3
- package/docs/terminal-setup.md +14 -0
- package/docs/usage.md +5 -3
- 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,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":"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","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"}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The two Devin (Windsurf) endpoints the search tools call. Both authenticate with the API key
|
|
3
|
+
* inside the request body; neither needs a JWT exchange, a rate-limit preflight, or host details.
|
|
4
|
+
*
|
|
5
|
+
* - `GetDevstralStream` is one turn of the SWE-grep planner: a Connect-streaming protobuf request
|
|
6
|
+
* carrying a chat transcript and tool schemas, answered by one text completion.
|
|
7
|
+
* - `GetWebSearchResults` is a Connect-unary JSON request answered by `{ results: [...] }`.
|
|
8
|
+
*
|
|
9
|
+
* There is no published schema. The field numbers and the client identity below are the minimum
|
|
10
|
+
* the backend accepts; dropping any of them makes it answer `invalid_argument`.
|
|
11
|
+
*/
|
|
12
|
+
import { gunzipSync, gzipSync } from "node:zlib";
|
|
13
|
+
const SERVICE = "https://server.self-serve.windsurf.com/exa.api_server_pb.ApiServerService";
|
|
14
|
+
const APP = "windsurf";
|
|
15
|
+
const APP_VERSION = "1.48.2";
|
|
16
|
+
const LANGUAGE_SERVER_VERSION = "1.9544.35";
|
|
17
|
+
const WEB_CLIENT_VERSION = "1.9600.41";
|
|
18
|
+
const TIMEOUT_MS = 30_000;
|
|
19
|
+
export class DevinError extends Error {
|
|
20
|
+
constructor() {
|
|
21
|
+
super(...arguments);
|
|
22
|
+
this.name = "DevinError";
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/** The credential is missing, revoked, or malformed; only signing in again fixes it. */
|
|
26
|
+
export class DevinAuthError extends DevinError {
|
|
27
|
+
constructor() {
|
|
28
|
+
super(...arguments);
|
|
29
|
+
this.name = "DevinAuthError";
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function errorFor(code, message = "") {
|
|
33
|
+
if (code === 401 || code === 403 || code === "unauthenticated" || code === "permission_denied") {
|
|
34
|
+
return new DevinAuthError("Devin rejected the credential.");
|
|
35
|
+
}
|
|
36
|
+
if (code === 429 || code === "resource_exhausted") {
|
|
37
|
+
return new DevinError("Devin rate-limited the request. Wait a moment and retry.");
|
|
38
|
+
}
|
|
39
|
+
return new DevinError(`Devin request failed (${code}${message ? `: ${message}` : ""}).`);
|
|
40
|
+
}
|
|
41
|
+
async function post(url, init, signal) {
|
|
42
|
+
const timeout = AbortSignal.timeout(TIMEOUT_MS);
|
|
43
|
+
let response;
|
|
44
|
+
try {
|
|
45
|
+
response = await fetch(url, {
|
|
46
|
+
...init,
|
|
47
|
+
method: "POST",
|
|
48
|
+
redirect: "error",
|
|
49
|
+
signal: signal ? AbortSignal.any([signal, timeout]) : timeout,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
catch (e) {
|
|
53
|
+
if (signal?.aborted)
|
|
54
|
+
throw e;
|
|
55
|
+
throw new DevinError(timeout.aborted ? "Devin did not answer in time." : `Could not reach Devin: ${String(e)}`);
|
|
56
|
+
}
|
|
57
|
+
if (!response.ok) {
|
|
58
|
+
const body = (await response.json().catch(() => undefined));
|
|
59
|
+
throw errorFor(body?.code ?? response.status, body?.message);
|
|
60
|
+
}
|
|
61
|
+
return response;
|
|
62
|
+
}
|
|
63
|
+
// --- Protobuf, just enough for these messages ---
|
|
64
|
+
function varint(value) {
|
|
65
|
+
const bytes = [];
|
|
66
|
+
while (value > 0x7f) {
|
|
67
|
+
bytes.push((value & 0x7f) | 0x80);
|
|
68
|
+
value >>>= 7;
|
|
69
|
+
}
|
|
70
|
+
bytes.push(value);
|
|
71
|
+
return bytes;
|
|
72
|
+
}
|
|
73
|
+
function field(no, value) {
|
|
74
|
+
if (typeof value === "number")
|
|
75
|
+
return Buffer.from([...varint(no << 3), ...varint(value)]);
|
|
76
|
+
const data = typeof value === "string" ? Buffer.from(value, "utf-8") : value;
|
|
77
|
+
return Buffer.concat([Buffer.from([...varint((no << 3) | 2), ...varint(data.length)]), data]);
|
|
78
|
+
}
|
|
79
|
+
/** The length-delimited values of field `no` at the top level of `data`. */
|
|
80
|
+
function readField(data, no) {
|
|
81
|
+
const out = [];
|
|
82
|
+
let i = 0;
|
|
83
|
+
const readVarint = () => {
|
|
84
|
+
let value = 0;
|
|
85
|
+
for (let shift = 0; i < data.length; shift += 7) {
|
|
86
|
+
const b = data[i++];
|
|
87
|
+
value += (b & 0x7f) * 2 ** shift;
|
|
88
|
+
if (!(b & 0x80))
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
return value;
|
|
92
|
+
};
|
|
93
|
+
while (i < data.length) {
|
|
94
|
+
const tag = readVarint();
|
|
95
|
+
const wire = tag & 7;
|
|
96
|
+
if (wire === 0)
|
|
97
|
+
readVarint();
|
|
98
|
+
else if (wire === 1)
|
|
99
|
+
i += 8;
|
|
100
|
+
else if (wire === 5)
|
|
101
|
+
i += 4;
|
|
102
|
+
else if (wire === 2) {
|
|
103
|
+
const length = readVarint();
|
|
104
|
+
if (tag >>> 3 === no)
|
|
105
|
+
out.push(data.subarray(i, i + length));
|
|
106
|
+
i += length;
|
|
107
|
+
}
|
|
108
|
+
else
|
|
109
|
+
break;
|
|
110
|
+
}
|
|
111
|
+
return out;
|
|
112
|
+
}
|
|
113
|
+
const ROLE = { user: 1, assistant: 2, tool: 4, system: 5 };
|
|
114
|
+
export function encodeChatRequest(apiKey, messages, tools) {
|
|
115
|
+
const metadata = Buffer.concat([
|
|
116
|
+
field(1, APP),
|
|
117
|
+
field(2, APP_VERSION),
|
|
118
|
+
field(3, apiKey),
|
|
119
|
+
field(7, LANGUAGE_SERVER_VERSION),
|
|
120
|
+
]);
|
|
121
|
+
const parts = [field(1, metadata)];
|
|
122
|
+
for (const m of messages) {
|
|
123
|
+
const msg = [field(2, ROLE[m.role]), field(3, m.content)];
|
|
124
|
+
if (m.call) {
|
|
125
|
+
msg.push(field(6, Buffer.concat([field(1, m.call.id), field(2, m.call.name), field(3, m.call.arguments)])));
|
|
126
|
+
}
|
|
127
|
+
if (m.callId)
|
|
128
|
+
msg.push(field(7, m.callId));
|
|
129
|
+
parts.push(field(2, Buffer.concat(msg)));
|
|
130
|
+
}
|
|
131
|
+
parts.push(field(3, tools));
|
|
132
|
+
return Buffer.concat(parts);
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Connect streaming envelopes: a flags byte (bit 0 gzip, bit 1 end-of-stream) and a length. Data
|
|
136
|
+
* frames carry text chunks in field 2; the end frame is JSON that names an error, if any.
|
|
137
|
+
*/
|
|
138
|
+
export function decodeChatResponse(body) {
|
|
139
|
+
let text = "";
|
|
140
|
+
let i = 0;
|
|
141
|
+
while (i + 5 <= body.length) {
|
|
142
|
+
const flags = body[i];
|
|
143
|
+
const length = body.readUInt32BE(i + 1);
|
|
144
|
+
let payload = body.subarray(i + 5, i + 5 + length);
|
|
145
|
+
i += 5 + length;
|
|
146
|
+
if (flags & 1)
|
|
147
|
+
payload = gunzipSync(payload);
|
|
148
|
+
if (flags & 2) {
|
|
149
|
+
const end = JSON.parse(payload.toString("utf-8") || "{}");
|
|
150
|
+
if (end.error)
|
|
151
|
+
throw errorFor(end.error.code ?? "unknown", end.error.message);
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
for (const chunk of readField(payload, 2))
|
|
155
|
+
text += chunk.toString("utf-8");
|
|
156
|
+
}
|
|
157
|
+
return text;
|
|
158
|
+
}
|
|
159
|
+
export async function chat(apiKey, messages, tools, signal) {
|
|
160
|
+
const message = gzipSync(encodeChatRequest(apiKey, messages, tools));
|
|
161
|
+
const envelope = Buffer.alloc(5);
|
|
162
|
+
envelope[0] = 1;
|
|
163
|
+
envelope.writeUInt32BE(message.length, 1);
|
|
164
|
+
const response = await post(`${SERVICE}/GetDevstralStream`, {
|
|
165
|
+
headers: {
|
|
166
|
+
"Content-Type": "application/connect+proto",
|
|
167
|
+
"Connect-Protocol-Version": "1",
|
|
168
|
+
"Connect-Content-Encoding": "gzip",
|
|
169
|
+
"Connect-Accept-Encoding": "gzip",
|
|
170
|
+
"Connect-Timeout-Ms": String(TIMEOUT_MS),
|
|
171
|
+
},
|
|
172
|
+
body: new Uint8Array(Buffer.concat([envelope, message])),
|
|
173
|
+
}, signal);
|
|
174
|
+
return decodeChatResponse(Buffer.from(await response.arrayBuffer()));
|
|
175
|
+
}
|
|
176
|
+
export async function webSearch(apiKey, query, limit, signal) {
|
|
177
|
+
const response = await post(`${SERVICE}/GetWebSearchResults`, {
|
|
178
|
+
headers: { "Content-Type": "application/json", "Connect-Protocol-Version": "1" },
|
|
179
|
+
body: JSON.stringify({
|
|
180
|
+
metadata: { apiKey, ideName: APP, ideVersion: WEB_CLIENT_VERSION, extensionVersion: WEB_CLIENT_VERSION },
|
|
181
|
+
query,
|
|
182
|
+
limit,
|
|
183
|
+
}),
|
|
184
|
+
}, signal);
|
|
185
|
+
const results = (await response.json().catch(() => undefined))?.results;
|
|
186
|
+
if (!Array.isArray(results))
|
|
187
|
+
throw new DevinError("Devin returned a malformed web search response.");
|
|
188
|
+
return results.flatMap((row) => {
|
|
189
|
+
const { url, title, summary } = (row ?? {});
|
|
190
|
+
if (typeof url !== "string" || !isWebUrl(url))
|
|
191
|
+
return [];
|
|
192
|
+
return [
|
|
193
|
+
{
|
|
194
|
+
url,
|
|
195
|
+
...(typeof title === "string" && title.trim() ? { title: title.trim() } : {}),
|
|
196
|
+
...(typeof summary === "string" && summary.trim() ? { summary: summary.trim() } : {}),
|
|
197
|
+
},
|
|
198
|
+
];
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
function isWebUrl(value) {
|
|
202
|
+
try {
|
|
203
|
+
const url = new URL(value);
|
|
204
|
+
return (url.protocol === "https:" || url.protocol === "http:") && !url.username && !url.password;
|
|
205
|
+
}
|
|
206
|
+
catch {
|
|
207
|
+
return false;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
//# sourceMappingURL=devin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"devin.js","sourceRoot":"","sources":["../../../src/extensions/search/devin.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The two Devin (Windsurf) endpoints the search tools call. Both authenticate with the API key\n * inside the request body; neither needs a JWT exchange, a rate-limit preflight, or host details.\n *\n * - `GetDevstralStream` is one turn of the SWE-grep planner: a Connect-streaming protobuf request\n * carrying a chat transcript and tool schemas, answered by one text completion.\n * - `GetWebSearchResults` is a Connect-unary JSON request answered by `{ results: [...] }`.\n *\n * There is no published schema. The field numbers and the client identity below are the minimum\n * the backend accepts; dropping any of them makes it answer `invalid_argument`.\n */\nimport { gunzipSync, gzipSync } from \"node:zlib\";\n\nconst SERVICE = \"https://server.self-serve.windsurf.com/exa.api_server_pb.ApiServerService\";\nconst APP = \"windsurf\";\nconst APP_VERSION = \"1.48.2\";\nconst LANGUAGE_SERVER_VERSION = \"1.9544.35\";\nconst WEB_CLIENT_VERSION = \"1.9600.41\";\nconst TIMEOUT_MS = 30_000;\n\nexport class DevinError extends Error {\n\toverride name = \"DevinError\";\n}\n\n/** The credential is missing, revoked, or malformed; only signing in again fixes it. */\nexport class DevinAuthError extends DevinError {\n\toverride name = \"DevinAuthError\";\n}\n\nfunction errorFor(code: string | number, message = \"\"): DevinError {\n\tif (code === 401 || code === 403 || code === \"unauthenticated\" || code === \"permission_denied\") {\n\t\treturn new DevinAuthError(\"Devin rejected the credential.\");\n\t}\n\tif (code === 429 || code === \"resource_exhausted\") {\n\t\treturn new DevinError(\"Devin rate-limited the request. Wait a moment and retry.\");\n\t}\n\treturn new DevinError(`Devin request failed (${code}${message ? `: ${message}` : \"\"}).`);\n}\n\nasync function post(url: string, init: RequestInit, signal?: AbortSignal): Promise<Response> {\n\tconst timeout = AbortSignal.timeout(TIMEOUT_MS);\n\tlet response: Response;\n\ttry {\n\t\tresponse = await fetch(url, {\n\t\t\t...init,\n\t\t\tmethod: \"POST\",\n\t\t\tredirect: \"error\",\n\t\t\tsignal: signal ? AbortSignal.any([signal, timeout]) : timeout,\n\t\t});\n\t} catch (e) {\n\t\tif (signal?.aborted) throw e;\n\t\tthrow new DevinError(timeout.aborted ? \"Devin did not answer in time.\" : `Could not reach Devin: ${String(e)}`);\n\t}\n\tif (!response.ok) {\n\t\tconst body = (await response.json().catch(() => undefined)) as { code?: string; message?: string } | undefined;\n\t\tthrow errorFor(body?.code ?? response.status, body?.message);\n\t}\n\treturn response;\n}\n\n// --- Protobuf, just enough for these messages ---\n\nfunction varint(value: number): number[] {\n\tconst bytes: number[] = [];\n\twhile (value > 0x7f) {\n\t\tbytes.push((value & 0x7f) | 0x80);\n\t\tvalue >>>= 7;\n\t}\n\tbytes.push(value);\n\treturn bytes;\n}\n\nfunction field(no: number, value: string | number | Buffer): Buffer {\n\tif (typeof value === \"number\") return Buffer.from([...varint(no << 3), ...varint(value)]);\n\tconst data = typeof value === \"string\" ? Buffer.from(value, \"utf-8\") : value;\n\treturn Buffer.concat([Buffer.from([...varint((no << 3) | 2), ...varint(data.length)]), data]);\n}\n\n/** The length-delimited values of field `no` at the top level of `data`. */\nfunction readField(data: Buffer, no: number): Buffer[] {\n\tconst out: Buffer[] = [];\n\tlet i = 0;\n\tconst readVarint = (): number => {\n\t\tlet value = 0;\n\t\tfor (let shift = 0; i < data.length; shift += 7) {\n\t\t\tconst b = data[i++]!;\n\t\t\tvalue += (b & 0x7f) * 2 ** shift;\n\t\t\tif (!(b & 0x80)) break;\n\t\t}\n\t\treturn value;\n\t};\n\twhile (i < data.length) {\n\t\tconst tag = readVarint();\n\t\tconst wire = tag & 7;\n\t\tif (wire === 0) readVarint();\n\t\telse if (wire === 1) i += 8;\n\t\telse if (wire === 5) i += 4;\n\t\telse if (wire === 2) {\n\t\t\tconst length = readVarint();\n\t\t\tif (tag >>> 3 === no) out.push(data.subarray(i, i + length));\n\t\t\ti += length;\n\t\t} else break;\n\t}\n\treturn out;\n}\n\n// --- Planner turn ---\n\nexport interface ChatMessage {\n\trole: \"system\" | \"user\" | \"assistant\" | \"tool\";\n\tcontent: string;\n\t/** On an assistant message: the tool call it made. */\n\tcall?: { id: string; name: string; arguments: string };\n\t/** On a tool message: the id of the call it answers. */\n\tcallId?: string;\n}\n\nconst ROLE = { user: 1, assistant: 2, tool: 4, system: 5 } as const;\n\nexport function encodeChatRequest(apiKey: string, messages: ChatMessage[], tools: string): Buffer {\n\tconst metadata = Buffer.concat([\n\t\tfield(1, APP),\n\t\tfield(2, APP_VERSION),\n\t\tfield(3, apiKey),\n\t\tfield(7, LANGUAGE_SERVER_VERSION),\n\t]);\n\tconst parts = [field(1, metadata)];\n\tfor (const m of messages) {\n\t\tconst msg = [field(2, ROLE[m.role]), field(3, m.content)];\n\t\tif (m.call) {\n\t\t\tmsg.push(field(6, Buffer.concat([field(1, m.call.id), field(2, m.call.name), field(3, m.call.arguments)])));\n\t\t}\n\t\tif (m.callId) msg.push(field(7, m.callId));\n\t\tparts.push(field(2, Buffer.concat(msg)));\n\t}\n\tparts.push(field(3, tools));\n\treturn Buffer.concat(parts);\n}\n\n/**\n * Connect streaming envelopes: a flags byte (bit 0 gzip, bit 1 end-of-stream) and a length. Data\n * frames carry text chunks in field 2; the end frame is JSON that names an error, if any.\n */\nexport function decodeChatResponse(body: Buffer): string {\n\tlet text = \"\";\n\tlet i = 0;\n\twhile (i + 5 <= body.length) {\n\t\tconst flags = body[i]!;\n\t\tconst length = body.readUInt32BE(i + 1);\n\t\tlet payload = body.subarray(i + 5, i + 5 + length);\n\t\ti += 5 + length;\n\t\tif (flags & 1) payload = gunzipSync(payload);\n\t\tif (flags & 2) {\n\t\t\tconst end = JSON.parse(payload.toString(\"utf-8\") || \"{}\") as { error?: { code?: string; message?: string } };\n\t\t\tif (end.error) throw errorFor(end.error.code ?? \"unknown\", end.error.message);\n\t\t\tcontinue;\n\t\t}\n\t\tfor (const chunk of readField(payload, 2)) text += chunk.toString(\"utf-8\");\n\t}\n\treturn text;\n}\n\nexport async function chat(\n\tapiKey: string,\n\tmessages: ChatMessage[],\n\ttools: string,\n\tsignal?: AbortSignal,\n): Promise<string> {\n\tconst message = gzipSync(encodeChatRequest(apiKey, messages, tools));\n\tconst envelope = Buffer.alloc(5);\n\tenvelope[0] = 1;\n\tenvelope.writeUInt32BE(message.length, 1);\n\tconst response = await post(\n\t\t`${SERVICE}/GetDevstralStream`,\n\t\t{\n\t\t\theaders: {\n\t\t\t\t\"Content-Type\": \"application/connect+proto\",\n\t\t\t\t\"Connect-Protocol-Version\": \"1\",\n\t\t\t\t\"Connect-Content-Encoding\": \"gzip\",\n\t\t\t\t\"Connect-Accept-Encoding\": \"gzip\",\n\t\t\t\t\"Connect-Timeout-Ms\": String(TIMEOUT_MS),\n\t\t\t},\n\t\t\tbody: new Uint8Array(Buffer.concat([envelope, message])),\n\t\t},\n\t\tsignal,\n\t);\n\treturn decodeChatResponse(Buffer.from(await response.arrayBuffer()));\n}\n\n// --- Web search ---\n\nexport interface WebResult {\n\turl: string;\n\ttitle?: string;\n\t/** Query-relevant excerpts of the page, joined by `...`; up to about 4,000 characters. */\n\tsummary?: string;\n}\n\nexport async function webSearch(\n\tapiKey: string,\n\tquery: string,\n\tlimit: number,\n\tsignal?: AbortSignal,\n): Promise<WebResult[]> {\n\tconst response = await post(\n\t\t`${SERVICE}/GetWebSearchResults`,\n\t\t{\n\t\t\theaders: { \"Content-Type\": \"application/json\", \"Connect-Protocol-Version\": \"1\" },\n\t\t\tbody: JSON.stringify({\n\t\t\t\tmetadata: { apiKey, ideName: APP, ideVersion: WEB_CLIENT_VERSION, extensionVersion: WEB_CLIENT_VERSION },\n\t\t\t\tquery,\n\t\t\t\tlimit,\n\t\t\t}),\n\t\t},\n\t\tsignal,\n\t);\n\tconst results = ((await response.json().catch(() => undefined)) as { results?: unknown } | undefined)?.results;\n\tif (!Array.isArray(results)) throw new DevinError(\"Devin returned a malformed web search response.\");\n\treturn results.flatMap((row): WebResult[] => {\n\t\tconst { url, title, summary } = (row ?? {}) as Record<string, unknown>;\n\t\tif (typeof url !== \"string\" || !isWebUrl(url)) return [];\n\t\treturn [\n\t\t\t{\n\t\t\t\turl,\n\t\t\t\t...(typeof title === \"string\" && title.trim() ? { title: title.trim() } : {}),\n\t\t\t\t...(typeof summary === \"string\" && summary.trim() ? { summary: summary.trim() } : {}),\n\t\t\t},\n\t\t];\n\t});\n}\n\nfunction isWebUrl(value: string): boolean {\n\ttry {\n\t\tconst url = new URL(value);\n\t\treturn (url.protocol === \"https:\" || url.protocol === \"http:\") && !url.username && !url.password;\n\t} catch {\n\t\treturn false;\n\t}\n}\n"]}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { ExtensionAPI } from "../../core/extensions/types.ts";
|
|
2
|
+
import { type WebResult } from "./devin.ts";
|
|
3
|
+
export declare function formatWebResults(query: string, results: WebResult[]): string;
|
|
4
|
+
export default function search(pi: ExtensionAPI): void;
|
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/extensions/search/index.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AAKnE,OAAO,EAAkB,KAAK,SAAS,EAAa,MAAM,YAAY,CAAC;AA8CvE,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,GAAG,MAAM,CAW5E;AAED,MAAM,CAAC,OAAO,UAAU,MAAM,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CA2DrD"}
|