deepseek-web-retrieval 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +93 -244
- package/README.md +16 -11
- package/dist/cli.js +1366 -615
- package/docs/agents/codex.md +28 -7
- package/docs/agents/opencode-v2.md +1 -1
- package/docs/agents/pi.md +88 -86
- package/docs/design/installer-design.md +72 -9
- package/docs/hosts.jsonc +2 -2
- package/docs/integration.md +14 -9
- package/docs/research/control-matrix.md +48 -13
- package/docs/research/mcp-compatibility.md +9 -4
- package/examples/pi.settings.jsonc +17 -0
- package/index.ts +2 -2
- package/package.json +35 -4
- package/src/core/auth.ts +4 -6
- package/src/core/client.ts +65 -53
- package/src/core/constants.ts +24 -24
- package/src/core/descriptions.ts +30 -6
- package/src/core/domains.ts +56 -67
- package/src/core/env.ts +112 -0
- package/src/core/format.ts +53 -50
- package/src/core/index.ts +9 -9
- package/src/core/jsonc.ts +6 -6
- package/src/core/object.ts +4 -0
- package/src/core/options.ts +20 -25
- package/src/core/search.ts +45 -44
- package/src/core/transport.ts +109 -113
- package/src/core/types.ts +47 -47
- package/src/core/url.ts +20 -20
- package/src/installer/apply.ts +256 -81
- package/src/installer/cli.ts +135 -104
- package/src/installer/detect.ts +46 -31
- package/src/installer/formats/index.ts +26 -28
- package/src/installer/formats/json.ts +81 -77
- package/src/installer/formats/rules.ts +55 -28
- package/src/installer/formats/toml.ts +88 -81
- package/src/installer/formats/value.ts +34 -30
- package/src/installer/hosts/codex.ts +100 -54
- package/src/installer/hosts/index.ts +126 -41
- package/src/installer/hosts/opencode.ts +70 -51
- package/src/installer/hosts/pi.ts +269 -75
- package/src/installer/hosts/shared.ts +19 -14
- package/src/installer/operation-lock.ts +54 -0
- package/src/installer/ownership.ts +129 -0
- package/src/installer/paths.ts +7 -5
- package/src/installer/persistence.ts +66 -0
- package/src/installer/render.ts +29 -29
- package/src/installer/revert.ts +33 -29
- package/src/installer/rulesContent.ts +24 -13
- package/src/installer/state.ts +116 -31
- package/src/installer/types.ts +74 -56
- package/src/installer/uninstall.ts +40 -17
- package/src/mcp/auth.ts +7 -8
- package/src/mcp/cli.ts +38 -20
- package/src/mcp/client.ts +35 -0
- package/src/mcp/server.ts +68 -97
- package/src/opencode/auth.ts +24 -26
- package/src/opencode/plugin.ts +99 -87
- package/src/pi/extension.ts +164 -0
- package/src/pi/settings.ts +49 -0
- package/src/mcp/config.ts +0 -112
package/src/mcp/server.ts
CHANGED
|
@@ -1,97 +1,64 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
import * as z from "zod/v4"
|
|
4
|
-
import
|
|
5
|
-
import {
|
|
1
|
+
import type { CallToolResult, ServerContext } from "@modelcontextprotocol/server";
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/server";
|
|
3
|
+
import * as z from "zod/v4";
|
|
4
|
+
import pkg from "../../package.json";
|
|
5
|
+
import type { RetrievalClient } from "../core/client";
|
|
6
|
+
import { retrievalClient } from "../core/client";
|
|
7
|
+
import { ENABLE_FETCH_ENV, ENABLE_SEARCH_ENV } from "../core/constants";
|
|
6
8
|
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
} from "../core/
|
|
13
|
-
import { formatSearchResults } from "../core/format"
|
|
14
|
-
import type { ResolvedOptions
|
|
15
|
-
import
|
|
16
|
-
import {
|
|
17
|
-
import type { Env } from "./config"
|
|
9
|
+
FETCH_TOOL_DESCRIPTION,
|
|
10
|
+
FETCH_URL_PARAMETER,
|
|
11
|
+
SEARCH_QUERY_PARAMETER,
|
|
12
|
+
SEARCH_TOOL_DESCRIPTION,
|
|
13
|
+
} from "../core/descriptions";
|
|
14
|
+
import type { Env } from "../core/env";
|
|
15
|
+
import { formatSearchResults, toStructuredResult } from "../core/format";
|
|
16
|
+
import type { ResolvedOptions } from "../core/types";
|
|
17
|
+
import { requireApiKey } from "./auth";
|
|
18
|
+
import { omitStructuredContent } from "./client";
|
|
18
19
|
|
|
19
|
-
export const MCP_SERVER_NAME = "deepseek-web-retrieval"
|
|
20
|
+
export const MCP_SERVER_NAME = "deepseek-web-retrieval";
|
|
20
21
|
|
|
21
22
|
const SEARCH_INPUT = z.object({
|
|
22
|
-
query: z
|
|
23
|
-
|
|
24
|
-
.min(1)
|
|
25
|
-
.describe(
|
|
26
|
-
"Search query, 128 characters or fewer including operators (site:, -site:, quoted phrases, lang:).",
|
|
27
|
-
),
|
|
28
|
-
})
|
|
29
|
-
|
|
30
|
-
const SEARCH_OUTPUT = z.object({
|
|
31
|
-
results: z.array(
|
|
32
|
-
z.object({
|
|
33
|
-
url: z.string(),
|
|
34
|
-
title: z.string().optional(),
|
|
35
|
-
content: z.string().optional(),
|
|
36
|
-
published: z.number().optional(),
|
|
37
|
-
}),
|
|
38
|
-
),
|
|
39
|
-
})
|
|
23
|
+
query: z.string().min(1).describe(SEARCH_QUERY_PARAMETER),
|
|
24
|
+
});
|
|
40
25
|
|
|
41
26
|
const FETCH_INPUT = z.object({
|
|
42
|
-
url: z.string().min(1).describe(
|
|
43
|
-
})
|
|
44
|
-
|
|
45
|
-
const FETCH_OUTPUT = z.object({
|
|
46
|
-
url: z.string(),
|
|
47
|
-
title: z.string(),
|
|
48
|
-
content: z.string(),
|
|
49
|
-
truncated: z.boolean(),
|
|
50
|
-
})
|
|
51
|
-
|
|
52
|
-
const SEARCH_DESCRIPTION =
|
|
53
|
-
"Search the web with DeepSeek's in-house search engine and return a short, filtered result " +
|
|
54
|
-
"list (title, URL, summary). " +
|
|
55
|
-
`${SEARCH_RANKED_FILTERED} ` +
|
|
56
|
-
`${SEARCH_EMPTY_IS_NORMAL} ` +
|
|
57
|
-
`${SEARCH_QUERY_OPERATORS} ` +
|
|
58
|
-
"To read a full page, pass a result URL to the deepseek_fetch tool."
|
|
59
|
-
|
|
60
|
-
const FETCH_DESCRIPTION =
|
|
61
|
-
"Fetch one web page as clean Markdown through DeepSeek's Fetch API. " +
|
|
62
|
-
`${FETCH_SERVICE_LIMITS} ` +
|
|
63
|
-
`${FETCH_OUTPUT_TRUNCATED}`
|
|
27
|
+
url: z.string().min(1).describe(FETCH_URL_PARAMETER),
|
|
28
|
+
});
|
|
64
29
|
|
|
65
30
|
/**
|
|
66
31
|
* Builds the host-agnostic MCP server exposing the two DeepSeek retrieval tools.
|
|
67
32
|
* `serveStdio` and `createMcpHandler` serve both protocol eras from this factory.
|
|
68
33
|
*/
|
|
69
|
-
export function createDeepSeekServer(
|
|
70
|
-
|
|
34
|
+
export function createDeepSeekServer(
|
|
35
|
+
config: ResolvedOptions,
|
|
36
|
+
env: Env = process.env,
|
|
37
|
+
client: RetrievalClient = retrievalClient,
|
|
38
|
+
): McpServer {
|
|
39
|
+
const server = new McpServer({ name: MCP_SERVER_NAME, version: pkg.version });
|
|
71
40
|
|
|
72
41
|
if (config.search.enabled) {
|
|
73
42
|
server.registerTool(
|
|
74
43
|
"deepseek_web_search",
|
|
75
44
|
{
|
|
76
45
|
title: "DeepSeek Web Search",
|
|
77
|
-
description:
|
|
46
|
+
description: SEARCH_TOOL_DESCRIPTION,
|
|
78
47
|
inputSchema: SEARCH_INPUT,
|
|
79
|
-
outputSchema: SEARCH_OUTPUT,
|
|
80
48
|
annotations: { readOnlyHint: true },
|
|
81
49
|
},
|
|
82
50
|
async ({ query }, ctx) => {
|
|
83
51
|
try {
|
|
84
|
-
const request = requestContext(config, env, ctx.mcpReq.signal)
|
|
85
|
-
const results = await searchWeb(query, config.search, request)
|
|
86
|
-
return {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
}
|
|
52
|
+
const request = requestContext(config, env, ctx.mcpReq.signal);
|
|
53
|
+
const results = await client.searchWeb(query, config.search, request);
|
|
54
|
+
return callResult(server, ctx, formatSearchResults(results), {
|
|
55
|
+
results: results.map(toStructuredResult),
|
|
56
|
+
});
|
|
90
57
|
} catch (error) {
|
|
91
|
-
return toErrorResult(error)
|
|
58
|
+
return toErrorResult(error);
|
|
92
59
|
}
|
|
93
60
|
},
|
|
94
|
-
)
|
|
61
|
+
);
|
|
95
62
|
}
|
|
96
63
|
|
|
97
64
|
if (config.fetch.enabled) {
|
|
@@ -99,57 +66,61 @@ export function createDeepSeekServer(config: ResolvedOptions, env: Env = process
|
|
|
99
66
|
"deepseek_fetch",
|
|
100
67
|
{
|
|
101
68
|
title: "DeepSeek Fetch",
|
|
102
|
-
description:
|
|
69
|
+
description: FETCH_TOOL_DESCRIPTION,
|
|
103
70
|
inputSchema: FETCH_INPUT,
|
|
104
|
-
outputSchema: FETCH_OUTPUT,
|
|
105
71
|
annotations: { readOnlyHint: true },
|
|
106
72
|
},
|
|
107
73
|
async ({ url }, ctx) => {
|
|
108
74
|
try {
|
|
109
|
-
const request = requestContext(config, env, ctx.mcpReq.signal)
|
|
110
|
-
const page = await fetchDocument(url, config.fetch, request)
|
|
111
|
-
return {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
truncated: page.truncated,
|
|
118
|
-
},
|
|
119
|
-
}
|
|
75
|
+
const request = requestContext(config, env, ctx.mcpReq.signal);
|
|
76
|
+
const page = await client.fetchDocument(url, config.fetch, request);
|
|
77
|
+
return callResult(server, ctx, page.content, {
|
|
78
|
+
url: page.url,
|
|
79
|
+
title: page.title,
|
|
80
|
+
content: page.content,
|
|
81
|
+
truncated: page.truncated,
|
|
82
|
+
});
|
|
120
83
|
} catch (error) {
|
|
121
|
-
return toErrorResult(error)
|
|
84
|
+
return toErrorResult(error);
|
|
122
85
|
}
|
|
123
86
|
},
|
|
124
|
-
)
|
|
87
|
+
);
|
|
125
88
|
}
|
|
126
89
|
|
|
127
|
-
return server
|
|
90
|
+
return server;
|
|
128
91
|
}
|
|
129
92
|
|
|
130
93
|
function requestContext(config: ResolvedOptions, env: Env, signal: AbortSignal) {
|
|
131
|
-
return { signal, auth: { apiKey: requireApiKey(env) }, retry: config.retry }
|
|
94
|
+
return { signal, auth: { apiKey: requireApiKey(env) }, retry: config.retry };
|
|
132
95
|
}
|
|
133
96
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
97
|
+
/**
|
|
98
|
+
* Text content always reaches the model; the structured mirror is dropped for
|
|
99
|
+
* clients that would replace the text with its serialized JSON (Codex does).
|
|
100
|
+
* The tools declare no `outputSchema` on purpose: the SDK rejects text-only
|
|
101
|
+
* results for a tool with a declared schema ("no structured content was
|
|
102
|
+
* provided"), and no host in the matrix validates the schema anyway.
|
|
103
|
+
*/
|
|
104
|
+
function callResult(
|
|
105
|
+
server: McpServer,
|
|
106
|
+
ctx: ServerContext,
|
|
107
|
+
text: string,
|
|
108
|
+
structured: Record<string, unknown>,
|
|
109
|
+
): CallToolResult {
|
|
110
|
+
if (omitStructuredContent(server, ctx)) return { content: [{ type: "text", text }] };
|
|
111
|
+
return { content: [{ type: "text", text }], structuredContent: structured };
|
|
141
112
|
}
|
|
142
113
|
|
|
143
114
|
/** Returns the both-disabled message, or undefined when a tool stays enabled. */
|
|
144
115
|
export function servePrecheck(config: ResolvedOptions): string | undefined {
|
|
145
|
-
if (config.search.enabled || config.fetch.enabled) return undefined
|
|
116
|
+
if (config.search.enabled || config.fetch.enabled) return undefined;
|
|
146
117
|
return (
|
|
147
118
|
`Both tools are disabled (${ENABLE_SEARCH_ENV} and ${ENABLE_FETCH_ENV} are false); ` +
|
|
148
119
|
"at least one must stay enabled."
|
|
149
|
-
)
|
|
120
|
+
);
|
|
150
121
|
}
|
|
151
122
|
|
|
152
123
|
function toErrorResult(error: unknown): CallToolResult {
|
|
153
|
-
const message = error instanceof Error ? error.message : String(error)
|
|
154
|
-
return { content: [{ type: "text", text: message }], isError: true }
|
|
124
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
125
|
+
return { content: [{ type: "text", text: message }], isError: true };
|
|
155
126
|
}
|
package/src/opencode/auth.ts
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
import type { Context } from "@opencode/plugin/promise/plugin"
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import type { ApiKeySource } from "../core/types"
|
|
1
|
+
import type { Context } from "@opencode/plugin/promise/plugin";
|
|
2
|
+
import { apiKeyFromEnv } from "../core/auth";
|
|
3
|
+
import { API_KEY_ENV } from "../core/constants";
|
|
4
|
+
import type { ApiKeySource } from "../core/types";
|
|
5
5
|
|
|
6
|
-
const DEEPSEEK_INTEGRATION_ID = "deepseek"
|
|
6
|
+
const DEEPSEEK_INTEGRATION_ID = "deepseek";
|
|
7
7
|
|
|
8
8
|
/** OpenCode-specific guidance for a missing key; the core stays agent-agnostic. */
|
|
9
9
|
function missingApiKeyError(source: ApiKeySource): Error {
|
|
10
10
|
if (source === "env") {
|
|
11
|
-
return new Error(`${API_KEY_ENV} is not set. Export it before starting OpenCode.`)
|
|
11
|
+
return new Error(`${API_KEY_ENV} is not set. Export it before starting OpenCode.`);
|
|
12
12
|
}
|
|
13
13
|
if (source === "opencode") {
|
|
14
14
|
return new Error(
|
|
15
15
|
'No DeepSeek credential is stored in OpenCode. Run "opencode auth login" and connect DeepSeek, or set apiKeySource to "env".',
|
|
16
|
-
)
|
|
16
|
+
);
|
|
17
17
|
}
|
|
18
18
|
return new Error(
|
|
19
19
|
`DeepSeek API key is not configured. Connect DeepSeek in OpenCode ("opencode auth login") or set ${API_KEY_ENV}.`,
|
|
20
|
-
)
|
|
20
|
+
);
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
/**
|
|
@@ -26,45 +26,43 @@ function missingApiKeyError(source: ApiKeySource): Error {
|
|
|
26
26
|
* `DEEPSEEK_API_KEY` remains available for headless setups.
|
|
27
27
|
*/
|
|
28
28
|
export async function resolveApiKey(ctx: Context, source: ApiKeySource): Promise<string> {
|
|
29
|
-
const envKey = apiKeyFromEnv(process.env)
|
|
29
|
+
const envKey = apiKeyFromEnv(process.env);
|
|
30
30
|
|
|
31
31
|
if (source === "env") {
|
|
32
|
-
if (envKey) return envKey
|
|
33
|
-
throw missingApiKeyError("env")
|
|
32
|
+
if (envKey) return envKey;
|
|
33
|
+
throw missingApiKeyError("env");
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
let stored: string | undefined
|
|
37
|
-
let storedError: unknown
|
|
36
|
+
let stored: string | undefined;
|
|
37
|
+
let storedError: unknown;
|
|
38
38
|
try {
|
|
39
|
-
const connection = await ctx.integration.connection.active(DEEPSEEK_INTEGRATION_ID)
|
|
40
|
-
const credential = connection
|
|
41
|
-
? await ctx.integration.connection.resolve(connection)
|
|
42
|
-
: undefined
|
|
39
|
+
const connection = await ctx.integration.connection.active(DEEPSEEK_INTEGRATION_ID);
|
|
40
|
+
const credential = connection ? await ctx.integration.connection.resolve(connection) : undefined;
|
|
43
41
|
if (credential?.type === "key") {
|
|
44
|
-
const key = credential.key.trim()
|
|
45
|
-
if (key) stored = key
|
|
42
|
+
const key = credential.key.trim();
|
|
43
|
+
if (key) stored = key;
|
|
46
44
|
}
|
|
47
45
|
} catch (error) {
|
|
48
|
-
storedError = error
|
|
46
|
+
storedError = error;
|
|
49
47
|
}
|
|
50
48
|
|
|
51
|
-
if (stored) return stored
|
|
49
|
+
if (stored) return stored;
|
|
52
50
|
if (source === "opencode") {
|
|
53
51
|
if (storedError) {
|
|
54
52
|
throw new Error(
|
|
55
53
|
'Could not read the DeepSeek credential from OpenCode. Run "opencode auth login" and connect DeepSeek.',
|
|
56
54
|
{ cause: storedError },
|
|
57
|
-
)
|
|
55
|
+
);
|
|
58
56
|
}
|
|
59
|
-
throw missingApiKeyError("opencode")
|
|
57
|
+
throw missingApiKeyError("opencode");
|
|
60
58
|
}
|
|
61
|
-
if (envKey) return envKey
|
|
59
|
+
if (envKey) return envKey;
|
|
62
60
|
if (storedError) {
|
|
63
61
|
throw new Error(
|
|
64
62
|
`DeepSeek API key is not configured, and the OpenCode credential could not be read. ` +
|
|
65
63
|
`Set ${API_KEY_ENV} or run "opencode auth login".`,
|
|
66
64
|
{ cause: storedError },
|
|
67
|
-
)
|
|
65
|
+
);
|
|
68
66
|
}
|
|
69
|
-
throw missingApiKeyError("auto")
|
|
67
|
+
throw missingApiKeyError("auto");
|
|
70
68
|
}
|
package/src/opencode/plugin.ts
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
|
-
import { Plugin } from "@opencode/plugin"
|
|
2
|
-
import type { Context } from "@opencode/plugin/promise/plugin"
|
|
3
|
-
import type { ToolContext } from "@opencode/plugin/promise/tool"
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import
|
|
8
|
-
import {
|
|
1
|
+
import { Plugin } from "@opencode/plugin";
|
|
2
|
+
import type { Context } from "@opencode/plugin/promise/plugin";
|
|
3
|
+
import type { ToolContext } from "@opencode/plugin/promise/tool";
|
|
4
|
+
import type { RetrievalClient } from "../core/client";
|
|
5
|
+
import { inspectFetchTarget, retrievalClient } from "../core/client";
|
|
6
|
+
import { FETCH_MARKDOWN_ONLY, FETCH_OUTPUT_TRUNCATED } from "../core/descriptions";
|
|
7
|
+
import { resolveFetchTimeoutMs, resolvePluginOptions } from "../core/options";
|
|
8
|
+
import type { PluginOptions, ResolvedOptions, SearchResult } from "../core/types";
|
|
9
|
+
import { resolveApiKey } from "./auth";
|
|
9
10
|
|
|
10
11
|
type FetchInput = {
|
|
11
|
-
readonly url: string
|
|
12
|
-
readonly format?: "markdown" | "text" | "html"
|
|
13
|
-
readonly timeout?: number
|
|
14
|
-
}
|
|
12
|
+
readonly url: string;
|
|
13
|
+
readonly format?: "markdown" | "text" | "html";
|
|
14
|
+
readonly timeout?: number;
|
|
15
|
+
};
|
|
15
16
|
|
|
16
17
|
const FETCH_INPUT = {
|
|
17
18
|
type: "object",
|
|
@@ -36,23 +37,23 @@ const FETCH_INPUT = {
|
|
|
36
37
|
},
|
|
37
38
|
required: ["url"],
|
|
38
39
|
additionalProperties: false,
|
|
39
|
-
} as const
|
|
40
|
+
} as const;
|
|
40
41
|
|
|
41
42
|
const DEEPSEEK_FETCH_DESCRIPTION =
|
|
42
43
|
"Fetch one web page as clean Markdown through DeepSeek's Fetch API. This separate tool remains " +
|
|
43
|
-
|
|
44
|
+
`available when the built-in webfetch is replaced. ${FETCH_MARKDOWN_ONLY} ${FETCH_OUTPUT_TRUNCATED}`;
|
|
44
45
|
|
|
45
46
|
const WEBFETCH_DESCRIPTION =
|
|
46
47
|
"Fetch content from an HTTP or HTTPS URL and return it as Markdown through DeepSeek's Fetch API. " +
|
|
47
|
-
|
|
48
|
-
|
|
48
|
+
"This is the built-in webfetch replacement enabled by fetchOverrideWebfetch. DeepSeek does not " +
|
|
49
|
+
`render JavaScript and some sites return 403/404. ${FETCH_MARKDOWN_ONLY} ${FETCH_OUTPUT_TRUNCATED}`;
|
|
49
50
|
|
|
50
51
|
/** OpenCode's WebSearch.Result shape, which wraps the published date in `time`. */
|
|
51
52
|
interface OpenCodeSearchResult {
|
|
52
|
-
readonly url: string
|
|
53
|
-
readonly title?: string
|
|
54
|
-
readonly content?: string
|
|
55
|
-
readonly time: { readonly published?: number }
|
|
53
|
+
readonly url: string;
|
|
54
|
+
readonly title?: string;
|
|
55
|
+
readonly content?: string;
|
|
56
|
+
readonly time: { readonly published?: number };
|
|
56
57
|
}
|
|
57
58
|
|
|
58
59
|
/** Maps the core's neutral SearchResult onto the shape OpenCode expects. */
|
|
@@ -61,17 +62,15 @@ function toOpenCodeResult(result: SearchResult): OpenCodeSearchResult {
|
|
|
61
62
|
url: result.url,
|
|
62
63
|
...(result.title ? { title: result.title } : {}),
|
|
63
64
|
...(result.content ? { content: result.content } : {}),
|
|
64
|
-
...(result.published !== undefined
|
|
65
|
-
|
|
66
|
-
: { time: {} }),
|
|
67
|
-
}
|
|
65
|
+
...(result.published !== undefined ? { time: { published: result.published } } : { time: {} }),
|
|
66
|
+
};
|
|
68
67
|
}
|
|
69
68
|
|
|
70
69
|
/** How the plugin registers itself with OpenCode; resolved where it is consumed. */
|
|
71
70
|
export interface Registration {
|
|
72
|
-
readonly setDefault: boolean
|
|
73
|
-
readonly overrideWebfetch: boolean
|
|
74
|
-
readonly registerTool: boolean
|
|
71
|
+
readonly setDefault: boolean;
|
|
72
|
+
readonly overrideWebfetch: boolean;
|
|
73
|
+
readonly registerTool: boolean;
|
|
75
74
|
}
|
|
76
75
|
|
|
77
76
|
export function resolveRegistration(input?: PluginOptions): Registration {
|
|
@@ -79,12 +78,12 @@ export function resolveRegistration(input?: PluginOptions): Registration {
|
|
|
79
78
|
setDefault: input?.setDefaultWebsearch === true,
|
|
80
79
|
overrideWebfetch: input?.fetchOverrideWebfetch === true,
|
|
81
80
|
registerTool: input?.fetchRegisterTool !== false,
|
|
82
|
-
}
|
|
81
|
+
};
|
|
83
82
|
}
|
|
84
83
|
|
|
85
84
|
/** One place builds the per-request context the core expects. */
|
|
86
85
|
function requestContext(config: ResolvedOptions, apiKey: string, signal: AbortSignal, timeoutMs?: number) {
|
|
87
|
-
return { signal, auth: { apiKey }, retry: config.retry, ...(timeoutMs === undefined ? {} : { timeoutMs }) }
|
|
86
|
+
return { signal, auth: { apiKey }, retry: config.retry, ...(timeoutMs === undefined ? {} : { timeoutMs }) };
|
|
88
87
|
}
|
|
89
88
|
|
|
90
89
|
async function runFetch(
|
|
@@ -94,25 +93,26 @@ async function runFetch(
|
|
|
94
93
|
context: ToolContext,
|
|
95
94
|
permission: string,
|
|
96
95
|
saveAll: boolean,
|
|
96
|
+
client: RetrievalClient,
|
|
97
97
|
) {
|
|
98
|
-
const format = input.format ?? "markdown"
|
|
98
|
+
const format = input.format ?? "markdown";
|
|
99
99
|
if (format !== "markdown") {
|
|
100
100
|
throw new Error(
|
|
101
101
|
`DeepSeek Fetch only returns Markdown; format "${format}" is not supported. Omit format or use "markdown".`,
|
|
102
|
-
)
|
|
102
|
+
);
|
|
103
103
|
}
|
|
104
104
|
|
|
105
105
|
if (typeof input.url !== "string") {
|
|
106
|
-
throw new Error('"url" must be a string')
|
|
106
|
+
throw new Error('"url" must be a string');
|
|
107
107
|
}
|
|
108
108
|
// Inspect before the permission prompt so an invalid or blocked target fails
|
|
109
109
|
// without asking the user; fetchPage enforces the policy again.
|
|
110
|
-
const target = inspectFetchTarget(input.url, config.fetch)
|
|
111
|
-
const timeoutMs = resolveFetchTimeoutMs(config.fetch.timeoutMs, input.timeout)
|
|
110
|
+
const target = inspectFetchTarget(input.url, config.fetch);
|
|
111
|
+
const timeoutMs = resolveFetchTimeoutMs(config.fetch.timeoutMs, input.timeout);
|
|
112
112
|
|
|
113
113
|
// Resolved before the permission prompt so a missing key fails fast instead
|
|
114
114
|
// of asking the user to approve a request that cannot run.
|
|
115
|
-
const apiKey = await resolveApiKey(ctx, config.auth.source)
|
|
115
|
+
const apiKey = await resolveApiKey(ctx, config.auth.source);
|
|
116
116
|
|
|
117
117
|
await ctx.permission.assert({
|
|
118
118
|
sessionID: context.sessionID,
|
|
@@ -122,9 +122,13 @@ async function runFetch(
|
|
|
122
122
|
save: saveAll ? ["*"] : [target.href],
|
|
123
123
|
metadata: saveAll ? { ...input, url: target.href, format } : { url: target.href },
|
|
124
124
|
source: { type: "tool", messageID: context.messageID, id: context.id },
|
|
125
|
-
})
|
|
125
|
+
});
|
|
126
126
|
|
|
127
|
-
const page = await fetchDocument(
|
|
127
|
+
const page = await client.fetchDocument(
|
|
128
|
+
target,
|
|
129
|
+
config.fetch,
|
|
130
|
+
requestContext(config, apiKey, context.signal, timeoutMs),
|
|
131
|
+
);
|
|
128
132
|
return {
|
|
129
133
|
content: page.content,
|
|
130
134
|
metadata: {
|
|
@@ -132,56 +136,64 @@ async function runFetch(
|
|
|
132
136
|
title: page.title,
|
|
133
137
|
truncated: page.truncated,
|
|
134
138
|
},
|
|
135
|
-
}
|
|
139
|
+
};
|
|
136
140
|
}
|
|
137
141
|
|
|
138
|
-
export
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
id: "deepseek",
|
|
148
|
-
name: "DeepSeek",
|
|
149
|
-
execute: async (input, { signal }) => {
|
|
150
|
-
const apiKey = await resolveApiKey(ctx, config.auth.source)
|
|
151
|
-
const results = await searchWeb(input.query, config.search, requestContext(config, apiKey, signal))
|
|
152
|
-
return results.map(toOpenCodeResult)
|
|
153
|
-
},
|
|
154
|
-
})
|
|
155
|
-
if (registration.setDefault) editor.default.set("deepseek")
|
|
156
|
-
})
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
if (config.fetch.enabled) {
|
|
160
|
-
await ctx.tool.transform((editor) => {
|
|
161
|
-
if (registration.overrideWebfetch) editor.remove("webfetch")
|
|
162
|
-
|
|
163
|
-
if (registration.registerTool) {
|
|
164
|
-
editor.add({
|
|
165
|
-
name: "deepseek_fetch",
|
|
166
|
-
description: DEEPSEEK_FETCH_DESCRIPTION,
|
|
167
|
-
input: FETCH_INPUT,
|
|
168
|
-
options: { codemode: false, permission: "deepseek_fetch" },
|
|
169
|
-
execute: (input, context) =>
|
|
170
|
-
runFetch(ctx, config, input as FetchInput, context, "deepseek_fetch", false),
|
|
171
|
-
})
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
if (registration.overrideWebfetch) {
|
|
142
|
+
export function createOpenCodePlugin(client: RetrievalClient = retrievalClient) {
|
|
143
|
+
return Plugin.define({
|
|
144
|
+
id: "deepseek-web-retrieval",
|
|
145
|
+
async setup(ctx) {
|
|
146
|
+
const config = resolvePluginOptions(ctx.options);
|
|
147
|
+
const registration = resolveRegistration(ctx.options);
|
|
148
|
+
|
|
149
|
+
if (config.search.enabled) {
|
|
150
|
+
await ctx.websearch.transform((editor) => {
|
|
175
151
|
editor.add({
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
})
|
|
152
|
+
id: "deepseek",
|
|
153
|
+
name: "DeepSeek",
|
|
154
|
+
execute: async (input, { signal }) => {
|
|
155
|
+
const apiKey = await resolveApiKey(ctx, config.auth.source);
|
|
156
|
+
const results = await client.searchWeb(
|
|
157
|
+
input.query,
|
|
158
|
+
config.search,
|
|
159
|
+
requestContext(config, apiKey, signal),
|
|
160
|
+
);
|
|
161
|
+
return results.map(toOpenCodeResult);
|
|
162
|
+
},
|
|
163
|
+
});
|
|
164
|
+
if (registration.setDefault) editor.default.set("deepseek");
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (config.fetch.enabled) {
|
|
169
|
+
await ctx.tool.transform((editor) => {
|
|
170
|
+
if (registration.overrideWebfetch) editor.remove("webfetch");
|
|
171
|
+
|
|
172
|
+
if (registration.registerTool) {
|
|
173
|
+
editor.add({
|
|
174
|
+
name: "deepseek_fetch",
|
|
175
|
+
description: DEEPSEEK_FETCH_DESCRIPTION,
|
|
176
|
+
input: FETCH_INPUT,
|
|
177
|
+
options: { codemode: false, permission: "deepseek_fetch" },
|
|
178
|
+
execute: (input, context) =>
|
|
179
|
+
runFetch(ctx, config, input as FetchInput, context, "deepseek_fetch", false, client),
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (registration.overrideWebfetch) {
|
|
184
|
+
editor.add({
|
|
185
|
+
name: "webfetch",
|
|
186
|
+
description: WEBFETCH_DESCRIPTION,
|
|
187
|
+
input: FETCH_INPUT,
|
|
188
|
+
options: { codemode: false, permission: "webfetch" },
|
|
189
|
+
execute: (input, context) =>
|
|
190
|
+
runFetch(ctx, config, input as FetchInput, context, "webfetch", true, client),
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
},
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export default createOpenCodePlugin();
|