dsh-lcx-codex 0.4.3 → 0.5.0-pre.1
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 +95 -60
- package/lib/abort-error.js +47 -0
- package/lib/client.js +597 -505
- package/lib/dsh-compat.js +37 -0
- package/lib/dsh-responses.js +92 -48
- package/lib/index.js +174 -105
- package/lib/invocation-policy-scope.js +4 -8
- package/lib/native-checkpoint.js +172 -80
- package/lib/pi-responses-runtime.js +252 -98
- package/lib/pi086-responses-allowlist.js +131 -0
- package/lib/responses-request.js +2 -2
- package/lib/responses-stream.js +62 -11
- package/lib/route.js +23 -32
- package/lib/search-accounting.js +22 -6
- package/lib/search-usage.js +32 -18
- package/lib/service-mutex.js +4 -8
- package/lib/token-budget.js +15 -21
- package/lib/transport.js +6 -6
- package/lib/types/client/search-media.d.ts +11 -2
- package/lib/types/index.d.ts +71 -43
- package/lib/web-search-alpha-history.js +62 -0
- package/lib/web-search-alpha.js +141 -124
- package/lib/web-search-capability.js +1 -1
- package/lib/web-search-hosted.js +29 -17
- package/lib/web-search-ref-store.js +27 -5
- package/package.json +41 -27
package/lib/transport.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { abortError } from "./abort-error.js";
|
|
1
2
|
const DEFAULT_TIMEOUT_MS = 300_000;
|
|
2
3
|
const DEFAULT_MAX_BYTES = 8 * 1024 * 1024;
|
|
3
4
|
const MAX_ERROR_BYTES = 512 * 1024;
|
|
@@ -6,8 +7,7 @@ function errorFacts(value) {
|
|
|
6
7
|
}
|
|
7
8
|
export function abortIfNeeded(signal) {
|
|
8
9
|
if (signal?.aborted)
|
|
9
|
-
throw (signal
|
|
10
|
-
Object.assign(new Error("request aborted"), { code: "LCX_ABORTED" }));
|
|
10
|
+
throw abortError(signal);
|
|
11
11
|
}
|
|
12
12
|
function makeError(message, code, extra = {}) {
|
|
13
13
|
const error = new Error(message);
|
|
@@ -63,11 +63,11 @@ async function sleep(ms, signal) {
|
|
|
63
63
|
return;
|
|
64
64
|
await new Promise((resolve, reject) => {
|
|
65
65
|
if (signal?.aborted)
|
|
66
|
-
return reject(signal
|
|
66
|
+
return reject(abortError(signal));
|
|
67
67
|
const onAbort = () => {
|
|
68
68
|
clearTimeout(timer);
|
|
69
69
|
signal?.removeEventListener("abort", onAbort);
|
|
70
|
-
reject(signal
|
|
70
|
+
reject(abortError(signal));
|
|
71
71
|
};
|
|
72
72
|
const timer = setTimeout(() => {
|
|
73
73
|
signal?.removeEventListener("abort", onAbort);
|
|
@@ -161,7 +161,7 @@ export async function fetchJsonWithRetry(url, body, headers = {}, signal, timeou
|
|
|
161
161
|
}
|
|
162
162
|
catch (error) {
|
|
163
163
|
if (signal?.aborted)
|
|
164
|
-
throw signal
|
|
164
|
+
throw abortError(signal, error);
|
|
165
165
|
const details = errorFacts(error);
|
|
166
166
|
const retryable = details.retryable === true ||
|
|
167
167
|
details.code === "LCX_HTTP_RETRYABLE" ||
|
|
@@ -231,7 +231,7 @@ export async function fetchSseWithRetry(url, body, headers = {}, signal, timeout
|
|
|
231
231
|
}
|
|
232
232
|
catch (error) {
|
|
233
233
|
if (signal?.aborted)
|
|
234
|
-
throw signal
|
|
234
|
+
throw abortError(signal, error);
|
|
235
235
|
const details = errorFacts(error);
|
|
236
236
|
const retryable = details.retryable === true ||
|
|
237
237
|
details.code === "LCX_HTTP_RETRYABLE" ||
|
|
@@ -12,5 +12,14 @@ export type StructuredMediaTool = "web_search" | "websearch_gpt_advanced";
|
|
|
12
12
|
/** Narrow LCX-owned tool-private media metadata without reading model-visible output. */
|
|
13
13
|
export declare function structuredSearchMedia(meta: unknown, expectedTool: StructuredMediaTool): readonly SearchMediaItem[];
|
|
14
14
|
export declare function mergeSearchMedia(structured: readonly SearchMediaItem[], fallback: readonly SearchMediaItem[]): readonly SearchMediaItem[];
|
|
15
|
-
/**
|
|
16
|
-
|
|
15
|
+
/**
|
|
16
|
+
* Extract bounded direct media links from visible assistant prose, in text order.
|
|
17
|
+
*
|
|
18
|
+
* Ownership (Issue #102): Markdown images are rendered by DSH itself, so their
|
|
19
|
+
* destinations are never returned, and a bare link to an image DSH already renders
|
|
20
|
+
* natively in the same text is skipped. Plain direct image links and direct video
|
|
21
|
+
* files are LCX's; everything else (pages, video sites) stays an ordinary link.
|
|
22
|
+
*/
|
|
23
|
+
export declare function extractDirectMediaLinks(text: string): readonly SearchMediaItem[];
|
|
24
|
+
/** Direct video files only (see `extractDirectMediaLinks`). */
|
|
25
|
+
export declare function extractDirectVideoLinks(text: string): readonly SearchMediaItem[];
|
package/lib/types/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import z from "@deepseek-ai/schemastery";
|
|
2
|
-
import type { Context } from "@deepseek-ai/cordis";
|
|
2
|
+
import type { Context, Volatile } from "@deepseek-ai/cordis";
|
|
3
3
|
import "@deepseek-ai/dsh-agent";
|
|
4
4
|
import "@deepseek-ai/dsh-tools";
|
|
5
5
|
import "@deepseek-ai/dsh-web";
|
|
@@ -26,56 +26,84 @@ type ConfigInput = {
|
|
|
26
26
|
assistantRetentionTokenReserve?: unknown;
|
|
27
27
|
assistantRetentionPerMessageTokenCap?: unknown;
|
|
28
28
|
};
|
|
29
|
+
type LiveSettingsConfig = {
|
|
30
|
+
enabled: Volatile<boolean>;
|
|
31
|
+
webSearch: Volatile<boolean>;
|
|
32
|
+
advancedHostedSearch: Volatile<boolean>;
|
|
33
|
+
alphaSearch: Volatile<boolean>;
|
|
34
|
+
grokNativeWebSearch: Volatile<boolean>;
|
|
35
|
+
grokNativeXSearch: Volatile<boolean>;
|
|
36
|
+
searchMediaPreview: Volatile<boolean>;
|
|
37
|
+
};
|
|
29
38
|
type HostContext = Context;
|
|
30
|
-
export declare const Config: z<Schemastery.ObjectS<{
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
39
|
+
export declare const Config: z<Schemastery.ObjectS<NoInfer<{
|
|
40
|
+
enabled: z<boolean, boolean, "volatile-defined">;
|
|
41
|
+
webSearch: z<boolean, boolean, "volatile-defined">;
|
|
42
|
+
advancedHostedSearch: z<boolean, boolean, "volatile-defined">;
|
|
43
|
+
alphaSearch: z<boolean, boolean, "volatile-defined">;
|
|
44
|
+
grokNativeWebSearch: z<boolean, boolean, "volatile-defined">;
|
|
45
|
+
grokNativeXSearch: z<boolean, boolean, "volatile-defined">;
|
|
46
|
+
searchMediaPreview: z<boolean, boolean, "volatile-defined">;
|
|
47
|
+
supportsLongCacheRetention: z<boolean, boolean, "defined">;
|
|
48
|
+
supportsExplicitPromptCacheMode: z<boolean, boolean, "defined">;
|
|
49
|
+
alphaCapabilityPath: z<string, string, "defined">;
|
|
50
|
+
alphaRefPath: z<string, string, "defined">;
|
|
51
|
+
alphaProfile: z<string, string, "defined">;
|
|
52
|
+
alphaGroup: z<string, string, "defined">;
|
|
53
|
+
alphaMaxOutputTokens: z<number, number, "defined">;
|
|
54
|
+
webSearchProvider: z<string, string, "defined">;
|
|
55
|
+
webMaxResults: z<number, number, "defined">;
|
|
56
|
+
timeoutMs: z<number, number, "defined">;
|
|
57
|
+
maxResponseBytes: z<number, number, "defined">;
|
|
58
|
+
maxAttempts: z<number, number, "defined">;
|
|
59
|
+
maxRequestImageBytes: z<number, number, "defined">;
|
|
60
|
+
requestImagePixelBudget: z<number, number, "defined">;
|
|
61
|
+
requestImageMaxBytes: z<number, number, "defined">;
|
|
62
|
+
portableReplayMaxChars: z<number, number, "defined">;
|
|
63
|
+
nativeRetentionTokenBudget: z<number, number, "defined">;
|
|
64
|
+
assistantRetentionTokenReserve: z<number, number, "defined">;
|
|
65
|
+
assistantRetentionPerMessageTokenCap: z<number, number, "defined">;
|
|
66
|
+
}>>, Schemastery.ObjectT<NoInfer<{
|
|
67
|
+
enabled: z<boolean, boolean, "volatile-defined">;
|
|
68
|
+
webSearch: z<boolean, boolean, "volatile-defined">;
|
|
69
|
+
advancedHostedSearch: z<boolean, boolean, "volatile-defined">;
|
|
70
|
+
alphaSearch: z<boolean, boolean, "volatile-defined">;
|
|
71
|
+
grokNativeWebSearch: z<boolean, boolean, "volatile-defined">;
|
|
72
|
+
grokNativeXSearch: z<boolean, boolean, "volatile-defined">;
|
|
73
|
+
searchMediaPreview: z<boolean, boolean, "volatile-defined">;
|
|
74
|
+
supportsLongCacheRetention: z<boolean, boolean, "defined">;
|
|
75
|
+
supportsExplicitPromptCacheMode: z<boolean, boolean, "defined">;
|
|
76
|
+
alphaCapabilityPath: z<string, string, "defined">;
|
|
77
|
+
alphaRefPath: z<string, string, "defined">;
|
|
78
|
+
alphaProfile: z<string, string, "defined">;
|
|
79
|
+
alphaGroup: z<string, string, "defined">;
|
|
80
|
+
alphaMaxOutputTokens: z<number, number, "defined">;
|
|
81
|
+
webSearchProvider: z<string, string, "defined">;
|
|
82
|
+
webMaxResults: z<number, number, "defined">;
|
|
83
|
+
timeoutMs: z<number, number, "defined">;
|
|
84
|
+
maxResponseBytes: z<number, number, "defined">;
|
|
85
|
+
maxAttempts: z<number, number, "defined">;
|
|
86
|
+
maxRequestImageBytes: z<number, number, "defined">;
|
|
87
|
+
requestImagePixelBudget: z<number, number, "defined">;
|
|
88
|
+
requestImageMaxBytes: z<number, number, "defined">;
|
|
89
|
+
portableReplayMaxChars: z<number, number, "defined">;
|
|
90
|
+
nativeRetentionTokenBudget: z<number, number, "defined">;
|
|
91
|
+
assistantRetentionTokenReserve: z<number, number, "defined">;
|
|
92
|
+
assistantRetentionPerMessageTokenCap: z<number, number, "defined">;
|
|
93
|
+
}>>, "plain">;
|
|
71
94
|
export declare function compactionPressureBand(totalTokens: number, contextWindow: number, policy: {
|
|
72
95
|
auto: number;
|
|
73
96
|
emergency: number;
|
|
97
|
+
outputReserve: number;
|
|
98
|
+
headroomTokens: number;
|
|
74
99
|
}): {
|
|
100
|
+
pressureBudget: number;
|
|
101
|
+
nativeThreshold: number;
|
|
102
|
+
emergencyThreshold: number;
|
|
75
103
|
ratioPercent: number;
|
|
76
104
|
band: string;
|
|
77
105
|
};
|
|
78
|
-
export declare function apply(ctx: HostContext, configInput
|
|
106
|
+
export declare function apply(ctx: HostContext, configInput: ConfigInput & LiveSettingsConfig): void;
|
|
79
107
|
export declare namespace apply {
|
|
80
108
|
export { inject };
|
|
81
109
|
export { Config };
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
const ASSISTANT_CONTEXT_TOKEN_LIMIT = 1_000;
|
|
2
|
+
const APPROX_BYTES_PER_TOKEN = 4;
|
|
3
|
+
function textBlocks(message) {
|
|
4
|
+
const type = message.role === "user" ? "input_text" : "output_text";
|
|
5
|
+
return message.content.flatMap((block) => block.type === "text" ? [{ type, text: block.text }] : []);
|
|
6
|
+
}
|
|
7
|
+
function approxTokens(text) {
|
|
8
|
+
return Math.ceil(Buffer.byteLength(text, "utf8") / APPROX_BYTES_PER_TOKEN);
|
|
9
|
+
}
|
|
10
|
+
// Codex's UTF-8 middle truncation for one over-budget assistant text block.
|
|
11
|
+
function truncateAssistantText(text, tokens) {
|
|
12
|
+
const bytes = Buffer.byteLength(text, "utf8");
|
|
13
|
+
const budget = tokens * APPROX_BYTES_PER_TOKEN;
|
|
14
|
+
if (bytes <= budget)
|
|
15
|
+
return text;
|
|
16
|
+
const leftBudget = Math.floor(budget / 2);
|
|
17
|
+
const rightBudget = budget - leftBudget;
|
|
18
|
+
let prefix = "";
|
|
19
|
+
let suffix = "";
|
|
20
|
+
let consumed = 0;
|
|
21
|
+
for (const char of text) {
|
|
22
|
+
const next = consumed + Buffer.byteLength(char, "utf8");
|
|
23
|
+
if (next <= leftBudget)
|
|
24
|
+
prefix += char;
|
|
25
|
+
if (consumed >= bytes - rightBudget)
|
|
26
|
+
suffix += char;
|
|
27
|
+
consumed = next;
|
|
28
|
+
}
|
|
29
|
+
return `${prefix}…${Math.ceil((bytes - budget) / APPROX_BYTES_PER_TOKEN)} tokens truncated…${suffix}`;
|
|
30
|
+
}
|
|
31
|
+
/** Codex recent_input on the canonical DSH message surface. */
|
|
32
|
+
export function recentAlphaInput(messages) {
|
|
33
|
+
const users = messages.flatMap((message, index) => message.role === "user" && message.source.kind === "user" && textBlocks(message).length ? [index] : []);
|
|
34
|
+
if (!users.length)
|
|
35
|
+
return undefined;
|
|
36
|
+
const start = users.at(-2) ?? users.at(-1);
|
|
37
|
+
const end = users.at(-1);
|
|
38
|
+
let assistantTokens = ASSISTANT_CONTEXT_TOKEN_LIMIT;
|
|
39
|
+
const items = [];
|
|
40
|
+
for (let index = start; index <= end; index++) {
|
|
41
|
+
const message = messages[index];
|
|
42
|
+
if (message.role === "user" && message.source.kind === "user") {
|
|
43
|
+
const content = textBlocks(message);
|
|
44
|
+
if (content.length)
|
|
45
|
+
items.push({ type: "message", role: "user", content });
|
|
46
|
+
}
|
|
47
|
+
else if (message.role === "assistant" && assistantTokens > 0) {
|
|
48
|
+
const content = [];
|
|
49
|
+
for (const block of textBlocks(message)) {
|
|
50
|
+
if (assistantTokens === 0)
|
|
51
|
+
break;
|
|
52
|
+
const cost = approxTokens(block.text);
|
|
53
|
+
content.push({ type: "output_text", text: cost <= assistantTokens
|
|
54
|
+
? block.text : truncateAssistantText(block.text, assistantTokens) });
|
|
55
|
+
assistantTokens = Math.max(0, assistantTokens - cost);
|
|
56
|
+
}
|
|
57
|
+
if (content.length)
|
|
58
|
+
items.push({ type: "message", role: "assistant", content });
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return items.length ? items : undefined;
|
|
62
|
+
}
|
package/lib/web-search-alpha.js
CHANGED
|
@@ -31,12 +31,16 @@ const RESPONSE_LENGTHS = ["short", "medium", "long"];
|
|
|
31
31
|
export const ALPHA_SEARCH_PARAMETERS = {
|
|
32
32
|
type: "object",
|
|
33
33
|
properties: {
|
|
34
|
-
action: {
|
|
34
|
+
action: {
|
|
35
|
+
type: "string",
|
|
36
|
+
enum: ALPHA_ACTIONS,
|
|
37
|
+
description: "Choose one action. search_query/image_query use query, domains, recency; open uses refId, lineNumber; find uses refId, pattern; click uses refId, linkId; screenshot uses refId, pageNumber; finance uses ticker, assetType, market; weather uses location, start, duration; sports uses fn, league, team, opponent, dateFrom, dateTo, numberOfGames, locale; time uses utcOffset. responseLength applies to every action. Other declared fields are ignored.",
|
|
38
|
+
},
|
|
35
39
|
query: { type: "string" },
|
|
36
40
|
domains: { type: "array", items: { type: "string" } },
|
|
37
41
|
recency: { type: "integer" },
|
|
38
|
-
refId: { type: "string" },
|
|
39
|
-
lineNumber: { type: "integer" },
|
|
42
|
+
refId: { type: "string", description: "Copy only the reference after opaqueRef=, including any lcx-alpha- handle. Do not include the opaqueRef= label. A public URL is allowed for open/find." },
|
|
43
|
+
lineNumber: { type: "integer", description: "Omit unless opening a specific line. Zero requests line 0." },
|
|
40
44
|
linkId: { type: "integer" },
|
|
41
45
|
pattern: { type: "string" },
|
|
42
46
|
pageNumber: { type: "integer" },
|
|
@@ -105,7 +109,9 @@ export const ALPHA_SEARCH_OUTPUT = {
|
|
|
105
109
|
export const ALPHA_SCHEMA_FINGERPRINT = createHash("sha256")
|
|
106
110
|
.update(JSON.stringify(ALPHA_SEARCH_PARAMETERS))
|
|
107
111
|
.digest("hex");
|
|
108
|
-
export const ALPHA_PROBE_VERSION =
|
|
112
|
+
export const ALPHA_PROBE_VERSION = 15;
|
|
113
|
+
export const ALPHA_PROBE_QUERY = "Python official documentation docs.python.org/3/";
|
|
114
|
+
export const ALPHA_PROBE_MAX_OPEN_CANDIDATES = 3;
|
|
109
115
|
function failure(message, code = "WEB_INVALID_REQUEST") {
|
|
110
116
|
return Object.assign(new Error(message), { code });
|
|
111
117
|
}
|
|
@@ -140,7 +146,7 @@ function date(value, field) {
|
|
|
140
146
|
return normalized;
|
|
141
147
|
}
|
|
142
148
|
function normalizeDomains(value) {
|
|
143
|
-
if (!Array.isArray(value) ||
|
|
149
|
+
if (!Array.isArray(value) || value.length > 100)
|
|
144
150
|
throw failure("websearch_alpha.domains is invalid");
|
|
145
151
|
return value.map((item) => {
|
|
146
152
|
const d = text(item, "domains", 253).toLowerCase();
|
|
@@ -149,35 +155,16 @@ function normalizeDomains(value) {
|
|
|
149
155
|
return d;
|
|
150
156
|
});
|
|
151
157
|
}
|
|
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
158
|
function isAlphaAction(value) {
|
|
174
159
|
return typeof value === "string" && ALPHA_ACTIONS.includes(value);
|
|
175
160
|
}
|
|
176
161
|
export function normalizeAlphaSearchArgs(args) {
|
|
177
162
|
if (!isObject(args) || !isAlphaAction(args.action))
|
|
178
163
|
throw failure("websearch_alpha.action is invalid");
|
|
179
|
-
|
|
180
|
-
|
|
164
|
+
// The advertised flat schema permits every declared field for every action.
|
|
165
|
+
// Ignore fields owned by another action; never forward them to Alpha.
|
|
166
|
+
const declared = new Set(Object.keys(ALPHA_SEARCH_PARAMETERS.properties));
|
|
167
|
+
if (Object.keys(args).some((k) => !declared.has(k)))
|
|
181
168
|
throw failure(`websearch_alpha.${args.action} received an unrelated field`);
|
|
182
169
|
const result = { action: args.action };
|
|
183
170
|
if (args.responseLength !== undefined) {
|
|
@@ -190,7 +177,7 @@ export function normalizeAlphaSearchArgs(args) {
|
|
|
190
177
|
case "search_query":
|
|
191
178
|
case "image_query":
|
|
192
179
|
result.query = text(args.query, "query", 16000);
|
|
193
|
-
if (args.domains !== undefined)
|
|
180
|
+
if (args.domains !== undefined && (!Array.isArray(args.domains) || args.domains.length > 0))
|
|
194
181
|
result.domains = normalizeDomains(args.domains);
|
|
195
182
|
if (args.recency !== undefined)
|
|
196
183
|
result.recency = integer(args.recency, "recency", 0, 3650);
|
|
@@ -326,30 +313,13 @@ export function alphaActionCommand(args) {
|
|
|
326
313
|
...(args.responseLength ? { response_length: args.responseLength } : {}),
|
|
327
314
|
};
|
|
328
315
|
}
|
|
329
|
-
function
|
|
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) {
|
|
316
|
+
export function buildAlphaSearchBody(args, model, sessionId, externalWebAccess = true, maxOutputTokens = 2500, input) {
|
|
342
317
|
if (typeof sessionId !== "string" || !sessionId)
|
|
343
318
|
throw failure("websearch_alpha requires a DSH session", "LCX_ALPHA_SESSION_REQUIRED");
|
|
344
319
|
return {
|
|
345
320
|
id: sessionId,
|
|
346
321
|
model,
|
|
347
|
-
input:
|
|
348
|
-
{
|
|
349
|
-
role: "user",
|
|
350
|
-
content: [{ type: "input_text", text: actionInput(args) }],
|
|
351
|
-
},
|
|
352
|
-
],
|
|
322
|
+
...(input?.length ? { input } : {}),
|
|
353
323
|
commands: alphaActionCommand(args),
|
|
354
324
|
settings: {
|
|
355
325
|
allowed_callers: ["direct"],
|
|
@@ -684,8 +654,26 @@ const ALPHA_SEMANTIC_FAILURE = /^\s*(?:reference(?: id)?\s+(?:is\s+)?(?:invalid|
|
|
|
684
654
|
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
655
|
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
656
|
const ALPHA_REFERENCE_ERROR_ENVELOPE = /^\s*(?:\*\*Result:\*\*[ \t]*)?Unable to (?:execute[ \t]+)?`?(open|find|click|screenshot)`?(?=[\s:])([^\r\n]*)/iu;
|
|
657
|
+
const ALPHA_FIND_SOURCE_ERROR_ENVELOPE = /^\s*The (?:request|search) failed: `[^`\r\n]+` is not a valid or available source reference, so\b/iu;
|
|
658
|
+
// Recorded HTTP-200 empty error view: no page body, but a newly produced view citation.
|
|
659
|
+
// Anchor the entire envelope so error examples inside fetched pages never match.
|
|
660
|
+
const ALPHA_EMPTY_FETCH_ERROR_VIEW = /^\s*(?:Internal[ \t]+)?Error[ \t]*\([ \t]*\)[ \t]*\r?\n[ \t]*\uE200cite\uE202turn\d+view\d+\uE201[ \t]*(?:\[wordlim:\s*\d+\][ \t]*)?\s*$/u;
|
|
661
|
+
// A notice-only open view: every body line belongs to the fetch failure,
|
|
662
|
+
// never arbitrary page prose or numbered page content.
|
|
663
|
+
const ALPHA_FETCH_NOTICE_VIEW = /^\s*(?:(?:Internal[ \t]+)?Error[ \t]*\([ \t]*\)[ \t]*\r?\n[ \t]*)?(?:\uE200cite\uE202turn\d+view\d+\uE201[ \t]*(?:\[wordlim:\s*\d+\][ \t]*)?\r?\n[ \t]*)?Failed to fetch[ \t]+https?:\/\/\S+[^\r\n]*?(?:\r?\n[ \t]*|[ \t]+)Error fetching[ \t]+https?:\/\/\S+[^\r\n]*\s*$/u;
|
|
664
|
+
function alphaPageFetchFailed(output, action) {
|
|
665
|
+
if (action !== "open" && action !== "click")
|
|
666
|
+
return false;
|
|
667
|
+
const text = String(output ?? "");
|
|
668
|
+
const explicit = text.match(ALPHA_FETCH_FAILURE_ENVELOPE);
|
|
669
|
+
return ALPHA_EMPTY_FETCH_ERROR_VIEW.test(text) ||
|
|
670
|
+
(action === "open" && ALPHA_FETCH_NOTICE_VIEW.test(text)) ||
|
|
671
|
+
explicit?.[1].toLowerCase() === action;
|
|
672
|
+
}
|
|
687
673
|
function alphaCommandErrorEnvelope(output, action) {
|
|
688
674
|
const text = String(output ?? "");
|
|
675
|
+
if (action === "find" && ALPHA_FIND_SOURCE_ERROR_ENVELOPE.test(text))
|
|
676
|
+
return true;
|
|
689
677
|
const command = text.match(ALPHA_COMMAND_ERROR_ENVELOPE);
|
|
690
678
|
if (command && command[1].toLowerCase() === action)
|
|
691
679
|
return true;
|
|
@@ -705,6 +693,8 @@ export function parseAlphaSearchResponse(response, options) {
|
|
|
705
693
|
(response.results !== undefined && !Array.isArray(response.results)))
|
|
706
694
|
throw failure("LCX Alpha Web Search returned an invalid response", "LCX_ALPHA_INVALID_RESPONSE");
|
|
707
695
|
const action = typeof options.action === "string" ? options.action : undefined;
|
|
696
|
+
if (alphaPageFetchFailed(response.output, action))
|
|
697
|
+
throw failure("LCX Alpha Web Search: the page could not be fetched; try another result", "LCX_ALPHA_PAGE_FETCH_FAILED");
|
|
708
698
|
if (ACTION_ERROR.test(response.output) ||
|
|
709
699
|
(["open", "find", "click", "screenshot"].includes(action ?? "") &&
|
|
710
700
|
(ALPHA_SEMANTIC_FAILURE.test(response.output) ||
|
|
@@ -818,6 +808,28 @@ export function renderAlphaSearchResult(value) {
|
|
|
818
808
|
parts.push(`检索时间:${String(data.retrievedAt ?? "")}`);
|
|
819
809
|
return [{ type: "text", text: parts.filter(Boolean).join("\n\n") }];
|
|
820
810
|
}
|
|
811
|
+
export function remapAlphaResultRefs(value, handles) {
|
|
812
|
+
const changes = Object.entries(handles).filter(([raw, handle]) => raw !== handle);
|
|
813
|
+
if (!changes.length)
|
|
814
|
+
return value;
|
|
815
|
+
const urlFields = new Set(["url", "source_url", "source_website_url", "image_url", "thumbnail_url", "href"]);
|
|
816
|
+
const replace = (input, key = "") => {
|
|
817
|
+
if (urlFields.has(key))
|
|
818
|
+
return input;
|
|
819
|
+
if (typeof input === "string") {
|
|
820
|
+
let output = input;
|
|
821
|
+
for (const [raw, handle] of changes)
|
|
822
|
+
output = output.replace(new RegExp(`(?<![\\w-])${raw.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&")}(?![\\w-])`, "gu"), handle);
|
|
823
|
+
return output;
|
|
824
|
+
}
|
|
825
|
+
if (Array.isArray(input))
|
|
826
|
+
return input.map((item) => replace(item));
|
|
827
|
+
if (isObject(input))
|
|
828
|
+
return Object.fromEntries(Object.entries(input).map(([field, item]) => [field, replace(item, field)]));
|
|
829
|
+
return input;
|
|
830
|
+
};
|
|
831
|
+
return replace(value);
|
|
832
|
+
}
|
|
821
833
|
export const ALPHA_STATEFUL_RETRY_MAX_ATTEMPTS = 1;
|
|
822
834
|
const alphaSessionLocks = new Map();
|
|
823
835
|
export function alphaSearchRetryOptions(maxResponseBytes) {
|
|
@@ -892,6 +904,8 @@ function alphaProbeStructuredBlock(block) {
|
|
|
892
904
|
const ALPHA_PROBE_SEMANTIC_FAILURE = /\breference(?: id)?\s+(?:is\s+)?(?:invalid|unavailable)\b/iu;
|
|
893
905
|
function alphaProbeResultHasEvidence(action, value) {
|
|
894
906
|
const data = isObject(value) ? value : {};
|
|
907
|
+
if (alphaPageFetchFailed(data.content, action))
|
|
908
|
+
return false;
|
|
895
909
|
if (alphaCommandErrorEnvelope(data.content, action))
|
|
896
910
|
return false;
|
|
897
911
|
if (ALPHA_PROBE_SEMANTIC_FAILURE.test(String(data.content ?? "")) &&
|
|
@@ -913,7 +927,7 @@ function alphaProbeResultHasEvidence(action, value) {
|
|
|
913
927
|
return Boolean(refs || pdfRefs || structuredResults || structuredBlocks);
|
|
914
928
|
return Boolean(refs || links || pdfRefs || structuredResults || structuredBlocks);
|
|
915
929
|
}
|
|
916
|
-
export async function probeAlphaCapabilities({ invoke, schemaFingerprint, trustedNativeProvenance = false, actionProbes = {}, clickProbeRef, screenshotProbeRef, }) {
|
|
930
|
+
export async function probeAlphaCapabilities({ invoke, startChain, schemaFingerprint, trustedNativeProvenance = false, actionProbes = {}, clickProbeRef, screenshotProbeRef, }) {
|
|
917
931
|
const actions = Object.fromEntries(ALPHA_ACTIONS.map((action) => [action, "unknown"]));
|
|
918
932
|
const result = {
|
|
919
933
|
classification: "unsupported",
|
|
@@ -923,78 +937,89 @@ export async function probeAlphaCapabilities({ invoke, schemaFingerprint, truste
|
|
|
923
937
|
probeVersion: ALPHA_PROBE_VERSION,
|
|
924
938
|
provenance: trustedNativeProvenance ? "trusted-native" : "unavailable",
|
|
925
939
|
};
|
|
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
940
|
let opened;
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
941
|
+
const chainIds = new Set();
|
|
942
|
+
// Two independent chains, each bounded to search + 3 candidate opens + find:
|
|
943
|
+
// at most 10 mandatory calls, 19 with all optional checks. Only page-fetch
|
|
944
|
+
// failures allow another candidate; never retry the same ref or failed find.
|
|
945
|
+
for (let chain = 0; chain < 2; chain++) {
|
|
946
|
+
try {
|
|
947
|
+
const chainId = await startChain(chain);
|
|
948
|
+
if (typeof chainId !== "string" || !chainId || chainIds.has(chainId)) {
|
|
949
|
+
result.classification = "unknown";
|
|
950
|
+
return result;
|
|
951
|
+
}
|
|
952
|
+
chainIds.add(chainId);
|
|
953
|
+
}
|
|
954
|
+
catch {
|
|
955
|
+
result.classification = "unknown";
|
|
956
|
+
return result;
|
|
957
|
+
}
|
|
958
|
+
let search;
|
|
959
|
+
try {
|
|
960
|
+
search = await invoke({ action: "search_query", query: ALPHA_PROBE_QUERY, responseLength: "short" });
|
|
961
|
+
actions.search_query = "supported";
|
|
962
|
+
}
|
|
963
|
+
catch (error) {
|
|
964
|
+
actions.search_query = probeFailureState(error);
|
|
965
|
+
result.classification = chain === 0 && actions.search_query === "unsupported" ? "unsupported" : "unknown";
|
|
966
|
+
return result;
|
|
967
|
+
}
|
|
968
|
+
const searchRefs = Array.isArray(search.refs) ? search.refs : [];
|
|
969
|
+
const candidates = [...new Set(searchRefs.filter((ref) => typeof ref === "string" && ref && !isAlphaHttpUrl(ref)))].slice(0, ALPHA_PROBE_MAX_OPEN_CANDIDATES);
|
|
970
|
+
if (!candidates.length) {
|
|
971
|
+
result.classification = chain === 0 ? "emulated-search-only" : "unknown";
|
|
963
972
|
actions.open = "unsupported";
|
|
964
|
-
|
|
973
|
+
actions.find = "unsupported";
|
|
974
|
+
actions.click = "unsupported";
|
|
975
|
+
return result;
|
|
976
|
+
}
|
|
977
|
+
opened = undefined;
|
|
978
|
+
let continuationTarget;
|
|
979
|
+
for (const candidate of candidates) {
|
|
980
|
+
try {
|
|
981
|
+
const value = await invoke({ action: "open", refId: candidate, responseLength: "short" });
|
|
982
|
+
if (alphaPageFetchFailed(value.content, "open"))
|
|
983
|
+
continue;
|
|
984
|
+
if (!alphaProbeResultHasEvidence("open", value)) {
|
|
985
|
+
actions.open = "unsupported";
|
|
986
|
+
result.classification = chain === 0 ? "emulated-search-only" : "unknown";
|
|
987
|
+
return result;
|
|
988
|
+
}
|
|
989
|
+
opened = value;
|
|
990
|
+
continuationTarget = candidate;
|
|
991
|
+
actions.open = "supported";
|
|
992
|
+
break;
|
|
993
|
+
}
|
|
994
|
+
catch (error) {
|
|
995
|
+
if (isObject(error) && error.code === "LCX_ALPHA_PAGE_FETCH_FAILED")
|
|
996
|
+
continue;
|
|
997
|
+
actions.open = probeFailureState(error);
|
|
998
|
+
result.classification = chain === 0 && actions.open === "unsupported" ? "emulated-search-only" : "unknown";
|
|
999
|
+
return result;
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
if (!opened) {
|
|
1003
|
+
actions.open = "unknown";
|
|
1004
|
+
result.classification = "unknown";
|
|
1005
|
+
return result;
|
|
1006
|
+
}
|
|
1007
|
+
const openedRefs = Array.isArray(opened.refs) ? opened.refs : [];
|
|
1008
|
+
const findTarget = openedRefs.find((ref) => typeof ref === "string" && !isAlphaHttpUrl(ref)) ?? continuationTarget;
|
|
1009
|
+
try {
|
|
1010
|
+
const found = await invoke({ action: "find", refId: findTarget, pattern: "Python", responseLength: "short" });
|
|
1011
|
+
actions.find = alphaProbeResultHasEvidence("find", found) ? "supported" : "unsupported";
|
|
1012
|
+
}
|
|
1013
|
+
catch (error) {
|
|
1014
|
+
actions.find = probeFailureState(error);
|
|
1015
|
+
}
|
|
1016
|
+
if (actions.find !== "supported") {
|
|
1017
|
+
result.classification = "unknown";
|
|
965
1018
|
return result;
|
|
966
1019
|
}
|
|
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
1020
|
}
|
|
995
|
-
let clickPage = opened
|
|
1021
|
+
let clickPage = opened;
|
|
996
1022
|
if (!(clickPage && Array.isArray(clickPage.links) && clickPage.links.length > 0) && clickProbeRef) {
|
|
997
|
-
clickProbeUsed = true;
|
|
998
1023
|
try {
|
|
999
1024
|
clickPage = await invoke({
|
|
1000
1025
|
action: "open",
|
|
@@ -1026,15 +1051,7 @@ export async function probeAlphaCapabilities({ invoke, schemaFingerprint, truste
|
|
|
1026
1051
|
actions.click = probeFailureState(error);
|
|
1027
1052
|
}
|
|
1028
1053
|
}
|
|
1029
|
-
|
|
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";
|
|
1054
|
+
result.classification = trustedNativeProvenance ? "native" : "command-capable";
|
|
1038
1055
|
if (screenshotProbeRef) {
|
|
1039
1056
|
try {
|
|
1040
1057
|
const pdf = await invoke({
|
|
@@ -67,7 +67,7 @@ export function alphaSearchParametersFor(record) {
|
|
|
67
67
|
...ALPHA_SEARCH_PARAMETERS,
|
|
68
68
|
properties: {
|
|
69
69
|
...ALPHA_SEARCH_PARAMETERS.properties,
|
|
70
|
-
action: {
|
|
70
|
+
action: { ...ALPHA_SEARCH_PARAMETERS.properties.action, enum: alphaAdvertisedActions(record) },
|
|
71
71
|
},
|
|
72
72
|
};
|
|
73
73
|
}
|