dsh-lcx-codex 0.3.3 → 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 +143 -41
- package/README.md +208 -100
- package/README_EN.md +133 -101
- 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-hosted.js
CHANGED
|
@@ -3,381 +3,31 @@ import { outputDomains, outputLineRange, parseWebRunOutput } from './web-run-out
|
|
|
3
3
|
export const HOSTED_SEARCH_PARAMETERS = {
|
|
4
4
|
type: 'object',
|
|
5
5
|
properties: {
|
|
6
|
-
query: {
|
|
7
|
-
type: 'string',
|
|
8
|
-
description: 'Search the web for the requested topic. Include date or freshness requirements in the query when needed.',
|
|
9
|
-
},
|
|
6
|
+
query: { type: 'string', description: 'Advanced Responses Hosted Web Search query. Use DSH web_search for ordinary searches.' },
|
|
10
7
|
searchContextSize: { type: 'string', enum: ['low', 'medium', 'high'] },
|
|
11
8
|
allowedDomains: { type: 'array', items: { type: 'string' } },
|
|
12
9
|
blockedDomains: { type: 'array', items: { type: 'string' } },
|
|
13
|
-
userLocation: {
|
|
14
|
-
type: 'object',
|
|
15
|
-
properties: {
|
|
16
|
-
country: { type: 'string' },
|
|
17
|
-
city: { type: 'string' },
|
|
18
|
-
region: { type: 'string' },
|
|
19
|
-
timezone: { type: 'string' },
|
|
20
|
-
},
|
|
21
|
-
additionalProperties: false,
|
|
22
|
-
},
|
|
10
|
+
userLocation: { type: 'object', properties: { country: { type: 'string' }, city: { type: 'string' }, region: { type: 'string' }, timezone: { type: 'string' } }, additionalProperties: false },
|
|
23
11
|
externalWebAccess: { type: 'boolean' },
|
|
24
12
|
returnTokenBudget: { type: 'string', enum: ['default', 'unlimited'] },
|
|
25
|
-
searchContentTypes: {
|
|
26
|
-
|
|
27
|
-
items: { type: 'string', enum: ['text', 'image'] },
|
|
28
|
-
},
|
|
29
|
-
imageSettings: {
|
|
30
|
-
type: 'object',
|
|
31
|
-
properties: {
|
|
32
|
-
maxResults: { type: 'integer' },
|
|
33
|
-
caption: { type: 'boolean' },
|
|
34
|
-
},
|
|
35
|
-
additionalProperties: false,
|
|
36
|
-
},
|
|
13
|
+
searchContentTypes: { type: 'array', items: { type: 'string', enum: ['text', 'image'] } },
|
|
14
|
+
imageSettings: { type: 'object', properties: { maxResults: { type: 'integer' }, caption: { type: 'boolean' } }, additionalProperties: false },
|
|
37
15
|
},
|
|
38
|
-
required: ['query'],
|
|
39
|
-
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
requestId: { type: 'string' },
|
|
57
|
-
responseId: { type: 'string' },
|
|
58
|
-
retrievedAt: { type: 'string' },
|
|
59
|
-
truncated: { type: 'boolean' },
|
|
60
|
-
},
|
|
61
|
-
required: ['mode', 'action', 'emulation', 'content', 'sources', 'citations', 'images', 'warnings', 'requestId', 'retrievedAt', 'truncated'],
|
|
62
|
-
additionalProperties: false,
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function failure(message, code) {
|
|
66
|
-
const error = new Error(message)
|
|
67
|
-
error.code = code
|
|
68
|
-
return error
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
export function normalizeHostedSearchArgs(args) {
|
|
72
|
-
if (args === null || typeof args !== 'object' || Array.isArray(args)) throw failure('websearch_gpt arguments must be an object', 'WEB_INVALID_REQUEST')
|
|
73
|
-
if (typeof args.query !== 'string' || args.query.trim().length === 0) throw failure('websearch_gpt.query must be a non-empty string', 'WEB_INVALID_REQUEST')
|
|
74
|
-
const known = new Set([
|
|
75
|
-
'query',
|
|
76
|
-
'searchContextSize',
|
|
77
|
-
'allowedDomains',
|
|
78
|
-
'blockedDomains',
|
|
79
|
-
'userLocation',
|
|
80
|
-
'externalWebAccess',
|
|
81
|
-
'returnTokenBudget',
|
|
82
|
-
'searchContentTypes',
|
|
83
|
-
'imageSettings',
|
|
84
|
-
])
|
|
85
|
-
if (Object.keys(args).some((key) => !known.has(key))) throw failure('websearch_gpt received an unknown field', 'WEB_INVALID_REQUEST')
|
|
86
|
-
const normalized = { query: args.query.trim() }
|
|
87
|
-
if (normalized.query.length > 16_000) throw failure('websearch_gpt.query is too long', 'WEB_INVALID_REQUEST')
|
|
88
|
-
if (args.searchContextSize !== undefined) {
|
|
89
|
-
if (!['low', 'medium', 'high'].includes(args.searchContextSize)) throw failure('websearch_gpt.searchContextSize is invalid', 'WEB_INVALID_REQUEST')
|
|
90
|
-
normalized.searchContextSize = args.searchContextSize
|
|
91
|
-
}
|
|
92
|
-
const allowedDomains = normalizeDomains(args.allowedDomains, 'allowedDomains')
|
|
93
|
-
const blockedDomains = normalizeDomains(args.blockedDomains, 'blockedDomains')
|
|
94
|
-
if (allowedDomains) normalized.allowedDomains = allowedDomains
|
|
95
|
-
if (blockedDomains) normalized.blockedDomains = blockedDomains
|
|
96
|
-
if (allowedDomains && blockedDomains) {
|
|
97
|
-
const blocked = new Set(blockedDomains)
|
|
98
|
-
if (allowedDomains.some((domain) => blocked.has(domain))) throw failure('websearch_gpt domain filters conflict', 'WEB_INVALID_REQUEST')
|
|
99
|
-
}
|
|
100
|
-
if (args.userLocation !== undefined) normalized.userLocation = normalizeUserLocation(args.userLocation)
|
|
101
|
-
if (args.externalWebAccess !== undefined) {
|
|
102
|
-
if (typeof args.externalWebAccess !== 'boolean') throw failure('websearch_gpt.externalWebAccess must be boolean', 'WEB_INVALID_REQUEST')
|
|
103
|
-
normalized.externalWebAccess = args.externalWebAccess
|
|
104
|
-
}
|
|
105
|
-
if (args.returnTokenBudget !== undefined) {
|
|
106
|
-
if (!['default', 'unlimited'].includes(args.returnTokenBudget)) throw failure('websearch_gpt.returnTokenBudget is invalid', 'WEB_INVALID_REQUEST')
|
|
107
|
-
normalized.returnTokenBudget = args.returnTokenBudget
|
|
108
|
-
}
|
|
109
|
-
if (args.searchContentTypes !== undefined) normalized.searchContentTypes = normalizeContentTypes(args.searchContentTypes)
|
|
110
|
-
if (args.imageSettings !== undefined) {
|
|
111
|
-
if (!normalized.searchContentTypes?.includes('image')) throw failure('websearch_gpt.imageSettings requires image search content', 'WEB_INVALID_REQUEST')
|
|
112
|
-
normalized.imageSettings = normalizeImageSettings(args.imageSettings)
|
|
113
|
-
}
|
|
114
|
-
return normalized
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
function normalizeDomains(value, field) {
|
|
118
|
-
if (value === undefined) return undefined
|
|
119
|
-
if (!Array.isArray(value) || value.length === 0 || value.length > 100) throw failure(`websearch_gpt.${field} must contain 1 to 100 domains`, 'WEB_INVALID_REQUEST')
|
|
120
|
-
const domains = value.map((item) => {
|
|
121
|
-
if (typeof item !== 'string') throw failure(`websearch_gpt.${field} contains an invalid domain`, 'WEB_INVALID_REQUEST')
|
|
122
|
-
const domain = item.trim().toLowerCase()
|
|
123
|
-
if (domain.length === 0 || domain.length > 253 || domain.includes('/') || domain.includes(':') || domain.endsWith('.')) {
|
|
124
|
-
throw failure(`websearch_gpt.${field} contains an invalid domain`, 'WEB_INVALID_REQUEST')
|
|
125
|
-
}
|
|
126
|
-
const labels = domain.split('.')
|
|
127
|
-
if (labels.length < 2 || labels.some((label) => !/^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/u.test(label))) {
|
|
128
|
-
throw failure(`websearch_gpt.${field} contains an invalid domain`, 'WEB_INVALID_REQUEST')
|
|
129
|
-
}
|
|
130
|
-
return domain
|
|
131
|
-
})
|
|
132
|
-
if (new Set(domains).size !== domains.length) throw failure(`websearch_gpt.${field} contains duplicate domains`, 'WEB_INVALID_REQUEST')
|
|
133
|
-
return domains
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
function normalizeUserLocation(value) {
|
|
137
|
-
if (!isRecord(value)) throw failure('websearch_gpt.userLocation must be an object', 'WEB_INVALID_REQUEST')
|
|
138
|
-
const known = new Set(['country', 'city', 'region', 'timezone'])
|
|
139
|
-
if (Object.keys(value).some((key) => !known.has(key))) throw failure('websearch_gpt.userLocation contains an unknown field', 'WEB_INVALID_REQUEST')
|
|
140
|
-
const result = {}
|
|
141
|
-
if (value.country !== undefined) {
|
|
142
|
-
if (typeof value.country !== 'string' || !/^[a-z]{2}$/iu.test(value.country.trim())) throw failure('websearch_gpt.userLocation.country must be an ISO alpha-2 code', 'WEB_INVALID_REQUEST')
|
|
143
|
-
result.country = value.country.trim().toUpperCase()
|
|
144
|
-
}
|
|
145
|
-
for (const field of ['city', 'region']) {
|
|
146
|
-
if (value[field] === undefined) continue
|
|
147
|
-
if (typeof value[field] !== 'string' || value[field].trim().length === 0 || value[field].trim().length > 200) throw failure(`websearch_gpt.userLocation.${field} is invalid`, 'WEB_INVALID_REQUEST')
|
|
148
|
-
result[field] = value[field].trim()
|
|
149
|
-
}
|
|
150
|
-
if (value.timezone !== undefined) {
|
|
151
|
-
if (typeof value.timezone !== 'string' || value.timezone.trim().length === 0) throw failure('websearch_gpt.userLocation.timezone is invalid', 'WEB_INVALID_REQUEST')
|
|
152
|
-
const timezone = value.timezone.trim()
|
|
153
|
-
try {
|
|
154
|
-
new Intl.DateTimeFormat('en-US', { timeZone: timezone }).format()
|
|
155
|
-
} catch {
|
|
156
|
-
throw failure('websearch_gpt.userLocation.timezone must be an IANA timezone', 'WEB_INVALID_REQUEST')
|
|
157
|
-
}
|
|
158
|
-
result.timezone = timezone
|
|
159
|
-
}
|
|
160
|
-
if (Object.keys(result).length === 0) throw failure('websearch_gpt.userLocation must contain a location field', 'WEB_INVALID_REQUEST')
|
|
161
|
-
return result
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
function normalizeContentTypes(value) {
|
|
165
|
-
if (!Array.isArray(value) || value.length === 0 || value.length > 2 || value.some((item) => !['text', 'image'].includes(item))) {
|
|
166
|
-
throw failure('websearch_gpt.searchContentTypes is invalid', 'WEB_INVALID_REQUEST')
|
|
167
|
-
}
|
|
168
|
-
if (new Set(value).size !== value.length) throw failure('websearch_gpt.searchContentTypes contains duplicates', 'WEB_INVALID_REQUEST')
|
|
169
|
-
return [...value]
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
function normalizeImageSettings(value) {
|
|
173
|
-
if (!isRecord(value) || Object.keys(value).some((key) => !['maxResults', 'caption'].includes(key))) throw failure('websearch_gpt.imageSettings is invalid', 'WEB_INVALID_REQUEST')
|
|
174
|
-
const result = {}
|
|
175
|
-
if (value.maxResults !== undefined) {
|
|
176
|
-
if (!Number.isInteger(value.maxResults) || value.maxResults < 1 || value.maxResults > 100) throw failure('websearch_gpt.imageSettings.maxResults is invalid', 'WEB_INVALID_REQUEST')
|
|
177
|
-
result.maxResults = value.maxResults
|
|
178
|
-
}
|
|
179
|
-
if (value.caption !== undefined) {
|
|
180
|
-
if (typeof value.caption !== 'boolean') throw failure('websearch_gpt.imageSettings.caption must be boolean', 'WEB_INVALID_REQUEST')
|
|
181
|
-
result.caption = value.caption
|
|
182
|
-
}
|
|
183
|
-
if (Object.keys(result).length === 0) throw failure('websearch_gpt.imageSettings must contain a setting', 'WEB_INVALID_REQUEST')
|
|
184
|
-
return result
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
export function buildHostedSearchBody(args, model) {
|
|
188
|
-
const tool = {
|
|
189
|
-
type: 'web_search',
|
|
190
|
-
...(args.searchContextSize ? { search_context_size: args.searchContextSize } : {}),
|
|
191
|
-
...(args.allowedDomains || args.blockedDomains ? {
|
|
192
|
-
filters: {
|
|
193
|
-
...(args.allowedDomains ? { allowed_domains: args.allowedDomains } : {}),
|
|
194
|
-
...(args.blockedDomains ? { blocked_domains: args.blockedDomains } : {}),
|
|
195
|
-
},
|
|
196
|
-
} : {}),
|
|
197
|
-
...(args.userLocation ? { user_location: { type: 'approximate', ...args.userLocation } } : {}),
|
|
198
|
-
...(args.externalWebAccess !== undefined ? { external_web_access: args.externalWebAccess } : {}),
|
|
199
|
-
...(args.returnTokenBudget ? { return_token_budget: args.returnTokenBudget } : {}),
|
|
200
|
-
...(args.searchContentTypes ? { search_content_types: args.searchContentTypes } : {}),
|
|
201
|
-
...(args.imageSettings ? {
|
|
202
|
-
image_settings: {
|
|
203
|
-
...(args.imageSettings.maxResults !== undefined ? { max_results: args.imageSettings.maxResults } : {}),
|
|
204
|
-
...(args.imageSettings.caption !== undefined ? { caption: args.imageSettings.caption } : {}),
|
|
205
|
-
},
|
|
206
|
-
} : {}),
|
|
207
|
-
}
|
|
208
|
-
return {
|
|
209
|
-
model,
|
|
210
|
-
input: [{ role: 'user', content: [{ type: 'input_text', text: args.query }] }],
|
|
211
|
-
tools: [tool],
|
|
212
|
-
tool_choice: 'required',
|
|
213
|
-
include: [
|
|
214
|
-
'web_search_call.action.sources',
|
|
215
|
-
...(args.searchContentTypes?.includes('image') ? ['web_search_call.results'] : []),
|
|
216
|
-
],
|
|
217
|
-
stream: false,
|
|
218
|
-
store: false,
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
function isRecord(value) {
|
|
223
|
-
return value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
function textFrom(value, seen = new Set()) {
|
|
227
|
-
if (typeof value === 'string') return value
|
|
228
|
-
if (!value || typeof value !== 'object' || seen.has(value)) return ''
|
|
229
|
-
seen.add(value)
|
|
230
|
-
if (Array.isArray(value)) return value.map((item) => textFrom(item, seen)).filter(Boolean).join('\n')
|
|
231
|
-
if (typeof value.text === 'string' && ['output_text', 'text', 'input_text'].includes(value.type)) return value.text
|
|
232
|
-
if (Array.isArray(value.content)) return textFrom(value.content, seen)
|
|
233
|
-
return ''
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
function sourceFrom(value) {
|
|
237
|
-
if (!isRecord(value) || typeof value.url !== 'string' || value.url.trim().length === 0) return undefined
|
|
238
|
-
return {
|
|
239
|
-
url: value.url,
|
|
240
|
-
...(typeof value.title === 'string' && value.title ? { title: value.title } : {}),
|
|
241
|
-
...(typeof value.snippet === 'string' && value.snippet ? { snippet: value.snippet } : {}),
|
|
242
|
-
...(typeof value.publishedAt === 'string' && value.publishedAt ? { publishedAt: value.publishedAt } : {}),
|
|
243
|
-
...(typeof value.published_at === 'string' && value.published_at ? { publishedAt: value.published_at } : {}),
|
|
244
|
-
...(typeof value.ref_id === 'string' && value.ref_id ? { refId: value.ref_id } : {}),
|
|
245
|
-
...(typeof value.domain === 'string' && value.domain ? { domain: value.domain } : {}),
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
function responseArtifacts(response) {
|
|
250
|
-
const sources = []
|
|
251
|
-
const citations = []
|
|
252
|
-
const images = []
|
|
253
|
-
const actions = []
|
|
254
|
-
for (const item of Array.isArray(response?.output) ? response.output : []) {
|
|
255
|
-
if (item?.type === 'web_search_call') {
|
|
256
|
-
actions.push(typeof item.action?.type === 'string' ? item.action.type : 'search')
|
|
257
|
-
if (Array.isArray(item.action?.sources)) {
|
|
258
|
-
for (const value of item.action.sources) {
|
|
259
|
-
const source = sourceFrom(value)
|
|
260
|
-
if (source) sources.push(source)
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
for (const value of Array.isArray(item.results) ? item.results : []) {
|
|
264
|
-
const image = imageFrom(value)
|
|
265
|
-
if (image) images.push(image)
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
if (item?.type !== 'message') continue
|
|
269
|
-
for (const part of Array.isArray(item.content) ? item.content : []) {
|
|
270
|
-
if (part?.type !== 'output_text') continue
|
|
271
|
-
for (const annotation of Array.isArray(part.annotations) ? part.annotations : []) {
|
|
272
|
-
if (annotation?.type !== 'url_citation') continue
|
|
273
|
-
const source = sourceFrom(annotation)
|
|
274
|
-
if (source) {
|
|
275
|
-
citations.push(source)
|
|
276
|
-
sources.push(source)
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
return { sources, citations, images, actions }
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
function httpUrl(value) {
|
|
285
|
-
try {
|
|
286
|
-
const url = new URL(value)
|
|
287
|
-
if (!['http:', 'https:'].includes(url.protocol)) return undefined
|
|
288
|
-
return url
|
|
289
|
-
} catch {
|
|
290
|
-
return undefined
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
function imageFrom(value) {
|
|
295
|
-
if (!isRecord(value) || value.type !== 'image_result') return undefined
|
|
296
|
-
const imageUrl = httpUrl(value.image_url)?.toString()
|
|
297
|
-
if (!imageUrl) return undefined
|
|
298
|
-
const thumbnailUrl = httpUrl(value.thumbnail_url)?.toString()
|
|
299
|
-
const sourceWebsiteUrl = httpUrl(value.source_website_url)?.toString()
|
|
300
|
-
return {
|
|
301
|
-
imageUrl,
|
|
302
|
-
...(thumbnailUrl ? { thumbnailUrl } : {}),
|
|
303
|
-
...(sourceWebsiteUrl ? { sourceWebsiteUrl } : {}),
|
|
304
|
-
...(typeof value.caption === 'string' && value.caption ? { caption: value.caption } : {}),
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
function canonicalUrl(value) {
|
|
309
|
-
const url = httpUrl(value)
|
|
310
|
-
if (url) {
|
|
311
|
-
url.hash = ''
|
|
312
|
-
for (const key of [...url.searchParams.keys()]) if (/^(utm_|gclid$|fbclid$)/iu.test(key)) url.searchParams.delete(key)
|
|
313
|
-
return url.toString()
|
|
314
|
-
}
|
|
315
|
-
return undefined
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
function uniqueByUrl(values) {
|
|
319
|
-
const result = []
|
|
320
|
-
const seen = new Set()
|
|
321
|
-
for (const value of values) {
|
|
322
|
-
const key = canonicalUrl(value.url)
|
|
323
|
-
if (!key || seen.has(key)) continue
|
|
324
|
-
seen.add(key)
|
|
325
|
-
result.push(value)
|
|
326
|
-
}
|
|
327
|
-
return result
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
export function parseHostedSearchResponse(response, requestId, maxResults = 8, retrievedAt = new Date().toISOString()) {
|
|
331
|
-
if (response?.error) {
|
|
332
|
-
const error = failure(response.error.message ?? 'LCX hosted Web Search failed', 'LCX_WEB_PROVIDER_ERROR')
|
|
333
|
-
throw error
|
|
334
|
-
}
|
|
335
|
-
if (response?.status !== 'completed') {
|
|
336
|
-
throw failure(`LCX hosted Web Search returned response status: ${String(response?.status ?? 'missing')}`, 'WEB_RESPONSE_INCOMPLETE')
|
|
337
|
-
}
|
|
338
|
-
const output = typeof response?.output_text === 'string' ? response.output_text : textFrom(response?.output ?? response?.content)
|
|
339
|
-
const outputBlocks = parseWebRunOutput(output)
|
|
340
|
-
const artifacts = responseArtifacts(response)
|
|
341
|
-
if (artifacts.actions.length === 0) throw failure('LCX hosted Web Search completed without executing web_search', 'WEB_SEARCH_NOT_EXECUTED')
|
|
342
|
-
const allSources = [...artifacts.sources]
|
|
343
|
-
for (const block of outputBlocks) {
|
|
344
|
-
if (block.url) allSources.push({ url: block.url, ...(block.title ? { title: block.title } : {}) })
|
|
345
|
-
}
|
|
346
|
-
const sources = uniqueByUrl(allSources)
|
|
347
|
-
const citations = uniqueByUrl(artifacts.citations)
|
|
348
|
-
const limited = sources.slice(0, Math.max(1, maxResults))
|
|
349
|
-
const images = artifacts.images.slice(0, Math.max(1, maxResults))
|
|
350
|
-
if (!output && limited.length === 0 && images.length === 0) throw failure('LCX hosted Web Search returned no text, sources, or images', 'WEB_NO_SOURCES')
|
|
351
|
-
return {
|
|
352
|
-
mode: 'hosted',
|
|
353
|
-
action: artifacts.actions[0],
|
|
354
|
-
emulation: 'native',
|
|
355
|
-
content: output,
|
|
356
|
-
sources: limited,
|
|
357
|
-
citations,
|
|
358
|
-
images,
|
|
359
|
-
warnings: artifacts.actions.length > 1 ? [`Multiple hosted search actions were returned: ${artifacts.actions.join(', ')}`] : [],
|
|
360
|
-
outputBlocks,
|
|
361
|
-
domains: outputDomains(outputBlocks),
|
|
362
|
-
...(outputLineRange(outputBlocks) ? { lineRange: outputLineRange(outputBlocks) } : {}),
|
|
363
|
-
requestId,
|
|
364
|
-
...(typeof response?.id === 'string' && response.id ? { responseId: response.id } : {}),
|
|
365
|
-
retrievedAt,
|
|
366
|
-
truncated: sources.length > limited.length || artifacts.images.length > images.length,
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
export function renderHostedSearchResult(value) {
|
|
371
|
-
const parts = []
|
|
372
|
-
if (value.content) parts.push(value.content)
|
|
373
|
-
if (value.sources?.length) {
|
|
374
|
-
parts.push(`来源:\n${value.sources.map((source) => `- [${source.title ?? source.url}](${source.url})${source.publishedAt ? `(${source.publishedAt})` : ''}${source.snippet ? ` — ${source.snippet}` : ''}`).join('\n')}`)
|
|
375
|
-
}
|
|
376
|
-
if (value.images?.length) {
|
|
377
|
-
parts.push(`图片:\n${value.images.map((image) => `- [${image.caption ?? image.imageUrl}](${image.imageUrl})${image.sourceWebsiteUrl ? `([来源页面](${image.sourceWebsiteUrl}))` : ''}`).join('\n')}`)
|
|
378
|
-
}
|
|
379
|
-
if (value.warnings?.length) parts.push(value.warnings.map((warning) => `警告:${warning}`).join('\n'))
|
|
380
|
-
if (value.retrievedAt) parts.push(`检索时间:${value.retrievedAt}`)
|
|
381
|
-
if (value.truncated) parts.push('来源已截断;如需更多结果,请缩小查询范围。')
|
|
382
|
-
return [{ type: 'text', text: parts.join('\n\n') || '没有找到结果。' }]
|
|
383
|
-
}
|
|
16
|
+
required: ['query'], additionalProperties: false,
|
|
17
|
+
}
|
|
18
|
+
export const HOSTED_SEARCH_OUTPUT = { type: 'object', properties: { mode: { type: 'string', enum: ['hosted'] }, action: { type: 'string' }, emulation: { type: 'string', enum: ['native'] }, content: { type: 'string' }, sources: { type: 'array', items: { type: 'object' } }, citations: { type: 'array', items: { type: 'object' } }, images: { type: 'array', items: { type: 'object' } }, warnings: { type: 'array', items: { type: 'string' } }, outputBlocks: { type: 'array', items: { type: 'object' } }, domains: { type: 'array', items: { type: 'string' } }, lineRange: { type: 'object' }, requestId: { type: 'string' }, responseId: { type: 'string' }, retrievedAt: { type: 'string' }, truncated: { type: 'boolean' } }, required: ['mode','action','emulation','content','sources','citations','images','warnings','requestId','retrievedAt','truncated'], additionalProperties: false }
|
|
19
|
+
function failure(message, code = 'WEB_INVALID_REQUEST') { const e = new Error(message); e.code = code; return e }
|
|
20
|
+
function isRecord(value) { return value !== null && typeof value === 'object' && !Array.isArray(value) }
|
|
21
|
+
function normalizeDomains(value, field) { if (value === undefined) return undefined; if (!Array.isArray(value) || value.length < 1 || value.length > 100) throw failure(`websearch_gpt_advanced.${field} must contain 1 to 100 domains`); const result = value.map((item) => { if (typeof item !== 'string') throw failure(`websearch_gpt_advanced.${field} contains an invalid domain`); const domain = item.trim().toLowerCase(); if (!domain || domain.length > 253 || domain.includes('/') || domain.includes(':') || domain.endsWith('.') || domain.split('.').length < 2) throw failure(`websearch_gpt_advanced.${field} contains an invalid domain`); return domain }); if (new Set(result).size !== result.length) throw failure(`websearch_gpt_advanced.${field} contains duplicates`); return result }
|
|
22
|
+
function normalizeLocation(value) { if (!isRecord(value)) throw failure('websearch_gpt_advanced.userLocation must be an object'); const result = {}; if (value.country !== undefined) { if (typeof value.country !== 'string' || !/^[a-z]{2}$/iu.test(value.country.trim())) throw failure('userLocation.country must be ISO alpha-2'); result.country = value.country.trim().toUpperCase() } for (const field of ['city','region']) if (value[field] !== undefined) { if (typeof value[field] !== 'string' || !value[field].trim() || value[field].length > 200) throw failure(`userLocation.${field} is invalid`); result[field] = value[field].trim() } if (value.timezone !== undefined) { try { new Intl.DateTimeFormat('en-US', { timeZone: value.timezone }).format() } catch { throw failure('userLocation.timezone must be an IANA timezone') }; result.timezone = value.timezone } if (!Object.keys(result).length) throw failure('userLocation is empty'); return result }
|
|
23
|
+
export function normalizeHostedSearchArgs(args) { if (!isRecord(args) || typeof args.query !== 'string' || !args.query.trim()) throw failure('websearch_gpt_advanced.query must be non-empty'); const query = args.query.trim(); if (query.length > 16_000) throw failure('query is too long'); const result = { query }; if (args.searchContextSize !== undefined) { if (!['low','medium','high'].includes(args.searchContextSize)) throw failure('searchContextSize is invalid'); result.searchContextSize = args.searchContextSize } const allowedDomains = normalizeDomains(args.allowedDomains, 'allowedDomains'); const blockedDomains = normalizeDomains(args.blockedDomains, 'blockedDomains'); if (allowedDomains) result.allowedDomains = allowedDomains; if (blockedDomains) result.blockedDomains = blockedDomains; if (allowedDomains && blockedDomains && allowedDomains.some((d) => new Set(blockedDomains).has(d))) throw failure('domain filters conflict'); if (args.userLocation !== undefined) result.userLocation = normalizeLocation(args.userLocation); if (args.externalWebAccess !== undefined) { if (typeof args.externalWebAccess !== 'boolean') throw failure('externalWebAccess must be boolean'); result.externalWebAccess = args.externalWebAccess } if (args.returnTokenBudget !== undefined) { if (!['default','unlimited'].includes(args.returnTokenBudget)) throw failure('returnTokenBudget is invalid'); result.returnTokenBudget = args.returnTokenBudget } if (args.searchContentTypes !== undefined) { if (!Array.isArray(args.searchContentTypes) || !args.searchContentTypes.length || args.searchContentTypes.length > 2 || args.searchContentTypes.some((v) => !['text','image'].includes(v))) throw failure('searchContentTypes is invalid'); result.searchContentTypes = [...new Set(args.searchContentTypes)] } if (args.imageSettings !== undefined) { if (!result.searchContentTypes?.includes('image') || !isRecord(args.imageSettings)) throw failure('imageSettings requires image search'); result.imageSettings = {}; if (args.imageSettings.maxResults !== undefined) { if (!Number.isInteger(args.imageSettings.maxResults) || args.imageSettings.maxResults < 1 || args.imageSettings.maxResults > 100) throw failure('imageSettings.maxResults is invalid'); result.imageSettings.maxResults = args.imageSettings.maxResults } if (args.imageSettings.caption !== undefined) { if (typeof args.imageSettings.caption !== 'boolean') throw failure('imageSettings.caption must be boolean'); result.imageSettings.caption = args.imageSettings.caption } } return result }
|
|
24
|
+
export function buildHostedSearchBody(args, model, options = {}) { const tool = { type: 'web_search', ...(args.searchContextSize ? { search_context_size: args.searchContextSize } : {}), ...(args.allowedDomains || args.blockedDomains ? { filters: { ...(args.allowedDomains ? { allowed_domains: args.allowedDomains } : {}), ...(args.blockedDomains ? { blocked_domains: args.blockedDomains } : {}) } } : {}), ...(args.userLocation ? { user_location: { type: 'approximate', ...args.userLocation } } : {}), ...(args.externalWebAccess !== undefined ? { external_web_access: args.externalWebAccess } : {}), ...(args.returnTokenBudget ? { return_token_budget: args.returnTokenBudget } : {}), ...(args.searchContentTypes ? { search_content_types: args.searchContentTypes } : {}), ...(args.imageSettings ? { image_settings: { ...(args.imageSettings.maxResults !== undefined ? { max_results: args.imageSettings.maxResults } : {}), ...(args.imageSettings.caption !== undefined ? { caption: args.imageSettings.caption } : {}) } } : {}) }; return { model, input: [{ role: 'user', content: [{ type: 'input_text', text: args.query }] }], tools: [tool], tool_choice: 'required', include: ['web_search_call.action.sources', ...(args.searchContentTypes?.includes('image') ? ['web_search_call.results'] : [])], stream: false, store: false, ...(options.promptCacheKey ? { prompt_cache_key: options.promptCacheKey } : {}) } }
|
|
25
|
+
function textFrom(value, seen = new Set()) { if (typeof value === 'string') return value; if (!value || typeof value !== 'object' || seen.has(value)) return ''; seen.add(value); if (Array.isArray(value)) return value.map((item) => textFrom(item, seen)).filter(Boolean).join('\n'); if (typeof value.text === 'string' && ['output_text','text','input_text'].includes(value.type)) return value.text; if (Array.isArray(value.content)) return textFrom(value.content, seen); return '' }
|
|
26
|
+
function httpUrl(value) { try { const url = new URL(value); return ['http:','https:'].includes(url.protocol) ? url : undefined } catch { return undefined } }
|
|
27
|
+
function sourceFrom(value) { if (!isRecord(value) || !httpUrl(value.url)) return undefined; return { url: httpUrl(value.url).toString(), ...(typeof value.title === 'string' && value.title ? { title: value.title } : {}), ...(typeof value.snippet === 'string' && value.snippet ? { snippet: value.snippet } : {}), ...(typeof value.publishedAt === 'string' ? { publishedAt: value.publishedAt } : typeof value.published_at === 'string' ? { publishedAt: value.published_at } : {}), ...(typeof value.ref_id === 'string' ? { refId: value.ref_id } : {}) } }
|
|
28
|
+
function imageFrom(value) { if (!isRecord(value) || value.type !== 'image_result' || !httpUrl(value.image_url)) return undefined; return { imageUrl: httpUrl(value.image_url).toString(), ...(httpUrl(value.thumbnail_url) ? { thumbnailUrl: httpUrl(value.thumbnail_url).toString() } : {}), ...(httpUrl(value.source_website_url) ? { sourceWebsiteUrl: httpUrl(value.source_website_url).toString() } : {}), ...(typeof value.caption === 'string' ? { caption: value.caption } : {}) } }
|
|
29
|
+
function responseArtifacts(response) { const sources = [], citations = [], images = [], actions = []; for (const item of Array.isArray(response?.output) ? response.output : []) { if (item?.type === 'web_search_call') { actions.push(typeof item.action?.type === 'string' ? item.action.type : 'search'); for (const value of item.action?.sources ?? []) { const source = sourceFrom(value); if (source) sources.push(source) } for (const value of item.results ?? []) { const image = imageFrom(value); if (image) images.push(image) } } if (item?.type === 'message') for (const part of item.content ?? []) if (part?.type === 'output_text') for (const annotation of part.annotations ?? []) if (annotation?.type === 'url_citation') { const source = sourceFrom(annotation); if (source) { citations.push(source); sources.push(source) } } } return { sources, citations, images, actions } }
|
|
30
|
+
function canonicalUrl(value) { const url = httpUrl(value); if (!url) return undefined; url.hash = ''; for (const key of [...url.searchParams.keys()]) if (/^(utm_|gclid$|fbclid$)/iu.test(key)) url.searchParams.delete(key); return url.toString() }
|
|
31
|
+
function uniqueByUrl(values) { const result = [], seen = new Set(); for (const value of values) { const key = canonicalUrl(value.url); if (!key || seen.has(key)) continue; seen.add(key); result.push(value) } return result }
|
|
32
|
+
export function parseHostedSearchResponse(response, requestId, maxResults = 8, retrievedAt = new Date().toISOString()) { if (response?.error) throw failure(response.error.message ?? 'Hosted Web Search failed', 'LCX_WEB_PROVIDER_ERROR'); if (response?.status !== 'completed') throw failure(`Hosted Web Search status: ${String(response?.status ?? 'missing')}`, 'WEB_RESPONSE_INCOMPLETE'); const output = typeof response.output_text === 'string' ? response.output_text : textFrom(response?.output ?? response?.content); const outputBlocks = parseWebRunOutput(output); const artifacts = responseArtifacts(response); if (!artifacts.actions.length) throw failure('Hosted Web Search completed without web_search', 'WEB_SEARCH_NOT_EXECUTED'); const allSources = [...artifacts.sources, ...outputBlocks.flatMap((block) => block.url ? [{ url: block.url, ...(block.title ? { title: block.title } : {}) }] : [])]; const sources = uniqueByUrl(allSources); const citations = uniqueByUrl(artifacts.citations); const limited = sources.slice(0, Math.max(1, maxResults)); const images = artifacts.images.slice(0, Math.max(1, maxResults)); if (!output && !limited.length && !images.length) throw failure('Hosted Web Search returned no output', 'WEB_NO_SOURCES'); return { mode: 'hosted', action: artifacts.actions[0], emulation: 'native', content: output, sources: limited, citations, images, warnings: artifacts.actions.length > 1 ? [`Multiple hosted search actions were returned: ${artifacts.actions.join(', ')}`] : [], outputBlocks, domains: outputDomains(outputBlocks), ...(outputLineRange(outputBlocks) ? { lineRange: outputLineRange(outputBlocks) } : {}), requestId, ...(typeof response.id === 'string' ? { responseId: response.id } : {}), retrievedAt, truncated: sources.length > limited.length || artifacts.images.length > images.length } }
|
|
33
|
+
export function renderHostedSearchResult(value) { const parts = []; if (value.content) parts.push(value.content); if (value.sources?.length) parts.push(`来源:\n${value.sources.map((s) => `- [${s.title ?? s.url}](${s.url})${s.snippet ? ` — ${s.snippet}` : ''}`).join('\n')}`); if (value.images?.length) parts.push(`图片:\n${value.images.map((i) => `- [${i.caption ?? i.imageUrl}](${i.imageUrl})`).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') }] }
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { JsonStore } from './json-store.js'
|
|
2
2
|
|
|
3
3
|
const VERSION = 1
|
|
4
4
|
|
|
@@ -31,12 +31,12 @@ function unavailable(refId) {
|
|
|
31
31
|
|
|
32
32
|
export class AlphaRefStore {
|
|
33
33
|
constructor(file) {
|
|
34
|
-
this.store = new
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
34
|
+
this.store = new JsonStore(
|
|
35
|
+
file,
|
|
36
|
+
() => ({ version: VERSION, sessions: {} }),
|
|
37
|
+
validData,
|
|
38
|
+
'LCX_ALPHA_REF_STORE_CORRUPT',
|
|
39
|
+
)
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
record(sessionId, routeFingerprint, refs) {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-lcx-codex",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "DSH
|
|
3
|
+
"version": "0.4.0-rc.13",
|
|
4
|
+
"description": "DSH Codex/Responses capabilities for existing openai-responses routes: hosted/alpha search and session-native V2 compaction",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"deepseek-harness",
|
|
7
7
|
"dsh",
|
|
@@ -35,11 +35,13 @@
|
|
|
35
35
|
},
|
|
36
36
|
"files": [
|
|
37
37
|
"lib/*.js",
|
|
38
|
-
"scripts
|
|
38
|
+
"scripts/*.mjs",
|
|
39
|
+
"assets/*.jpg",
|
|
39
40
|
"cordis.patch.yml",
|
|
40
41
|
"LICENSE",
|
|
41
42
|
"README.md",
|
|
42
43
|
"README_EN.md",
|
|
44
|
+
"ARCHITECTURE.md",
|
|
43
45
|
"CHANGELOG.md"
|
|
44
46
|
],
|
|
45
47
|
"dsh": {
|
|
@@ -54,26 +56,30 @@
|
|
|
54
56
|
"@earendil-works/pi-ai": "0.82.1"
|
|
55
57
|
},
|
|
56
58
|
"devDependencies": {
|
|
57
|
-
"@deepseek-ai/dsh-
|
|
59
|
+
"@deepseek-ai/dsh-llm": "0.1.1-rc.2",
|
|
60
|
+
"@deepseek-ai/dsh-settings": "0.1.1-rc.2",
|
|
61
|
+
"@deepseek-ai/dsh-tools": "0.1.1-rc.2",
|
|
58
62
|
"@deepseek-ai/schemastery": "^3.18.1"
|
|
59
63
|
},
|
|
60
64
|
"peerDependencies": {
|
|
61
65
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
62
|
-
"@deepseek-ai/dsh-
|
|
63
|
-
"@deepseek-ai/dsh-client-
|
|
64
|
-
"@deepseek-ai/dsh-client-ui-
|
|
65
|
-
"@deepseek-ai/dsh-
|
|
66
|
-
"@deepseek-ai/dsh-
|
|
67
|
-
"@deepseek-ai/dsh-
|
|
68
|
-
"@deepseek-ai/dsh-
|
|
66
|
+
"@deepseek-ai/dsh-attachment": "^0.1.1-rc.2",
|
|
67
|
+
"@deepseek-ai/dsh-client-runtime": "^0.1.1-rc.2",
|
|
68
|
+
"@deepseek-ai/dsh-client-ui-settings": "^0.1.1-rc.2",
|
|
69
|
+
"@deepseek-ai/dsh-client-ui-slots": "^0.1.1-rc.2",
|
|
70
|
+
"@deepseek-ai/dsh-credentials": "^0.1.1-rc.2",
|
|
71
|
+
"@deepseek-ai/dsh-llm": "^0.1.1-rc.2",
|
|
72
|
+
"@deepseek-ai/dsh-session": "^0.1.1-rc.2",
|
|
73
|
+
"@deepseek-ai/dsh-settings": "^0.1.1-rc.2",
|
|
74
|
+
"@deepseek-ai/dsh-tools": "^0.1.1-rc.2",
|
|
69
75
|
"@deepseek-ai/schemastery": "^3.18.1",
|
|
70
76
|
"react": "^18.2.0"
|
|
71
77
|
},
|
|
72
78
|
"scripts": {
|
|
73
79
|
"test": "node --test tests/*.test.mjs",
|
|
74
|
-
"
|
|
75
|
-
"
|
|
76
|
-
"
|
|
80
|
+
"probe:alpha": "node scripts/probe-alpha.mjs",
|
|
81
|
+
"pack:local": "npm pack --ignore-scripts",
|
|
82
|
+
"test:schema": "node scripts/validate-dsh-schema.mjs"
|
|
77
83
|
},
|
|
78
84
|
"engines": {
|
|
79
85
|
"node": ">=20"
|
package/scripts/probe-alpha.mjs
CHANGED
|
@@ -2,90 +2,42 @@ import { readFileSync } from 'node:fs'
|
|
|
2
2
|
import { homedir } from 'node:os'
|
|
3
3
|
import { join } from 'node:path'
|
|
4
4
|
import { randomUUID } from 'node:crypto'
|
|
5
|
-
import {
|
|
6
|
-
ALPHA_SCHEMA_FINGERPRINT,
|
|
7
|
-
buildAlphaSearchBody,
|
|
8
|
-
normalizeAlphaSearchArgs,
|
|
9
|
-
parseAlphaSearchResponse,
|
|
10
|
-
probeAlphaCapabilities,
|
|
11
|
-
} from '../lib/web-search-alpha.js'
|
|
5
|
+
import { ALPHA_SCHEMA_FINGERPRINT, buildAlphaSearchBody, normalizeAlphaSearchArgs, parseAlphaSearchResponse, probeAlphaCapabilities } from '../lib/web-search-alpha.js'
|
|
12
6
|
import { AlphaCapabilityStore, alphaCapabilityFingerprint } from '../lib/web-search-capability.js'
|
|
13
|
-
import { fetchJsonWithRetry } from '../lib/transport.js'
|
|
14
7
|
|
|
15
|
-
function required(name, value) {
|
|
16
|
-
const normalized = String(value ?? '').trim()
|
|
17
|
-
if (!normalized) throw new Error(`${name} is required`)
|
|
18
|
-
return normalized
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function apiKey() {
|
|
22
|
-
const file = String(process.env.LCX_API_KEY_FILE ?? '').trim()
|
|
23
|
-
if (file) return required('LCX_API_KEY_FILE content', readFileSync(file, 'utf8'))
|
|
24
|
-
return required('LCX_API_KEY or LCX_API_KEY_FILE', process.env.LCX_API_KEY)
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const key = apiKey()
|
|
28
8
|
const baseURL = String(process.env.LCX_BASE_URL ?? 'https://api.lcxbot.com/v1').replace(/\/+$/u, '')
|
|
29
|
-
const model =
|
|
30
|
-
const provider = String(process.env.LCX_PROVIDER ?? 'lcx')
|
|
9
|
+
const model = String(process.env.LCX_MODEL ?? '').trim()
|
|
10
|
+
const provider = String(process.env.LCX_PROVIDER ?? 'lcx').trim()
|
|
31
11
|
const profile = String(process.env.LCX_ALPHA_PROFILE ?? '')
|
|
32
12
|
const group = String(process.env.LCX_ALPHA_GROUP ?? '')
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
const
|
|
13
|
+
const keyFile = String(process.env.LCX_API_KEY_FILE ?? '').trim()
|
|
14
|
+
if (!model) throw new Error('Set LCX_MODEL to the exact GPT model id')
|
|
15
|
+
if (!keyFile) throw new Error('Set LCX_API_KEY_FILE to a local file containing the bearer key')
|
|
16
|
+
const apiKey = readFileSync(keyFile, 'utf8').trim()
|
|
17
|
+
if (!apiKey) throw new Error('LCX_API_KEY_FILE is empty')
|
|
18
|
+
const storeFile = process.env.LCX_ALPHA_CAPABILITY_PATH ?? join(process.env.DSH_HOME ?? join(homedir(), '.dsh'), 'storages', 'lcx-codex', 'web-alpha-capabilities.json')
|
|
19
|
+
const sessionId = `lcx-probe-${randomUUID()}`
|
|
39
20
|
|
|
40
|
-
|
|
41
|
-
const args = normalizeAlphaSearchArgs(
|
|
21
|
+
async function invoke(rawArgs) {
|
|
22
|
+
const args = normalizeAlphaSearchArgs(rawArgs)
|
|
42
23
|
const requestId = randomUUID()
|
|
43
|
-
const response = await
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
timeoutMs,
|
|
54
|
-
{ maxAttempts: 1, maxResponseBytes: 4 * 1024 * 1024 },
|
|
55
|
-
)
|
|
56
|
-
return parseAlphaSearchResponse(response, { action: args.action, capability: 'unknown', requestId })
|
|
24
|
+
const response = await fetch(`${baseURL}/alpha/search`, {
|
|
25
|
+
method: 'POST',
|
|
26
|
+
headers: { 'content-type': 'application/json', authorization: `Bearer ${apiKey}`, 'x-client-request-id': requestId, 'session-id': sessionId },
|
|
27
|
+
body: JSON.stringify(buildAlphaSearchBody(args, model, sessionId, true, 2500)),
|
|
28
|
+
})
|
|
29
|
+
const text = await response.text()
|
|
30
|
+
let body
|
|
31
|
+
try { body = text ? JSON.parse(text) : {} } catch { body = { output: text, results: [] } }
|
|
32
|
+
if (!response.ok) { const e = new Error(body?.error?.message ?? body?.message ?? `HTTP ${response.status}`); e.status = response.status; throw e }
|
|
33
|
+
return parseAlphaSearchResponse(body, { action: args.action, capability: 'command-capable', requestId })
|
|
57
34
|
}
|
|
58
35
|
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
finance: { ticker: '
|
|
62
|
-
weather: { location: 'United States, California, San Francisco', duration: 1 },
|
|
63
|
-
sports: { fn: 'schedule', league: 'nba', numberOfGames: 1 },
|
|
64
|
-
time: { utcOffset: '+00:00' },
|
|
36
|
+
const structured = process.env.LCX_ALPHA_PROBE_STRUCTURED === '1'
|
|
37
|
+
const actionProbes = structured ? {
|
|
38
|
+
image_query: { query: 'OpenAI logo' }, finance: { ticker: 'MSFT', assetType: 'equity', market: 'USA' }, weather: { location: 'San Francisco, CA' }, sports: { fn: 'standings', league: 'nba' }, time: { utcOffset: '+00:00' },
|
|
65
39
|
} : {}
|
|
66
|
-
|
|
67
|
-
const record = await probeAlphaCapabilities({
|
|
68
|
-
invoke,
|
|
69
|
-
schemaFingerprint: ALPHA_SCHEMA_FINGERPRINT,
|
|
70
|
-
trustedNativeProvenance,
|
|
71
|
-
actionProbes,
|
|
72
|
-
clickProbeRef,
|
|
73
|
-
screenshotProbeRef,
|
|
74
|
-
})
|
|
40
|
+
const result = await probeAlphaCapabilities({ invoke, schemaFingerprint: ALPHA_SCHEMA_FINGERPRINT, trustedNativeProvenance: process.env.LCX_ALPHA_TRUST_NATIVE === '1', actionProbes, clickProbeRef: process.env.LCX_ALPHA_CLICK_PROBE_REF, screenshotProbeRef: process.env.LCX_ALPHA_SCREENSHOT_PROBE_REF })
|
|
75
41
|
const fingerprint = alphaCapabilityFingerprint({ baseURL, provider, model, profile, group, schemaFingerprint: ALPHA_SCHEMA_FINGERPRINT })
|
|
76
|
-
new AlphaCapabilityStore(
|
|
77
|
-
|
|
78
|
-
const endpoint = new URL(baseURL)
|
|
79
|
-
process.stdout.write(`${JSON.stringify({
|
|
80
|
-
endpoint: endpoint.host,
|
|
81
|
-
provider,
|
|
82
|
-
model,
|
|
83
|
-
profile,
|
|
84
|
-
group,
|
|
85
|
-
fingerprint: fingerprint.slice(0, 12),
|
|
86
|
-
classification: record.classification,
|
|
87
|
-
provenance: record.provenance,
|
|
88
|
-
actions: record.actions,
|
|
89
|
-
probedAt: record.probedAt,
|
|
90
|
-
capabilityPath,
|
|
91
|
-
}, null, 2)}\n`)
|
|
42
|
+
new AlphaCapabilityStore(storeFile).put(fingerprint, result)
|
|
43
|
+
console.log(JSON.stringify({ fingerprint, storeFile, classification: result.classification, actions: result.actions, probedAt: result.probedAt, provenance: result.provenance }, null, 2))
|