deepseek-web-retrieval 0.6.0 → 0.8.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.
Files changed (50) hide show
  1. package/CHANGELOG.md +23 -1
  2. package/README.md +113 -53
  3. package/dist/cli.js +2427 -1813
  4. package/docs/adr/0002-shared-config-base-layer.md +43 -0
  5. package/docs/agents/claude-code.md +273 -0
  6. package/docs/agents/codex.md +50 -27
  7. package/docs/agents/opencode-v2.md +59 -33
  8. package/docs/agents/pi.md +50 -27
  9. package/docs/configuration.md +109 -0
  10. package/docs/design/config-layering-design.md +180 -0
  11. package/docs/design/installer-design.md +24 -13
  12. package/docs/hosts.jsonc +5 -1
  13. package/docs/integration.md +15 -9
  14. package/docs/research/control-matrix.md +49 -11
  15. package/docs/research/mcp-compatibility.md +6 -3
  16. package/docs/research/ori-question-sourcing.md +336 -0
  17. package/docs/research/result-quality-passthrough.md +159 -0
  18. package/docs/research/search-api-defaults.md +18 -2
  19. package/docs/research/search-inline-content.md +244 -0
  20. package/examples/opencode.global.jsonc +3 -2
  21. package/package.json +2 -1
  22. package/src/core/auth.ts +3 -0
  23. package/src/core/client.ts +26 -4
  24. package/src/core/config-file.ts +168 -0
  25. package/src/core/config-summary.ts +82 -0
  26. package/src/core/constants.ts +15 -1
  27. package/src/core/descriptions.ts +6 -2
  28. package/src/core/env.ts +102 -32
  29. package/src/core/format.ts +19 -1
  30. package/src/core/options.ts +22 -2
  31. package/src/core/search.ts +6 -0
  32. package/src/core/types.ts +11 -0
  33. package/src/installer/cli.ts +50 -16
  34. package/src/installer/config-command.ts +116 -0
  35. package/src/installer/hosts/claude-code.ts +325 -0
  36. package/src/installer/hosts/codex.ts +9 -4
  37. package/src/installer/hosts/index.ts +3 -0
  38. package/src/installer/hosts/opencode.ts +9 -5
  39. package/src/installer/hosts/pi.ts +34 -9
  40. package/src/installer/ownership.ts +54 -1
  41. package/src/installer/paths.ts +7 -1
  42. package/src/installer/state.ts +73 -20
  43. package/src/installer/types.ts +11 -1
  44. package/src/installer/uninstall.ts +4 -1
  45. package/src/mcp/auth.ts +2 -3
  46. package/src/mcp/cli.ts +4 -3
  47. package/src/mcp/serve.ts +10 -1
  48. package/src/mcp/server.ts +7 -2
  49. package/src/opencode/plugin.ts +22 -2
  50. package/src/pi/extension.ts +78 -9
@@ -1,14 +1,17 @@
1
1
  import { homedir } from "node:os";
2
2
  import { Type } from "@earendil-works/pi-ai";
3
- import type { AgentToolResult, ExtensionAPI } from "@earendil-works/pi-coding-agent";
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
6
  import type { RequestOptions, RetrievalClient } from "../core/client";
7
7
  import { inspectFetchTarget, retrievalClient } from "../core/client";
8
+ import { sharedFileOptions } from "../core/config-file";
9
+ import { configDeviations } from "../core/config-summary";
8
10
  import { API_KEY_ENV, ENABLE_FETCH_ENV, ENABLE_SEARCH_ENV } from "../core/constants";
9
11
  import {
10
12
  FETCH_TOOL_DESCRIPTION,
11
13
  FETCH_URL_PARAMETER,
14
+ SEARCH_ORI_QUESTION_PARAMETER,
12
15
  SEARCH_QUERY_PARAMETER,
13
16
  SEARCH_TOOL_DESCRIPTION,
14
17
  } from "../core/descriptions";
@@ -22,6 +25,11 @@ import { readExtensionSettings, SETTINGS_NAMESPACE } from "./settings";
22
25
 
23
26
  const SEARCH_PARAMETERS = Type.Object({
24
27
  query: Type.String({ minLength: 1, description: SEARCH_QUERY_PARAMETER }),
28
+ original_question: Type.Optional(
29
+ Type.String({
30
+ description: `${SEARCH_ORI_QUESTION_PARAMETER} When left blank, the most recent user message in the session is sent instead, truncated to fit.`,
31
+ }),
32
+ ),
25
33
  });
26
34
 
27
35
  const SEARCH_OUTPUT = Type.Object({
@@ -31,6 +39,9 @@ const SEARCH_OUTPUT = Type.Object({
31
39
  title: Type.Optional(Type.String()),
32
40
  content: Type.Optional(Type.String()),
33
41
  published: Type.Optional(Type.Number()),
42
+ rerankScore: Type.Optional(Type.Number()),
43
+ authority: Type.Optional(Type.Number()),
44
+ siteName: Type.Optional(Type.String()),
34
45
  }),
35
46
  ),
36
47
  });
