@sisu-ai/tool-web-search-openai 8.0.1 → 8.0.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 +84 -39
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,46 +1,68 @@
|
|
|
1
|
-
import { z } from
|
|
1
|
+
import { z } from "zod";
|
|
2
2
|
// Uses OpenAI Responses API web_search tool
|
|
3
3
|
export const openAIWebSearch = {
|
|
4
|
-
name:
|
|
5
|
-
description:
|
|
4
|
+
name: "webSearch",
|
|
5
|
+
description: "Search the web using OpenAI's built-in web search tool.",
|
|
6
6
|
schema: z.object({ query: z.string() }),
|
|
7
7
|
handler: async ({ query }, ctx) => {
|
|
8
8
|
const deps = (ctx?.deps ?? {});
|
|
9
9
|
const depsOpenAI = (deps.openai ?? {});
|
|
10
10
|
const cliApiKey = depsOpenAI.apiKey ?? deps.apiKey;
|
|
11
|
-
const apiKey = cliApiKey ||
|
|
11
|
+
const apiKey = cliApiKey || process.env.OPENAI_API_KEY || process.env.API_KEY;
|
|
12
12
|
if (!apiKey)
|
|
13
|
-
throw new Error(
|
|
14
|
-
const cliRespBase = depsOpenAI.responsesBaseUrl ??
|
|
15
|
-
|
|
16
|
-
const
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
13
|
+
throw new Error("Missing OPENAI_API_KEY or API_KEY");
|
|
14
|
+
const cliRespBase = (depsOpenAI.responsesBaseUrl ??
|
|
15
|
+
deps.responsesBaseUrl);
|
|
16
|
+
const cliBase = (depsOpenAI.baseUrl ?? deps.baseUrl);
|
|
17
|
+
const envBase = process.env.OPENAI_RESPONSES_BASE_URL ||
|
|
18
|
+
process.env.OPENAI_BASE_URL ||
|
|
19
|
+
process.env.BASE_URL;
|
|
20
|
+
const baseUrl = ((cliRespBase || cliBase || envBase) ??
|
|
21
|
+
"https://api.openai.com").replace(/\/$/, "");
|
|
22
|
+
const fromMeta = ctx?.model?.meta
|
|
23
|
+
?.responseModel ||
|
|
24
|
+
(ctx?.model).responseModel;
|
|
25
|
+
const fromAdapterName = typeof ctx?.model?.name === "string" &&
|
|
26
|
+
ctx.model.name.startsWith("openai:")
|
|
27
|
+
? ctx.model.name.slice("openai:".length)
|
|
21
28
|
: undefined;
|
|
22
|
-
const cliRespModel = depsOpenAI.responsesModel ?? deps.responsesModel;
|
|
23
|
-
const cliModel = depsOpenAI.model ?? deps.model;
|
|
24
|
-
let model = cliRespModel ||
|
|
29
|
+
const cliRespModel = (depsOpenAI.responsesModel ?? deps.responsesModel);
|
|
30
|
+
const cliModel = (depsOpenAI.model ?? deps.model);
|
|
31
|
+
let model = cliRespModel ||
|
|
32
|
+
cliModel ||
|
|
33
|
+
process.env.OPENAI_RESPONSES_MODEL ||
|
|
34
|
+
process.env.OPENAI_MODEL ||
|
|
35
|
+
fromMeta ||
|
|
36
|
+
fromAdapterName ||
|
|
37
|
+
"gpt-4.1-mini";
|
|
25
38
|
const url = `${baseUrl}/v1/responses`;
|
|
26
39
|
const body = {
|
|
27
40
|
model,
|
|
28
41
|
input: query,
|
|
29
|
-
tools: [{ type:
|
|
30
|
-
tool_choice: { type:
|
|
42
|
+
tools: [{ type: "web_search" }],
|
|
43
|
+
tool_choice: { type: "web_search" },
|
|
31
44
|
};
|
|
32
|
-
const DEBUG = String(process.env.DEBUG_LLM ||
|
|
45
|
+
const DEBUG = String(process.env.DEBUG_LLM || "").toLowerCase() === "true" ||
|
|
46
|
+
process.env.DEBUG_LLM === "1";
|
|
33
47
|
if (DEBUG) {
|
|
34
|
-
console.error(
|
|
48
|
+
console.error("[DEBUG_LLM] request", {
|
|
49
|
+
url,
|
|
50
|
+
headers: {
|
|
51
|
+
Authorization: "Bearer ***",
|
|
52
|
+
"Content-Type": "application/json",
|
|
53
|
+
Accept: "application/json",
|
|
54
|
+
},
|
|
55
|
+
body,
|
|
56
|
+
});
|
|
35
57
|
}
|
|
36
58
|
const doRequest = async (modelToUse) => fetch(url, {
|
|
37
|
-
method:
|
|
59
|
+
method: "POST",
|
|
38
60
|
headers: {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
61
|
+
"Content-Type": "application/json",
|
|
62
|
+
Authorization: `Bearer ${apiKey}`,
|
|
63
|
+
Accept: "application/json",
|
|
42
64
|
},
|
|
43
|
-
body: JSON.stringify({ ...body, model: modelToUse })
|
|
65
|
+
body: JSON.stringify({ ...body, model: modelToUse }),
|
|
44
66
|
});
|
|
45
67
|
let res = await doRequest(model);
|
|
46
68
|
let raw = await res.text();
|
|
@@ -50,20 +72,33 @@ export const openAIWebSearch = {
|
|
|
50
72
|
const j = JSON.parse(raw);
|
|
51
73
|
details = j.error?.message ?? raw;
|
|
52
74
|
}
|
|
53
|
-
catch {
|
|
75
|
+
catch {
|
|
76
|
+
/* ignore JSON parse error */
|
|
77
|
+
}
|
|
54
78
|
if (DEBUG) {
|
|
55
|
-
console.error(
|
|
79
|
+
console.error("[DEBUG_LLM] response_error", {
|
|
80
|
+
status: res.status,
|
|
81
|
+
statusText: res.statusText,
|
|
82
|
+
body: typeof raw === "string" ? raw.slice(0, 500) : raw,
|
|
83
|
+
});
|
|
56
84
|
}
|
|
57
85
|
// Retry once with a safe default model if we suspect model/tool mismatch
|
|
58
86
|
const msg = String(details).toLowerCase();
|
|
59
|
-
const shouldRetry = res.status === 400 ||
|
|
60
|
-
|
|
61
|
-
|
|
87
|
+
const shouldRetry = res.status === 400 ||
|
|
88
|
+
msg.includes("tool") ||
|
|
89
|
+
msg.includes("web_search");
|
|
90
|
+
if (shouldRetry && model !== "gpt-4.1-mini") {
|
|
91
|
+
const fallback = "gpt-4.1-mini";
|
|
62
92
|
if (DEBUG) {
|
|
63
93
|
try {
|
|
64
|
-
console.error(
|
|
94
|
+
console.error("[DEBUG_LLM] retrying with fallback model", {
|
|
95
|
+
from: model,
|
|
96
|
+
to: fallback,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
/* ignore JSON parse error */
|
|
65
101
|
}
|
|
66
|
-
catch { /* ignore JSON parse error */ }
|
|
67
102
|
}
|
|
68
103
|
model = fallback;
|
|
69
104
|
res = await doRequest(model);
|
|
@@ -74,7 +109,9 @@ export const openAIWebSearch = {
|
|
|
74
109
|
const j2 = JSON.parse(raw);
|
|
75
110
|
d2 = j2.error?.message ?? raw;
|
|
76
111
|
}
|
|
77
|
-
catch {
|
|
112
|
+
catch {
|
|
113
|
+
/* ignore JSON parse error */
|
|
114
|
+
}
|
|
78
115
|
throw new Error(`OpenAI web search failed: ${res.status} ${res.statusText} — ${String(d2).slice(0, 500)}`);
|
|
79
116
|
}
|
|
80
117
|
}
|
|
@@ -82,20 +119,28 @@ export const openAIWebSearch = {
|
|
|
82
119
|
throw new Error(`OpenAI web search failed: ${res.status} ${res.statusText} — ${String(details).slice(0, 500)}`);
|
|
83
120
|
}
|
|
84
121
|
}
|
|
85
|
-
const ct = res.headers.get(
|
|
86
|
-
if (!ct.toLowerCase().includes(
|
|
122
|
+
const ct = res.headers.get("content-type") || "";
|
|
123
|
+
if (!ct.toLowerCase().includes("application/json")) {
|
|
87
124
|
if (DEBUG) {
|
|
88
|
-
console.error(
|
|
125
|
+
console.error("[DEBUG_LLM] non_json_response", {
|
|
126
|
+
contentType: ct,
|
|
127
|
+
snippet: typeof raw === "string" ? raw.slice(0, 200) : raw,
|
|
128
|
+
});
|
|
89
129
|
}
|
|
90
130
|
throw new Error(`OpenAI web search returned non-JSON content (content-type: ${ct}). Check OPENAI_BASE_URL/BASE_URL and API key. Snippet: ${String(raw).slice(0, 200)}`);
|
|
91
131
|
}
|
|
92
132
|
const json = raw ? JSON.parse(raw) : {};
|
|
93
133
|
if (DEBUG) {
|
|
94
|
-
console.log(
|
|
134
|
+
console.log("[DEBUG_LLM] response_ok", {
|
|
135
|
+
keys: Object.keys(json ?? {}),
|
|
136
|
+
outputType: Array.isArray(json?.output) ? "array" : typeof json?.output,
|
|
137
|
+
});
|
|
95
138
|
}
|
|
96
|
-
const results = json.output?.find?.((p) => p.type ===
|
|
97
|
-
|
|
139
|
+
const results = json.output?.find?.((p) => p.type === "web_search_results")
|
|
140
|
+
?.web_search_results ??
|
|
141
|
+
json.output?.[0]?.content?.find?.((c) => c.type === "web_search_results")
|
|
142
|
+
?.web_search_results;
|
|
98
143
|
return results ?? json;
|
|
99
|
-
}
|
|
144
|
+
},
|
|
100
145
|
};
|
|
101
146
|
export default openAIWebSearch;
|