dsh-lcx-codex 0.3.4 → 0.4.0-rc.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/ARCHITECTURE.md +93 -0
- package/CHANGELOG.md +139 -44
- package/README.md +208 -102
- package/README_EN.md +133 -103
- package/assets/dsh-lcx-codex-banner.jpg +0 -0
- package/cordis.patch.yml +10 -4
- package/lib/client.js +117 -194
- package/lib/compact-v2.js +124 -273
- package/lib/dsh-responses.js +299 -0
- package/lib/index.js +548 -1593
- package/lib/json-store.js +34 -0
- package/lib/legacy-v3.js +20 -0
- package/lib/native-checkpoint.js +93 -0
- package/lib/responses-replay.js +281 -0
- package/lib/route.js +222 -0
- package/lib/service-mutex.js +72 -0
- package/lib/transport.js +59 -380
- package/lib/web-run-output.js +13 -152
- package/lib/web-search-alpha.js +26 -448
- package/lib/web-search-capability.js +8 -10
- package/lib/web-search-hosted.js +22 -372
- package/lib/web-search-ref-store.js +7 -7
- package/package.json +20 -14
- package/scripts/probe-alpha.mjs +28 -76
- package/scripts/validate-dsh-schema.mjs +31 -0
- package/lib/checkpoint-store-v3.js +0 -459
- package/lib/compact.js +0 -752
- package/lib/dsh-pi-responses.js +0 -339
- package/lib/private-file.js +0 -18
- package/lib/session-lease.js +0 -70
- package/lib/web-search-store.js +0 -181
package/lib/web-search-alpha.js
CHANGED
|
@@ -1,451 +1,29 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto'
|
|
2
2
|
import { outputDomains, outputLineRange, outputLinks, outputPdfRefs, parseWebRunOutput } from './web-run-output.js'
|
|
3
3
|
|
|
4
|
-
export const ALPHA_ACTIONS = ['search_query',
|
|
5
|
-
const LEAGUES = ['nba',
|
|
6
|
-
const ASSET_TYPES = ['equity',
|
|
7
|
-
const RESPONSE_LENGTHS = ['short',
|
|
8
|
-
|
|
9
|
-
export const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
opponent: { type: 'string' },
|
|
31
|
-
dateFrom: { type: 'string' },
|
|
32
|
-
dateTo: { type: 'string' },
|
|
33
|
-
numberOfGames: { type: 'integer' },
|
|
34
|
-
locale: { type: 'string' },
|
|
35
|
-
utcOffset: { type: 'string' },
|
|
36
|
-
responseLength: { type: 'string', enum: RESPONSE_LENGTHS },
|
|
37
|
-
},
|
|
38
|
-
required: ['action'],
|
|
39
|
-
additionalProperties: false,
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export const ALPHA_SEARCH_OUTPUT = {
|
|
43
|
-
type: 'object',
|
|
44
|
-
properties: {
|
|
45
|
-
mode: { type: 'string', enum: ['alpha'] },
|
|
46
|
-
action: { type: 'string' },
|
|
47
|
-
capability: { type: 'string' },
|
|
48
|
-
emulation: { type: 'string', enum: ['native', 'unknown'] },
|
|
49
|
-
content: { type: 'string' },
|
|
50
|
-
results: { type: 'array', items: { type: 'object' } },
|
|
51
|
-
refs: { type: 'array', items: { type: 'string' } },
|
|
52
|
-
sources: { type: 'array', items: { type: 'object' } },
|
|
53
|
-
citations: { type: 'array', items: { type: 'object' } },
|
|
54
|
-
outputBlocks: { type: 'array', items: { type: 'object' } },
|
|
55
|
-
links: { type: 'array', items: { type: 'object' } },
|
|
56
|
-
pdfRefs: { type: 'array', items: { type: 'string' } },
|
|
57
|
-
domains: { type: 'array', items: { type: 'string' } },
|
|
58
|
-
lineRange: { type: 'object' },
|
|
59
|
-
requestId: { type: 'string' },
|
|
60
|
-
responseId: { type: 'string' },
|
|
61
|
-
retrievedAt: { type: 'string' },
|
|
62
|
-
warnings: { type: 'array', items: { type: 'string' } },
|
|
63
|
-
},
|
|
64
|
-
required: ['mode', 'action', 'capability', 'emulation', 'content', 'results', 'refs', 'sources', 'citations', 'outputBlocks', 'links', 'pdfRefs', 'domains', 'requestId', 'retrievedAt', 'warnings'],
|
|
65
|
-
additionalProperties: false,
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export const ALPHA_SCHEMA_FINGERPRINT = createHash('sha256').update(JSON.stringify(ALPHA_SEARCH_PARAMETERS), 'utf8').digest('hex')
|
|
69
|
-
|
|
70
|
-
function failure(message, code = 'WEB_INVALID_REQUEST') {
|
|
71
|
-
const error = new Error(message)
|
|
72
|
-
error.code = code
|
|
73
|
-
return error
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
function isRecord(value) {
|
|
77
|
-
return value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function text(value, field, max = 1000) {
|
|
81
|
-
if (typeof value !== 'string' || value.trim().length === 0 || value.trim().length > max) throw failure(`websearch_alpha.${field} is invalid`)
|
|
82
|
-
return value.trim()
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
function integer(value, field, minimum = 0, maximum = 10_000) {
|
|
86
|
-
if (!Number.isSafeInteger(value) || value < minimum || value > maximum) throw failure(`websearch_alpha.${field} is invalid`)
|
|
87
|
-
return value
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
function optionalText(args, result, field, wireField = field, max = 1000) {
|
|
91
|
-
if (args[field] !== undefined) result[wireField] = text(args[field], field, max)
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
function date(value, field) {
|
|
95
|
-
const normalized = text(value, field, 10)
|
|
96
|
-
if (!/^\d{4}-\d{2}-\d{2}$/u.test(normalized) || Number.isNaN(Date.parse(`${normalized}T00:00:00Z`))) throw failure(`websearch_alpha.${field} must use YYYY-MM-DD`)
|
|
97
|
-
return normalized
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
function domains(value) {
|
|
101
|
-
if (!Array.isArray(value) || value.length === 0 || value.length > 100) throw failure('websearch_alpha.domains is invalid')
|
|
102
|
-
const result = value.map((item) => {
|
|
103
|
-
const domain = text(item, 'domains', 253).toLowerCase()
|
|
104
|
-
if (domain.includes('/') || domain.includes(':') || !domain.includes('.')) throw failure('websearch_alpha.domains contains an invalid domain')
|
|
105
|
-
return domain
|
|
106
|
-
})
|
|
107
|
-
if (new Set(result).size !== result.length) throw failure('websearch_alpha.domains contains duplicates')
|
|
108
|
-
return result
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
const FIELDS = {
|
|
112
|
-
search_query: ['query', 'domains', 'recency'],
|
|
113
|
-
image_query: ['query', 'domains', 'recency'],
|
|
114
|
-
open: ['refId', 'lineNumber'],
|
|
115
|
-
find: ['refId', 'pattern'],
|
|
116
|
-
click: ['refId', 'linkId'],
|
|
117
|
-
screenshot: ['refId', 'pageNumber'],
|
|
118
|
-
finance: ['ticker', 'assetType', 'market'],
|
|
119
|
-
weather: ['location', 'start', 'duration'],
|
|
120
|
-
sports: ['fn', 'league', 'team', 'opponent', 'dateFrom', 'dateTo', 'numberOfGames', 'locale'],
|
|
121
|
-
time: ['utcOffset'],
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
export function normalizeAlphaSearchArgs(args) {
|
|
125
|
-
if (!isRecord(args) || !ALPHA_ACTIONS.includes(args.action)) throw failure('websearch_alpha.action is invalid')
|
|
126
|
-
const allowed = new Set(['action', 'responseLength', ...FIELDS[args.action]])
|
|
127
|
-
if (Object.keys(args).some((key) => !allowed.has(key))) throw failure(`websearch_alpha.${args.action} received an unrelated field`)
|
|
128
|
-
const result = { action: args.action }
|
|
129
|
-
if (args.responseLength !== undefined) {
|
|
130
|
-
if (!RESPONSE_LENGTHS.includes(args.responseLength)) throw failure('websearch_alpha.responseLength is invalid')
|
|
131
|
-
result.responseLength = args.responseLength
|
|
132
|
-
}
|
|
133
|
-
switch (args.action) {
|
|
134
|
-
case 'search_query':
|
|
135
|
-
case 'image_query':
|
|
136
|
-
result.query = text(args.query, 'query', 16_000)
|
|
137
|
-
if (args.domains !== undefined) result.domains = domains(args.domains)
|
|
138
|
-
if (args.recency !== undefined) result.recency = integer(args.recency, 'recency', 0, 3650)
|
|
139
|
-
break
|
|
140
|
-
case 'open':
|
|
141
|
-
result.refId = text(args.refId, 'refId')
|
|
142
|
-
if (args.lineNumber !== undefined) result.lineNumber = integer(args.lineNumber, 'lineNumber')
|
|
143
|
-
break
|
|
144
|
-
case 'find':
|
|
145
|
-
result.refId = text(args.refId, 'refId')
|
|
146
|
-
result.pattern = text(args.pattern, 'pattern')
|
|
147
|
-
break
|
|
148
|
-
case 'click':
|
|
149
|
-
result.refId = text(args.refId, 'refId')
|
|
150
|
-
result.linkId = integer(args.linkId, 'linkId')
|
|
151
|
-
break
|
|
152
|
-
case 'screenshot':
|
|
153
|
-
result.refId = text(args.refId, 'refId')
|
|
154
|
-
result.pageNumber = integer(args.pageNumber, 'pageNumber')
|
|
155
|
-
break
|
|
156
|
-
case 'finance':
|
|
157
|
-
result.ticker = text(args.ticker, 'ticker', 40)
|
|
158
|
-
if (!ASSET_TYPES.includes(args.assetType)) throw failure('websearch_alpha.assetType is invalid')
|
|
159
|
-
result.assetType = args.assetType
|
|
160
|
-
optionalText(args, result, 'market', 'market', 20)
|
|
161
|
-
break
|
|
162
|
-
case 'weather':
|
|
163
|
-
result.location = text(args.location, 'location', 500)
|
|
164
|
-
if (args.start !== undefined) result.start = date(args.start, 'start')
|
|
165
|
-
if (args.duration !== undefined) result.duration = integer(args.duration, 'duration', 1, 14)
|
|
166
|
-
break
|
|
167
|
-
case 'sports':
|
|
168
|
-
if (!['schedule', 'standings'].includes(args.fn)) throw failure('websearch_alpha.fn is invalid')
|
|
169
|
-
if (!LEAGUES.includes(args.league)) throw failure('websearch_alpha.league is invalid')
|
|
170
|
-
result.fn = args.fn
|
|
171
|
-
result.league = args.league
|
|
172
|
-
for (const field of ['team', 'opponent', 'locale']) optionalText(args, result, field, field, 100)
|
|
173
|
-
if (args.dateFrom !== undefined) result.dateFrom = date(args.dateFrom, 'dateFrom')
|
|
174
|
-
if (args.dateTo !== undefined) result.dateTo = date(args.dateTo, 'dateTo')
|
|
175
|
-
if (args.numberOfGames !== undefined) result.numberOfGames = integer(args.numberOfGames, 'numberOfGames', 1, 100)
|
|
176
|
-
break
|
|
177
|
-
case 'time':
|
|
178
|
-
result.utcOffset = text(args.utcOffset, 'utcOffset', 6)
|
|
179
|
-
if (!/^[+-](?:0\d|1\d|2[0-3]):[0-5]\d$/u.test(result.utcOffset)) throw failure('websearch_alpha.utcOffset is invalid')
|
|
180
|
-
break
|
|
181
|
-
}
|
|
182
|
-
return result
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
export function alphaActionCommand(args) {
|
|
186
|
-
let value
|
|
187
|
-
switch (args.action) {
|
|
188
|
-
case 'search_query':
|
|
189
|
-
case 'image_query':
|
|
190
|
-
value = { q: args.query, ...(args.recency !== undefined ? { recency: args.recency } : {}), ...(args.domains ? { domains: args.domains } : {}) }
|
|
191
|
-
break
|
|
192
|
-
case 'open': value = { ref_id: args.refId, ...(args.lineNumber !== undefined ? { lineno: args.lineNumber } : {}) }; break
|
|
193
|
-
case 'find': value = { ref_id: args.refId, pattern: args.pattern }; break
|
|
194
|
-
case 'click': value = { ref_id: args.refId, id: args.linkId }; break
|
|
195
|
-
case 'screenshot': value = { ref_id: args.refId, pageno: args.pageNumber }; break
|
|
196
|
-
case 'finance': value = { ticker: args.ticker, type: args.assetType, ...(args.market ? { market: args.market } : {}) }; break
|
|
197
|
-
case 'weather': value = { location: args.location, ...(args.start ? { start: args.start } : {}), ...(args.duration !== undefined ? { duration: args.duration } : {}) }; break
|
|
198
|
-
case 'sports':
|
|
199
|
-
value = {
|
|
200
|
-
tool: 'sports',
|
|
201
|
-
fn: args.fn,
|
|
202
|
-
league: args.league,
|
|
203
|
-
...(args.team ? { team: args.team } : {}),
|
|
204
|
-
...(args.opponent ? { opponent: args.opponent } : {}),
|
|
205
|
-
...(args.dateFrom ? { date_from: args.dateFrom } : {}),
|
|
206
|
-
...(args.dateTo ? { date_to: args.dateTo } : {}),
|
|
207
|
-
...(args.numberOfGames !== undefined ? { num_games: args.numberOfGames } : {}),
|
|
208
|
-
...(args.locale ? { locale: args.locale } : {}),
|
|
209
|
-
}
|
|
210
|
-
break
|
|
211
|
-
case 'time': value = { utc_offset: args.utcOffset }; break
|
|
212
|
-
default: throw failure('websearch_alpha.action is invalid')
|
|
213
|
-
}
|
|
214
|
-
return { [args.action]: [value], ...(args.responseLength ? { response_length: args.responseLength } : {}) }
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
function actionInput(args) {
|
|
218
|
-
if (args.query) return args.query
|
|
219
|
-
if (args.refId) return `${args.action}: ${args.refId}`
|
|
220
|
-
if (args.ticker) return `${args.action}: ${args.ticker}`
|
|
221
|
-
if (args.location) return `${args.action}: ${args.location}`
|
|
222
|
-
if (args.utcOffset) return `${args.action}: ${args.utcOffset}`
|
|
223
|
-
return `${args.action}: ${args.league ?? ''}`.trim()
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
export function buildAlphaSearchBody(args, model, sessionId, externalWebAccess = true, maxOutputTokens = 2500) {
|
|
227
|
-
if (typeof sessionId !== 'string' || sessionId.length === 0) throw failure('websearch_alpha requires a DSH session', 'LCX_ALPHA_SESSION_REQUIRED')
|
|
228
|
-
return {
|
|
229
|
-
id: sessionId,
|
|
230
|
-
model,
|
|
231
|
-
input: [{ role: 'user', content: [{ type: 'input_text', text: actionInput(args) }] }],
|
|
232
|
-
commands: alphaActionCommand(args),
|
|
233
|
-
settings: { allowed_callers: ['direct'], external_web_access: externalWebAccess },
|
|
234
|
-
max_output_tokens: maxOutputTokens,
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
function httpUrl(value) {
|
|
239
|
-
try {
|
|
240
|
-
const url = new URL(value)
|
|
241
|
-
return ['http:', 'https:'].includes(url.protocol) ? url.toString() : undefined
|
|
242
|
-
} catch {
|
|
243
|
-
return undefined
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
function safeResult(value, seen = new Set()) {
|
|
248
|
-
if (value === null || typeof value !== 'object') return value
|
|
249
|
-
if (seen.has(value)) return undefined
|
|
250
|
-
seen.add(value)
|
|
251
|
-
if (Array.isArray(value)) return value.map((item) => safeResult(item, seen)).filter((item) => item !== undefined)
|
|
252
|
-
const result = {}
|
|
253
|
-
for (const [key, item] of Object.entries(value)) {
|
|
254
|
-
if (key === 'encrypted_output' || key === 'encrypted_content') continue
|
|
255
|
-
if (['url', 'source_url', 'source_website_url', 'image_url', 'thumbnail_url'].includes(key)) {
|
|
256
|
-
const url = httpUrl(item)
|
|
257
|
-
if (url) result[key] = url
|
|
258
|
-
continue
|
|
259
|
-
}
|
|
260
|
-
const safe = safeResult(item, seen)
|
|
261
|
-
if (safe !== undefined) result[key] = safe
|
|
262
|
-
}
|
|
263
|
-
return result
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
function responseArtifacts(results) {
|
|
267
|
-
const refs = []
|
|
268
|
-
const refRecords = []
|
|
269
|
-
const sources = []
|
|
270
|
-
const seenRefs = new Set()
|
|
271
|
-
const seenSources = new Set()
|
|
272
|
-
const visit = (value) => {
|
|
273
|
-
if (!value || typeof value !== 'object') return
|
|
274
|
-
if (Array.isArray(value)) {
|
|
275
|
-
for (const item of value) visit(item)
|
|
276
|
-
return
|
|
277
|
-
}
|
|
278
|
-
const refId = typeof value.ref_id === 'string' && value.ref_id.length > 0
|
|
279
|
-
? value.ref_id
|
|
280
|
-
: typeof value.id === 'string' && /^turn[\w-]+$/u.test(value.id) ? value.id : undefined
|
|
281
|
-
const url = httpUrl(value.url ?? value.source_url ?? value.source_website_url)
|
|
282
|
-
if (refId && !seenRefs.has(refId)) {
|
|
283
|
-
seenRefs.add(refId)
|
|
284
|
-
refs.push(refId)
|
|
285
|
-
refRecords.push({ refId, ...(url ? { url } : {}) })
|
|
286
|
-
}
|
|
287
|
-
if (url && !seenSources.has(url)) {
|
|
288
|
-
seenSources.add(url)
|
|
289
|
-
sources.push({
|
|
290
|
-
url,
|
|
291
|
-
...(typeof value.title === 'string' && value.title ? { title: value.title } : {}),
|
|
292
|
-
...(typeof value.snippet === 'string' && value.snippet ? { snippet: value.snippet } : {}),
|
|
293
|
-
...(refId ? { refId } : {}),
|
|
294
|
-
})
|
|
295
|
-
}
|
|
296
|
-
for (const item of Object.values(value)) visit(item)
|
|
297
|
-
}
|
|
298
|
-
visit(results)
|
|
299
|
-
return { refs, refRecords, sources }
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
const ALPHA_ACTION_ERROR_PATTERN = /^\s*(?:Error parsing function call\b|Invalid function_name=|Invalid function call\b)/iu
|
|
303
|
-
|
|
304
|
-
export function parseAlphaSearchResponse(response, options) {
|
|
305
|
-
if (!isRecord(response) || typeof response.output !== 'string' || (response.results !== undefined && !Array.isArray(response.results))) {
|
|
306
|
-
throw failure('LCX Alpha Web Search returned an invalid response', 'LCX_ALPHA_INVALID_RESPONSE')
|
|
307
|
-
}
|
|
308
|
-
if (ALPHA_ACTION_ERROR_PATTERN.test(response.output)) {
|
|
309
|
-
throw failure('LCX Alpha Web Search could not execute the requested action', 'LCX_ALPHA_ACTION_FAILED')
|
|
310
|
-
}
|
|
311
|
-
const results = safeResult(response.results ?? [])
|
|
312
|
-
const artifacts = responseArtifacts(results)
|
|
313
|
-
const outputBlocks = parseWebRunOutput(response.output)
|
|
314
|
-
for (const refId of outputBlocks.flatMap((block) => block.references ?? [])) {
|
|
315
|
-
if (!artifacts.refs.includes(refId)) artifacts.refs.push(refId)
|
|
316
|
-
}
|
|
317
|
-
const blockSources = outputBlocks.flatMap((block) => block.url ? [{ url: block.url, ...(block.title ? { title: block.title } : {}) }] : [])
|
|
318
|
-
for (const source of blockSources) {
|
|
319
|
-
const canonical = httpUrl(source.url)
|
|
320
|
-
if (canonical && !artifacts.sources.some((item) => httpUrl(item.url) === canonical)) artifacts.sources.push(source)
|
|
321
|
-
}
|
|
322
|
-
return {
|
|
323
|
-
mode: 'alpha',
|
|
324
|
-
action: options.action,
|
|
325
|
-
capability: options.capability,
|
|
326
|
-
emulation: options.capability === 'native' ? 'native' : 'unknown',
|
|
327
|
-
content: response.output,
|
|
328
|
-
results,
|
|
329
|
-
refs: artifacts.refs,
|
|
330
|
-
sources: artifacts.sources,
|
|
331
|
-
citations: artifacts.sources.map((source) => ({ ...source })),
|
|
332
|
-
outputBlocks,
|
|
333
|
-
links: outputLinks(outputBlocks),
|
|
334
|
-
pdfRefs: outputPdfRefs(outputBlocks),
|
|
335
|
-
domains: outputDomains(outputBlocks),
|
|
336
|
-
...(outputLineRange(outputBlocks) ? { lineRange: outputLineRange(outputBlocks) } : {}),
|
|
337
|
-
requestId: options.requestId,
|
|
338
|
-
...(typeof response.id === 'string' && response.id ? { responseId: response.id } : {}),
|
|
339
|
-
retrievedAt: options.retrievedAt ?? new Date().toISOString(),
|
|
340
|
-
warnings: options.capability === 'command-capable' ? ['Alpha command behavior is verified, but trusted native backend provenance is unavailable.'] : [],
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
function probeFailureState(error) {
|
|
345
|
-
if ([404, 405].includes(error?.status) || error?.code === 'LCX_ALPHA_ACTION_UNAVAILABLE' || /channel does not support|unsupported action|not implemented/iu.test(String(error?.message ?? ''))) {
|
|
346
|
-
return 'unsupported'
|
|
347
|
-
}
|
|
348
|
-
return 'unknown'
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
export async function probeAlphaCapabilities({
|
|
352
|
-
invoke,
|
|
353
|
-
schemaFingerprint,
|
|
354
|
-
trustedNativeProvenance = false,
|
|
355
|
-
actionProbes = {},
|
|
356
|
-
clickProbeRef,
|
|
357
|
-
screenshotProbeRef,
|
|
358
|
-
}) {
|
|
359
|
-
const actions = Object.fromEntries(ALPHA_ACTIONS.map((action) => [action, 'unknown']))
|
|
360
|
-
const result = {
|
|
361
|
-
classification: 'unsupported',
|
|
362
|
-
actions,
|
|
363
|
-
probedAt: new Date().toISOString(),
|
|
364
|
-
schemaFingerprint,
|
|
365
|
-
provenance: trustedNativeProvenance ? 'trusted-native' : 'unavailable',
|
|
366
|
-
}
|
|
367
|
-
let search
|
|
368
|
-
try {
|
|
369
|
-
search = await invoke({ action: 'search_query', query: 'OpenAI official documentation', responseLength: 'short' })
|
|
370
|
-
actions.search_query = 'supported'
|
|
371
|
-
} catch (error) {
|
|
372
|
-
actions.search_query = probeFailureState(error)
|
|
373
|
-
result.classification = actions.search_query === 'unsupported' ? 'unsupported' : 'unknown'
|
|
374
|
-
return result
|
|
375
|
-
}
|
|
376
|
-
const searchRef = search?.refs?.[0]
|
|
377
|
-
if (!searchRef) {
|
|
378
|
-
result.classification = 'emulated-search-only'
|
|
379
|
-
for (const action of ['open', 'find', 'click']) actions[action] = 'unsupported'
|
|
380
|
-
return result
|
|
381
|
-
}
|
|
382
|
-
let opened
|
|
383
|
-
try {
|
|
384
|
-
opened = await invoke({ action: 'open', refId: searchRef, responseLength: 'short' })
|
|
385
|
-
actions.open = 'supported'
|
|
386
|
-
} catch (error) {
|
|
387
|
-
actions.open = probeFailureState(error)
|
|
388
|
-
result.classification = actions.open === 'unsupported' ? 'emulated-search-only' : 'unknown'
|
|
389
|
-
return result
|
|
390
|
-
}
|
|
391
|
-
try {
|
|
392
|
-
await invoke({ action: 'find', refId: opened?.refs?.[0] ?? searchRef, pattern: 'OpenAI', responseLength: 'short' })
|
|
393
|
-
actions.find = 'supported'
|
|
394
|
-
} catch (error) {
|
|
395
|
-
actions.find = probeFailureState(error)
|
|
396
|
-
}
|
|
397
|
-
let clickPage = opened
|
|
398
|
-
if (!(clickPage?.links?.length > 0) && clickProbeRef) {
|
|
399
|
-
try {
|
|
400
|
-
clickPage = await invoke({ action: 'open', refId: clickProbeRef, responseLength: 'short' })
|
|
401
|
-
} catch (error) {
|
|
402
|
-
actions.click = probeFailureState(error)
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
const clickRef = clickPage?.refs?.[0]
|
|
406
|
-
const linkId = clickPage?.links?.[0]?.id
|
|
407
|
-
if (clickRef && Number.isSafeInteger(linkId)) {
|
|
408
|
-
try {
|
|
409
|
-
await invoke({ action: 'click', refId: clickRef, linkId, responseLength: 'short' })
|
|
410
|
-
actions.click = 'supported'
|
|
411
|
-
} catch (error) {
|
|
412
|
-
actions.click = probeFailureState(error)
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
if (actions.find === 'supported' || actions.click === 'supported') {
|
|
416
|
-
result.classification = trustedNativeProvenance ? 'native' : 'command-capable'
|
|
417
|
-
} else if (actions.find === 'unsupported' && actions.click === 'unsupported') {
|
|
418
|
-
result.classification = 'emulated-search-only'
|
|
419
|
-
} else {
|
|
420
|
-
result.classification = 'unknown'
|
|
421
|
-
}
|
|
422
|
-
if (screenshotProbeRef) {
|
|
423
|
-
try {
|
|
424
|
-
const pdf = await invoke({ action: 'open', refId: screenshotProbeRef, responseLength: 'short' })
|
|
425
|
-
const pdfRef = pdf?.pdfRefs?.[0]
|
|
426
|
-
if (pdfRef) {
|
|
427
|
-
await invoke({ action: 'screenshot', refId: pdfRef, pageNumber: 0, responseLength: 'short' })
|
|
428
|
-
actions.screenshot = 'supported'
|
|
429
|
-
}
|
|
430
|
-
} catch (error) {
|
|
431
|
-
actions.screenshot = probeFailureState(error)
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
for (const [action, args] of Object.entries(actionProbes)) {
|
|
435
|
-
try {
|
|
436
|
-
await invoke({ action, ...args, responseLength: 'short' })
|
|
437
|
-
actions[action] = 'supported'
|
|
438
|
-
} catch (error) {
|
|
439
|
-
actions[action] = probeFailureState(error)
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
return result
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
export function renderAlphaSearchResult(value) {
|
|
446
|
-
const parts = [value.content]
|
|
447
|
-
if (value.sources?.length) parts.push(`来源:\n${value.sources.map((source) => `- [${source.title ?? source.url}](${source.url})`).join('\n')}`)
|
|
448
|
-
if (value.warnings?.length) parts.push(value.warnings.map((warning) => `警告:${warning}`).join('\n'))
|
|
449
|
-
parts.push(`检索时间:${value.retrievedAt}`)
|
|
450
|
-
return [{ type: 'text', text: parts.filter(Boolean).join('\n\n') }]
|
|
451
|
-
}
|
|
4
|
+
export const ALPHA_ACTIONS = ['search_query','image_query','open','find','click','screenshot','finance','weather','sports','time']
|
|
5
|
+
const LEAGUES = ['nba','wnba','nfl','nhl','mlb','epl','ncaamb','ncaawb','ipl']
|
|
6
|
+
const ASSET_TYPES = ['equity','fund','crypto','index']
|
|
7
|
+
const RESPONSE_LENGTHS = ['short','medium','long']
|
|
8
|
+
export const ALPHA_SEARCH_PARAMETERS = { type: 'object', properties: { action: { type: 'string', enum: ALPHA_ACTIONS }, query: { type: 'string' }, domains: { type: 'array', items: { type: 'string' } }, recency: { type: 'integer' }, refId: { type: 'string' }, lineNumber: { type: 'integer' }, linkId: { type: 'integer' }, pattern: { type: 'string' }, pageNumber: { type: 'integer' }, ticker: { type: 'string' }, assetType: { type: 'string', enum: ASSET_TYPES }, market: { type: 'string' }, location: { type: 'string' }, start: { type: 'string' }, duration: { type: 'integer' }, fn: { type: 'string', enum: ['schedule','standings'] }, league: { type: 'string', enum: LEAGUES }, team: { type: 'string' }, opponent: { type: 'string' }, dateFrom: { type: 'string' }, dateTo: { type: 'string' }, numberOfGames: { type: 'integer' }, locale: { type: 'string' }, utcOffset: { type: 'string' }, responseLength: { type: 'string', enum: RESPONSE_LENGTHS } }, required: ['action'], additionalProperties: false }
|
|
9
|
+
export const ALPHA_SEARCH_OUTPUT = { type: 'object', properties: { mode: { type:'string' }, action: { type:'string' }, capability:{type:'string'}, emulation:{type:'string'}, content:{type:'string'}, results:{type:'array',items:{type:'object'}}, refs:{type:'array',items:{type:'string'}}, sources:{type:'array',items:{type:'object'}}, citations:{type:'array',items:{type:'object'}}, outputBlocks:{type:'array',items:{type:'object'}}, links:{type:'array',items:{type:'object'}}, pdfRefs:{type:'array',items:{type:'string'}}, domains:{type:'array',items:{type:'string'}}, lineRange:{type:'object'}, requestId:{type:'string'}, responseId:{type:'string'}, retrievedAt:{type:'string'}, warnings:{type:'array',items:{type:'string'}} }, required:['mode','action','capability','emulation','content','results','refs','sources','citations','outputBlocks','links','pdfRefs','domains','requestId','retrievedAt','warnings'], additionalProperties:false }
|
|
10
|
+
export const ALPHA_SCHEMA_FINGERPRINT = createHash('sha256').update(JSON.stringify(ALPHA_SEARCH_PARAMETERS)).digest('hex')
|
|
11
|
+
function failure(message, code='WEB_INVALID_REQUEST') { const e=new Error(message); e.code=code; return e }
|
|
12
|
+
function isObject(v){ return v!==null && typeof v==='object' && !Array.isArray(v) }
|
|
13
|
+
function text(value,field,max=1000){ if(typeof value!=='string'||!value.trim()||value.trim().length>max) throw failure(`websearch_alpha.${field} is invalid`); return value.trim() }
|
|
14
|
+
function integer(value,field,min=0,max=10000){ if(!Number.isSafeInteger(value)||value<min||value>max) throw failure(`websearch_alpha.${field} is invalid`); return value }
|
|
15
|
+
function date(value,field){ const normalized=text(value,field,10); if(!/^\d{4}-\d{2}-\d{2}$/u.test(normalized)||Number.isNaN(Date.parse(`${normalized}T00:00:00Z`))) throw failure(`websearch_alpha.${field} must use YYYY-MM-DD`); return normalized }
|
|
16
|
+
function normalizeDomains(value){ if(!Array.isArray(value)||!value.length||value.length>100) throw failure('websearch_alpha.domains is invalid'); return value.map((item)=>{ const d=text(item,'domains',253).toLowerCase(); if(d.includes('/')||d.includes(':')||!d.includes('.')) throw failure('websearch_alpha.domains contains an invalid domain'); return d }) }
|
|
17
|
+
const FIELDS={search_query:['query','domains','recency'],image_query:['query','domains','recency'],open:['refId','lineNumber'],find:['refId','pattern'],click:['refId','linkId'],screenshot:['refId','pageNumber'],finance:['ticker','assetType','market'],weather:['location','start','duration'],sports:['fn','league','team','opponent','dateFrom','dateTo','numberOfGames','locale'],time:['utcOffset']}
|
|
18
|
+
export function normalizeAlphaSearchArgs(args){ if(!isObject(args)||!ALPHA_ACTIONS.includes(args.action)) throw failure('websearch_alpha.action is invalid'); const allowed=new Set(['action','responseLength',...FIELDS[args.action]]); if(Object.keys(args).some((k)=>!allowed.has(k))) throw failure(`websearch_alpha.${args.action} received an unrelated field`); const result={action:args.action}; if(args.responseLength!==undefined){ if(!RESPONSE_LENGTHS.includes(args.responseLength)) throw failure('websearch_alpha.responseLength is invalid'); result.responseLength=args.responseLength } switch(args.action){ case 'search_query': case 'image_query': result.query=text(args.query,'query',16000); if(args.domains!==undefined) result.domains=normalizeDomains(args.domains); if(args.recency!==undefined) result.recency=integer(args.recency,'recency',0,3650); break; case 'open': result.refId=text(args.refId,'refId'); if(args.lineNumber!==undefined) result.lineNumber=integer(args.lineNumber,'lineNumber'); break; case 'find': result.refId=text(args.refId,'refId'); result.pattern=text(args.pattern,'pattern'); break; case 'click': result.refId=text(args.refId,'refId'); result.linkId=integer(args.linkId,'linkId'); break; case 'screenshot': result.refId=text(args.refId,'refId'); result.pageNumber=integer(args.pageNumber,'pageNumber'); break; case 'finance': result.ticker=text(args.ticker,'ticker',40); if(!ASSET_TYPES.includes(args.assetType)) throw failure('websearch_alpha.assetType is invalid'); result.assetType=args.assetType; if(args.market!==undefined) result.market=text(args.market,'market',20); break; case 'weather': result.location=text(args.location,'location',500); if(args.start!==undefined) result.start=date(args.start,'start'); if(args.duration!==undefined) result.duration=integer(args.duration,'duration',1,14); break; case 'sports': if(!['schedule','standings'].includes(args.fn)||!LEAGUES.includes(args.league)) throw failure('websearch_alpha sports args are invalid'); result.fn=args.fn; result.league=args.league; for(const f of ['team','opponent','locale']) if(args[f]!==undefined) result[f]=text(args[f],f,100); if(args.dateFrom!==undefined) result.dateFrom=date(args.dateFrom,'dateFrom'); if(args.dateTo!==undefined) result.dateTo=date(args.dateTo,'dateTo'); if(args.numberOfGames!==undefined) result.numberOfGames=integer(args.numberOfGames,'numberOfGames',1,100); break; case 'time': result.utcOffset=text(args.utcOffset,'utcOffset',6); if(!/^[+-](?:0\d|1\d|2[0-3]):[0-5]\d$/u.test(result.utcOffset)) throw failure('websearch_alpha.utcOffset is invalid'); break } return result }
|
|
19
|
+
export function alphaActionCommand(args){ let value; switch(args.action){ case 'search_query': case 'image_query': value={q:args.query,...(args.recency!==undefined?{recency:args.recency}:{}),...(args.domains?{domains:args.domains}:{})}; break; case 'open': value={ref_id:args.refId,...(args.lineNumber!==undefined?{lineno:args.lineNumber}:{})}; break; case 'find': value={ref_id:args.refId,pattern:args.pattern}; break; case 'click': value={ref_id:args.refId,id:args.linkId}; break; case 'screenshot': value={ref_id:args.refId,pageno:args.pageNumber}; break; case 'finance': value={ticker:args.ticker,type:args.assetType,...(args.market?{market:args.market}:{})}; break; case 'weather': value={location:args.location,...(args.start?{start:args.start}:{}),...(args.duration!==undefined?{duration:args.duration}:{})}; break; case 'sports': value={tool:'sports',fn:args.fn,league:args.league,...(args.team?{team:args.team}:{}),...(args.opponent?{opponent:args.opponent}:{}),...(args.dateFrom?{date_from:args.dateFrom}:{}),...(args.dateTo?{date_to:args.dateTo}:{}),...(args.numberOfGames!==undefined?{num_games:args.numberOfGames}:{}),...(args.locale?{locale:args.locale}:{})}; break; case 'time': value={utc_offset:args.utcOffset}; break; default: throw failure('websearch_alpha.action is invalid') } return {[args.action]:[value],...(args.responseLength?{response_length:args.responseLength}:{})} }
|
|
20
|
+
function actionInput(args){ return args.query??(args.refId?`${args.action}: ${args.refId}`:args.ticker?`${args.action}: ${args.ticker}`:args.location?`${args.action}: ${args.location}`:args.utcOffset?`${args.action}: ${args.utcOffset}`:`${args.action}: ${args.league??''}`.trim()) }
|
|
21
|
+
export function buildAlphaSearchBody(args,model,sessionId,externalWebAccess=true,maxOutputTokens=2500){ if(typeof sessionId!=='string'||!sessionId) throw failure('websearch_alpha requires a DSH session','LCX_ALPHA_SESSION_REQUIRED'); return {id:sessionId,model,input:[{role:'user',content:[{type:'input_text',text:actionInput(args)}]}],commands:alphaActionCommand(args),settings:{allowed_callers:['direct'],external_web_access:externalWebAccess},max_output_tokens:maxOutputTokens} }
|
|
22
|
+
function httpUrl(value){ try{ const u=new URL(value); return ['http:','https:'].includes(u.protocol)?u.toString():undefined }catch{return undefined} }
|
|
23
|
+
function safeResult(value,seen=new Set()){ if(value===null||typeof value!=='object') return value; if(seen.has(value)) return undefined; seen.add(value); if(Array.isArray(value)) return value.map((x)=>safeResult(x,seen)).filter((x)=>x!==undefined); const result={}; for(const [key,item] of Object.entries(value)){ if(key==='encrypted_output'||key==='encrypted_content') continue; if(['url','source_url','source_website_url','image_url','thumbnail_url'].includes(key)){ const u=httpUrl(item); if(u) result[key]=u; continue } const safe=safeResult(item,seen); if(safe!==undefined) result[key]=safe } return result }
|
|
24
|
+
function artifacts(results){ const refs=[],refRecords=[],sources=[],seenRefs=new Set(),seenSources=new Set(); const visit=(value)=>{ if(!value||typeof value!=='object') return; if(Array.isArray(value)){ value.forEach(visit); return } const refId=typeof value.ref_id==='string'&&value.ref_id?value.ref_id:typeof value.id==='string'&&/^turn[\w-]+$/u.test(value.id)?value.id:undefined; const url=httpUrl(value.url??value.source_url??value.source_website_url); if(refId&&!seenRefs.has(refId)){seenRefs.add(refId);refs.push(refId);refRecords.push({refId,...(url?{url}:{})})} if(url&&!seenSources.has(url)){seenSources.add(url);sources.push({url,...(typeof value.title==='string'?{title:value.title}:{}),...(typeof value.snippet==='string'?{snippet:value.snippet}:{}),...(refId?{refId}:{})})} Object.values(value).forEach(visit) }; visit(results); return {refs,refRecords,sources} }
|
|
25
|
+
const ACTION_ERROR=/^\s*(?:Error parsing function call\b|Invalid function_name=|Invalid function call\b)/iu
|
|
26
|
+
export function parseAlphaSearchResponse(response,options){ if(!isObject(response)||typeof response.output!=='string'||(response.results!==undefined&&!Array.isArray(response.results))) throw failure('LCX Alpha Web Search returned an invalid response','LCX_ALPHA_INVALID_RESPONSE'); if(ACTION_ERROR.test(response.output)) throw failure('LCX Alpha Web Search could not execute the requested action','LCX_ALPHA_ACTION_FAILED'); const results=safeResult(response.results??[]); const a=artifacts(results); const outputBlocks=parseWebRunOutput(response.output); for(const block of outputBlocks) for(const ref of block.references??[]){ if(!a.refs.includes(ref)) a.refs.push(ref); if(!a.refRecords.some((item)=>item.refId===ref)) a.refRecords.push({refId:ref,...(block.url?{url:block.url}:{})}) } for(const source of outputBlocks.flatMap((b)=>b.url?[{url:b.url,...(b.title?{title:b.title}:{})}]:[])) if(!a.sources.some((x)=>x.url===source.url)) a.sources.push(source); return {mode:'alpha',action:options.action,capability:options.capability,emulation:options.capability==='native'?'native':'unknown',content:response.output,results,refs:a.refs,sources:a.sources,citations:a.sources.map((s)=>({...s})),outputBlocks,links:outputLinks(outputBlocks),pdfRefs:outputPdfRefs(outputBlocks),domains:outputDomains(outputBlocks),...(outputLineRange(outputBlocks)?{lineRange:outputLineRange(outputBlocks)}:{}),requestId:options.requestId,...(typeof response.id==='string'&&response.id?{responseId:response.id}:{}),retrievedAt:options.retrievedAt??new Date().toISOString(),warnings:options.capability==='command-capable'?['Alpha command behavior is verified, but trusted native backend provenance is unavailable.']:[],refRecords:a.refRecords} }
|
|
27
|
+
export function renderAlphaSearchResult(value){ const parts=[value.content]; if(value.sources?.length) parts.push(`来源:\n${value.sources.map((s)=>`- [${s.title??s.url}](${s.url})`).join('\n')}`); if(value.warnings?.length) parts.push(value.warnings.map((w)=>`警告:${w}`).join('\n')); parts.push(`检索时间:${value.retrievedAt}`); return [{type:'text',text:parts.filter(Boolean).join('\n\n')}] }
|
|
28
|
+
function probeFailureState(error){ if([404,405].includes(error?.status)||error?.code==='LCX_ALPHA_ACTION_UNAVAILABLE'||/channel does not support|unsupported action|not implemented/iu.test(String(error?.message??''))) return 'unsupported'; return 'unknown' }
|
|
29
|
+
export async function probeAlphaCapabilities({invoke,schemaFingerprint,trustedNativeProvenance=false,actionProbes={},clickProbeRef,screenshotProbeRef}){ const actions=Object.fromEntries(ALPHA_ACTIONS.map((action)=>[action,'unknown'])); const result={classification:'unsupported',actions,probedAt:new Date().toISOString(),schemaFingerprint,provenance:trustedNativeProvenance?'trusted-native':'unavailable'}; let search; try{ search=await invoke({action:'search_query',query:'OpenAI official documentation',responseLength:'short'}); actions.search_query='supported' } catch(error){ actions.search_query=probeFailureState(error); result.classification=actions.search_query==='unsupported'?'unsupported':'unknown'; return result } const searchRef=search?.refs?.[0]; if(!searchRef){ result.classification='emulated-search-only'; actions.open='unsupported'; actions.find='unsupported'; actions.click='unsupported'; return result } let opened; try{ opened=await invoke({action:'open',refId:searchRef,responseLength:'short'}); actions.open='supported' } catch(error){ actions.open=probeFailureState(error); result.classification=actions.open==='unsupported'?'emulated-search-only':'unknown'; return result } try{ await invoke({action:'find',refId:opened?.refs?.[0]??searchRef,pattern:'OpenAI',responseLength:'short'}); actions.find='supported' }catch(error){ actions.find=probeFailureState(error) } let clickPage=opened; if(!(clickPage?.links?.length>0)&&clickProbeRef){ try{clickPage=await invoke({action:'open',refId:clickProbeRef,responseLength:'short'})}catch(error){actions.click=probeFailureState(error)} } const clickRef=clickPage?.refs?.[0], linkId=clickPage?.links?.[0]?.id; if(clickRef&&Number.isSafeInteger(linkId)){ try{await invoke({action:'click',refId:clickRef,linkId,responseLength:'short'});actions.click='supported'}catch(error){actions.click=probeFailureState(error)} } if(actions.find==='supported'||actions.click==='supported') result.classification=trustedNativeProvenance?'native':'command-capable'; else if(actions.find==='unsupported'&&actions.click==='unsupported') result.classification='emulated-search-only'; else result.classification='unknown'; if(screenshotProbeRef){ try{const pdf=await invoke({action:'open',refId:screenshotProbeRef,responseLength:'short'});const pdfRef=pdf?.pdfRefs?.[0];if(pdfRef){await invoke({action:'screenshot',refId:pdfRef,pageNumber:0,responseLength:'short'});actions.screenshot='supported'}}catch(error){actions.screenshot=probeFailureState(error)} } for(const [action,args] of Object.entries(actionProbes)){ try{await invoke({action,...args,responseLength:'short'});actions[action]='supported'}catch(error){actions[action]=probeFailureState(error)} } return result }
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto'
|
|
2
|
-
import {
|
|
2
|
+
import { JsonStore } from './json-store.js'
|
|
3
3
|
|
|
4
4
|
const VERSION = 1
|
|
5
5
|
const CLASSIFICATIONS = new Set(['native', 'command-capable', 'emulated-search-only', 'unsupported', 'unknown'])
|
|
@@ -30,9 +30,7 @@ export function alphaCapabilityFingerprint(config) {
|
|
|
30
30
|
const url = new URL(rawBaseURL)
|
|
31
31
|
url.hash = ''
|
|
32
32
|
baseURL = url.toString().replace(/\/+$/u, '')
|
|
33
|
-
} catch {
|
|
34
|
-
// Keep invalid configuration distinct; request validation reports it later.
|
|
35
|
-
}
|
|
33
|
+
} catch {}
|
|
36
34
|
const canonical = {
|
|
37
35
|
baseURL,
|
|
38
36
|
provider: String(config.provider ?? ''),
|
|
@@ -50,12 +48,12 @@ export function alphaCapabilityUsable(record) {
|
|
|
50
48
|
|
|
51
49
|
export class AlphaCapabilityStore {
|
|
52
50
|
constructor(file) {
|
|
53
|
-
this.store = new
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
51
|
+
this.store = new JsonStore(
|
|
52
|
+
file,
|
|
53
|
+
() => ({ version: VERSION, capabilities: {} }),
|
|
54
|
+
validData,
|
|
55
|
+
'LCX_ALPHA_CAPABILITY_STORE_CORRUPT',
|
|
56
|
+
)
|
|
59
57
|
}
|
|
60
58
|
|
|
61
59
|
get(fingerprint) {
|