dsh-lcx-codex 0.4.2 → 0.4.3-pre.13
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 +75 -224
- package/THIRD_PARTY_NOTICES.md +64 -0
- package/cordis.patch.yml +3 -20
- package/lib/auxiliary-usage.js +63 -0
- package/lib/client.js +1398 -167
- package/lib/compact-v2.js +218 -199
- package/lib/dsh-compat.js +294 -100
- package/lib/dsh-responses.js +512 -277
- package/lib/grok-native-search.js +391 -0
- package/lib/index.js +1066 -758
- package/lib/invocation-policy-scope.js +261 -0
- package/lib/json-store.js +57 -31
- package/lib/native-checkpoint.js +520 -194
- package/lib/pi-responses-runtime.js +1571 -0
- package/lib/responses-request.js +109 -121
- package/lib/responses-stream.js +1280 -447
- package/lib/route.js +425 -369
- package/lib/search-accounting.js +86 -0
- package/lib/search-usage.js +86 -0
- package/lib/service-mutex.js +73 -64
- package/lib/token-budget.js +176 -108
- package/lib/transport.js +308 -68
- package/lib/types/client/index.d.ts +18 -0
- package/lib/types/client/search-media.d.ts +16 -0
- package/lib/types/index.d.ts +83 -0
- package/lib/web-run-output.js +189 -18
- package/lib/web-search-alpha.js +1067 -163
- package/lib/web-search-capability.js +80 -65
- package/lib/web-search-hosted.js +321 -33
- package/lib/web-search-ref-store.js +145 -60
- package/package.json +112 -32
- package/ARCHITECTURE.md +0 -117
- package/CHANGELOG.md +0 -224
- package/README_EN.md +0 -277
- package/assets/dsh-lcx-codex-banner.jpg +0 -0
- package/lib/legacy-v3.js +0 -20
- package/lib/responses-replay.js +0 -68
- package/scripts/probe-alpha.mjs +0 -43
- package/scripts/validate-dsh-schema.mjs +0 -31
package/lib/web-search-alpha.js
CHANGED
|
@@ -1,173 +1,1077 @@
|
|
|
1
|
-
import { createHash } from
|
|
2
|
-
import { isIP } from
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { isIP } from "node:net";
|
|
3
|
+
import { ServiceMutex } from "./service-mutex.js";
|
|
4
|
+
import { fetchJsonWithRetry } from "./transport.js";
|
|
5
|
+
import { mergeWebRunLinks, outputDomains, outputLineRange, outputLinks, outputPdfRefs, parseWebRunOutput, } from "./web-run-output.js";
|
|
6
|
+
export const ALPHA_ACTIONS = [
|
|
7
|
+
"search_query",
|
|
8
|
+
"image_query",
|
|
9
|
+
"open",
|
|
10
|
+
"find",
|
|
11
|
+
"click",
|
|
12
|
+
"screenshot",
|
|
13
|
+
"finance",
|
|
14
|
+
"weather",
|
|
15
|
+
"sports",
|
|
16
|
+
"time",
|
|
17
|
+
];
|
|
18
|
+
const LEAGUES = [
|
|
19
|
+
"nba",
|
|
20
|
+
"wnba",
|
|
21
|
+
"nfl",
|
|
22
|
+
"nhl",
|
|
23
|
+
"mlb",
|
|
24
|
+
"epl",
|
|
25
|
+
"ncaamb",
|
|
26
|
+
"ncaawb",
|
|
27
|
+
"ipl",
|
|
28
|
+
];
|
|
29
|
+
const ASSET_TYPES = ["equity", "fund", "crypto", "index"];
|
|
30
|
+
const RESPONSE_LENGTHS = ["short", "medium", "long"];
|
|
31
|
+
export const ALPHA_SEARCH_PARAMETERS = {
|
|
32
|
+
type: "object",
|
|
33
|
+
properties: {
|
|
34
|
+
action: { type: "string", enum: ALPHA_ACTIONS },
|
|
35
|
+
query: { type: "string" },
|
|
36
|
+
domains: { type: "array", items: { type: "string" } },
|
|
37
|
+
recency: { type: "integer" },
|
|
38
|
+
refId: { type: "string" },
|
|
39
|
+
lineNumber: { type: "integer" },
|
|
40
|
+
linkId: { type: "integer" },
|
|
41
|
+
pattern: { type: "string" },
|
|
42
|
+
pageNumber: { type: "integer" },
|
|
43
|
+
ticker: { type: "string" },
|
|
44
|
+
assetType: { type: "string", enum: ASSET_TYPES },
|
|
45
|
+
market: { type: "string" },
|
|
46
|
+
location: { type: "string" },
|
|
47
|
+
start: { type: "string" },
|
|
48
|
+
duration: { type: "integer" },
|
|
49
|
+
fn: { type: "string", enum: ["schedule", "standings"] },
|
|
50
|
+
league: { type: "string", enum: LEAGUES },
|
|
51
|
+
team: { type: "string" },
|
|
52
|
+
opponent: { type: "string" },
|
|
53
|
+
dateFrom: { type: "string" },
|
|
54
|
+
dateTo: { type: "string" },
|
|
55
|
+
numberOfGames: { type: "integer" },
|
|
56
|
+
locale: { type: "string" },
|
|
57
|
+
utcOffset: { type: "string" },
|
|
58
|
+
responseLength: { type: "string", enum: RESPONSE_LENGTHS },
|
|
59
|
+
},
|
|
60
|
+
required: ["action"],
|
|
61
|
+
additionalProperties: false,
|
|
62
|
+
};
|
|
63
|
+
export const ALPHA_SEARCH_OUTPUT = {
|
|
64
|
+
type: "object",
|
|
65
|
+
properties: {
|
|
66
|
+
mode: { type: "string" },
|
|
67
|
+
action: { type: "string" },
|
|
68
|
+
capability: { type: "string" },
|
|
69
|
+
emulation: { type: "string" },
|
|
70
|
+
content: { type: "string" },
|
|
71
|
+
results: { type: "array", items: { type: "object" } },
|
|
72
|
+
refs: { type: "array", items: { type: "string" } },
|
|
73
|
+
sources: { type: "array", items: { type: "object" } },
|
|
74
|
+
citations: { type: "array", items: { type: "object" } },
|
|
75
|
+
outputBlocks: { type: "array", items: { type: "object" } },
|
|
76
|
+
links: { type: "array", items: { type: "object" } },
|
|
77
|
+
pdfRefs: { type: "array", items: { type: "string" } },
|
|
78
|
+
domains: { type: "array", items: { type: "string" } },
|
|
79
|
+
lineRange: { type: "object" },
|
|
80
|
+
requestId: { type: "string" },
|
|
81
|
+
responseId: { type: "string" },
|
|
82
|
+
retrievedAt: { type: "string" },
|
|
83
|
+
warnings: { type: "array", items: { type: "string" } },
|
|
84
|
+
},
|
|
85
|
+
required: [
|
|
86
|
+
"mode",
|
|
87
|
+
"action",
|
|
88
|
+
"capability",
|
|
89
|
+
"emulation",
|
|
90
|
+
"content",
|
|
91
|
+
"results",
|
|
92
|
+
"refs",
|
|
93
|
+
"sources",
|
|
94
|
+
"citations",
|
|
95
|
+
"outputBlocks",
|
|
96
|
+
"links",
|
|
97
|
+
"pdfRefs",
|
|
98
|
+
"domains",
|
|
99
|
+
"requestId",
|
|
100
|
+
"retrievedAt",
|
|
101
|
+
"warnings",
|
|
102
|
+
],
|
|
103
|
+
additionalProperties: false,
|
|
104
|
+
};
|
|
105
|
+
export const ALPHA_SCHEMA_FINGERPRINT = createHash("sha256")
|
|
106
|
+
.update(JSON.stringify(ALPHA_SEARCH_PARAMETERS))
|
|
107
|
+
.digest("hex");
|
|
108
|
+
export const ALPHA_PROBE_VERSION = 12;
|
|
109
|
+
function failure(message, code = "WEB_INVALID_REQUEST") {
|
|
110
|
+
return Object.assign(new Error(message), { code });
|
|
111
|
+
}
|
|
112
|
+
function isObject(v) {
|
|
113
|
+
return v !== null && typeof v === "object" && !Array.isArray(v);
|
|
114
|
+
}
|
|
115
|
+
function text(value, field, max = 1000) {
|
|
116
|
+
if (typeof value !== "string" || !value.trim() || value.trim().length > max)
|
|
117
|
+
throw failure(`websearch_alpha.${field} is invalid`);
|
|
118
|
+
return value.trim();
|
|
119
|
+
}
|
|
120
|
+
function integer(value, field, min = 0, max = 10000) {
|
|
121
|
+
if (typeof value !== "number" ||
|
|
122
|
+
!Number.isSafeInteger(value) ||
|
|
123
|
+
value < min ||
|
|
124
|
+
value > max)
|
|
125
|
+
throw failure(`websearch_alpha.${field} is invalid`);
|
|
126
|
+
return value;
|
|
127
|
+
}
|
|
128
|
+
function date(value, field) {
|
|
129
|
+
const normalized = text(value, field, 10);
|
|
130
|
+
if (!/^\d{4}-\d{2}-\d{2}$/u.test(normalized))
|
|
131
|
+
throw failure(`websearch_alpha.${field} must use YYYY-MM-DD`);
|
|
132
|
+
const year = Number(normalized.slice(0, 4));
|
|
133
|
+
const month = Number(normalized.slice(5, 7));
|
|
134
|
+
const day = Number(normalized.slice(8, 10));
|
|
135
|
+
const utc = new Date(Date.UTC(year, month - 1, day));
|
|
136
|
+
if (utc.getUTCFullYear() !== year ||
|
|
137
|
+
utc.getUTCMonth() !== month - 1 ||
|
|
138
|
+
utc.getUTCDate() !== day)
|
|
139
|
+
throw failure(`websearch_alpha.${field} must be a real calendar date`);
|
|
140
|
+
return normalized;
|
|
141
|
+
}
|
|
142
|
+
function normalizeDomains(value) {
|
|
143
|
+
if (!Array.isArray(value) || !value.length || value.length > 100)
|
|
144
|
+
throw failure("websearch_alpha.domains is invalid");
|
|
145
|
+
return value.map((item) => {
|
|
146
|
+
const d = text(item, "domains", 253).toLowerCase();
|
|
147
|
+
if (d.includes("/") || d.includes(":") || !d.includes("."))
|
|
148
|
+
throw failure("websearch_alpha.domains contains an invalid domain");
|
|
149
|
+
return d;
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
const FIELDS = {
|
|
153
|
+
search_query: ["query", "domains", "recency"],
|
|
154
|
+
image_query: ["query", "domains", "recency"],
|
|
155
|
+
open: ["refId", "lineNumber"],
|
|
156
|
+
find: ["refId", "pattern"],
|
|
157
|
+
click: ["refId", "linkId"],
|
|
158
|
+
screenshot: ["refId", "pageNumber"],
|
|
159
|
+
finance: ["ticker", "assetType", "market"],
|
|
160
|
+
weather: ["location", "start", "duration"],
|
|
161
|
+
sports: [
|
|
162
|
+
"fn",
|
|
163
|
+
"league",
|
|
164
|
+
"team",
|
|
165
|
+
"opponent",
|
|
166
|
+
"dateFrom",
|
|
167
|
+
"dateTo",
|
|
168
|
+
"numberOfGames",
|
|
169
|
+
"locale",
|
|
170
|
+
],
|
|
171
|
+
time: ["utcOffset"],
|
|
172
|
+
};
|
|
173
|
+
function isAlphaAction(value) {
|
|
174
|
+
return typeof value === "string" && ALPHA_ACTIONS.includes(value);
|
|
175
|
+
}
|
|
176
|
+
export function normalizeAlphaSearchArgs(args) {
|
|
177
|
+
if (!isObject(args) || !isAlphaAction(args.action))
|
|
178
|
+
throw failure("websearch_alpha.action is invalid");
|
|
179
|
+
const allowed = new Set(["action", "responseLength", ...FIELDS[args.action]]);
|
|
180
|
+
if (Object.keys(args).some((k) => !allowed.has(k)))
|
|
181
|
+
throw failure(`websearch_alpha.${args.action} received an unrelated field`);
|
|
182
|
+
const result = { action: args.action };
|
|
183
|
+
if (args.responseLength !== undefined) {
|
|
184
|
+
if (typeof args.responseLength !== "string" ||
|
|
185
|
+
!RESPONSE_LENGTHS.includes(args.responseLength))
|
|
186
|
+
throw failure("websearch_alpha.responseLength is invalid");
|
|
187
|
+
result.responseLength = args.responseLength;
|
|
188
|
+
}
|
|
189
|
+
switch (args.action) {
|
|
190
|
+
case "search_query":
|
|
191
|
+
case "image_query":
|
|
192
|
+
result.query = text(args.query, "query", 16000);
|
|
193
|
+
if (args.domains !== undefined)
|
|
194
|
+
result.domains = normalizeDomains(args.domains);
|
|
195
|
+
if (args.recency !== undefined)
|
|
196
|
+
result.recency = integer(args.recency, "recency", 0, 3650);
|
|
197
|
+
break;
|
|
198
|
+
case "open":
|
|
199
|
+
result.refId = text(args.refId, "refId");
|
|
200
|
+
if (args.lineNumber !== undefined)
|
|
201
|
+
result.lineNumber = integer(args.lineNumber, "lineNumber");
|
|
202
|
+
break;
|
|
203
|
+
case "find":
|
|
204
|
+
result.refId = text(args.refId, "refId");
|
|
205
|
+
result.pattern = text(args.pattern, "pattern");
|
|
206
|
+
break;
|
|
207
|
+
case "click":
|
|
208
|
+
result.refId = text(args.refId, "refId");
|
|
209
|
+
if (isAlphaHttpUrl(result.refId))
|
|
210
|
+
throw failure("websearch_alpha.click requires an opaque stored ref", "LCX_ALPHA_REF_REQUIRED");
|
|
211
|
+
result.linkId = integer(args.linkId, "linkId");
|
|
212
|
+
break;
|
|
213
|
+
case "screenshot":
|
|
214
|
+
result.refId = text(args.refId, "refId");
|
|
215
|
+
result.pageNumber = integer(args.pageNumber, "pageNumber");
|
|
216
|
+
break;
|
|
217
|
+
case "finance":
|
|
218
|
+
result.ticker = text(args.ticker, "ticker", 40);
|
|
219
|
+
if (typeof args.assetType !== "string" ||
|
|
220
|
+
!ASSET_TYPES.includes(args.assetType))
|
|
221
|
+
throw failure("websearch_alpha.assetType is invalid");
|
|
222
|
+
result.assetType = args.assetType;
|
|
223
|
+
if (args.market !== undefined)
|
|
224
|
+
result.market = text(args.market, "market", 20);
|
|
225
|
+
break;
|
|
226
|
+
case "weather":
|
|
227
|
+
result.location = text(args.location, "location", 500);
|
|
228
|
+
if (args.start !== undefined)
|
|
229
|
+
result.start = date(args.start, "start");
|
|
230
|
+
if (args.duration !== undefined)
|
|
231
|
+
result.duration = integer(args.duration, "duration", 1, 14);
|
|
232
|
+
break;
|
|
233
|
+
case "sports":
|
|
234
|
+
if (typeof args.fn !== "string" ||
|
|
235
|
+
!["schedule", "standings"].includes(args.fn) ||
|
|
236
|
+
typeof args.league !== "string" ||
|
|
237
|
+
!LEAGUES.includes(args.league))
|
|
238
|
+
throw failure("websearch_alpha sports args are invalid");
|
|
239
|
+
result.fn = args.fn;
|
|
240
|
+
result.league = args.league;
|
|
241
|
+
for (const f of ["team", "opponent", "locale"])
|
|
242
|
+
if (args[f] !== undefined)
|
|
243
|
+
result[f] = text(args[f], f, 100);
|
|
244
|
+
if (args.dateFrom !== undefined)
|
|
245
|
+
result.dateFrom = date(args.dateFrom, "dateFrom");
|
|
246
|
+
if (args.dateTo !== undefined)
|
|
247
|
+
result.dateTo = date(args.dateTo, "dateTo");
|
|
248
|
+
if (args.numberOfGames !== undefined)
|
|
249
|
+
result.numberOfGames = integer(args.numberOfGames, "numberOfGames", 1, 100);
|
|
250
|
+
break;
|
|
251
|
+
case "time": {
|
|
252
|
+
const utcOffset = text(args.utcOffset, "utcOffset", 6);
|
|
253
|
+
result.utcOffset = utcOffset;
|
|
254
|
+
if (!/^[+-](?:0\d|1\d|2[0-3]):[0-5]\d$/u.test(utcOffset))
|
|
255
|
+
throw failure("websearch_alpha.utcOffset is invalid");
|
|
256
|
+
break;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
return result;
|
|
260
|
+
}
|
|
261
|
+
export function alphaActionCommand(args) {
|
|
262
|
+
let value;
|
|
263
|
+
switch (args.action) {
|
|
264
|
+
case "search_query":
|
|
265
|
+
case "image_query":
|
|
266
|
+
value = {
|
|
267
|
+
q: args.query,
|
|
268
|
+
...(args.recency !== undefined ? { recency: args.recency } : {}),
|
|
269
|
+
...(args.domains ? { domains: args.domains } : {}),
|
|
270
|
+
};
|
|
271
|
+
break;
|
|
272
|
+
case "open":
|
|
273
|
+
value = {
|
|
274
|
+
ref_id: args.refId,
|
|
275
|
+
...(args.lineNumber !== undefined ? { lineno: args.lineNumber } : {}),
|
|
276
|
+
};
|
|
277
|
+
break;
|
|
278
|
+
case "find":
|
|
279
|
+
value = { ref_id: args.refId, pattern: args.pattern };
|
|
280
|
+
break;
|
|
281
|
+
case "click":
|
|
282
|
+
if (isAlphaHttpUrl(args.refId))
|
|
283
|
+
throw failure("websearch_alpha.click requires an opaque stored ref", "LCX_ALPHA_REF_REQUIRED");
|
|
284
|
+
value = { ref_id: args.refId, id: args.linkId };
|
|
285
|
+
break;
|
|
286
|
+
case "screenshot":
|
|
287
|
+
value = { ref_id: args.refId, pageno: args.pageNumber };
|
|
288
|
+
break;
|
|
289
|
+
case "finance":
|
|
290
|
+
value = {
|
|
291
|
+
ticker: args.ticker,
|
|
292
|
+
type: args.assetType,
|
|
293
|
+
...(args.market ? { market: args.market } : {}),
|
|
294
|
+
};
|
|
295
|
+
break;
|
|
296
|
+
case "weather":
|
|
297
|
+
value = {
|
|
298
|
+
location: args.location,
|
|
299
|
+
...(args.start ? { start: args.start } : {}),
|
|
300
|
+
...(args.duration !== undefined ? { duration: args.duration } : {}),
|
|
301
|
+
};
|
|
302
|
+
break;
|
|
303
|
+
case "sports":
|
|
304
|
+
value = {
|
|
305
|
+
tool: "sports",
|
|
306
|
+
fn: args.fn,
|
|
307
|
+
league: args.league,
|
|
308
|
+
...(args.team ? { team: args.team } : {}),
|
|
309
|
+
...(args.opponent ? { opponent: args.opponent } : {}),
|
|
310
|
+
...(args.dateFrom ? { date_from: args.dateFrom } : {}),
|
|
311
|
+
...(args.dateTo ? { date_to: args.dateTo } : {}),
|
|
312
|
+
...(args.numberOfGames !== undefined
|
|
313
|
+
? { num_games: args.numberOfGames }
|
|
314
|
+
: {}),
|
|
315
|
+
...(args.locale ? { locale: args.locale } : {}),
|
|
316
|
+
};
|
|
317
|
+
break;
|
|
318
|
+
case "time":
|
|
319
|
+
value = { utc_offset: args.utcOffset };
|
|
320
|
+
break;
|
|
321
|
+
default:
|
|
322
|
+
throw failure("websearch_alpha.action is invalid");
|
|
323
|
+
}
|
|
324
|
+
return {
|
|
325
|
+
[args.action]: [value],
|
|
326
|
+
...(args.responseLength ? { response_length: args.responseLength } : {}),
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
function actionInput(args) {
|
|
330
|
+
return String(args.query ??
|
|
331
|
+
(args.refId
|
|
332
|
+
? `${args.action}: ${args.refId}`
|
|
333
|
+
: args.ticker
|
|
334
|
+
? `${args.action}: ${args.ticker}`
|
|
335
|
+
: args.location
|
|
336
|
+
? `${args.action}: ${args.location}`
|
|
337
|
+
: args.utcOffset
|
|
338
|
+
? `${args.action}: ${args.utcOffset}`
|
|
339
|
+
: `${args.action}: ${args.league ?? ""}`.trim()));
|
|
340
|
+
}
|
|
341
|
+
export function buildAlphaSearchBody(args, model, sessionId, externalWebAccess = true, maxOutputTokens = 2500) {
|
|
342
|
+
if (typeof sessionId !== "string" || !sessionId)
|
|
343
|
+
throw failure("websearch_alpha requires a DSH session", "LCX_ALPHA_SESSION_REQUIRED");
|
|
344
|
+
return {
|
|
345
|
+
id: sessionId,
|
|
346
|
+
model,
|
|
347
|
+
input: [
|
|
348
|
+
{
|
|
349
|
+
role: "user",
|
|
350
|
+
content: [{ type: "input_text", text: actionInput(args) }],
|
|
351
|
+
},
|
|
352
|
+
],
|
|
353
|
+
commands: alphaActionCommand(args),
|
|
354
|
+
settings: {
|
|
355
|
+
allowed_callers: ["direct"],
|
|
356
|
+
external_web_access: externalWebAccess,
|
|
357
|
+
},
|
|
358
|
+
max_output_tokens: maxOutputTokens,
|
|
359
|
+
};
|
|
360
|
+
}
|
|
24
361
|
function ipv4Octets(hostname) {
|
|
25
|
-
|
|
26
|
-
|
|
362
|
+
const octets = hostname.split(".").map((part) => Number(part));
|
|
363
|
+
return octets.length === 4 &&
|
|
364
|
+
octets.every((part) => Number.isInteger(part) && part >= 0 && part <= 255)
|
|
365
|
+
? octets
|
|
366
|
+
: undefined;
|
|
27
367
|
}
|
|
28
|
-
|
|
29
368
|
function isPublicIpv4(hostname) {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
369
|
+
const octets = ipv4Octets(hostname);
|
|
370
|
+
if (!octets)
|
|
371
|
+
return false;
|
|
372
|
+
const [first, second, third] = octets;
|
|
373
|
+
return (first !== 0 &&
|
|
374
|
+
first !== 10 &&
|
|
375
|
+
first !== 127 &&
|
|
376
|
+
!(first === 100 && second >= 64 && second <= 127) &&
|
|
377
|
+
!(first === 169 && second === 254) &&
|
|
378
|
+
!(first === 172 && second >= 16 && second <= 31) &&
|
|
379
|
+
!(first === 192 && second === 0 && third === 0) &&
|
|
380
|
+
!(first === 192 && second === 0 && third === 2) &&
|
|
381
|
+
!(first === 192 && second === 88 && third === 99) &&
|
|
382
|
+
!(first === 192 && second === 168) &&
|
|
383
|
+
!(first === 198 && second >= 18 && second <= 19) &&
|
|
384
|
+
!(first === 198 && second === 51 && third === 100) &&
|
|
385
|
+
!(first === 203 && second === 0 && third === 113) &&
|
|
386
|
+
first < 224);
|
|
387
|
+
}
|
|
47
388
|
function ipv6Groups(hostname) {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
389
|
+
const segments = hostname
|
|
390
|
+
.replace(/^\[|\]$/gu, "")
|
|
391
|
+
.toLowerCase()
|
|
392
|
+
.split("::");
|
|
393
|
+
if (segments.length > 2)
|
|
394
|
+
return undefined;
|
|
395
|
+
const left = segments[0] ? segments[0].split(":") : [];
|
|
396
|
+
const right = segments.length === 2 && segments[1] ? segments[1].split(":") : [];
|
|
397
|
+
const missing = segments.length === 2 ? 8 - left.length - right.length : 0;
|
|
398
|
+
const values = [...left, ...Array(Math.max(0, missing)).fill("0"), ...right];
|
|
399
|
+
if (values.length !== 8 ||
|
|
400
|
+
(segments.length === 2 && missing < 1) ||
|
|
401
|
+
values.some((part) => !/^[0-9a-f]{1,4}$/u.test(part)))
|
|
402
|
+
return undefined;
|
|
403
|
+
return values.map((part) => Number.parseInt(part, 16));
|
|
404
|
+
}
|
|
58
405
|
function isPublicIpv6(hostname) {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
406
|
+
const groups = ipv6Groups(hostname);
|
|
407
|
+
if (!groups)
|
|
408
|
+
return false;
|
|
409
|
+
const [first, second] = groups;
|
|
410
|
+
if ((first & 0xe000) !== 0x2000)
|
|
411
|
+
return false;
|
|
412
|
+
if (first === 0x3fff && (second & 0xf000) === 0)
|
|
413
|
+
return false;
|
|
414
|
+
if (first === 0x0100 && second === 0)
|
|
415
|
+
return false;
|
|
416
|
+
if ((first & 0xfe00) === 0xfc00 ||
|
|
417
|
+
(first & 0xffc0) === 0xfe80 ||
|
|
418
|
+
(first & 0xffc0) === 0xfec0 ||
|
|
419
|
+
(first & 0xff00) === 0xff00)
|
|
420
|
+
return false;
|
|
421
|
+
if (first === 0x2001 &&
|
|
422
|
+
(second === 0 ||
|
|
423
|
+
second === 0x0002 ||
|
|
424
|
+
(second >= 0x0010 && second <= 0x002f) ||
|
|
425
|
+
second === 0x0db8))
|
|
426
|
+
return false;
|
|
427
|
+
if (first === 0x0064 && second === 0xff9b && groups[2] === 0x0001)
|
|
428
|
+
return false;
|
|
429
|
+
return first !== 0x2002;
|
|
430
|
+
}
|
|
431
|
+
const RESERVED_HOSTNAME_SUFFIXES = [
|
|
432
|
+
".localhost",
|
|
433
|
+
".local",
|
|
434
|
+
".home.arpa",
|
|
435
|
+
".internal",
|
|
436
|
+
".test",
|
|
437
|
+
".example",
|
|
438
|
+
".invalid",
|
|
439
|
+
".onion",
|
|
440
|
+
".alt",
|
|
441
|
+
];
|
|
442
|
+
const RESERVED_DNS_SUFFIXES = ["ipv4only.arpa", "in-addr.arpa", "ip6.arpa"];
|
|
74
443
|
function isPublicHostname(hostname) {
|
|
75
|
-
|
|
444
|
+
return (hostname.includes(".") &&
|
|
445
|
+
!RESERVED_HOSTNAME_SUFFIXES.some((suffix) => hostname.endsWith(suffix)) &&
|
|
446
|
+
!RESERVED_DNS_SUFFIXES.some((suffix) => hostname === suffix || hostname.endsWith(`.${suffix}`)));
|
|
76
447
|
}
|
|
77
|
-
|
|
78
448
|
export function isPublicAlphaTarget(url) {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
449
|
+
const hostname = String(url?.hostname ?? "")
|
|
450
|
+
.replace(/^\[|\]$/gu, "")
|
|
451
|
+
.replace(/\.$/u, "")
|
|
452
|
+
.toLowerCase();
|
|
453
|
+
if (!hostname)
|
|
454
|
+
return false;
|
|
455
|
+
const family = isIP(hostname);
|
|
456
|
+
if (family === 4)
|
|
457
|
+
return isPublicIpv4(hostname);
|
|
458
|
+
if (family === 6)
|
|
459
|
+
return isPublicIpv6(hostname);
|
|
460
|
+
return isPublicHostname(hostname);
|
|
461
|
+
}
|
|
87
462
|
export function isAlphaHttpUrl(value) {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
export function
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
}
|
|
118
|
-
function
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
463
|
+
if (typeof value !== "string" || !value.trim())
|
|
464
|
+
return false;
|
|
465
|
+
try {
|
|
466
|
+
const url = new URL(value);
|
|
467
|
+
return ["http:", "https:"].includes(url.protocol) && Boolean(url.hostname);
|
|
468
|
+
}
|
|
469
|
+
catch {
|
|
470
|
+
return false;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
export function isAlphaContinuationUrl(value) {
|
|
474
|
+
if (typeof value !== "string" || !value.trim() || value !== value.trim())
|
|
475
|
+
return false;
|
|
476
|
+
try {
|
|
477
|
+
const url = new URL(value);
|
|
478
|
+
return (["http:", "https:"].includes(url.protocol) &&
|
|
479
|
+
Boolean(url.hostname) &&
|
|
480
|
+
!url.username &&
|
|
481
|
+
!url.password &&
|
|
482
|
+
isPublicAlphaTarget(url));
|
|
483
|
+
}
|
|
484
|
+
catch {
|
|
485
|
+
return false;
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
export function alphaRefRequiresStore(action, refId) {
|
|
489
|
+
return (action === "click" ||
|
|
490
|
+
(["open", "find", "screenshot"].includes(action) &&
|
|
491
|
+
!isAlphaContinuationUrl(refId)));
|
|
492
|
+
}
|
|
493
|
+
function httpUrl(value) {
|
|
494
|
+
return isAlphaContinuationUrl(value) ? new URL(value).toString() : undefined;
|
|
495
|
+
}
|
|
496
|
+
function safeResult(value, seen = new Set()) {
|
|
497
|
+
if (value === null || typeof value !== "object")
|
|
498
|
+
return value;
|
|
499
|
+
if (seen.has(value))
|
|
500
|
+
return undefined;
|
|
501
|
+
seen.add(value);
|
|
502
|
+
if (Array.isArray(value))
|
|
503
|
+
return value.map((x) => safeResult(x, seen)).filter((x) => x !== undefined);
|
|
504
|
+
const result = {};
|
|
505
|
+
for (const [key, item] of Object.entries(value)) {
|
|
506
|
+
if (key === "encrypted_output" || key === "encrypted_content")
|
|
507
|
+
continue;
|
|
508
|
+
if ([
|
|
509
|
+
"url",
|
|
510
|
+
"source_url",
|
|
511
|
+
"source_website_url",
|
|
512
|
+
"image_url",
|
|
513
|
+
"thumbnail_url",
|
|
514
|
+
].includes(key)) {
|
|
515
|
+
const u = httpUrl(item);
|
|
516
|
+
if (u)
|
|
517
|
+
result[key] = u;
|
|
518
|
+
continue;
|
|
519
|
+
}
|
|
520
|
+
const safe = safeResult(item, seen);
|
|
521
|
+
if (safe !== undefined)
|
|
522
|
+
result[key] = safe;
|
|
523
|
+
}
|
|
524
|
+
return result;
|
|
525
|
+
}
|
|
526
|
+
function sha256(value) {
|
|
527
|
+
return createHash("sha256").update(value).digest("hex");
|
|
528
|
+
}
|
|
529
|
+
function canonicalJson(value, seen = new Set()) {
|
|
530
|
+
if (value === null || typeof value === "string" || typeof value === "boolean")
|
|
531
|
+
return JSON.stringify(value);
|
|
532
|
+
if (typeof value === "number")
|
|
533
|
+
return Number.isFinite(value) ? JSON.stringify(value) : undefined;
|
|
534
|
+
if (typeof value !== "object" || seen.has(value))
|
|
535
|
+
return undefined;
|
|
536
|
+
seen.add(value);
|
|
537
|
+
let result;
|
|
538
|
+
if (Array.isArray(value)) {
|
|
539
|
+
const items = value.map(item => canonicalJson(item, seen));
|
|
540
|
+
if (items.every((item) => item !== undefined))
|
|
541
|
+
result = `[${items.join(",")}]`;
|
|
542
|
+
}
|
|
543
|
+
else {
|
|
544
|
+
const entries = Object.entries(value).sort(([left], [right]) => left.localeCompare(right));
|
|
545
|
+
const items = [];
|
|
546
|
+
for (const [key, item] of entries) {
|
|
547
|
+
const encoded = canonicalJson(item, seen);
|
|
548
|
+
if (encoded === undefined) {
|
|
549
|
+
result = undefined;
|
|
550
|
+
break;
|
|
551
|
+
}
|
|
552
|
+
items.push(`${JSON.stringify(key)}:${encoded}`);
|
|
553
|
+
result = `{${items.join(",")}}`;
|
|
554
|
+
}
|
|
555
|
+
if (entries.length === 0)
|
|
556
|
+
result = "{}";
|
|
557
|
+
}
|
|
558
|
+
seen.delete(value);
|
|
559
|
+
return result;
|
|
560
|
+
}
|
|
561
|
+
function explicitArtifactFingerprint(value) {
|
|
562
|
+
const explicit = Object.fromEntries(["provenance", "provider_provenance"]
|
|
563
|
+
.filter(key => Object.hasOwn(value, key))
|
|
564
|
+
.map(key => [key, value[key]]));
|
|
565
|
+
if (Object.keys(explicit).length === 0)
|
|
566
|
+
return undefined;
|
|
567
|
+
const canonical = canonicalJson(explicit);
|
|
568
|
+
return canonical === undefined ? sha256("invalid-explicit-provenance") : sha256(canonical);
|
|
569
|
+
}
|
|
570
|
+
function alphaRefCollision(refId) {
|
|
571
|
+
return Object.assign(new Error(`Alpha reference collision in one provider response: ${refId}`), {
|
|
572
|
+
code: "LCX_ALPHA_REF_COLLISION",
|
|
573
|
+
});
|
|
574
|
+
}
|
|
575
|
+
function addRefObservation(artifacts, observation) {
|
|
576
|
+
const accepted = artifacts.observations.get(observation.refId);
|
|
577
|
+
if (!accepted) {
|
|
578
|
+
artifacts.observations.set(observation.refId, observation);
|
|
579
|
+
artifacts.refs.push(observation.refId);
|
|
580
|
+
artifacts.refRecords.push(observation);
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
if ((accepted.url && observation.url && accepted.url !== observation.url) ||
|
|
584
|
+
(accepted.provenance.artifactFingerprint && observation.provenance.artifactFingerprint &&
|
|
585
|
+
accepted.provenance.artifactFingerprint !== observation.provenance.artifactFingerprint)) {
|
|
586
|
+
throw alphaRefCollision(observation.refId);
|
|
587
|
+
}
|
|
588
|
+
const merged = {
|
|
589
|
+
refId: accepted.refId,
|
|
590
|
+
...(accepted.url || observation.url ? { url: accepted.url ?? observation.url } : {}),
|
|
591
|
+
provenance: {
|
|
592
|
+
...accepted.provenance,
|
|
593
|
+
...(accepted.provenance.artifactFingerprint || observation.provenance.artifactFingerprint
|
|
594
|
+
? { artifactFingerprint: accepted.provenance.artifactFingerprint ?? observation.provenance.artifactFingerprint }
|
|
595
|
+
: {}),
|
|
596
|
+
},
|
|
597
|
+
};
|
|
598
|
+
artifacts.observations.set(observation.refId, merged);
|
|
599
|
+
artifacts.refRecords[artifacts.refRecords.indexOf(accepted)] = merged;
|
|
600
|
+
}
|
|
601
|
+
function artifacts(results, provenance) {
|
|
602
|
+
const result = { refs: [], refRecords: [], sources: [], observations: new Map() };
|
|
603
|
+
const seenSources = new Set();
|
|
604
|
+
const visit = (value, inheritedArtifact) => {
|
|
605
|
+
if (!isObject(value) && !Array.isArray(value))
|
|
606
|
+
return;
|
|
607
|
+
if (Array.isArray(value)) {
|
|
608
|
+
value.forEach(item => visit(item, inheritedArtifact));
|
|
609
|
+
return;
|
|
610
|
+
}
|
|
611
|
+
const artifactFingerprint = explicitArtifactFingerprint(value) ?? inheritedArtifact;
|
|
612
|
+
const candidate = typeof value.ref_id === "string" && value.ref_id
|
|
613
|
+
? value.ref_id
|
|
614
|
+
: typeof value.id === "string" && /^turn[\w-]+$/u.test(value.id)
|
|
615
|
+
? value.id
|
|
616
|
+
: undefined;
|
|
617
|
+
const refId = isAlphaHttpUrl(candidate) ? undefined : candidate;
|
|
618
|
+
const url = httpUrl(value.url ?? value.source_url ?? value.source_website_url ?? candidate);
|
|
619
|
+
if (refId)
|
|
620
|
+
addRefObservation(result, {
|
|
621
|
+
refId,
|
|
622
|
+
...(url ? { url } : {}),
|
|
623
|
+
provenance: { ...provenance, ...(artifactFingerprint ? { artifactFingerprint } : {}) },
|
|
624
|
+
});
|
|
625
|
+
if (url && !seenSources.has(url)) {
|
|
626
|
+
seenSources.add(url);
|
|
627
|
+
result.sources.push({
|
|
628
|
+
url,
|
|
629
|
+
...(typeof value.title === "string" ? { title: value.title } : {}),
|
|
630
|
+
...(typeof value.snippet === "string"
|
|
631
|
+
? { snippet: value.snippet }
|
|
632
|
+
: {}),
|
|
633
|
+
...(refId ? { refId } : {}),
|
|
634
|
+
});
|
|
635
|
+
}
|
|
636
|
+
Object.entries(value)
|
|
637
|
+
.filter(([key]) => key !== "encrypted_output" && key !== "encrypted_content")
|
|
638
|
+
.forEach(([, item]) => visit(item, artifactFingerprint));
|
|
639
|
+
};
|
|
640
|
+
visit(results);
|
|
641
|
+
return result;
|
|
642
|
+
}
|
|
643
|
+
function structuredLinks(results) {
|
|
644
|
+
const links = [];
|
|
645
|
+
const visit = (value) => {
|
|
646
|
+
if (Array.isArray(value)) {
|
|
647
|
+
value.forEach(visit);
|
|
648
|
+
return;
|
|
649
|
+
}
|
|
650
|
+
if (!isObject(value))
|
|
651
|
+
return;
|
|
652
|
+
if (Array.isArray(value.links)) {
|
|
653
|
+
for (const item of value.links) {
|
|
654
|
+
if (!isObject(item))
|
|
655
|
+
continue;
|
|
656
|
+
const id = item.id ?? item.link_id ?? item.linkId;
|
|
657
|
+
if (typeof id !== "number" || !Number.isSafeInteger(id))
|
|
658
|
+
continue;
|
|
659
|
+
const url = httpUrl(item.url ?? item.href);
|
|
660
|
+
const label = (typeof item.label === "string" && item.label.trim()) ||
|
|
661
|
+
(typeof item.title === "string" && item.title.trim()) ||
|
|
662
|
+
url ||
|
|
663
|
+
String(id);
|
|
664
|
+
const domain = typeof item.domain === "string" && item.domain.trim()
|
|
665
|
+
? item.domain.trim().slice(0, 253)
|
|
666
|
+
: undefined;
|
|
667
|
+
if (!links.some((link) => link.id === id)) {
|
|
668
|
+
links.push({
|
|
669
|
+
id,
|
|
670
|
+
label: String(label).slice(0, 500),
|
|
671
|
+
...(domain ? { domain } : {}),
|
|
672
|
+
...(url ? { url } : {}),
|
|
673
|
+
});
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
Object.values(value).forEach(visit);
|
|
678
|
+
};
|
|
679
|
+
visit(results);
|
|
680
|
+
return links;
|
|
681
|
+
}
|
|
682
|
+
const ACTION_ERROR = /^\s*(?:Error parsing function call\b|Invalid function_name=|Invalid function call\b)/iu;
|
|
683
|
+
const ALPHA_SEMANTIC_FAILURE = /^\s*(?:reference(?: id)?\s+(?:is\s+)?(?:invalid|unavailable)\b|unable to access requested content\b|service unavailable\b)/iu;
|
|
684
|
+
const ALPHA_COMMAND_ERROR_ENVELOPE = /^\s*Internal Error\s*\([^\r\n)]*\)\s*(?:\r?\n[ \t]*)+(?:\uE200cite\uE202[^\uE201\r\n]+\uE201[ \t]*(?:\[wordlim:\s*\d+\][ \t]*)?)?Unable to resolve (open|find|click|screenshot) call\s*:/iu;
|
|
685
|
+
const ALPHA_FETCH_FAILURE_ENVELOPE = /^\s*Internal Error\s*\([^\r\n)]*\)\s*(?:\r?\n[ \t]*)+(?:\uE200cite\uE202[^\uE201\r\n]+\uE201[ \t]*(?:\[wordlim:\s*\d+\][ \t]*)?)?Source:\s*(open|find|click|screenshot)\s*\([^\r\n]*\)\s*;\s*Total lines:\s*\d+\s*(?:\r?\n)L0:[ \t]*Failed to fetch \S+:[ \t]*\(\d{3}\)/iu;
|
|
686
|
+
const ALPHA_REFERENCE_ERROR_ENVELOPE = /^\s*(?:\*\*Result:\*\*[ \t]*)?Unable to (?:execute[ \t]+)?`?(open|find|click|screenshot)`?(?=[\s:])([^\r\n]*)/iu;
|
|
687
|
+
function alphaCommandErrorEnvelope(output, action) {
|
|
688
|
+
const text = String(output ?? "");
|
|
689
|
+
const command = text.match(ALPHA_COMMAND_ERROR_ENVELOPE);
|
|
690
|
+
if (command && command[1].toLowerCase() === action)
|
|
691
|
+
return true;
|
|
692
|
+
const fetchFailure = text.match(ALPHA_FETCH_FAILURE_ENVELOPE);
|
|
693
|
+
if (fetchFailure && fetchFailure[1].toLowerCase() === action)
|
|
694
|
+
return true;
|
|
695
|
+
// Match the action failure header, never error phrases quoted inside a page.
|
|
696
|
+
const reference = text.match(ALPHA_REFERENCE_ERROR_ENVELOPE);
|
|
697
|
+
return Boolean(reference &&
|
|
698
|
+
reference[1].toLowerCase() === action &&
|
|
699
|
+
/\b(?:reference(?: id)?|turn[\w-]+)\b/iu.test(reference[2]) &&
|
|
700
|
+
/\b(?:invalid|unavailable|not available|unresolved)\b/iu.test(reference[2]));
|
|
701
|
+
}
|
|
702
|
+
export function parseAlphaSearchResponse(response, options) {
|
|
703
|
+
if (!isObject(response) ||
|
|
704
|
+
typeof response.output !== "string" ||
|
|
705
|
+
(response.results !== undefined && !Array.isArray(response.results)))
|
|
706
|
+
throw failure("LCX Alpha Web Search returned an invalid response", "LCX_ALPHA_INVALID_RESPONSE");
|
|
707
|
+
const action = typeof options.action === "string" ? options.action : undefined;
|
|
708
|
+
if (ACTION_ERROR.test(response.output) ||
|
|
709
|
+
(["open", "find", "click", "screenshot"].includes(action ?? "") &&
|
|
710
|
+
(ALPHA_SEMANTIC_FAILURE.test(response.output) ||
|
|
711
|
+
alphaCommandErrorEnvelope(response.output, action))))
|
|
712
|
+
throw failure("LCX Alpha Web Search could not execute the requested action", "LCX_ALPHA_ACTION_FAILED");
|
|
713
|
+
const results = safeResult(response.results ?? []);
|
|
714
|
+
const originKind = typeof response.id === "string" && response.id ? "response" : "request";
|
|
715
|
+
const originValue = originKind === "response" ? String(response.id) : String(options.requestId ?? "");
|
|
716
|
+
const provenance = {
|
|
717
|
+
action: action ?? "unknown",
|
|
718
|
+
originKind,
|
|
719
|
+
originFingerprint: sha256(originValue),
|
|
720
|
+
};
|
|
721
|
+
const a = artifacts(response.results ?? [], provenance);
|
|
722
|
+
const outputBlocks = parseWebRunOutput(response.output);
|
|
723
|
+
for (const block of outputBlocks)
|
|
724
|
+
if (block.url) {
|
|
725
|
+
const url = httpUrl(block.url);
|
|
726
|
+
if (url)
|
|
727
|
+
block.url = url;
|
|
728
|
+
else
|
|
729
|
+
delete block.url;
|
|
730
|
+
}
|
|
731
|
+
for (const block of outputBlocks)
|
|
732
|
+
for (const ref of block.references ?? []) {
|
|
733
|
+
if (isAlphaHttpUrl(ref))
|
|
734
|
+
continue;
|
|
735
|
+
addRefObservation(a, {
|
|
736
|
+
refId: ref,
|
|
737
|
+
...(block.url ? { url: block.url } : {}),
|
|
738
|
+
provenance,
|
|
739
|
+
});
|
|
740
|
+
}
|
|
741
|
+
for (const source of outputBlocks.flatMap((b) => b.url ? [{ url: b.url, ...(b.title ? { title: b.title } : {}) }] : []))
|
|
742
|
+
if (!a.sources.some((x) => x.url === source.url))
|
|
743
|
+
a.sources.push(source);
|
|
744
|
+
return {
|
|
745
|
+
mode: "alpha",
|
|
746
|
+
action: options.action,
|
|
747
|
+
capability: options.capability,
|
|
748
|
+
emulation: options.capability === "native" ? "native" : "unknown",
|
|
749
|
+
content: response.output,
|
|
750
|
+
results,
|
|
751
|
+
refs: a.refs,
|
|
752
|
+
sources: a.sources,
|
|
753
|
+
citations: a.sources.map((s) => ({ ...s })),
|
|
754
|
+
outputBlocks,
|
|
755
|
+
links: mergeWebRunLinks(outputLinks(outputBlocks), structuredLinks(results)),
|
|
756
|
+
pdfRefs: outputPdfRefs(outputBlocks),
|
|
757
|
+
domains: outputDomains(outputBlocks),
|
|
758
|
+
...(outputLineRange(outputBlocks)
|
|
759
|
+
? { lineRange: outputLineRange(outputBlocks) }
|
|
760
|
+
: {}),
|
|
761
|
+
requestId: options.requestId,
|
|
762
|
+
...(typeof response.id === "string" && response.id
|
|
763
|
+
? { responseId: response.id }
|
|
764
|
+
: {}),
|
|
765
|
+
retrievedAt: options.retrievedAt ?? new Date().toISOString(),
|
|
766
|
+
warnings: options.capability === "command-capable"
|
|
767
|
+
? [
|
|
768
|
+
"Alpha command behavior is verified, but trusted native backend provenance is unavailable.",
|
|
769
|
+
]
|
|
770
|
+
: [],
|
|
771
|
+
refRecords: a.refRecords,
|
|
772
|
+
};
|
|
773
|
+
}
|
|
774
|
+
function opaqueRefList(value) {
|
|
775
|
+
return Array.isArray(value)
|
|
776
|
+
? value.filter((ref) => typeof ref === "string" && Boolean(ref) && !isAlphaHttpUrl(ref))
|
|
777
|
+
: [];
|
|
778
|
+
}
|
|
779
|
+
function sourceUrlForRef(sources, refId) {
|
|
780
|
+
const match = sources.find((source) => source.refId === refId && typeof source.url === "string" && source.url);
|
|
781
|
+
return typeof match?.url === "string" ? match.url : undefined;
|
|
782
|
+
}
|
|
783
|
+
export function renderAlphaSearchResult(value) {
|
|
784
|
+
const data = isObject(value) ? value : {};
|
|
785
|
+
const parts = [];
|
|
786
|
+
if (typeof data.content === "string")
|
|
787
|
+
parts.push(data.content);
|
|
788
|
+
const sources = Array.isArray(data.sources) ? data.sources.filter(isObject) : [];
|
|
789
|
+
const refs = opaqueRefList(data.refs);
|
|
790
|
+
if (refs.length) {
|
|
791
|
+
parts.push(`可继续操作的引用:\n${refs.map((ref) => {
|
|
792
|
+
const url = sourceUrlForRef(sources, ref);
|
|
793
|
+
return url ? `- opaqueRef=${ref} url=${url}` : `- opaqueRef=${ref}`;
|
|
794
|
+
}).join("\n")}`);
|
|
795
|
+
}
|
|
796
|
+
const links = Array.isArray(data.links)
|
|
797
|
+
? data.links.filter((link) => isObject(link) && Number.isSafeInteger(link.id))
|
|
798
|
+
: [];
|
|
799
|
+
if (links.length) {
|
|
800
|
+
parts.push(`可点击链接:\n${links.map((link) => {
|
|
801
|
+
const bits = [`linkId=${String(link.id)}`];
|
|
802
|
+
if (typeof link.label === "string" && link.label)
|
|
803
|
+
bits.push(`label=${link.label}`);
|
|
804
|
+
if (typeof link.domain === "string" && link.domain)
|
|
805
|
+
bits.push(`domain=${link.domain}`);
|
|
806
|
+
if (typeof link.url === "string" && link.url)
|
|
807
|
+
bits.push(`url=${link.url}`);
|
|
808
|
+
return `- ${bits.join(" ")}`;
|
|
809
|
+
}).join("\n")}`);
|
|
810
|
+
}
|
|
811
|
+
const pdfRefs = opaqueRefList(data.pdfRefs);
|
|
812
|
+
if (pdfRefs.length)
|
|
813
|
+
parts.push(`PDF引用:\n${pdfRefs.map((ref) => `- opaqueRef=${ref}`).join("\n")}`);
|
|
814
|
+
if (sources.length)
|
|
815
|
+
parts.push(`来源:\n${sources.map((source) => `- [${String(source.title ?? source.url ?? "")}](${String(source.url ?? "")})`).join("\n")}`);
|
|
816
|
+
if (Array.isArray(data.warnings) && data.warnings.length)
|
|
817
|
+
parts.push(data.warnings.map((warning) => `警告:${String(warning)}`).join("\n"));
|
|
818
|
+
parts.push(`检索时间:${String(data.retrievedAt ?? "")}`);
|
|
819
|
+
return [{ type: "text", text: parts.filter(Boolean).join("\n\n") }];
|
|
820
|
+
}
|
|
821
|
+
export const ALPHA_STATEFUL_RETRY_MAX_ATTEMPTS = 1;
|
|
822
|
+
const alphaSessionLocks = new Map();
|
|
823
|
+
export function alphaSearchRetryOptions(maxResponseBytes) {
|
|
824
|
+
return {
|
|
825
|
+
maxAttempts: ALPHA_STATEFUL_RETRY_MAX_ATTEMPTS,
|
|
826
|
+
...(typeof maxResponseBytes === "number" ? { maxResponseBytes } : {}),
|
|
827
|
+
};
|
|
828
|
+
}
|
|
829
|
+
export async function runWithAlphaSessionLock(sessionId, signal, task) {
|
|
830
|
+
if (typeof sessionId !== "string" || !sessionId)
|
|
831
|
+
throw failure("websearch_alpha requires a DSH session", "LCX_ALPHA_SESSION_REQUIRED");
|
|
832
|
+
let entry = alphaSessionLocks.get(sessionId);
|
|
833
|
+
if (!entry) {
|
|
834
|
+
entry = { mutex: new ServiceMutex(), users: 0 };
|
|
835
|
+
alphaSessionLocks.set(sessionId, entry);
|
|
836
|
+
}
|
|
837
|
+
entry.users += 1;
|
|
838
|
+
try {
|
|
839
|
+
return await entry.mutex.run(signal, task);
|
|
840
|
+
}
|
|
841
|
+
finally {
|
|
842
|
+
entry.users -= 1;
|
|
843
|
+
if (entry.users === 0 && alphaSessionLocks.get(sessionId) === entry)
|
|
844
|
+
alphaSessionLocks.delete(sessionId);
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
export async function fetchAlphaSearchJson(options) {
|
|
848
|
+
return runWithAlphaSessionLock(options.sessionId, options.signal, () => fetchJsonWithRetry(options.url, options.body, options.headers, options.signal, options.timeoutMs, alphaSearchRetryOptions(options.maxResponseBytes)));
|
|
849
|
+
}
|
|
850
|
+
function probeFailureState(error) {
|
|
851
|
+
const detail = isObject(error) ? error : {};
|
|
852
|
+
if ([404, 405].includes(detail.status) ||
|
|
853
|
+
["LCX_ALPHA_ACTION_UNAVAILABLE", "LCX_ALPHA_ACTION_FAILED"].includes(String(detail.code ?? "")) ||
|
|
854
|
+
/channel does not support|unsupported action|not implemented/iu.test(String(detail.message ?? "")))
|
|
855
|
+
return "unsupported";
|
|
856
|
+
return "unknown";
|
|
857
|
+
}
|
|
858
|
+
function alphaProbeStructuredResult(value, seen = new Set()) {
|
|
859
|
+
if (!value || typeof value !== "object" || seen.has(value))
|
|
860
|
+
return false;
|
|
861
|
+
seen.add(value);
|
|
862
|
+
if (Array.isArray(value))
|
|
863
|
+
return value.some((item) => alphaProbeStructuredResult(item, seen));
|
|
864
|
+
if (!isObject(value))
|
|
865
|
+
return false;
|
|
866
|
+
const refId = typeof value.ref_id === "string" && value.ref_id
|
|
867
|
+
? value.ref_id
|
|
868
|
+
: typeof value.id === "string" && /^turn[\w-]+$/u.test(value.id)
|
|
869
|
+
? value.id
|
|
870
|
+
: undefined;
|
|
871
|
+
if (refId && !isAlphaHttpUrl(refId))
|
|
872
|
+
return true;
|
|
873
|
+
if (["line", "line_number", "page", "page_number", "pageno"].some((field) => Number.isSafeInteger(value[field])))
|
|
874
|
+
return true;
|
|
875
|
+
return Object.values(value).some((item) => alphaProbeStructuredResult(item, seen));
|
|
876
|
+
}
|
|
877
|
+
function alphaProbeStructuredBlock(block) {
|
|
878
|
+
if (!isObject(block))
|
|
879
|
+
return false;
|
|
880
|
+
const references = Array.isArray(block.references) ? block.references : [];
|
|
881
|
+
const links = Array.isArray(block.links) ? block.links : [];
|
|
882
|
+
const metadata = Array.isArray(block.metadata) ? block.metadata : [];
|
|
883
|
+
const lines = Array.isArray(block.lines) ? block.lines : [];
|
|
884
|
+
if (references.some((ref) => typeof ref === "string" && ref && !isAlphaHttpUrl(ref)))
|
|
885
|
+
return true;
|
|
886
|
+
if (links.some((link) => isObject(link) && Number.isSafeInteger(link.id)))
|
|
887
|
+
return true;
|
|
888
|
+
if (metadata.some((item) => item === "HTML" || item === "PDF" || /^\d+ (?:lines|pages)$/u.test(String(item))))
|
|
889
|
+
return true;
|
|
890
|
+
return lines.some((line) => isObject(line) && (Number.isSafeInteger(line.line) || Number.isSafeInteger(line.page)));
|
|
891
|
+
}
|
|
892
|
+
const ALPHA_PROBE_SEMANTIC_FAILURE = /\breference(?: id)?\s+(?:is\s+)?(?:invalid|unavailable)\b/iu;
|
|
893
|
+
function alphaProbeResultHasEvidence(action, value) {
|
|
894
|
+
const data = isObject(value) ? value : {};
|
|
895
|
+
if (alphaCommandErrorEnvelope(data.content, action))
|
|
896
|
+
return false;
|
|
897
|
+
if (ALPHA_PROBE_SEMANTIC_FAILURE.test(String(data.content ?? "")) &&
|
|
898
|
+
!(Array.isArray(data.results) && data.results.length))
|
|
899
|
+
return false;
|
|
900
|
+
const refs = Array.isArray(data.refs) &&
|
|
901
|
+
data.refs.some((ref) => typeof ref === "string" && ref && !isAlphaHttpUrl(ref));
|
|
902
|
+
const links = Array.isArray(data.links) &&
|
|
903
|
+
data.links.some((link) => isObject(link) && Number.isSafeInteger(link.id));
|
|
904
|
+
const pdfRefs = Array.isArray(data.pdfRefs) &&
|
|
905
|
+
data.pdfRefs.some((ref) => typeof ref === "string" && ref && !isAlphaHttpUrl(ref));
|
|
906
|
+
const structuredResults = Array.isArray(data.results) &&
|
|
907
|
+
data.results.some((item) => alphaProbeStructuredResult(item));
|
|
908
|
+
const structuredBlocks = Array.isArray(data.outputBlocks) &&
|
|
909
|
+
data.outputBlocks.some((block) => alphaProbeStructuredBlock(block));
|
|
910
|
+
if (action === "find")
|
|
911
|
+
return Boolean(refs || structuredResults || structuredBlocks);
|
|
912
|
+
if (action === "click" || action === "screenshot")
|
|
913
|
+
return Boolean(refs || pdfRefs || structuredResults || structuredBlocks);
|
|
914
|
+
return Boolean(refs || links || pdfRefs || structuredResults || structuredBlocks);
|
|
915
|
+
}
|
|
916
|
+
export async function probeAlphaCapabilities({ invoke, schemaFingerprint, trustedNativeProvenance = false, actionProbes = {}, clickProbeRef, screenshotProbeRef, }) {
|
|
917
|
+
const actions = Object.fromEntries(ALPHA_ACTIONS.map((action) => [action, "unknown"]));
|
|
918
|
+
const result = {
|
|
919
|
+
classification: "unsupported",
|
|
920
|
+
actions,
|
|
921
|
+
probedAt: new Date().toISOString(),
|
|
922
|
+
schemaFingerprint,
|
|
923
|
+
probeVersion: ALPHA_PROBE_VERSION,
|
|
924
|
+
provenance: trustedNativeProvenance ? "trusted-native" : "unavailable",
|
|
925
|
+
};
|
|
926
|
+
let search;
|
|
927
|
+
try {
|
|
928
|
+
search = await invoke({
|
|
929
|
+
action: "search_query",
|
|
930
|
+
query: "OpenAI official documentation",
|
|
931
|
+
responseLength: "short",
|
|
932
|
+
});
|
|
933
|
+
actions.search_query = "supported";
|
|
934
|
+
}
|
|
935
|
+
catch (error) {
|
|
936
|
+
actions.search_query = probeFailureState(error);
|
|
937
|
+
result.classification =
|
|
938
|
+
actions.search_query === "unsupported" ? "unsupported" : "unknown";
|
|
939
|
+
return result;
|
|
940
|
+
}
|
|
941
|
+
const searchSources = Array.isArray(search.sources) ? search.sources : [];
|
|
942
|
+
const searchUrl = searchSources
|
|
943
|
+
.map((source) => isObject(source) ? httpUrl(source.url) : undefined)
|
|
944
|
+
.find((value) => value !== undefined);
|
|
945
|
+
const searchRefs = Array.isArray(search.refs) ? search.refs : [];
|
|
946
|
+
const searchRef = searchRefs[0];
|
|
947
|
+
const continuationTarget = searchUrl ?? searchRef;
|
|
948
|
+
if (!continuationTarget) {
|
|
949
|
+
result.classification = "emulated-search-only";
|
|
950
|
+
actions.open = "unsupported";
|
|
951
|
+
actions.find = "unsupported";
|
|
952
|
+
actions.click = "unsupported";
|
|
953
|
+
return result;
|
|
954
|
+
}
|
|
955
|
+
let opened;
|
|
956
|
+
try {
|
|
957
|
+
opened = await invoke({
|
|
958
|
+
action: "open",
|
|
959
|
+
refId: continuationTarget,
|
|
960
|
+
responseLength: "short",
|
|
961
|
+
});
|
|
962
|
+
if (!alphaProbeResultHasEvidence("open", opened)) {
|
|
963
|
+
actions.open = "unsupported";
|
|
964
|
+
result.classification = "emulated-search-only";
|
|
965
|
+
return result;
|
|
966
|
+
}
|
|
967
|
+
actions.open = "supported";
|
|
968
|
+
}
|
|
969
|
+
catch (error) {
|
|
970
|
+
actions.open = probeFailureState(error);
|
|
971
|
+
result.classification =
|
|
972
|
+
actions.open === "unsupported" ? "emulated-search-only" : "unknown";
|
|
973
|
+
return result;
|
|
974
|
+
}
|
|
975
|
+
const openedSources = opened && Array.isArray(opened.sources) ? opened.sources : [];
|
|
976
|
+
const openedUrl = openedSources
|
|
977
|
+
.map((source) => isObject(source) ? httpUrl(source.url) : undefined)
|
|
978
|
+
.find((value) => value !== undefined);
|
|
979
|
+
const openedRefs = opened && Array.isArray(opened.refs) ? opened.refs : [];
|
|
980
|
+
const findTarget = searchUrl ?? openedUrl ?? openedRefs[0] ?? continuationTarget;
|
|
981
|
+
try {
|
|
982
|
+
const found = await invoke({
|
|
983
|
+
action: "find",
|
|
984
|
+
refId: findTarget,
|
|
985
|
+
pattern: "OpenAI",
|
|
986
|
+
responseLength: "short",
|
|
987
|
+
});
|
|
988
|
+
actions.find = alphaProbeResultHasEvidence("find", found)
|
|
989
|
+
? "supported"
|
|
990
|
+
: "unsupported";
|
|
991
|
+
}
|
|
992
|
+
catch (error) {
|
|
993
|
+
actions.find = probeFailureState(error);
|
|
994
|
+
}
|
|
995
|
+
let clickPage = opened, clickProbeUsed = false;
|
|
996
|
+
if (!(clickPage && Array.isArray(clickPage.links) && clickPage.links.length > 0) && clickProbeRef) {
|
|
997
|
+
clickProbeUsed = true;
|
|
998
|
+
try {
|
|
999
|
+
clickPage = await invoke({
|
|
1000
|
+
action: "open",
|
|
1001
|
+
refId: clickProbeRef,
|
|
1002
|
+
responseLength: "short",
|
|
1003
|
+
});
|
|
1004
|
+
}
|
|
1005
|
+
catch (error) {
|
|
1006
|
+
actions.click = probeFailureState(error);
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
const clickRefs = clickPage && Array.isArray(clickPage.refs) ? clickPage.refs : [];
|
|
1010
|
+
const clickLinks = clickPage && Array.isArray(clickPage.links) ? clickPage.links : [];
|
|
1011
|
+
const clickRef = clickRefs[0];
|
|
1012
|
+
const linkId = isObject(clickLinks[0]) ? clickLinks[0].id : undefined;
|
|
1013
|
+
if (clickRef && Number.isSafeInteger(linkId)) {
|
|
1014
|
+
try {
|
|
1015
|
+
const clicked = await invoke({
|
|
1016
|
+
action: "click",
|
|
1017
|
+
refId: clickRef,
|
|
1018
|
+
linkId,
|
|
1019
|
+
responseLength: "short",
|
|
1020
|
+
});
|
|
1021
|
+
actions.click = alphaProbeResultHasEvidence("click", clicked)
|
|
1022
|
+
? "supported"
|
|
1023
|
+
: "unsupported";
|
|
1024
|
+
}
|
|
1025
|
+
catch (error) {
|
|
1026
|
+
actions.click = probeFailureState(error);
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
if (actions.find === "supported" ||
|
|
1030
|
+
(actions.click === "supported" && !clickProbeUsed))
|
|
1031
|
+
result.classification = trustedNativeProvenance
|
|
1032
|
+
? "native"
|
|
1033
|
+
: "command-capable";
|
|
1034
|
+
else if (actions.find === "unsupported" && actions.click === "unsupported")
|
|
1035
|
+
result.classification = "emulated-search-only";
|
|
1036
|
+
else
|
|
1037
|
+
result.classification = "unknown";
|
|
1038
|
+
if (screenshotProbeRef) {
|
|
1039
|
+
try {
|
|
1040
|
+
const pdf = await invoke({
|
|
1041
|
+
action: "open",
|
|
1042
|
+
refId: screenshotProbeRef,
|
|
1043
|
+
responseLength: "short",
|
|
1044
|
+
});
|
|
1045
|
+
const pdfRefs = Array.isArray(pdf.pdfRefs) ? pdf.pdfRefs : [];
|
|
1046
|
+
const pdfRef = pdfRefs[0];
|
|
1047
|
+
if (pdfRef) {
|
|
1048
|
+
const shot = await invoke({
|
|
1049
|
+
action: "screenshot",
|
|
1050
|
+
refId: pdfRef,
|
|
1051
|
+
pageNumber: 0,
|
|
1052
|
+
responseLength: "short",
|
|
1053
|
+
});
|
|
1054
|
+
actions.screenshot = alphaProbeResultHasEvidence("screenshot", shot)
|
|
1055
|
+
? "supported"
|
|
1056
|
+
: "unsupported";
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
catch (error) {
|
|
1060
|
+
actions.screenshot = probeFailureState(error);
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
for (const [action, args] of Object.entries(actionProbes)) {
|
|
1064
|
+
if (!["image_query", "finance", "weather", "sports", "time"].includes(action))
|
|
1065
|
+
continue;
|
|
1066
|
+
try {
|
|
1067
|
+
const value = await invoke({ action, ...args, responseLength: "short" });
|
|
1068
|
+
actions[action] = alphaProbeResultHasEvidence(action, value)
|
|
1069
|
+
? "supported"
|
|
1070
|
+
: "unknown";
|
|
1071
|
+
}
|
|
1072
|
+
catch (error) {
|
|
1073
|
+
actions[action] = probeFailureState(error);
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
return result;
|
|
173
1077
|
}
|