@@ -50,14 +61,15 @@ const FETCH_OUTPUT = Type.Object({
50
61
  const NEVER_ABORTS = new AbortController().signal;
51
62
 
52
63
  /**
53
- * Resolves the extension's options: the shared DEEPSEEK_* environment
54
- * pipeline, with the settings namespace layered on top. Settings outrank the
55
- * environment because the installer wrote them as the explicit install-time
56
- * intent (the off tier lands there — the installer cannot write the shell
57
- * environment).
64
+ * Resolves the extension's options: defaults < shared config file < the shared
65
+ * DEEPSEEK_* environment pipeline < the settings namespace. The file's path
66
+ * follows the passed environment (XDG_CONFIG_HOME), so the function stays
67
+ * injectable. Settings outrank both lower layers because the installer wrote
68
+ * them as the explicit install-time intent (the off tier lands there — the
69
+ * installer cannot write the shell environment).
58
70
  */
59
71
  export function resolveExtensionOptions(env: Env = process.env, settings: ExtensionSettings = {}): ResolvedOptions {
60
- const options: Record<string, unknown> = { ...pluginOptionsFromEnv(env) };
72
+ const options: Record<string, unknown> = { ...sharedFileOptions({ env }), ...pluginOptionsFromEnv(env) };
61
73
  if (settings.enableSearch === false) options.searchEnabled = false;
62
74
  if (settings.enableFetch === false) options.fetchEnabled = false;
63
75
  return resolvePluginOptions(options);
@@ -83,9 +95,15 @@ export function searchTool(config: ResolvedOptions, client: RetrievalClient = re
83
95
  outputSchema: SEARCH_OUTPUT,
84
96
  annotations: { readOnlyHint: true, openWorldHint: true },
85
97
  exposure: "direct",
86
- async execute(_toolCallId, params, signal): Promise<AgentToolResult> {
98
+ async execute(_toolCallId, params, signal, _onUpdate, ctx): Promise<AgentToolResult> {
87
99
  try {
88
- const results = await client.searchWeb(params.query, config.search, requestContext(config, signal));
100
+ // The explicit parameter wins; only when the model left it blank does
101
+ // the session fallback get a say.
102
+ const explicit = params.original_question?.trim() ? params.original_question : undefined;
103
+ const results = await client.searchWeb(params.query, config.search, {
104
+ ...requestContext(config, signal),
105
+ oriQuestion: explicit ?? sessionOriQuestion(ctx),
106
+ });
89
107
  return {
90
108
  content: [{ type: "text", text: formatSearchResults(results) }],
91
109
  structuredContent: { results: results.map(toStructuredResult) },
@@ -141,6 +159,11 @@ export default function deepSeekWebRetrieval(pi: ExtensionAPI): void {
141
159
  const config = resolveExtensionOptions(process.env, readExtensionSettings(homedir(), process.cwd()));
142
160
  const precheck = extensionPrecheck(config);
143
161
  if (precheck) throw new Error(precheck);
162
+ // Deviations only, printed once at registration: one stderr line so a
163
+ // multi-layer resolution is visible where Pi surfaces extension output;
164
+ // nothing deviating means no line at all (same contract as the MCP server).
165
+ const deviations = configDeviations(config);
166
+ if (deviations) console.error(`deepseek-web-retrieval config: ${deviations}`);
144
167
  if (config.search.enabled) pi.registerTool(searchTool(config));
145
168
  if (config.fetch.enabled) pi.registerTool(fetchTool(config));
146
169
  }
@@ -149,6 +172,52 @@ function requestContext(config: ResolvedOptions, signal: AbortSignal | undefined
149
172
  return { signal: signal ?? NEVER_ABORTS, auth: { apiKey: requireApiKey() }, retry: config.retry };
150
173
  }
151
174
 
175
+ /**
176
+ * Best-effort `ori_question` fallback for when the model left the tool input
177
+ * blank: the most recent user message text in the session projection, readable
178
+ * synchronously at call time. Best-effort is the contract — Pi's runtime may
179
+ * call `execute` without a context (the fifth argument is typed non-optional
180
+ * but `wrapToolDefinition` passes `ctx ?? ctxFactory?.()`), and any surprise
181
+ * (missing manager, thrown error, odd shape) must degrade to "no fallback"
182
+ * rather than fail the search itself. Length handling stays in the core
183
+ * client, which trims and clamps to the API's 128-char budget.
184
+ */
185
+ function sessionOriQuestion(ctx: ExtensionToolContext | undefined): string | undefined {
186
+ try {
187
+ const messages = ctx?.sessionManager?.buildSessionProjection()?.messages;
188
+ if (!Array.isArray(messages)) return undefined;
189
+ for (let i = messages.length - 1; i >= 0; i--) {
190
+ const text = userMessageText(messages[i]);
191
+ if (text !== undefined) return text;
192
+ }
193
+ return undefined;
194
+ } catch {
195
+ return undefined;
196
+ }
197
+ }
198
+
199
+ /** The trimmed text of a user message's string or `text`-block content, if any. */
200
+ function userMessageText(message: unknown): string | undefined {
201
+ if (typeof message !== "object" || message === null) return undefined;
202
+ const { role, content } = message as { role?: unknown; content?: unknown };
203
+ if (role !== "user") return undefined;
204
+ const text =
205
+ typeof content === "string"
206
+ ? content
207
+ : Array.isArray(content)
208
+ ? content
209
+ .map((part) =>
210
+ typeof part === "object" && part !== null && (part as { type?: unknown }).type === "text"
211
+ ? (part as { text?: unknown }).text
212
+ : undefined,
213
+ )
214
+ .filter((part): part is string => typeof part === "string")
215
+ .join(" ")
216
+ : undefined;
217
+ const trimmed = text?.trim();
218
+ return trimmed ? trimmed : undefined;
219
+ }
220
+
152
221
  function requireApiKey(): string {
153
222
  const key = apiKeyFromEnv();
154
223
  if (key) return key;