dsh-lcx-codex 0.4.1 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +24 -0
- package/CHANGELOG.md +15 -0
- package/README.md +177 -125
- package/README_EN.md +180 -116
- package/cordis.patch.yml +1 -0
- package/lib/client.js +15 -18
- package/lib/compact-v2.js +21 -37
- package/lib/dsh-responses.js +13 -6
- package/lib/index.js +69 -43
- package/lib/native-checkpoint.js +18 -7
- package/lib/responses-replay.js +63 -276
- package/lib/responses-request.js +145 -0
- package/lib/responses-stream.js +539 -0
- package/lib/route.js +58 -43
- package/lib/transport.js +10 -4
- package/lib/web-search-alpha.js +5 -2
- package/package.json +4 -4
package/lib/route.js
CHANGED
|
@@ -3,11 +3,12 @@
|
|
|
3
3
|
import { createHash, randomUUID } from 'node:crypto'
|
|
4
4
|
import { attributionHeaders, resolveRetryPolicy } from '@deepseek-ai/dsh-llm'
|
|
5
5
|
import { settingsNamespace } from '@deepseek-ai/dsh-settings'
|
|
6
|
+
import { getBuiltinModels } from '@earendil-works/pi-ai/providers/all'
|
|
6
7
|
|
|
7
8
|
/** @typedef {Record<string, string>} HeaderMap */
|
|
8
9
|
/** @typedef {Parameters<typeof resolveRetryPolicy>[0]} RetryPolicyConfig */
|
|
9
10
|
/** @typedef {'none' | 'short' | 'long'} CacheRetention */
|
|
10
|
-
/** @typedef {{ supportsDeveloperRole?: boolean, supportsStrictMode?: boolean, supportsLongCacheRetention?: boolean }} ResponsesCompat */
|
|
11
|
+
/** @typedef {{ supportsDeveloperRole?: boolean, sessionAffinityFormat?: 'openai' | 'openai-nosession' | 'openrouter', supportsStrictMode?: boolean, supportsLongCacheRetention?: boolean, supportsOpenAIGrammarTools?: boolean, supportsAdditionalTools?: boolean, supportsToolSearch?: boolean, supportsExplicitPromptCacheMode?: boolean }} ResponsesCompat */
|
|
11
12
|
/** @typedef {{ provider: string, model: string, baseURL: string, sessionId: string }} RouteIdentity */
|
|
12
13
|
/** @typedef {{ provider?: unknown, model?: unknown, sessionId?: unknown }} RouteOptions */
|
|
13
14
|
/** @typedef {{ id?: unknown, compat?: unknown }} ProviderModelProfile */
|
|
@@ -50,6 +51,7 @@ import { settingsNamespace } from '@deepseek-ai/dsh-settings'
|
|
|
50
51
|
* @property {HeaderMap} [headers]
|
|
51
52
|
* @property {unknown} [cacheRetention]
|
|
52
53
|
* @property {unknown} [supportsLongCacheRetention]
|
|
54
|
+
* @property {unknown} [supportsExplicitPromptCacheMode]
|
|
53
55
|
* @property {unknown} [responsesCompat]
|
|
54
56
|
* @property {number} [timeoutMs]
|
|
55
57
|
* @property {number} [maxAttempts]
|
|
@@ -76,11 +78,6 @@ export function normalizeBaseURL(value) {
|
|
|
76
78
|
return String(value ?? '').trim().replace(/\/+$/u, '')
|
|
77
79
|
}
|
|
78
80
|
|
|
79
|
-
/** @param {unknown} model */
|
|
80
|
-
export function isGptModel(model) {
|
|
81
|
-
return /(^|[^a-z])gpt(?:[^a-z]|$)/iu.test(String(model ?? ''))
|
|
82
|
-
}
|
|
83
|
-
|
|
84
81
|
/** @param {unknown} baseURL */
|
|
85
82
|
export function baseURLFingerprint(baseURL) {
|
|
86
83
|
return createHash('sha256').update(normalizeBaseURL(baseURL), 'utf8').digest('hex')
|
|
@@ -148,7 +145,7 @@ export function settingsValue(ctx, namespace) {
|
|
|
148
145
|
}
|
|
149
146
|
|
|
150
147
|
/** @type {Set<keyof ResponsesCompat>} */
|
|
151
|
-
const RESPONSES_COMPAT_FIELDS = new Set(['supportsDeveloperRole', 'supportsStrictMode', 'supportsLongCacheRetention'])
|
|
148
|
+
const RESPONSES_COMPAT_FIELDS = new Set(['supportsDeveloperRole', 'sessionAffinityFormat', 'supportsStrictMode', 'supportsLongCacheRetention', 'supportsOpenAIGrammarTools', 'supportsAdditionalTools', 'supportsToolSearch', 'supportsExplicitPromptCacheMode'])
|
|
152
149
|
|
|
153
150
|
/**
|
|
154
151
|
* @param {ResponsesCompat} target
|
|
@@ -156,7 +153,22 @@ const RESPONSES_COMPAT_FIELDS = new Set(['supportsDeveloperRole', 'supportsStric
|
|
|
156
153
|
*/
|
|
157
154
|
function copyResponsesCompat(target, source) {
|
|
158
155
|
if (!source || typeof source !== 'object') return
|
|
159
|
-
|
|
156
|
+
const values = /** @type {Record<string, unknown>} */ (source)
|
|
157
|
+
const output = /** @type {Record<string, unknown>} */ (/** @type {unknown} */ (target))
|
|
158
|
+
for (const field of RESPONSES_COMPAT_FIELDS) {
|
|
159
|
+
const value = values[field]
|
|
160
|
+
if (field === 'sessionAffinityFormat') {
|
|
161
|
+
if (['openai', 'openai-nosession', 'openrouter'].includes(String(value))) output[field] = value
|
|
162
|
+
} else if (typeof value === 'boolean') output[field] = value
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** @param {unknown} provider @param {unknown} modelId */
|
|
167
|
+
function builtinResponsesModel(provider, modelId) {
|
|
168
|
+
try {
|
|
169
|
+
return getBuiltinModels(/** @type {any} */ (String(provider ?? '')))
|
|
170
|
+
.find((model) => model?.id === String(modelId ?? '') && model?.api === 'openai-responses')
|
|
171
|
+
} catch { return undefined }
|
|
160
172
|
}
|
|
161
173
|
|
|
162
174
|
/**
|
|
@@ -183,50 +195,51 @@ function configuredResponsesCompat(profile, modelId) {
|
|
|
183
195
|
* @returns {ResolvedResponsesRoute | undefined}
|
|
184
196
|
*/
|
|
185
197
|
export function resolveResponsesRouteConfig(ctx, options, fallbackConfig) {
|
|
186
|
-
if (!isGptModel(options?.model)) return undefined
|
|
187
198
|
const provider = String(options?.provider ?? '')
|
|
199
|
+
const model = String(options?.model ?? '')
|
|
188
200
|
const section = settingsValue(ctx, 'llm-pi-ai')
|
|
189
201
|
const profile = section?.providers?.[provider]
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
responsesCompat: fallbackConfig.responsesCompat && typeof fallbackConfig.responsesCompat === 'object' ? /** @type {ResponsesCompat} */ ({ ...fallbackConfig.responsesCompat }) : undefined,
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
if (profile.api !== 'openai-responses') return undefined
|
|
204
|
-
const baseURL = profile.baseURL ?? (provider === fallbackConfig.provider ? fallbackConfig.baseURL : undefined)
|
|
205
|
-
const apiKeyEnv = profile.apiKeyEnv ?? (provider === fallbackConfig.provider ? fallbackConfig.apiKeyEnv : undefined)
|
|
202
|
+
const fallbackOwned = provider === fallbackConfig.provider
|
|
203
|
+
if (profile === undefined && !fallbackOwned) return undefined
|
|
204
|
+
const configured = /** @type {ProviderProfile} */ (profile ?? {})
|
|
205
|
+
|
|
206
|
+
const builtin = builtinResponsesModel(provider, model)
|
|
207
|
+
const api = configured.api ?? builtin?.api ?? (fallbackOwned ? 'openai-responses' : undefined)
|
|
208
|
+
if (api !== 'openai-responses') return undefined
|
|
209
|
+
|
|
210
|
+
const baseURL = configured.baseURL ?? builtin?.baseUrl ?? (fallbackOwned ? fallbackConfig.baseURL : undefined)
|
|
211
|
+
const apiKeyEnv = configured.apiKeyEnv ?? (fallbackOwned ? fallbackConfig.apiKeyEnv : undefined)
|
|
206
212
|
if (!baseURL || !apiKeyEnv) return undefined
|
|
207
|
-
|
|
213
|
+
|
|
214
|
+
/** @type {ResponsesCompat} */
|
|
215
|
+
const responsesCompat = {}
|
|
216
|
+
copyResponsesCompat(responsesCompat, builtin?.compat)
|
|
217
|
+
copyResponsesCompat(responsesCompat, fallbackOwned ? fallbackConfig.responsesCompat : undefined)
|
|
218
|
+
copyResponsesCompat(responsesCompat, configuredResponsesCompat(configured, model))
|
|
219
|
+
// Host Pi rc.2 withholds this field; the plugin opt-in still requires Pi's exact model capability.
|
|
220
|
+
const promptCacheModel = fallbackOwned && fallbackConfig.supportsExplicitPromptCacheMode === true
|
|
221
|
+
? builtinResponsesModel('openai', model)
|
|
222
|
+
: undefined
|
|
223
|
+
const promptCacheCompat = /** @type {ResponsesCompat | undefined} */ (promptCacheModel?.compat)
|
|
224
|
+
if (promptCacheCompat?.supportsExplicitPromptCacheMode === true) responsesCompat.supportsExplicitPromptCacheMode = true
|
|
225
|
+
const resolvedCompat = Object.keys(responsesCompat).length > 0 ? responsesCompat : undefined
|
|
226
|
+
|
|
208
227
|
return {
|
|
209
228
|
...fallbackConfig,
|
|
210
229
|
provider,
|
|
211
|
-
model
|
|
230
|
+
model,
|
|
212
231
|
api: 'openai-responses',
|
|
213
232
|
baseURL: normalizeBaseURL(baseURL),
|
|
214
233
|
apiKeyEnv,
|
|
215
|
-
headers:
|
|
216
|
-
cacheRetention: /** @type {CacheRetention} */ (['none', 'short', 'long'].includes(/** @type {string} */ (
|
|
217
|
-
supportsLongCacheRetention:
|
|
218
|
-
responsesCompat,
|
|
219
|
-
timeoutMs: Number.isInteger(
|
|
220
|
-
maxAttempts: retryAttempts(
|
|
221
|
-
maxRequestImageBytes: Number.isSafeInteger(
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
requestImagePixelBudget: Number.isSafeInteger(profile.requestImagePixelBudget) && /** @type {number} */ (profile.requestImagePixelBudget) > 0
|
|
225
|
-
? profile.requestImagePixelBudget
|
|
226
|
-
: fallbackConfig.requestImagePixelBudget,
|
|
227
|
-
requestImageMaxBytes: Number.isSafeInteger(profile.requestImageMaxBytes) && /** @type {number} */ (profile.requestImageMaxBytes) > 0
|
|
228
|
-
? profile.requestImageMaxBytes
|
|
229
|
-
: fallbackConfig.requestImageMaxBytes,
|
|
234
|
+
headers: configured.headers && typeof configured.headers === 'object' ? { ...configured.headers } : { ...(fallbackConfig.headers ?? {}) },
|
|
235
|
+
cacheRetention: /** @type {CacheRetention} */ (['none', 'short', 'long'].includes(/** @type {string} */ (configured.cacheRetention)) ? configured.cacheRetention : (['none', 'short', 'long'].includes(/** @type {string} */ (fallbackConfig.cacheRetention)) ? fallbackConfig.cacheRetention : 'short')),
|
|
236
|
+
supportsLongCacheRetention: resolvedCompat?.supportsLongCacheRetention ?? /** @type {boolean | undefined} */ (fallbackConfig.supportsLongCacheRetention) ?? true,
|
|
237
|
+
responsesCompat: resolvedCompat,
|
|
238
|
+
timeoutMs: Number.isInteger(configured.timeoutMs) && /** @type {number} */ (configured.timeoutMs) > 0 ? /** @type {number} */ (configured.timeoutMs) : fallbackConfig.timeoutMs,
|
|
239
|
+
maxAttempts: retryAttempts(configured.retryPolicy, fallbackConfig.maxAttempts),
|
|
240
|
+
maxRequestImageBytes: Number.isSafeInteger(configured.maxRequestImageBytes) && /** @type {number} */ (configured.maxRequestImageBytes) > 0 ? /** @type {number} */ (configured.maxRequestImageBytes) : fallbackConfig.maxRequestImageBytes,
|
|
241
|
+
requestImagePixelBudget: Number.isSafeInteger(configured.requestImagePixelBudget) && /** @type {number} */ (configured.requestImagePixelBudget) > 0 ? /** @type {number} */ (configured.requestImagePixelBudget) : fallbackConfig.requestImagePixelBudget,
|
|
242
|
+
requestImageMaxBytes: Number.isSafeInteger(configured.requestImageMaxBytes) && /** @type {number} */ (configured.requestImageMaxBytes) > 0 ? /** @type {number} */ (configured.requestImageMaxBytes) : fallbackConfig.requestImageMaxBytes,
|
|
230
243
|
}
|
|
231
244
|
}
|
|
232
245
|
|
|
@@ -258,6 +271,8 @@ function hasExplicitSessionAffinity(headers) { return ['session-id', 'session_id
|
|
|
258
271
|
/** @param {Partial<ResolvedResponsesRoute> | null | undefined} config */
|
|
259
272
|
function sessionAffinityFormat(config) {
|
|
260
273
|
if (config?.api && config.api !== 'openai-responses') return 'none'
|
|
274
|
+
const explicit = config?.responsesCompat?.sessionAffinityFormat
|
|
275
|
+
if (['openai', 'openai-nosession', 'openrouter'].includes(String(explicit))) return explicit
|
|
261
276
|
const provider = String(config?.provider ?? '').toLowerCase()
|
|
262
277
|
const baseURL = String(config?.baseURL ?? '').toLowerCase()
|
|
263
278
|
return provider === 'openrouter' || baseURL.includes('openrouter.ai') ? 'openrouter' : 'openai'
|
|
@@ -284,7 +299,7 @@ export async function authenticatedHeaders(ctx, config, sessionId, requestId) {
|
|
|
284
299
|
else if (format === 'openrouter') headers['x-session-id'] = sid
|
|
285
300
|
}
|
|
286
301
|
if (requestId !== null && !hasHeader(explicit, 'x-client-request-id')) {
|
|
287
|
-
const correlation = requestId ?? (sid && format === 'openai' ? sid : (!sid ? randomUUID() : undefined))
|
|
302
|
+
const correlation = requestId ?? (sid && (format === 'openai' || format === 'openai-nosession') ? sid : (!sid ? randomUUID() : undefined))
|
|
288
303
|
if (correlation) headers['x-client-request-id'] = correlation
|
|
289
304
|
}
|
|
290
305
|
return { ...headers, ...explicit }
|
package/lib/transport.js
CHANGED
|
@@ -6,9 +6,14 @@ export function abortIfNeeded(signal) {
|
|
|
6
6
|
}
|
|
7
7
|
function makeError(message, code, extra = {}) { const error = new Error(message); error.code = code; Object.assign(error, extra); return error }
|
|
8
8
|
function retryableStatus(status) { return status === 408 || status === 409 || status === 425 || status === 429 || (status >= 500 && status <= 599) }
|
|
9
|
-
function
|
|
10
|
-
const
|
|
9
|
+
function retryAfterFromHeaders(headers) {
|
|
10
|
+
const rawMillis = headers?.get?.('retry-after-ms'); const millis = Number(rawMillis); if (rawMillis !== null && rawMillis !== undefined && rawMillis !== '' && Number.isFinite(millis) && millis >= 0) return Math.min(millis, 30_000)
|
|
11
11
|
const retryAfter = headers?.get?.('retry-after'); if (retryAfter) { const seconds = Number(retryAfter); if (Number.isFinite(seconds) && seconds >= 0) return Math.min(seconds * 1000, 30_000); const date = Date.parse(retryAfter); if (!Number.isNaN(date)) return Math.min(Math.max(0, date - Date.now()), 30_000) }
|
|
12
|
+
return undefined
|
|
13
|
+
}
|
|
14
|
+
function delayFromHeaders(headers, attempt) {
|
|
15
|
+
const retryAfter = retryAfterFromHeaders(headers)
|
|
16
|
+
if (retryAfter !== undefined) return retryAfter
|
|
12
17
|
return Math.min(10_000, 500 * 2 ** Math.max(0, attempt - 1))
|
|
13
18
|
}
|
|
14
19
|
async function sleep(ms, signal) { if (ms <= 0) return; await new Promise((resolve, reject) => { if (signal?.aborted) return reject(signal.reason); const timer = setTimeout(resolve, ms); signal?.addEventListener('abort', () => { clearTimeout(timer); reject(signal.reason) }, { once: true }) }) }
|
|
@@ -25,7 +30,8 @@ export async function fetchJsonWithRetry(url, body, headers = {}, signal, timeou
|
|
|
25
30
|
const text = await readLimited(response, maxResponseBytes)
|
|
26
31
|
if (!response.ok) {
|
|
27
32
|
const retryable = retryableStatus(response.status)
|
|
28
|
-
const
|
|
33
|
+
const providerRetryAfterMs = retryAfterFromHeaders(response.headers)
|
|
34
|
+
const error = makeError(`HTTP ${response.status}`, retryable ? 'LCX_HTTP_RETRYABLE' : 'LCX_HTTP_ERROR', { status: response.status, retryable, requestId: response.headers.get('x-request-id') ?? response.headers.get('request-id') ?? undefined, ...(providerRetryAfterMs === undefined ? {} : { providerRetryAfterMs }) })
|
|
29
35
|
if (!error.retryable || attempt >= maxAttempts) throw error; last = error; await sleep(delayFromHeaders(response.headers, attempt), signal); continue
|
|
30
36
|
}
|
|
31
37
|
let parsed
|
|
@@ -50,7 +56,7 @@ export async function fetchSseWithRetry(url, body, headers = {}, signal, timeout
|
|
|
50
56
|
const response = await fetch(url, { method: 'POST', headers: { 'content-type': 'application/json', accept: 'text/event-stream', ...headers }, body: JSON.stringify(body), signal: requestSignal, redirect: 'error' })
|
|
51
57
|
if (!response.ok) {
|
|
52
58
|
await readLimited(response, Math.min(options.maxResponseBytes ?? DEFAULT_MAX_BYTES, 512 * 1024))
|
|
53
|
-
const retryable = retryableStatus(response.status); const error = makeError(`HTTP ${response.status}`, retryable ? 'LCX_HTTP_RETRYABLE' : 'LCX_HTTP_ERROR', { status: response.status, retryable, requestId: response.headers.get('x-request-id') ?? response.headers.get('request-id') ?? undefined })
|
|
59
|
+
const retryable = retryableStatus(response.status); const providerRetryAfterMs = retryAfterFromHeaders(response.headers); const error = makeError(`HTTP ${response.status}`, retryable ? 'LCX_HTTP_RETRYABLE' : 'LCX_HTTP_ERROR', { status: response.status, retryable, requestId: response.headers.get('x-request-id') ?? response.headers.get('request-id') ?? undefined, ...(providerRetryAfterMs === undefined ? {} : { providerRetryAfterMs }) })
|
|
54
60
|
if (!retryable || attempt >= maxAttempts) throw error; last = error; await sleep(delayFromHeaders(response.headers, attempt), signal); continue
|
|
55
61
|
}
|
|
56
62
|
if (typeof options.consume === 'function') return await options.consume(response, { requestSignal }); return response
|
package/lib/web-search-alpha.js
CHANGED
|
@@ -9,7 +9,7 @@ const RESPONSE_LENGTHS = ['short','medium','long']
|
|
|
9
9
|
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 }
|
|
10
10
|
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 }
|
|
11
11
|
export const ALPHA_SCHEMA_FINGERPRINT = createHash('sha256').update(JSON.stringify(ALPHA_SEARCH_PARAMETERS)).digest('hex')
|
|
12
|
-
export const ALPHA_PROBE_VERSION =
|
|
12
|
+
export const ALPHA_PROBE_VERSION = 11
|
|
13
13
|
function failure(message, code='WEB_INVALID_REQUEST') { const e=new Error(message); e.code=code; return e }
|
|
14
14
|
function isObject(v){ return v!==null && typeof v==='object' && !Array.isArray(v) }
|
|
15
15
|
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() }
|
|
@@ -101,7 +101,9 @@ function safeResult(value,seen=new Set()){ if(value===null||typeof value!=='obje
|
|
|
101
101
|
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 candidate=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 refId=isAlphaHttpUrl(candidate)?undefined:candidate; const url=httpUrl(value.url??value.source_url??value.source_website_url??candidate); 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} }
|
|
102
102
|
const ACTION_ERROR=/^\s*(?:Error parsing function call\b|Invalid function_name=|Invalid function call\b)/iu
|
|
103
103
|
const ALPHA_SEMANTIC_FAILURE=/^\s*(?:reference(?: id)?\s+(?:is\s+)?(?:invalid|unavailable)\b|unable to access requested content\b|service unavailable\b)/iu
|
|
104
|
-
|
|
104
|
+
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
|
|
105
|
+
function alphaCommandErrorEnvelope(output,action){ const match=String(output??'').match(ALPHA_COMMAND_ERROR_ENVELOPE); return match?.[1]?.toLowerCase()===action }
|
|
106
|
+
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)||(['open','find','click','screenshot'].includes(options.action)&&(ALPHA_SEMANTIC_FAILURE.test(response.output)||alphaCommandErrorEnvelope(response.output,options.action)))) 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) if(block.url){ const url=httpUrl(block.url); if(url) block.url=url; else delete block.url } for(const block of outputBlocks) for(const ref of block.references??[]){ if(isAlphaHttpUrl(ref)) continue; 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} }
|
|
105
107
|
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')}] }
|
|
106
108
|
function probeFailureState(error){ if([404,405].includes(error?.status)||['LCX_ALPHA_ACTION_UNAVAILABLE','LCX_ALPHA_ACTION_FAILED'].includes(error?.code)||/channel does not support|unsupported action|not implemented/iu.test(String(error?.message??''))) return 'unsupported'; return 'unknown' }
|
|
107
109
|
function alphaProbeStructuredResult(value,seen=new Set()){
|
|
@@ -122,6 +124,7 @@ function alphaProbeStructuredBlock(block){
|
|
|
122
124
|
}
|
|
123
125
|
const ALPHA_PROBE_SEMANTIC_FAILURE=/\breference(?: id)?\s+(?:is\s+)?(?:invalid|unavailable)\b/iu
|
|
124
126
|
function alphaProbeResultHasEvidence(action,value){
|
|
127
|
+
if(alphaCommandErrorEnvelope(value?.content,action)) return false
|
|
125
128
|
if(ALPHA_PROBE_SEMANTIC_FAILURE.test(String(value?.content??''))&&!(Array.isArray(value?.results)&&value.results.length)) return false
|
|
126
129
|
const refs=Array.isArray(value?.refs)&&value.refs.some((ref)=>typeof ref==='string'&&ref&&!isAlphaHttpUrl(ref))
|
|
127
130
|
const links=Array.isArray(value?.links)&&value.links.some((link)=>Number.isSafeInteger(link?.id))
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-lcx-codex",
|
|
3
|
-
"version": "0.4.
|
|
4
|
-
"description": "DSH
|
|
3
|
+
"version": "0.4.2",
|
|
4
|
+
"description": "DSH GPT Responses lifecycle owner with Native V2 compaction/replay and Hosted/Alpha Search capabilities",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"deepseek-harness",
|
|
7
7
|
"dsh",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
}
|
|
54
54
|
},
|
|
55
55
|
"dependencies": {
|
|
56
|
-
"@earendil-works/pi-ai": "0.
|
|
56
|
+
"@earendil-works/pi-ai": "0.84.3"
|
|
57
57
|
},
|
|
58
58
|
"devDependencies": {
|
|
59
59
|
"@deepseek-ai/dsh-llm": "0.1.1-rc.2",
|
|
@@ -84,7 +84,7 @@
|
|
|
84
84
|
"test:schema": "node scripts/validate-dsh-schema.mjs"
|
|
85
85
|
},
|
|
86
86
|
"engines": {
|
|
87
|
-
"node": ">=
|
|
87
|
+
"node": "^22.19.0 || >=24.0.0"
|
|
88
88
|
},
|
|
89
89
|
"publishConfig": {
|
|
90
90
|
"access": "public"
|