deepseek-web-retrieval 0.8.0 → 0.10.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 +18 -1
- package/README.md +33 -16
- package/dist/cli.js +307 -174
- package/docs/adr/0001-refuse-drift-between-plan-and-apply.md +7 -3
- package/docs/adr/0002-shared-config-base-layer.md +5 -1
- package/docs/adr/0003-need-content-per-call-boolean.md +49 -0
- package/docs/agents/claude-code.md +16 -6
- package/docs/agents/codex.md +14 -8
- package/docs/agents/domain.md +2 -2
- package/docs/agents/issue-tracker.md +5 -5
- package/docs/agents/opencode-v2.md +29 -15
- package/docs/agents/pi.md +41 -12
- package/docs/configuration.md +24 -7
- package/docs/design/config-layering-design.md +10 -5
- package/docs/design/installer-design.md +57 -25
- package/docs/design/search-content-design.md +133 -0
- package/docs/integration.md +19 -16
- package/docs/research/control-matrix.md +39 -34
- package/docs/research/mcp-compatibility.md +3 -1
- package/docs/research/ori-question-sourcing.md +31 -20
- package/docs/research/result-quality-passthrough.md +9 -7
- package/docs/research/search-api-defaults.md +4 -2
- package/docs/research/search-inline-content.md +49 -9
- package/package.json +3 -2
- package/src/core/auth.ts +7 -0
- package/src/core/client.ts +98 -27
- package/src/core/config-summary.ts +3 -0
- package/src/core/constants.ts +30 -2
- package/src/core/descriptions.ts +17 -2
- package/src/core/env.ts +7 -1
- package/src/core/format.ts +35 -5
- package/src/core/options.ts +56 -0
- package/src/core/search.ts +106 -15
- package/src/core/types.ts +29 -0
- package/src/installer/apply.ts +4 -4
- package/src/installer/cli.ts +2 -2
- package/src/installer/config-command.ts +4 -3
- package/src/installer/hosts/claude-code.ts +3 -17
- package/src/installer/hosts/codex.ts +3 -17
- package/src/installer/hosts/index.ts +2 -2
- package/src/installer/hosts/opencode.ts +1 -1
- package/src/installer/hosts/pi.ts +14 -15
- package/src/installer/hosts/shared.ts +23 -0
- package/src/installer/ownership.ts +2 -2
- package/src/installer/paths.ts +2 -2
- package/src/installer/{state.ts → record.ts} +15 -15
- package/src/installer/types.ts +1 -1
- package/src/installer/uninstall.ts +4 -4
- package/src/mcp/serve.ts +1 -2
- package/src/mcp/server.ts +19 -19
- package/src/opencode/plugin.ts +15 -10
- package/src/pi/extension.ts +26 -17
- package/src/mcp/auth.ts +0 -11
package/src/mcp/serve.ts
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { serveStdio } from "@modelcontextprotocol/server/stdio";
|
|
2
|
-
import { apiKeyFromEnv } from "../core/auth";
|
|
2
|
+
import { apiKeyFromEnv, MISSING_API_KEY_MESSAGE } from "../core/auth";
|
|
3
3
|
import { sharedFileOptions } from "../core/config-file";
|
|
4
4
|
import { configDeviations } from "../core/config-summary";
|
|
5
5
|
import { resolveEnvOptions } from "../core/env";
|
|
6
|
-
import { MISSING_API_KEY_MESSAGE } from "./auth";
|
|
7
6
|
import { isJsonReplacingClient } from "./client";
|
|
8
7
|
import { createDeepSeekServer, servePrecheck } from "./server";
|
|
9
8
|
|
package/src/mcp/server.ts
CHANGED
|
@@ -2,20 +2,24 @@ import type { CallToolResult, ServerContext } from "@modelcontextprotocol/server
|
|
|
2
2
|
import { McpServer } from "@modelcontextprotocol/server";
|
|
3
3
|
import * as z from "zod/v4";
|
|
4
4
|
import pkg from "../../package.json";
|
|
5
|
+
import { requireApiKey } from "../core/auth";
|
|
5
6
|
import type { RetrievalClient } from "../core/client";
|
|
6
|
-
import { retrievalClient } from "../core/client";
|
|
7
|
-
import { ENABLE_FETCH_ENV, ENABLE_SEARCH_ENV } from "../core/constants";
|
|
7
|
+
import { requestContext, retrievalClient } from "../core/client";
|
|
8
|
+
import { ENABLE_FETCH_ENV, ENABLE_SEARCH_ENV, MAX_FETCH_CHARS, MAX_SEARCH_COUNT } from "../core/constants";
|
|
8
9
|
import {
|
|
10
|
+
FETCH_MAX_CHARS_PARAMETER,
|
|
9
11
|
FETCH_TOOL_DESCRIPTION,
|
|
10
12
|
FETCH_URL_PARAMETER,
|
|
13
|
+
SEARCH_COUNT_PARAMETER,
|
|
14
|
+
SEARCH_NEED_CONTENT_PARAMETER,
|
|
11
15
|
SEARCH_ORI_QUESTION_PARAMETER,
|
|
12
16
|
SEARCH_QUERY_PARAMETER,
|
|
13
17
|
SEARCH_TOOL_DESCRIPTION,
|
|
14
18
|
} from "../core/descriptions";
|
|
15
19
|
import type { Env } from "../core/env";
|
|
16
20
|
import { formatSearchResults, toStructuredResult } from "../core/format";
|
|
21
|
+
import { bothDisabledPrecheck, withPerCallCount } from "../core/options";
|
|
17
22
|
import type { ResolvedOptions } from "../core/types";
|
|
18
|
-
import { requireApiKey } from "./auth";
|
|
19
23
|
import { omitStructuredContent } from "./client";
|
|
20
24
|
|
|
21
25
|
export const MCP_SERVER_NAME = "deepseek-web-retrieval";
|
|
@@ -23,10 +27,13 @@ export const MCP_SERVER_NAME = "deepseek-web-retrieval";
|
|
|
23
27
|
const SEARCH_INPUT = z.object({
|
|
24
28
|
query: z.string().min(1).describe(SEARCH_QUERY_PARAMETER),
|
|
25
29
|
original_question: z.string().optional().describe(SEARCH_ORI_QUESTION_PARAMETER),
|
|
30
|
+
count: z.number().int().min(1).max(MAX_SEARCH_COUNT).optional().describe(SEARCH_COUNT_PARAMETER),
|
|
31
|
+
need_content: z.boolean().optional().describe(SEARCH_NEED_CONTENT_PARAMETER),
|
|
26
32
|
});
|
|
27
33
|
|
|
28
34
|
const FETCH_INPUT = z.object({
|
|
29
35
|
url: z.string().min(1).describe(FETCH_URL_PARAMETER),
|
|
36
|
+
maxChars: z.number().int().min(0).max(MAX_FETCH_CHARS).optional().describe(FETCH_MAX_CHARS_PARAMETER),
|
|
30
37
|
});
|
|
31
38
|
|
|
32
39
|
/**
|
|
@@ -49,15 +56,16 @@ export function createDeepSeekServer(
|
|
|
49
56
|
inputSchema: SEARCH_INPUT,
|
|
50
57
|
annotations: { readOnlyHint: true },
|
|
51
58
|
},
|
|
52
|
-
async ({ query, original_question }, ctx) => {
|
|
59
|
+
async ({ query, original_question, count, need_content }, ctx) => {
|
|
53
60
|
try {
|
|
54
|
-
const request = requestContext(config, env, ctx.mcpReq.signal);
|
|
55
|
-
const
|
|
61
|
+
const request = requestContext(config, requireApiKey(env), ctx.mcpReq.signal);
|
|
62
|
+
const outcome = await client.searchWeb(query, withPerCallCount(config.search, count), {
|
|
56
63
|
...request,
|
|
57
64
|
oriQuestion: original_question,
|
|
65
|
+
needContent: need_content === true,
|
|
58
66
|
});
|
|
59
|
-
return callResult(server, ctx, formatSearchResults(results), {
|
|
60
|
-
results: results.map(toStructuredResult),
|
|
67
|
+
return callResult(server, ctx, formatSearchResults(outcome.results, outcome), {
|
|
68
|
+
results: outcome.results.map(toStructuredResult),
|
|
61
69
|
});
|
|
62
70
|
} catch (error) {
|
|
63
71
|
return toErrorResult(error);
|
|
@@ -75,9 +83,9 @@ export function createDeepSeekServer(
|
|
|
75
83
|
inputSchema: FETCH_INPUT,
|
|
76
84
|
annotations: { readOnlyHint: true },
|
|
77
85
|
},
|
|
78
|
-
async ({ url }, ctx) => {
|
|
86
|
+
async ({ url, maxChars }, ctx) => {
|
|
79
87
|
try {
|
|
80
|
-
const request = requestContext(config, env, ctx.mcpReq.signal);
|
|
88
|
+
const request = requestContext(config, requireApiKey(env), ctx.mcpReq.signal, { maxChars });
|
|
81
89
|
const page = await client.fetchDocument(url, config.fetch, request);
|
|
82
90
|
return callResult(server, ctx, page.content, {
|
|
83
91
|
url: page.url,
|
|
@@ -95,10 +103,6 @@ export function createDeepSeekServer(
|
|
|
95
103
|
return server;
|
|
96
104
|
}
|
|
97
105
|
|
|
98
|
-
function requestContext(config: ResolvedOptions, env: Env, signal: AbortSignal) {
|
|
99
|
-
return { signal, auth: { apiKey: requireApiKey(env) }, retry: config.retry };
|
|
100
|
-
}
|
|
101
|
-
|
|
102
106
|
/**
|
|
103
107
|
* Text content always reaches the model; the structured mirror is dropped for
|
|
104
108
|
* clients that would replace the text with its serialized JSON (Codex does).
|
|
@@ -118,11 +122,7 @@ function callResult(
|
|
|
118
122
|
|
|
119
123
|
/** Returns the both-disabled message, or undefined when a tool stays enabled. */
|
|
120
124
|
export function servePrecheck(config: ResolvedOptions): string | undefined {
|
|
121
|
-
|
|
122
|
-
return (
|
|
123
|
-
`Both tools are disabled (${ENABLE_SEARCH_ENV} and ${ENABLE_FETCH_ENV} are false); ` +
|
|
124
|
-
"at least one must stay enabled."
|
|
125
|
-
);
|
|
125
|
+
return bothDisabledPrecheck(config, `${ENABLE_SEARCH_ENV} and ${ENABLE_FETCH_ENV} are false`);
|
|
126
126
|
}
|
|
127
127
|
|
|
128
128
|
function toErrorResult(error: unknown): CallToolResult {
|
package/src/opencode/plugin.ts
CHANGED
|
@@ -2,10 +2,10 @@ import { Plugin } from "@opencode/plugin";
|
|
|
2
2
|
import type { Context } from "@opencode/plugin/promise/plugin";
|
|
3
3
|
import type { ToolContext } from "@opencode/plugin/promise/tool";
|
|
4
4
|
import type { RetrievalClient } from "../core/client";
|
|
5
|
-
import { inspectFetchTarget, retrievalClient } from "../core/client";
|
|
5
|
+
import { inspectFetchTarget, requestContext, retrievalClient } from "../core/client";
|
|
6
6
|
import { sharedFileOptions } from "../core/config-file";
|
|
7
7
|
import { configDeviations } from "../core/config-summary";
|
|
8
|
-
import { FETCH_MARKDOWN_ONLY, FETCH_OUTPUT_TRUNCATED } from "../core/descriptions";
|
|
8
|
+
import { FETCH_MARKDOWN_ONLY, FETCH_MAX_CHARS_PARAMETER, FETCH_OUTPUT_TRUNCATED } from "../core/descriptions";
|
|
9
9
|
import { pluginOptionsFromEnv } from "../core/env";
|
|
10
10
|
import { formatQualityMarker } from "../core/format";
|
|
11
11
|
import { resolveFetchTimeoutMs, resolvePluginOptions } from "../core/options";
|
|
@@ -16,6 +16,7 @@ type FetchInput = {
|
|
|
16
16
|
readonly url: string;
|
|
17
17
|
readonly format?: "markdown" | "text" | "html";
|
|
18
18
|
readonly timeout?: number;
|
|
19
|
+
readonly maxChars?: number;
|
|
19
20
|
};
|
|
20
21
|
|
|
21
22
|
const FETCH_INPUT = {
|
|
@@ -38,6 +39,12 @@ const FETCH_INPUT = {
|
|
|
38
39
|
description:
|
|
39
40
|
"Optional timeout in seconds (maximum: 120). The effective limit is the shorter of this and fetchTimeoutMs.",
|
|
40
41
|
},
|
|
42
|
+
maxChars: {
|
|
43
|
+
type: "integer",
|
|
44
|
+
minimum: 0,
|
|
45
|
+
maximum: 1_000_000,
|
|
46
|
+
description: FETCH_MAX_CHARS_PARAMETER,
|
|
47
|
+
},
|
|
41
48
|
},
|
|
42
49
|
required: ["url"],
|
|
43
50
|
additionalProperties: false,
|
|
@@ -89,11 +96,6 @@ export function resolveRegistration(input?: PluginOptions): Registration {
|
|
|
89
96
|
};
|
|
90
97
|
}
|
|
91
98
|
|
|
92
|
-
/** One place builds the per-request context the core expects. */
|
|
93
|
-
function requestContext(config: ResolvedOptions, apiKey: string, signal: AbortSignal, timeoutMs?: number) {
|
|
94
|
-
return { signal, auth: { apiKey }, retry: config.retry, ...(timeoutMs === undefined ? {} : { timeoutMs }) };
|
|
95
|
-
}
|
|
96
|
-
|
|
97
99
|
async function runFetch(
|
|
98
100
|
ctx: Context,
|
|
99
101
|
config: ResolvedOptions,
|
|
@@ -135,7 +137,7 @@ async function runFetch(
|
|
|
135
137
|
const page = await client.fetchDocument(
|
|
136
138
|
target,
|
|
137
139
|
config.fetch,
|
|
138
|
-
requestContext(config, apiKey, context.signal, timeoutMs),
|
|
140
|
+
requestContext(config, apiKey, context.signal, { timeoutMs, maxChars: input.maxChars }),
|
|
139
141
|
);
|
|
140
142
|
return {
|
|
141
143
|
content: page.content,
|
|
@@ -173,12 +175,15 @@ export function createOpenCodePlugin(client: RetrievalClient = retrievalClient)
|
|
|
173
175
|
name: "DeepSeek",
|
|
174
176
|
execute: async (input, { signal }) => {
|
|
175
177
|
const apiKey = await resolveApiKey(ctx, config.auth.source);
|
|
176
|
-
const
|
|
178
|
+
const outcome = await client.searchWeb(
|
|
177
179
|
input.query,
|
|
178
180
|
config.search,
|
|
179
181
|
requestContext(config, apiKey, signal),
|
|
180
182
|
);
|
|
181
|
-
|
|
183
|
+
// OpenCode's result shape is closed, so the effective-query header
|
|
184
|
+
// has nowhere to ride on this path (documented in the guide);
|
|
185
|
+
// degradation notes still surface through thrown error messages.
|
|
186
|
+
return outcome.results.map(toOpenCodeResult);
|
|
182
187
|
},
|
|
183
188
|
});
|
|
184
189
|
if (registration.setDefault) editor.default.set("deepseek");
|
package/src/pi/extension.ts
CHANGED
|
@@ -3,14 +3,17 @@ import { Type } from "@earendil-works/pi-ai";
|
|
|
3
3
|
import type { AgentToolResult, ExtensionAPI, ExtensionToolContext } from "@earendil-works/pi-coding-agent";
|
|
4
4
|
import { defineTool } from "@earendil-works/pi-coding-agent";
|
|
5
5
|
import { apiKeyFromEnv } from "../core/auth";
|
|
6
|
-
import type {
|
|
7
|
-
import { inspectFetchTarget, retrievalClient } from "../core/client";
|
|
6
|
+
import type { RetrievalClient } from "../core/client";
|
|
7
|
+
import { inspectFetchTarget, requestContext, retrievalClient } from "../core/client";
|
|
8
8
|
import { sharedFileOptions } from "../core/config-file";
|
|
9
9
|
import { configDeviations } from "../core/config-summary";
|
|
10
|
-
import { API_KEY_ENV, ENABLE_FETCH_ENV, ENABLE_SEARCH_ENV } from "../core/constants";
|
|
10
|
+
import { API_KEY_ENV, ENABLE_FETCH_ENV, ENABLE_SEARCH_ENV, MAX_FETCH_CHARS, MAX_SEARCH_COUNT } from "../core/constants";
|
|
11
11
|
import {
|
|
12
|
+
FETCH_MAX_CHARS_PARAMETER,
|
|
12
13
|
FETCH_TOOL_DESCRIPTION,
|
|
13
14
|
FETCH_URL_PARAMETER,
|
|
15
|
+
SEARCH_COUNT_PARAMETER,
|
|
16
|
+
SEARCH_NEED_CONTENT_PARAMETER,
|
|
14
17
|
SEARCH_ORI_QUESTION_PARAMETER,
|
|
15
18
|
SEARCH_QUERY_PARAMETER,
|
|
16
19
|
SEARCH_TOOL_DESCRIPTION,
|
|
@@ -18,7 +21,7 @@ import {
|
|
|
18
21
|
import type { Env } from "../core/env";
|
|
19
22
|
import { pluginOptionsFromEnv } from "../core/env";
|
|
20
23
|
import { formatSearchResults, toStructuredResult } from "../core/format";
|
|
21
|
-
import { resolvePluginOptions } from "../core/options";
|
|
24
|
+
import { bothDisabledPrecheck, resolvePluginOptions, withPerCallCount } from "../core/options";
|
|
22
25
|
import type { ResolvedOptions } from "../core/types";
|
|
23
26
|
import type { ExtensionSettings } from "./settings";
|
|
24
27
|
import { readExtensionSettings, SETTINGS_NAMESPACE } from "./settings";
|
|
@@ -30,6 +33,8 @@ const SEARCH_PARAMETERS = Type.Object({
|
|
|
30
33
|
description: `${SEARCH_ORI_QUESTION_PARAMETER} When left blank, the most recent user message in the session is sent instead, truncated to fit.`,
|
|
31
34
|
}),
|
|
32
35
|
),
|
|
36
|
+
count: Type.Optional(Type.Integer({ minimum: 1, maximum: MAX_SEARCH_COUNT, description: SEARCH_COUNT_PARAMETER })),
|
|
37
|
+
need_content: Type.Optional(Type.Boolean({ description: SEARCH_NEED_CONTENT_PARAMETER })),
|
|
33
38
|
});
|
|
34
39
|
|
|
35
40
|
const SEARCH_OUTPUT = Type.Object({
|
|
@@ -48,6 +53,9 @@ const SEARCH_OUTPUT = Type.Object({
|
|
|
48
53
|
|
|
49
54
|
const FETCH_PARAMETERS = Type.Object({
|
|
50
55
|
url: Type.String({ minLength: 1, description: FETCH_URL_PARAMETER }),
|
|
56
|
+
maxChars: Type.Optional(
|
|
57
|
+
Type.Integer({ minimum: 0, maximum: MAX_FETCH_CHARS, description: FETCH_MAX_CHARS_PARAMETER }),
|
|
58
|
+
),
|
|
51
59
|
});
|
|
52
60
|
|
|
53
61
|
const FETCH_OUTPUT = Type.Object({
|
|
@@ -77,10 +85,10 @@ export function resolveExtensionOptions(env: Env = process.env, settings: Extens
|
|
|
77
85
|
|
|
78
86
|
/** Returns the both-disabled message, or undefined when a tool stays enabled. */
|
|
79
87
|
export function extensionPrecheck(config: ResolvedOptions): string | undefined {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
88
|
+
return bothDisabledPrecheck(
|
|
89
|
+
config,
|
|
90
|
+
`${ENABLE_SEARCH_ENV} and ${ENABLE_FETCH_ENV} are false, or the ` +
|
|
91
|
+
`${SETTINGS_NAMESPACE} settings namespace disabled them`,
|
|
84
92
|
);
|
|
85
93
|
}
|
|
86
94
|
|
|
@@ -100,13 +108,14 @@ export function searchTool(config: ResolvedOptions, client: RetrievalClient = re
|
|
|
100
108
|
// The explicit parameter wins; only when the model left it blank does
|
|
101
109
|
// the session fallback get a say.
|
|
102
110
|
const explicit = params.original_question?.trim() ? params.original_question : undefined;
|
|
103
|
-
const
|
|
104
|
-
...requestContext(config, signal),
|
|
111
|
+
const outcome = await client.searchWeb(params.query, withPerCallCount(config.search, params.count), {
|
|
112
|
+
...requestContext(config, requireApiKey(), signal ?? NEVER_ABORTS),
|
|
105
113
|
oriQuestion: explicit ?? sessionOriQuestion(ctx),
|
|
114
|
+
needContent: params.need_content === true,
|
|
106
115
|
});
|
|
107
116
|
return {
|
|
108
|
-
content: [{ type: "text", text: formatSearchResults(results) }],
|
|
109
|
-
structuredContent: { results: results.map(toStructuredResult) },
|
|
117
|
+
content: [{ type: "text", text: formatSearchResults(outcome.results, outcome) }],
|
|
118
|
+
structuredContent: { results: outcome.results.map(toStructuredResult) },
|
|
110
119
|
details: undefined,
|
|
111
120
|
};
|
|
112
121
|
} catch (error) {
|
|
@@ -130,7 +139,11 @@ export function fetchTool(config: ResolvedOptions, client: RetrievalClient = ret
|
|
|
130
139
|
async execute(_toolCallId, params, signal): Promise<AgentToolResult> {
|
|
131
140
|
try {
|
|
132
141
|
const target = inspectFetchTarget(params.url, config.fetch);
|
|
133
|
-
const page = await client.fetchDocument(
|
|
142
|
+
const page = await client.fetchDocument(
|
|
143
|
+
target,
|
|
144
|
+
config.fetch,
|
|
145
|
+
requestContext(config, requireApiKey(), signal ?? NEVER_ABORTS, { maxChars: params.maxChars }),
|
|
146
|
+
);
|
|
134
147
|
return {
|
|
135
148
|
content: [{ type: "text", text: page.content }],
|
|
136
149
|
structuredContent: {
|
|
@@ -168,10 +181,6 @@ export default function deepSeekWebRetrieval(pi: ExtensionAPI): void {
|
|
|
168
181
|
if (config.fetch.enabled) pi.registerTool(fetchTool(config));
|
|
169
182
|
}
|
|
170
183
|
|
|
171
|
-
function requestContext(config: ResolvedOptions, signal: AbortSignal | undefined): RequestOptions {
|
|
172
|
-
return { signal: signal ?? NEVER_ABORTS, auth: { apiKey: requireApiKey() }, retry: config.retry };
|
|
173
|
-
}
|
|
174
|
-
|
|
175
184
|
/**
|
|
176
185
|
* Best-effort `ori_question` fallback for when the model left the tool input
|
|
177
186
|
* blank: the most recent user message text in the session projection, readable
|
package/src/mcp/auth.ts
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { apiKeyFromEnv, MISSING_API_KEY_MESSAGE } from "../core/auth";
|
|
2
|
-
import type { Env } from "../core/env";
|
|
3
|
-
|
|
4
|
-
export { MISSING_API_KEY_MESSAGE };
|
|
5
|
-
|
|
6
|
-
/** Resolves the API key from the environment or fails with shared guidance. */
|
|
7
|
-
export function requireApiKey(env: Env): string {
|
|
8
|
-
const apiKey = apiKeyFromEnv(env);
|
|
9
|
-
if (!apiKey) throw new Error(MISSING_API_KEY_MESSAGE);
|
|
10
|
-
return apiKey;
|
|
11
|
-
}
|