opencode-websearch-deepseek 0.1.2 → 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/README.md CHANGED
@@ -63,8 +63,8 @@ in:
63
63
 
64
64
  | Variable | Required | Default | Description |
65
65
  | ------------------- | -------- | ------------------ | ------------------------------------------------------------------ |
66
- | `DEEPSEEK_API_KEY` | yes | — | DeepSeek API key. |
67
- | `WEBSEARCH_API_KEY` | no | — | Fallback key used when `DEEPSEEK_API_KEY` is not set. |
66
+ | `DEEPSEEK_API_KEY` | no\* | — | DeepSeek API key (takes precedence over the stored credential). |
67
+ | `WEBSEARCH_API_KEY` | no | — | Fallback env key used when `DEEPSEEK_API_KEY` is not set. |
68
68
  | `WEBSEARCH_MODEL` | no | `deepseek-v4-flash`| Model used for search and synthesis. |
69
69
  | `WEBSEARCH_MAX_USES`| no | `5` | Max server-side searches per query (positive integer). |
70
70
  | `WEBSEARCH_THINKING`| no | `enabled` | `enabled` or `disabled`; disables extended thinking when set to `disabled`. |
@@ -72,6 +72,39 @@ in:
72
72
  > `WEBSEARCH_MODEL` is passed through as-is. DeepSeek maps unknown model names
73
73
  > to its default, so any server-side-search-capable DeepSeek model works.
74
74
 
75
+ > \* If no environment key is set, the plugin reuses the API key OpenCode
76
+ > stores for the `deepseek` provider (for example after `opencode auth login`),
77
+ > so no environment variable is required when that provider is already
78
+ > authenticated.
79
+
80
+ ### Plugin options
81
+
82
+ As an alternative to environment variables, configure the plugin with the
83
+ `plugins` object form (read from `ctx.options`):
84
+
85
+ ```jsonc
86
+ {
87
+ "$schema": "https://opencode.ai/config.json",
88
+ "plugins": [
89
+ {
90
+ "package": "opencode-websearch-deepseek",
91
+ "options": {
92
+ "apiKey": "{file:~/.config/opencode/deepseek.key}",
93
+ "model": "deepseek-v4-flash",
94
+ "maxUses": 5,
95
+ "thinking": "enabled"
96
+ }
97
+ }
98
+ ]
99
+ }
100
+ ```
101
+
102
+ Precedence for the API key: `options.apiKey` → `DEEPSEEK_API_KEY` /
103
+ `WEBSEARCH_API_KEY` → stored `deepseek` provider credential. `model`, `maxUses`,
104
+ and `thinking` fall back to their environment variables and defaults.
105
+ OpenCode interpolates `{file:...}` (and `{env:...}`) in config values, so the
106
+ key can live in a file instead of the OS environment.
107
+
75
108
  > The plugin always selects the DeepSeek provider. Without an API key, a
76
109
  > `websearch` call fails with a clear error rather than silently falling back
77
110
  > to a different provider.
package/dist/index.d.ts CHANGED
@@ -83,17 +83,60 @@ interface WebsearchEditor {
83
83
  set(providerID: string | false): void;
84
84
  };
85
85
  }
86
+ /** A credential returned by OpenCode for a configured provider. */
87
+ export interface StoredCredential {
88
+ type?: string;
89
+ key?: string;
90
+ }
91
+ /** Minimal slice of OpenCode's integration API used to read a stored provider key. */
92
+ export interface IntegrationContext {
93
+ connection: {
94
+ active(integrationID: string): Promise<unknown>;
95
+ resolve(connection: unknown): Promise<StoredCredential | undefined>;
96
+ };
97
+ }
98
+ /**
99
+ * Options accepted through the `plugins` object form in `opencode.json(c)` and
100
+ * forwarded to the plugin via `ctx.options`:
101
+ *
102
+ * ```jsonc
103
+ * { "plugins": [{ "package": "opencode-websearch-deepseek", "options": {
104
+ * "apiKey": "...", "model": "deepseek-v4-flash", "maxUses": 5, "thinking": "enabled"
105
+ * } }] }
106
+ * ```
107
+ */
108
+ export interface WebsearchOptions {
109
+ /** DeepSeek API key; takes precedence over env and the stored credential. */
110
+ apiKey?: string;
111
+ /** Model used for search and synthesis. */
112
+ model?: string;
113
+ /** Max server-side searches per query. */
114
+ maxUses?: number | string;
115
+ /** `enabled` or `disabled`. */
116
+ thinking?: string;
117
+ }
86
118
  /** Context passed to the plugin's `setup`. */
87
119
  export interface WebsearchContext {
88
120
  websearch: {
89
121
  transform(callback: (editor: WebsearchEditor) => void): Promise<unknown> | unknown;
90
122
  };
123
+ /** Plugin options from the `plugins` object form. */
124
+ options?: Record<string, unknown>;
125
+ /** Present in OpenCode 2.x; used to reuse the DeepSeek provider credential. */
126
+ integration?: IntegrationContext;
91
127
  }
