opencode-websearch-deepseek 0.3.1 → 0.3.2
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/dist/index.d.ts +1 -1
- package/dist/index.js +49 -11
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -131,7 +131,7 @@ export interface WebsearchContext {
|
|
|
131
131
|
* variables, then the credential OpenCode stores for the `deepseek` provider
|
|
132
132
|
* (configured via `opencode auth login`).
|
|
133
133
|
*/
|
|
134
|
-
export declare function resolveApiKey(ctx: WebsearchContext, options?: WebsearchOptions): Promise<string | undefined>;
|
|
134
|
+
export declare function resolveApiKey(ctx: WebsearchContext, options?: WebsearchOptions, signal?: AbortSignal): Promise<string | undefined>;
|
|
135
135
|
/**
|
|
136
136
|
* Resolve the `max_uses` value for the search tool declaration.
|
|
137
137
|
* Falls back to {@link DEFAULT_MAX_USES} for missing or invalid input.
|
package/dist/index.js
CHANGED
|
@@ -56,7 +56,7 @@ function readString(value) {
|
|
|
56
56
|
* variables, then the credential OpenCode stores for the `deepseek` provider
|
|
57
57
|
* (configured via `opencode auth login`).
|
|
58
58
|
*/
|
|
59
|
-
export async function resolveApiKey(ctx, options) {
|
|
59
|
+
export async function resolveApiKey(ctx, options, signal) {
|
|
60
60
|
const fromOptions = readString(options?.apiKey);
|
|
61
61
|
if (fromOptions)
|
|
62
62
|
return fromOptions;
|
|
@@ -67,13 +67,15 @@ export async function resolveApiKey(ctx, options) {
|
|
|
67
67
|
if (!integration)
|
|
68
68
|
return undefined;
|
|
69
69
|
try {
|
|
70
|
-
const connection = await integration.connection.active("deepseek");
|
|
70
|
+
const connection = await withAbort(integration.connection.active("deepseek"), signal);
|
|
71
71
|
if (!connection)
|
|
72
72
|
return undefined;
|
|
73
|
-
const credential = await integration.connection.resolve(connection);
|
|
73
|
+
const credential = await withAbort(integration.connection.resolve(connection), signal);
|
|
74
74
|
return readString(credential?.key);
|
|
75
75
|
}
|
|
76
76
|
catch (error) {
|
|
77
|
+
if (signal?.aborted)
|
|
78
|
+
throwCancellation(signal, error);
|
|
77
79
|
// A genuine integration failure is not "no key": surface it with context.
|
|
78
80
|
throw new Error("Failed to read the DeepSeek credential from OpenCode", { cause: error });
|
|
79
81
|
}
|
|
@@ -93,21 +95,52 @@ function throwCancellation(signal, fallback) {
|
|
|
93
95
|
if (reason instanceof Error)
|
|
94
96
|
throw reason;
|
|
95
97
|
if (typeof reason === "object" && reason !== null) {
|
|
96
|
-
// Preserve AbortError-like objects (DOMException has a message); wrap the
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
98
|
+
// Preserve AbortError-like objects (DOMException has a message); wrap the
|
|
99
|
+
// rest. Guard the property reads: a hostile reason may throw from a getter.
|
|
100
|
+
try {
|
|
101
|
+
const candidate = reason;
|
|
102
|
+
if (candidate.name === "AbortError" && typeof candidate.message === "string" && candidate.message) {
|
|
103
|
+
throw reason;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
if (error === reason)
|
|
108
|
+
throw error;
|
|
109
|
+
}
|
|
100
110
|
throw new Error("The web search was aborted", { cause: reason });
|
|
101
111
|
}
|
|
112
|
+
if (typeof reason === "string" && reason.trim())
|
|
113
|
+
throw new Error(reason.trim());
|
|
102
114
|
if (reason !== undefined && reason !== null) {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
throw new Error(text);
|
|
115
|
+
// Non-string primitives (e.g. 0) are not useful messages; keep them as cause.
|
|
116
|
+
throw new Error("The web search was aborted", { cause: reason });
|
|
106
117
|
}
|
|
107
118
|
if (fallback instanceof Error)
|
|
108
119
|
throw fallback;
|
|
109
120
|
throw new Error("The web search was aborted");
|
|
110
121
|
}
|
|
122
|
+
/** Reject with the signal's reason if `signal` aborts before `promise` settles. */
|
|
123
|
+
function withAbort(promise, signal) {
|
|
124
|
+
if (!signal)
|
|
125
|
+
return promise;
|
|
126
|
+
if (signal.aborted) {
|
|
127
|
+
// The passed promise is already created; swallow its later rejection so it
|
|
128
|
+
// does not surface as an unhandled rejection.
|
|
129
|
+
promise.catch(() => { });
|
|
130
|
+
return Promise.reject(signal.reason ?? new Error("The web search was aborted"));
|
|
131
|
+
}
|
|
132
|
+
return new Promise((resolve, reject) => {
|
|
133
|
+
const onAbort = () => reject(signal.reason ?? new Error("The web search was aborted"));
|
|
134
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
135
|
+
promise.then((value) => {
|
|
136
|
+
signal.removeEventListener("abort", onAbort);
|
|
137
|
+
resolve(value);
|
|
138
|
+
}, (error) => {
|
|
139
|
+
signal.removeEventListener("abort", onAbort);
|
|
140
|
+
reject(error);
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
}
|
|
111
144
|
/**
|
|
112
145
|
* Validate a `max_uses` value: a positive safe integer, given as a number or a
|
|
113
146
|
* plain-integer string. Returns `undefined` when unset or invalid.
|
|
@@ -243,6 +276,8 @@ export function toResults(answer, sources = []) {
|
|
|
243
276
|
const valid = (Array.isArray(sources) ? sources : []).filter((source) => typeof source?.url === "string" && source.url.length > 0);
|
|
244
277
|
const results = [];
|
|
245
278
|
if (answer) {
|
|
279
|
+
// The synthesized answer reuses the first source's URL so it links to a
|
|
280
|
+
// real page; the same source is also listed below (intentional).
|
|
246
281
|
results.push({
|
|
247
282
|
url: valid[0]?.url ?? FALLBACK_URL,
|
|
248
283
|
title: "DeepSeek answer",
|
|
@@ -280,7 +315,7 @@ export const plugin = {
|
|
|
280
315
|
execute: async ({ query }, context) => {
|
|
281
316
|
const signal = context?.signal;
|
|
282
317
|
try {
|
|
283
|
-
const apiKey = await resolveApiKey(ctx, options);
|
|
318
|
+
const apiKey = await resolveApiKey(ctx, options, signal);
|
|
284
319
|
if (!apiKey) {
|
|
285
320
|
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");
|
|
286
321
|
}
|
|
@@ -320,6 +355,9 @@ export const plugin = {
|
|
|
320
355
|
throw new Error(`DeepSeek API error ${response.status}: ${text.slice(0, 300)}`);
|
|
321
356
|
}
|
|
322
357
|
const data = (await response.json());
|
|
358
|
+
// A cancellation may arrive after the response completed.
|
|
359
|
+
if (signal?.aborted)
|
|
360
|
+
throwCancellation(signal, undefined);
|
|
323
361
|
const { answer, sources } = extractAnswerAndSources(data);
|
|
324
362
|
return toResults(answer, sources);
|
|
325
363
|
}
|
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.3.
|
|
4
|
+
"version": "0.3.2",
|
|
5
5
|
"description": "DeepSeek-powered web search provider for OpenCode's built-in websearch tool.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"license": "MIT",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"build": "tsc -p tsconfig.json",
|
|
45
45
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
46
46
|
"test": "npm run build && node --test",
|
|
47
|
-
"prepare": "node -e \"try{require.resolve('typescript')}catch{process.exit(0)};require('node:child_process').execSync('npm run build',{stdio:'inherit'})\"",
|
|
47
|
+
"prepare": "node -e \"const fs=require('node:fs');if(fs.existsSync('dist/index.js'))process.exit(0);try{require.resolve('typescript')}catch{process.exit(0)};require('node:child_process').execSync('npm run build',{stdio:'inherit'})\"",
|
|
48
48
|
"prepack": "npm run build",
|
|
49
49
|
"clean": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\""
|
|
50
50
|
},
|