deepseek-web-retrieval 0.2.0 → 0.3.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 +187 -1
- package/README.md +13 -9
- package/dist/cli.js +4228 -178
- package/docs/adr/0001-refuse-drift-between-plan-and-apply.md +29 -0
- package/docs/agents/codex.md +193 -0
- package/docs/agents/domain.md +51 -0
- package/docs/agents/issue-tracker.md +45 -0
- package/docs/{opencode-v2.md → agents/opencode-v2.md} +30 -7
- package/docs/{pi.md → agents/pi.md} +35 -7
- package/docs/agents/triage-labels.md +15 -0
- package/docs/design/installer-design.md +184 -0
- package/docs/hosts.jsonc +59 -33
- package/docs/integration.md +45 -52
- package/docs/research/control-matrix.md +257 -0
- package/docs/{mcp-compatibility.md → research/mcp-compatibility.md} +39 -108
- package/package.json +5 -3
- package/src/core/auth.ts +1 -1
- package/src/core/client.ts +42 -249
- package/src/core/constants.ts +5 -0
- package/src/core/descriptions.ts +19 -0
- package/src/core/format.ts +31 -1
- package/src/core/jsonc.ts +16 -0
- package/src/core/options.ts +2 -3
- package/src/core/search.ts +5 -7
- package/src/core/transport.ts +253 -0
- package/src/core/types.ts +13 -11
- package/src/installer/apply.ts +176 -0
- package/src/installer/cli.ts +215 -0
- package/src/installer/detect.ts +85 -0
- package/src/installer/formats/index.ts +58 -0
- package/src/installer/formats/json.ts +151 -0
- package/src/installer/formats/rules.ts +62 -0
- package/src/installer/formats/toml.ts +163 -0
- package/src/installer/formats/value.ts +57 -0
- package/src/installer/hosts/codex.ts +128 -0
- package/src/installer/hosts/index.ts +134 -0
- package/src/installer/hosts/opencode.ts +118 -0
- package/src/installer/hosts/pi.ts +115 -0
- package/src/installer/hosts/shared.ts +31 -0
- package/src/installer/paths.ts +18 -0
- package/src/installer/render.ts +62 -0
- package/src/installer/revert.ts +61 -0
- package/src/installer/rulesContent.ts +32 -0
- package/src/installer/state.ts +63 -0
- package/src/installer/types.ts +126 -0
- package/src/installer/uninstall.ts +33 -0
- package/src/mcp/auth.ts +2 -1
- package/src/mcp/cli.ts +21 -5
- package/src/mcp/config.ts +70 -27
- package/src/mcp/server.ts +81 -65
- package/src/opencode/auth.ts +3 -2
- package/src/opencode/plugin.ts +93 -56
- package/src/mcp/format.ts +0 -31
package/src/opencode/plugin.ts
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
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
|
-
import {
|
|
5
|
-
import {
|
|
4
|
+
import { fetchDocument, inspectFetchTarget, searchWeb } from "../core/client"
|
|
5
|
+
import { FETCH_MARKDOWN_ONLY, FETCH_OUTPUT_TRUNCATED } from "../core/descriptions"
|
|
6
6
|
import { resolveFetchTimeoutMs, resolvePluginOptions } from "../core/options"
|
|
7
|
-
import {
|
|
8
|
-
import type { ResolvedOptions } from "../core/types"
|
|
9
|
-
import { parseFetchUrl } from "../core/url"
|
|
7
|
+
import type { PluginOptions, ResolvedOptions, SearchResult } from "../core/types"
|
|
10
8
|
import { resolveApiKey } from "./auth"
|
|
11
9
|
|
|
12
10
|
type FetchInput = {
|
|
@@ -41,10 +39,53 @@ const FETCH_INPUT = {
|
|
|
41
39
|
} as const
|
|
42
40
|
|
|
43
41
|
const DEEPSEEK_FETCH_DESCRIPTION =
|
|
44
|
-
|
|
42
|
+
"Fetch one web page as clean Markdown through DeepSeek's Fetch API. This separate tool remains " +
|
|
43
|
+
`available when the built-in webfetch is replaced. ${FETCH_MARKDOWN_ONLY} ${FETCH_OUTPUT_TRUNCATED}`
|
|
45
44
|
|
|
46
45
|
const WEBFETCH_DESCRIPTION =
|
|
47
|
-
|
|
46
|
+
"Fetch content from an HTTP or HTTPS URL and return it as Markdown through DeepSeek's Fetch API. " +
|
|
47
|
+
"This is the built-in webfetch replacement enabled by fetchOverrideWebfetch. DeepSeek does not " +
|
|
48
|
+
`render JavaScript and some sites return 403/404. ${FETCH_MARKDOWN_ONLY} ${FETCH_OUTPUT_TRUNCATED}`
|
|
49
|
+
|
|
50
|
+
/** OpenCode's WebSearch.Result shape, which wraps the published date in `time`. */
|
|
51
|
+
interface OpenCodeSearchResult {
|
|
52
|
+
readonly url: string
|
|
53
|
+
readonly title?: string
|
|
54
|
+
readonly content?: string
|
|
55
|
+
readonly time: { readonly published?: number }
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Maps the core's neutral SearchResult onto the shape OpenCode expects. */
|
|
59
|
+
function toOpenCodeResult(result: SearchResult): OpenCodeSearchResult {
|
|
60
|
+
return {
|
|
61
|
+
url: result.url,
|
|
62
|
+
...(result.title ? { title: result.title } : {}),
|
|
63
|
+
...(result.content ? { content: result.content } : {}),
|
|
64
|
+
...(result.published !== undefined
|
|
65
|
+
? { time: { published: result.published } }
|
|
66
|
+
: { time: {} }),
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** How the plugin registers itself with OpenCode; resolved where it is consumed. */
|
|
71
|
+
export interface Registration {
|
|
72
|
+
readonly setDefault: boolean
|
|
73
|
+
readonly overrideWebfetch: boolean
|
|
74
|
+
readonly registerTool: boolean
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function resolveRegistration(input?: PluginOptions): Registration {
|
|
78
|
+
return {
|
|
79
|
+
setDefault: input?.setDefaultWebsearch === true,
|
|
80
|
+
overrideWebfetch: input?.fetchOverrideWebfetch === true,
|
|
81
|
+
registerTool: input?.fetchRegisterTool !== false,
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** One place builds the per-request context the core expects. */
|
|
86
|
+
function requestContext(config: ResolvedOptions, apiKey: string, signal: AbortSignal, timeoutMs?: number) {
|
|
87
|
+
return { signal, auth: { apiKey }, retry: config.retry, ...(timeoutMs === undefined ? {} : { timeoutMs }) }
|
|
88
|
+
}
|
|
48
89
|
|
|
49
90
|
async function runFetch(
|
|
50
91
|
ctx: Context,
|
|
@@ -64,10 +105,9 @@ async function runFetch(
|
|
|
64
105
|
if (typeof input.url !== "string") {
|
|
65
106
|
throw new Error('"url" must be a string')
|
|
66
107
|
}
|
|
67
|
-
|
|
68
|
-
//
|
|
69
|
-
|
|
70
|
-
assertFetchHostPolicy(target, config.fetch)
|
|
108
|
+
// Inspect before the permission prompt so an invalid or blocked target fails
|
|
109
|
+
// without asking the user; fetchPage enforces the policy again.
|
|
110
|
+
const target = inspectFetchTarget(input.url, config.fetch)
|
|
71
111
|
const timeoutMs = resolveFetchTimeoutMs(config.fetch.timeoutMs, input.timeout)
|
|
72
112
|
|
|
73
113
|
// Resolved before the permission prompt so a missing key fails fast instead
|
|
@@ -84,18 +124,13 @@ async function runFetch(
|
|
|
84
124
|
source: { type: "tool", messageID: context.messageID, id: context.id },
|
|
85
125
|
})
|
|
86
126
|
|
|
87
|
-
const page = await
|
|
88
|
-
target,
|
|
89
|
-
{ ...config.fetch, timeoutMs },
|
|
90
|
-
{ signal: context.signal, auth: { apiKey }, retry: config.retry },
|
|
91
|
-
)
|
|
92
|
-
const formatted = formatFetchedContent(page.title, page.content, config.fetch.maxChars)
|
|
127
|
+
const page = await fetchDocument(target, config.fetch, requestContext(config, apiKey, context.signal, timeoutMs))
|
|
93
128
|
return {
|
|
94
|
-
content:
|
|
129
|
+
content: page.content,
|
|
95
130
|
metadata: {
|
|
96
131
|
url: page.url,
|
|
97
132
|
title: page.title,
|
|
98
|
-
truncated:
|
|
133
|
+
truncated: page.truncated,
|
|
99
134
|
},
|
|
100
135
|
}
|
|
101
136
|
}
|
|
@@ -104,47 +139,49 @@ export default Plugin.define({
|
|
|
104
139
|
id: "deepseek-web-retrieval",
|
|
105
140
|
async setup(ctx) {
|
|
106
141
|
const config = resolvePluginOptions(ctx.options)
|
|
142
|
+
const registration = resolveRegistration(ctx.options)
|
|
107
143
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
signal
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
144
|
+
if (config.search.enabled) {
|
|
145
|
+
await ctx.websearch.transform((editor) => {
|
|
146
|
+
editor.add({
|
|
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")
|
|
120
156
|
})
|
|
121
|
-
|
|
122
|
-
})
|
|
157
|
+
}
|
|
123
158
|
|
|
124
|
-
|
|
125
|
-
|
|
159
|
+
if (config.fetch.enabled) {
|
|
160
|
+
await ctx.tool.transform((editor) => {
|
|
161
|
+
if (registration.overrideWebfetch) editor.remove("webfetch")
|
|
126
162
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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
|
+
}
|
|
137
173
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
174
|
+
if (registration.overrideWebfetch) {
|
|
175
|
+
editor.add({
|
|
176
|
+
name: "webfetch",
|
|
177
|
+
description: WEBFETCH_DESCRIPTION,
|
|
178
|
+
input: FETCH_INPUT,
|
|
179
|
+
options: { codemode: false, permission: "webfetch" },
|
|
180
|
+
execute: (input, context) =>
|
|
181
|
+
runFetch(ctx, config, input as FetchInput, context, "webfetch", true),
|
|
182
|
+
})
|
|
183
|
+
}
|
|
184
|
+
})
|
|
185
|
+
}
|
|
149
186
|
},
|
|
150
187
|
})
|
package/src/mcp/format.ts
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import type { WebSearchResult } from "../core/types"
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Renders processed search results as Markdown for the text content block.
|
|
5
|
-
* Hosts that ignore `structuredContent` (Gemini CLI, Continue) read this text.
|
|
6
|
-
*/
|
|
7
|
-
export function formatSearchResults(results: readonly WebSearchResult[]): string {
|
|
8
|
-
if (results.length === 0) return "No results found."
|
|
9
|
-
|
|
10
|
-
return results
|
|
11
|
-
.map((result, index) => {
|
|
12
|
-
const title = result.title?.trim() || result.url
|
|
13
|
-
const lines = [`${index + 1}. [${escapeBrackets(title)}](${result.url})`]
|
|
14
|
-
if (result.content) {
|
|
15
|
-
lines.push(` ${result.content.replaceAll("\n", " ")}`)
|
|
16
|
-
}
|
|
17
|
-
if (result.time.published !== undefined) {
|
|
18
|
-
lines.push(` Published: ${formatPublished(result.time.published)}`)
|
|
19
|
-
}
|
|
20
|
-
return lines.join("\n")
|
|
21
|
-
})
|
|
22
|
-
.join("\n\n")
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function escapeBrackets(value: string): string {
|
|
26
|
-
return value.replaceAll("[", "\\[").replaceAll("]", "\\]")
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function formatPublished(epochMs: number): string {
|
|
30
|
-
return new Date(epochMs).toISOString().slice(0, 10)
|
|
31
|
-
}
|