128
+ /**
129
+ * Resolve the DeepSeek API key, in order of precedence: the `apiKey` plugin
130
+ * option, then the `DEEPSEEK_API_KEY`/`WEBSEARCH_API_KEY` environment
131
+ * variables, then the credential OpenCode stores for the `deepseek` provider
132
+ * (configured via `opencode auth login`).
133
+ */
134
+ export declare function resolveApiKey(ctx: WebsearchContext, options?: WebsearchOptions): Promise<string | undefined>;
92
135
  /**
93
136
  * Resolve the `max_uses` value for the search tool declaration.
94
137
  * Falls back to {@link DEFAULT_MAX_USES} for missing or invalid input.
95
138
  */
96
- export declare function resolveMaxUses(raw?: string | undefined): number;
139
+ export declare function resolveMaxUses(raw?: string | number | undefined): number;
97
140
  /**
98
141
  * Resolve the thinking mode. Only `disabled` disables extended thinking;
99
142
  * anything else (including unset) keeps the historical `enabled` behavior.
package/dist/index.js CHANGED
@@ -46,6 +46,38 @@ export const SYSTEM_PROMPT = [
46
46
  "",
47
47
  "Your response must be the final answer, not another search request.",
48
48
  ].join("\n");
49
+ /** Return a trimmed non-empty string, or undefined for any other value. */
50
+ function readString(value) {
51
+ return typeof value === "string" && value.trim() ? value.trim() : undefined;
52
+ }
53
+ /**
54
+ * Resolve the DeepSeek API key, in order of precedence: the `apiKey` plugin
55
+ * option, then the `DEEPSEEK_API_KEY`/`WEBSEARCH_API_KEY` environment
56
+ * variables, then the credential OpenCode stores for the `deepseek` provider
57
+ * (configured via `opencode auth login`).
58
+ */
59
+ export async function resolveApiKey(ctx, options) {
60
+ const fromOptions = readString(options?.apiKey);
61
+ if (fromOptions)
62
+ return fromOptions;
63
+ const fromEnv = readEnv("DEEPSEEK_API_KEY") ?? readEnv("WEBSEARCH_API_KEY");
64
+ if (fromEnv)
65
+ return fromEnv;
66
+ const integration = ctx.integration;
67
+ if (!integration)
68
+ return undefined;
69
+ try {
70
+ const connection = await integration.connection.active("deepseek");
71
+ if (!connection)
72
+ return undefined;
73
+ const credential = await integration.connection.resolve(connection);
74
+ const key = credential?.key;
75
+ return typeof key === "string" && key.trim() ? key.trim() : undefined;
76
+ }
77
+ catch {
78
+ return undefined;
79
+ }
80
+ }
49
81
  /** Read an environment variable, treating blank strings as unset. */
50
82
  function readEnv(name, env = process.env) {
51
83
  const value = env[name];
@@ -73,6 +105,9 @@ function throwCancellation(signal, fallback) {
73
105
  * Falls back to {@link DEFAULT_MAX_USES} for missing or invalid input.
74
106
  */
75
107
  export function resolveMaxUses(raw = process.env.WEBSEARCH_MAX_USES) {
108
+ if (typeof raw === "number") {
109
+ return Number.isSafeInteger(raw) && raw > 0 ? raw : DEFAULT_MAX_USES;
110
+ }
76
111
  const value = (typeof raw === "string" ? raw : "").trim();
77
112
  // Strict: only a plain positive integer. Rejects "1e3", "5.5", "5abc" and
78
113
  // values outside the safe-integer range (e.g. a 24-digit number).
@@ -206,22 +241,23 @@ export function toResults(answer, sources = []) {
206
241
  export const plugin = {
207
242
  id: "websearch.deepseek",
208
243
  async setup(ctx) {
244
+ const options = (ctx.options ?? {});
209
245
  await ctx.websearch.transform((editor) => {
210
246
  editor.add({
211
247
  id: "deepseek",
212
248
  name: "DeepSeek Web Search",
213
249
  execute: async ({ query }, { signal } = {}) => {
214
- const apiKey = readEnv("DEEPSEEK_API_KEY") ?? readEnv("WEBSEARCH_API_KEY");
250
+ const apiKey = await resolveApiKey(ctx, options);
215
251
  if (!apiKey) {
216
- throw new Error("DEEPSEEK_API_KEY is not set in the OpenCode environment");
252
+ throw new Error("No DeepSeek API key: set the DEEPSEEK_API_KEY env var, pass the apiKey plugin option, or sign in to the deepseek provider in OpenCode");
217
253
  }
218
254
  if (!/^[\x21-\x7e]+$/.test(apiKey)) {
219
255
  throw new Error("The API key contains invalid characters");
220
256
  }
221
257
  const body = buildRequestBody(query, {
222
- model: readEnv("WEBSEARCH_MODEL") ?? DEFAULT_MODEL,
223
- maxUses: resolveMaxUses(),
224
- thinking: resolveThinking(),
258
+ model: readString(options.model) ?? readEnv("WEBSEARCH_MODEL") ?? DEFAULT_MODEL,
259
+ maxUses: resolveMaxUses(options.maxUses ?? readEnv("WEBSEARCH_MAX_USES")),
260
+ thinking: resolveThinking(readString(options.thinking) ?? readEnv("WEBSEARCH_THINKING")),
225
261
  });
226
262
  const response = await fetch(API_URL, {
227
263
  method: "POST",
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
3
  "name": "opencode-websearch-deepseek",
4
- "version": "0.1.2",
4
+ "version": "0.3.0",
5
5
  "description": "DeepSeek-powered web search provider for OpenCode's built-in websearch tool.",
6
6
  "type": "module",
7
7
  "license": "MIT",