@goodandready/dsh-subscriptions 0.7.41 → 0.7.42
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/lib/accounts.js +5 -1
- package/lib/client-version-service.js +73 -0
- package/lib/client-version.js +3 -1
- package/lib/client.js +12 -0
- package/lib/compat.js +2 -1
- package/lib/config-schema.js +5 -1
- package/lib/index.js +62 -4
- package/lib/messages.js +10 -9
- package/lib/routes/status.js +8 -0
- package/lib/vendors/codex.js +1 -1
- package/lib/web-search-provider.js +64 -0
- package/package.json +1 -1
package/lib/accounts.js
CHANGED
|
@@ -50,6 +50,7 @@ import { accountIdentityKey, credentialState } from './account-reliability.js'
|
|
|
50
50
|
import { recordSwitch, recordExhaust, recordBroken, computeHealthScore, healthBadge } from './health.js'
|
|
51
51
|
import { executeWithRetry } from './backoff.js'
|
|
52
52
|
import { getVendor } from './vendors/index.js'
|
|
53
|
+
import { versionForProvider } from './client-version-service.js'
|
|
53
54
|
|
|
54
55
|
const SKEW_MS = 60 * 1000
|
|
55
56
|
const USAGE_TTL_MS = 2 * 60 * 1000
|
|
@@ -99,6 +100,8 @@ export function vendorConfig(provider, cfg) {
|
|
|
99
100
|
return String(value).trim()
|
|
100
101
|
}
|
|
101
102
|
const modelsKey = `${provider}Models`
|
|
103
|
+
const manualVersion = provider === 'claude' ? pick('CcVersion', '') : pick('ClientVersion', '')
|
|
104
|
+
const resolvedVersion = versionForProvider(provider, manualVersion, d.clientVersion || d.claudeCcVersion || '')
|
|
102
105
|
return {
|
|
103
106
|
clientId: pick('ClientId', d.clientId),
|
|
104
107
|
clientSecret: pick('ClientSecret', d.clientSecret || ''),
|
|
@@ -106,7 +109,8 @@ export function vendorConfig(provider, cfg) {
|
|
|
106
109
|
baseUrl: pick('BaseUrl', d.baseUrl || ''),
|
|
107
110
|
originator: pick('Originator', d.originator || ''),
|
|
108
111
|
systemPrefix: pick('SystemPrefix', d.systemPrefix || ''),
|
|
109
|
-
clientVersion:
|
|
112
|
+
clientVersion: resolvedVersion.version,
|
|
113
|
+
...(provider === 'claude' ? { claudeCcVersion: resolvedVersion.version } : {}),
|
|
110
114
|
// #93/#92: per-vendor request shaping (codex only consumes these today).
|
|
111
115
|
verbosity: pick('Verbosity', ''),
|
|
112
116
|
fastMode: !!(cfg && cfg[`${provider}FastMode`]),
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { execFileSync } from 'node:child_process'
|
|
2
|
+
import { resolveClientVersion } from './client-version.js'
|
|
3
|
+
|
|
4
|
+
const PACKAGES = { codex: '@openai/codex', claude: '@anthropic-ai/claude-code' }
|
|
5
|
+
const BINARIES = { codex: 'codex', claude: 'claude' }
|
|
6
|
+
const TTL_MS = 6 * 60 * 60 * 1000
|
|
7
|
+
const state = {
|
|
8
|
+
codex: { npm: '', local: '', at: 0 },
|
|
9
|
+
claude: { npm: '', local: '', at: 0 },
|
|
10
|
+
}
|
|
11
|
+
let inflight = null
|
|
12
|
+
|
|
13
|
+
function npmMetaUrl(name) {
|
|
14
|
+
return 'https://registry.npmjs.org/' + encodeURIComponent(name) + '/latest'
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function localVersion(binary) {
|
|
18
|
+
try {
|
|
19
|
+
const out = execFileSync(binary, ['--version'], { encoding: 'utf8', timeout: 1500, stdio: ['ignore', 'pipe', 'ignore'] })
|
|
20
|
+
const m = /\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?/.exec(String(out))
|
|
21
|
+
return m ? m[0] : ''
|
|
22
|
+
} catch { return '' }
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async function npmVersion(name, fetchImpl, signal) {
|
|
26
|
+
try {
|
|
27
|
+
const res = await fetchImpl(npmMetaUrl(name), { headers: { Accept: 'application/json' }, signal })
|
|
28
|
+
if (!res.ok) return ''
|
|
29
|
+
const json = await res.json()
|
|
30
|
+
return typeof json?.version === 'string' ? json.version : ''
|
|
31
|
+
} catch { return '' }
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function clientVersionInfo(fallbacks = {}, configs = {}) {
|
|
35
|
+
const out = {}
|
|
36
|
+
for (const provider of Object.keys(PACKAGES)) {
|
|
37
|
+
const npm = state[provider].npm
|
|
38
|
+
const local = state[provider].local
|
|
39
|
+
out[provider] = resolveClientVersion({
|
|
40
|
+
configValue: configs[provider] || '',
|
|
41
|
+
npmVersion: npm,
|
|
42
|
+
localVersion: local,
|
|
43
|
+
fallback: fallbacks[provider] || '',
|
|
44
|
+
})
|
|
45
|
+
}
|
|
46
|
+
return out
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** @param {{ fetchImpl?: typeof fetch, force?: boolean, signal?: AbortSignal, localVersionFn?: (binary: string) => string }} [options] */
|
|
50
|
+
export async function refreshClientVersions({ fetchImpl = fetch, force = false, signal, localVersionFn = localVersion } = {}) {
|
|
51
|
+
const now = Date.now()
|
|
52
|
+
if (!force && Object.values(state).every((row) => row.at && now - row.at < TTL_MS)) return clientVersionInfo()
|
|
53
|
+
if (inflight) return inflight
|
|
54
|
+
inflight = (async () => {
|
|
55
|
+
for (const [provider, name] of Object.entries(PACKAGES)) {
|
|
56
|
+
const npm = await npmVersion(name, fetchImpl, signal)
|
|
57
|
+
const local = localVersionFn(BINARIES[provider])
|
|
58
|
+
state[provider] = { npm, local, at: now }
|
|
59
|
+
}
|
|
60
|
+
return clientVersionInfo()
|
|
61
|
+
})().finally(() => { inflight = null })
|
|
62
|
+
return inflight
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function resetClientVersionCache() {
|
|
66
|
+
for (const key of Object.keys(state)) state[key] = { npm: '', local: '', at: 0 }
|
|
67
|
+
inflight = null
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function versionForProvider(provider, configValue, fallback) {
|
|
71
|
+
const cached = state[provider] || {}
|
|
72
|
+
return resolveClientVersion({ configValue, npmVersion: cached.npm, localVersion: cached.local, fallback })
|
|
73
|
+
}
|
package/lib/client-version.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
/** #798: choose a client version with deterministic source precedence.
|
|
1
|
+
/** #798: choose a client version with deterministic source precedence.
|
|
2
|
+
* @param {{ configValue?: string, npmVersion?: string, localVersion?: string, fallback?: string }} [options]
|
|
3
|
+
*/
|
|
2
4
|
export function resolveClientVersion({ configValue, npmVersion, localVersion, fallback } = {}) {
|
|
3
5
|
const values = [
|
|
4
6
|
['config', configValue],
|
package/lib/client.js
CHANGED
|
@@ -249,6 +249,7 @@ window.__ModuleLoader__.load({
|
|
|
249
249
|
"subsLogged": "connected",
|
|
250
250
|
"subsNotLogged": "not connected",
|
|
251
251
|
"subsCooldown": "cooldown",
|
|
252
|
+
"clientVersions": "Client versions",
|
|
252
253
|
"settingsLoading": "Loading settings…",
|
|
253
254
|
"settingsUnavailable": "Settings are unavailable",
|
|
254
255
|
"settingsRetry": "Retry",
|
|
@@ -656,6 +657,7 @@ window.__ModuleLoader__.load({
|
|
|
656
657
|
"subsLogged": "已连接",
|
|
657
658
|
"subsNotLogged": "未连接",
|
|
658
659
|
"subsCooldown": "冷却中",
|
|
660
|
+
"clientVersions": "客户端版本",
|
|
659
661
|
"settingsLoading": "正在加载设置…",
|
|
660
662
|
"settingsUnavailable": "设置加载失败",
|
|
661
663
|
"settingsRetry": "重试",
|
|
@@ -1103,6 +1105,7 @@ window.__ModuleLoader__.load({
|
|
|
1103
1105
|
const [accounts, setAccounts] = React.useState([])
|
|
1104
1106
|
const [providers, setProviders] = React.useState([])
|
|
1105
1107
|
const [modelsCatalog, setModelsCatalog] = React.useState([])
|
|
1108
|
+
const [clientVersions, setClientVersions] = React.useState(null)
|
|
1106
1109
|
const [modelQuery, setModelQuery] = React.useState('')
|
|
1107
1110
|
const [modelSort, setModelSort] = React.useState('name')
|
|
1108
1111
|
const [modelLimit, setModelLimit] = React.useState(40)
|
|
@@ -1490,6 +1493,7 @@ window.__ModuleLoader__.load({
|
|
|
1490
1493
|
setAccounts((data && data.accounts) || [])
|
|
1491
1494
|
setProviders((data && data.providers) || [])
|
|
1492
1495
|
setModelsCatalog((data && data.modelsCatalog) || [])
|
|
1496
|
+
setClientVersions((data && data.clientVersions) || null)
|
|
1493
1497
|
}
|
|
1494
1498
|
|
|
1495
1499
|
let reloadSeq = 0
|
|
@@ -1969,6 +1973,14 @@ window.__ModuleLoader__.load({
|
|
|
1969
1973
|
className: updater.message.includes(t('updateSuccessRestart')) ? 'dsub-alert-ok' : 'dsub-alert-bad',
|
|
1970
1974
|
style: { marginBottom: '12px' }
|
|
1971
1975
|
}, updater.message) : null,
|
|
1976
|
+
clientVersions ? h('div', {
|
|
1977
|
+
'data-pane': 'overview',
|
|
1978
|
+
className: 'dsub-sub',
|
|
1979
|
+
style: { marginBottom: '10px' },
|
|
1980
|
+
}, t('clientVersions') + ': ' + ['codex', 'claude'].map((provider) => {
|
|
1981
|
+
const row = clientVersions[provider]
|
|
1982
|
+
return provider + ' ' + (row && row.version ? row.version : '?') + ' (' + (row && row.source || 'none') + ')'
|
|
1983
|
+
}).join(' · ')) : null,
|
|
1972
1984
|
smoke.result ? h('div', {
|
|
1973
1985
|
'data-pane': 'overview',
|
|
1974
1986
|
className: smoke.result.ok ? 'dsub-alert-ok' : 'dsub-alert-bad',
|
package/lib/compat.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/** #816: normalize host API shape differences in one place. */
|
|
2
2
|
export function toolCallId(value) {
|
|
3
3
|
if (!value || typeof value !== 'object') return ''
|
|
4
|
-
return String(value.toolCallId || value.tool_call_id || value.callId || value.call_id || value.id || '')
|
|
4
|
+
return String(value.toolCallId || value.tool_call_id || value.toolUseId || value.tool_use_id || value.callId || value.call_id || value.id || '')
|
|
5
5
|
}
|
|
6
6
|
|
|
7
7
|
export function normalizeRpcResult(value) {
|
|
@@ -14,6 +14,7 @@ export function normalizeRpcResult(value) {
|
|
|
14
14
|
|
|
15
15
|
export function hostCompatibilityError(detail) {
|
|
16
16
|
const suffix = detail ? `: ${String(detail)}` : ''
|
|
17
|
+
/** @type {Error & { code?: string }} */
|
|
17
18
|
const err = new Error(`unsupported DSH host API${suffix}`)
|
|
18
19
|
err.code = 'DSH_HOST_INCOMPATIBLE'
|
|
19
20
|
return err
|
package/lib/config-schema.js
CHANGED
|
@@ -117,7 +117,11 @@ export const Config = z.object({
|
|
|
117
117
|
codexRedirectUri: z.string().volatile().default(''),
|
|
118
118
|
codexBaseUrl: z.string().volatile().default(''),
|
|
119
119
|
codexClientVersion: z.string().volatile().default('')
|
|
120
|
-
.description('Codex CLI identity version for /models catalog gating (e.g. 0.160.0). Empty uses
|
|
120
|
+
.description('Codex CLI identity version for /models catalog gating (e.g. 0.160.0). Empty uses npm/local/built-in resolution.'),
|
|
121
|
+
codexWebSearch: z.boolean().volatile().default(true)
|
|
122
|
+
.description('#815 Expose Codex subscription search as the host web_search provider. Off releases the seam.'),
|
|
123
|
+
codexWebSearchModel: z.string().volatile().default('gpt-5.6-luna')
|
|
124
|
+
.description('#815 Model used for Codex web-search turns.'),
|
|
121
125
|
claudeClientId: z.string().volatile().default(''),
|
|
122
126
|
claudeRedirectUri: z.string().volatile().default(''),
|
|
123
127
|
grokClientId: z.string().volatile().default(''),
|
package/lib/index.js
CHANGED
|
@@ -24,6 +24,11 @@ import { createResetCreditService } from './reset-credits.js'
|
|
|
24
24
|
import { maskEmail, maskLabel } from './mask.js'
|
|
25
25
|
import { proxyFetch } from './proxy.js'
|
|
26
26
|
import { shouldBypassProxy } from './feature-helpers.js'
|
|
27
|
+
import { refreshClientVersions } from './client-version-service.js'
|
|
28
|
+
import { normalizeRpcResult, hostCompatibilityError } from './compat.js'
|
|
29
|
+
import { CodexWebSearchProvider } from './web-search-provider.js'
|
|
30
|
+
import { identityHeaders } from './vendors/codex.js'
|
|
31
|
+
import { webSearchRetryDelay } from './feature-helpers.js'
|
|
27
32
|
import { HistoryStore, resolveHistoryDir } from './history.js'
|
|
28
33
|
import { createDiagnosticsReport } from './diagnostics.js'
|
|
29
34
|
import { createAccountsView } from './accounts-view.js'
|
|
@@ -49,7 +54,7 @@ export { pushToWebdav, pullFromWebdav, pushToGist, pullFromGist, buildS3SigV4Hea
|
|
|
49
54
|
import { syncVaultPush, syncVaultPull, getVaultSyncStatus } from './vault-sync.js'
|
|
50
55
|
|
|
51
56
|
export const name = '@goodandready/dsh-subscriptions'
|
|
52
|
-
export const inject = ['llm', 'credentials', 'webServer', 'settings', 'tools']
|
|
57
|
+
export const inject = ['llm', 'credentials', 'webServer', 'settings', 'tools', 'web']
|
|
53
58
|
|
|
54
59
|
const NS = 'dsh-subscriptions'
|
|
55
60
|
|
|
@@ -168,11 +173,16 @@ export function apply(ctx, rawConfig) {
|
|
|
168
173
|
const parsed = plainConfig(next)
|
|
169
174
|
const prevConfig = cachedConfig
|
|
170
175
|
try {
|
|
176
|
+
let rpc
|
|
171
177
|
if (typeof settingsSvc?.update === 'function') {
|
|
172
|
-
await settingsSvc.update(NS, structuredClone(parsed), expectedRevision !== undefined ? expectedRevision : describeRow()?.revision)
|
|
178
|
+
rpc = await settingsSvc.update(NS, structuredClone(parsed), expectedRevision !== undefined ? expectedRevision : describeRow()?.revision)
|
|
173
179
|
} else if (typeof settingsSvc?.replace === 'function') {
|
|
174
|
-
await settingsSvc.replace(NS, structuredClone(parsed), expectedRevision !== undefined ? expectedRevision : describeRow()?.revision)
|
|
180
|
+
rpc = await settingsSvc.replace(NS, structuredClone(parsed), expectedRevision !== undefined ? expectedRevision : describeRow()?.revision)
|
|
181
|
+
} else {
|
|
182
|
+
throw hostCompatibilityError('settings.update/replace is missing')
|
|
175
183
|
}
|
|
184
|
+
const result = rpc === undefined ? { ok: true } : normalizeRpcResult(rpc)
|
|
185
|
+
if (!result.ok) throw new Error('settings update was not acknowledged by the host')
|
|
176
186
|
cachedConfig = parsed
|
|
177
187
|
clearKeyRotationCache()
|
|
178
188
|
clearOllamaHealthCache()
|
|
@@ -214,6 +224,9 @@ export function apply(ctx, rawConfig) {
|
|
|
214
224
|
const row = describeRow()
|
|
215
225
|
if (row && row.value) syncSnapshot(plainConfig(Config(plainConfig(row.value))))
|
|
216
226
|
else syncSnapshot()
|
|
227
|
+
if ((live().slots || []).some((slot) => slot && (slot.provider === 'codex' || slot.provider === 'claude'))) {
|
|
228
|
+
refreshClientVersions({ fetchImpl: fetch }).catch(() => {})
|
|
229
|
+
}
|
|
217
230
|
reconcileSlots().catch(() => {})
|
|
218
231
|
} catch { /* keep existing snapshot on error */ }
|
|
219
232
|
}
|
|
@@ -228,10 +241,14 @@ export function apply(ctx, rawConfig) {
|
|
|
228
241
|
}, 'dsh-subscriptions: settings change listener')
|
|
229
242
|
}
|
|
230
243
|
|
|
231
|
-
Promise.resolve().then(() => {
|
|
244
|
+
Promise.resolve().then(async () => {
|
|
232
245
|
reconcileSlots().catch((err) => {
|
|
233
246
|
if (logger && logger.warn) logger.warn('[dsh-subscriptions] reconcileSlots initial error: ' + (err && err.message || err))
|
|
234
247
|
})
|
|
248
|
+
const logged = await store.loggedInProviders().catch(() => [])
|
|
249
|
+
if (logged.some((provider) => provider === 'codex' || provider === 'claude')) {
|
|
250
|
+
refreshClientVersions({ fetchImpl: fetch }).catch(() => {})
|
|
251
|
+
}
|
|
235
252
|
})
|
|
236
253
|
|
|
237
254
|
const fetchForRef = (ref) => {
|
|
@@ -280,6 +297,47 @@ export function apply(ctx, rawConfig) {
|
|
|
280
297
|
}
|
|
281
298
|
const recordHistory = (entry) => history.add(entry)
|
|
282
299
|
|
|
300
|
+
// #815: register Codex subscription search behind the host web seam.
|
|
301
|
+
if (ctx.web && typeof ctx.web.registerSearchProvider === 'function') {
|
|
302
|
+
const provider = new CodexWebSearchProvider({
|
|
303
|
+
available: () => !!live().codexWebSearch,
|
|
304
|
+
search: async (request, signal) => {
|
|
305
|
+
const rows = await store.listAccounts('codex')
|
|
306
|
+
const row = (rows || []).find((account) => account && account.hasToken && account.ref)
|
|
307
|
+
if (!row) throw new Error('Codex web search requires a connected Codex account')
|
|
308
|
+
const raw = await store.resolveRaw(row.ref)
|
|
309
|
+
if (!raw) throw new Error('Codex web search requires a connected Codex account')
|
|
310
|
+
const blob = await store.ensureFresh('codex', parseBlob(raw), row.ref)
|
|
311
|
+
const cfg = vendorConfig('codex', live())
|
|
312
|
+
const base = (cfg.baseUrl || 'https://chatgpt.com/backend-api/codex').replace(/\/$/, '')
|
|
313
|
+
const fetchFn = fetchForRef(row.ref) || fetch
|
|
314
|
+
let attempt = 0
|
|
315
|
+
for (;;) {
|
|
316
|
+
const res = await fetchFn(base + '/responses', {
|
|
317
|
+
method: 'POST',
|
|
318
|
+
headers: identityHeaders(blob, cfg, { 'Content-Type': 'application/json', Accept: 'application/json' }),
|
|
319
|
+
body: JSON.stringify({
|
|
320
|
+
model: live().codexWebSearchModel || 'gpt-5.6-luna',
|
|
321
|
+
input: [{ role: 'user', content: [{ type: 'input_text', text: String(request.query || '') }] }],
|
|
322
|
+
tools: [{ type: 'web_search' }],
|
|
323
|
+
stream: false,
|
|
324
|
+
}),
|
|
325
|
+
signal,
|
|
326
|
+
})
|
|
327
|
+
if (res.ok) return res.json()
|
|
328
|
+
const delay = webSearchRetryDelay(attempt, res.status)
|
|
329
|
+
if (delay == null) throw new Error('Codex web search failed: HTTP ' + res.status)
|
|
330
|
+
await new Promise((resolve) => setTimeout(resolve, delay))
|
|
331
|
+
attempt += 1
|
|
332
|
+
}
|
|
333
|
+
},
|
|
334
|
+
})
|
|
335
|
+
ctx.effect(() => {
|
|
336
|
+
const dispose = ctx.web.registerSearchProvider(provider)
|
|
337
|
+
return () => { if (typeof dispose === 'function') dispose() }
|
|
338
|
+
}, 'dsh-subscriptions: codex web search provider')
|
|
339
|
+
}
|
|
340
|
+
|
|
283
341
|
// #85: host-only reset credit service for Codex accounts.
|
|
284
342
|
const resetCredits = createResetCreditService({ loadBlob: (ref) => store.loadBlob(ref) })
|
|
285
343
|
function refForSlot(provider, indexOrAlias) {
|
package/lib/messages.js
CHANGED
|
@@ -2,6 +2,7 @@ import { createHash } from 'node:crypto'
|
|
|
2
2
|
import { extractImages, toOpenAiImage, toAnthropicImage, toGeminiImage, toCodexImage } from './images.js'
|
|
3
3
|
import { normalizeTools } from './tools-normalizer.js'
|
|
4
4
|
import { geminiFunctionDeclarations } from './gemini-schema.js'
|
|
5
|
+
import { toolCallId as callIdOf } from './compat.js'
|
|
5
6
|
|
|
6
7
|
function flattenText(content) {
|
|
7
8
|
if (typeof content === 'string') return content
|
|
@@ -45,7 +46,7 @@ export function openaiMessages(options) {
|
|
|
45
46
|
function: { name: block.name, arguments: block.arguments || '{}' },
|
|
46
47
|
})),
|
|
47
48
|
...((message.tool_calls || message.toolCalls || []).map((c) => ({
|
|
48
|
-
id: c
|
|
49
|
+
id: callIdOf(c),
|
|
49
50
|
type: 'function',
|
|
50
51
|
function: {
|
|
51
52
|
name: c.name || (c.function && c.function.name),
|
|
@@ -61,7 +62,7 @@ export function openaiMessages(options) {
|
|
|
61
62
|
continue
|
|
62
63
|
}
|
|
63
64
|
if (message.role === 'tool') {
|
|
64
|
-
const toolCallId =
|
|
65
|
+
const toolCallId = callIdOf(message)
|
|
65
66
|
const content = typeof message.content === 'string'
|
|
66
67
|
? message.content
|
|
67
68
|
: (flattenText(message.content) || JSON.stringify(message.content || ''))
|
|
@@ -89,7 +90,7 @@ export function openaiMessages(options) {
|
|
|
89
90
|
for (const result of results) {
|
|
90
91
|
out.push({
|
|
91
92
|
role: 'tool',
|
|
92
|
-
tool_call_id:
|
|
93
|
+
tool_call_id: callIdOf(result),
|
|
93
94
|
content: typeof result.content === 'string' ? result.content : (flattenText(result.content) || '(no output)'),
|
|
94
95
|
})
|
|
95
96
|
}
|
|
@@ -213,7 +214,7 @@ export function codexResponsesBody(options, fallbackInstructions, vendorCfg) {
|
|
|
213
214
|
const calls = [
|
|
214
215
|
...toolCalls(message.content),
|
|
215
216
|
...((message.tool_calls || message.toolCalls || []).map((c) => ({
|
|
216
|
-
id: c
|
|
217
|
+
id: callIdOf(c),
|
|
217
218
|
name: c.name || (c.function && c.function.name),
|
|
218
219
|
arguments: typeof c.arguments === 'string' ? c.arguments : (c.function && c.function.arguments) || c.arguments || '{}',
|
|
219
220
|
}))),
|
|
@@ -232,7 +233,7 @@ export function codexResponsesBody(options, fallbackInstructions, vendorCfg) {
|
|
|
232
233
|
}
|
|
233
234
|
if (message.role === 'tool') {
|
|
234
235
|
flush()
|
|
235
|
-
const callId =
|
|
236
|
+
const callId = callIdOf(message)
|
|
236
237
|
const outText = typeof message.content === 'string'
|
|
237
238
|
? message.content
|
|
238
239
|
: (flattenText(message.content) || JSON.stringify(message.content || ''))
|
|
@@ -259,7 +260,7 @@ export function codexResponsesBody(options, fallbackInstructions, vendorCfg) {
|
|
|
259
260
|
}
|
|
260
261
|
for (const result of results) {
|
|
261
262
|
flush()
|
|
262
|
-
const callId =
|
|
263
|
+
const callId = callIdOf(result)
|
|
263
264
|
const outText = typeof result.content === 'string'
|
|
264
265
|
? result.content
|
|
265
266
|
: (flattenText(result.content) || (result.output != null ? (typeof result.output === 'string' ? result.output : JSON.stringify(result.output)) : ''))
|
|
@@ -324,7 +325,7 @@ export function anthropicPayload(options, extraSystem) {
|
|
|
324
325
|
const calls = [
|
|
325
326
|
...toolCalls(message.content),
|
|
326
327
|
...((message.tool_calls || message.toolCalls || []).map((c) => ({
|
|
327
|
-
id: c
|
|
328
|
+
id: callIdOf(c),
|
|
328
329
|
name: c.name || (c.function && c.function.name),
|
|
329
330
|
arguments: c.arguments || (c.function && c.function.arguments) || "{}",
|
|
330
331
|
}))),
|
|
@@ -342,7 +343,7 @@ export function anthropicPayload(options, extraSystem) {
|
|
|
342
343
|
continue
|
|
343
344
|
}
|
|
344
345
|
if (message.role === "tool") {
|
|
345
|
-
const toolUseId =
|
|
346
|
+
const toolUseId = callIdOf(message)
|
|
346
347
|
const images = extractImages(message.content)
|
|
347
348
|
let content = typeof message.content === "string"
|
|
348
349
|
? message.content
|
|
@@ -375,7 +376,7 @@ export function anthropicPayload(options, extraSystem) {
|
|
|
375
376
|
if (blk) blocks.push(blk)
|
|
376
377
|
}
|
|
377
378
|
for (const result of toolResults(message.content)) {
|
|
378
|
-
const toolUseId =
|
|
379
|
+
const toolUseId = callIdOf(result)
|
|
379
380
|
const content = typeof result.content === "string"
|
|
380
381
|
? result.content
|
|
381
382
|
: (flattenText(result.content) || (result.output != null ? (typeof result.output === "string" ? result.output : JSON.stringify(result.output)) : ""))
|
package/lib/routes/status.js
CHANGED
|
@@ -30,6 +30,7 @@ import { assessHealth } from '../health-report.js'
|
|
|
30
30
|
import { probeOllama } from '../ollama-health.js'
|
|
31
31
|
import { defaultSnapshotStore } from '../vault-snapshots.js'
|
|
32
32
|
import { collectSourceChain } from './sources.js'
|
|
33
|
+
import { clientVersionInfo } from '../client-version-service.js'
|
|
33
34
|
|
|
34
35
|
export function registerStatusRoutes(ctx, state) {
|
|
35
36
|
const {
|
|
@@ -71,6 +72,13 @@ export function registerStatusRoutes(ctx, state) {
|
|
|
71
72
|
liveConfig: live(),
|
|
72
73
|
hideDeprecated: !!live().hideDeprecatedModels,
|
|
73
74
|
}),
|
|
75
|
+
clientVersions: clientVersionInfo({
|
|
76
|
+
codex: getVendor('codex').defaults().clientVersion,
|
|
77
|
+
claude: getVendor('claude').defaults().clientVersion,
|
|
78
|
+
}, {
|
|
79
|
+
codex: live().codexClientVersion,
|
|
80
|
+
claude: live().claudeCcVersion,
|
|
81
|
+
}),
|
|
74
82
|
queue: defaultConcurrencyQueue.stats(),
|
|
75
83
|
}
|
|
76
84
|
}
|
package/lib/vendors/codex.js
CHANGED
|
@@ -60,7 +60,7 @@ export function authorizeUrl(cfg, pkce) {
|
|
|
60
60
|
})
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
function identityHeaders(blob, config, extra) {
|
|
63
|
+
export function identityHeaders(blob, config, extra) {
|
|
64
64
|
const originator = headerSafeValue(config.originator || 'codex_cli_rs')
|
|
65
65
|
const activeOrgId = headerSafeValue(blob.selectedOrgId || blob.accountId || '')
|
|
66
66
|
return {
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
const MAX_BYTES = 64 * 1024
|
|
2
|
+
|
|
3
|
+
function outputText(payload) {
|
|
4
|
+
if (typeof payload?.output_text === 'string') return payload.output_text
|
|
5
|
+
if (!Array.isArray(payload?.output)) return ''
|
|
6
|
+
return payload.output
|
|
7
|
+
.flatMap((item) => Array.isArray(item?.content) ? item.content : [])
|
|
8
|
+
.filter((block) => block && (block.type === 'output_text' || block.type === 'text'))
|
|
9
|
+
.map((block) => String(block.text || ''))
|
|
10
|
+
.join('')
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function citationSources(payload) {
|
|
14
|
+
const sources = []
|
|
15
|
+
const seen = new Set()
|
|
16
|
+
const visit = (value) => {
|
|
17
|
+
if (!value || typeof value !== 'object') return
|
|
18
|
+
if (Array.isArray(value)) { value.forEach(visit); return }
|
|
19
|
+
for (const key of Object.keys(value)) {
|
|
20
|
+
if (key !== 'content' && key !== 'annotations' && key !== 'citations') continue
|
|
21
|
+
const nested = value[key]
|
|
22
|
+
if (!Array.isArray(nested)) continue
|
|
23
|
+
for (const item of nested) {
|
|
24
|
+
if (item && typeof item === 'object') visit(item)
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
const annotations = Array.isArray(value.annotations) ? value.annotations : []
|
|
28
|
+
for (const annotation of annotations) {
|
|
29
|
+
const url = String(annotation?.url || annotation?.link || '')
|
|
30
|
+
if (!url || seen.has(url)) continue
|
|
31
|
+
seen.add(url)
|
|
32
|
+
sources.push({ url, ...(annotation.title ? { title: String(annotation.title) } : {}) })
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
visit(payload?.output)
|
|
36
|
+
visit(payload?.citations)
|
|
37
|
+
return sources
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** #815: map a Codex responses payload into the host WebSearchResult contract. */
|
|
41
|
+
export function normalizeCodexSearch(payload, maxBytes = MAX_BYTES) {
|
|
42
|
+
return {
|
|
43
|
+
content: String(outputText(payload) || '').slice(0, maxBytes),
|
|
44
|
+
sources: citationSources(payload).slice(0, 50),
|
|
45
|
+
truncated: false,
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export class CodexWebSearchProvider {
|
|
50
|
+
constructor({ available, search }) {
|
|
51
|
+
this.id = 'subscriptions-codex'
|
|
52
|
+
this._available = available
|
|
53
|
+
this._search = search
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
available() {
|
|
57
|
+
try { return !!this._available() } catch { return false }
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async search(request, signal) {
|
|
61
|
+
if (!this.available()) throw new Error('Codex web search is not available')
|
|
62
|
+
return normalizeCodexSearch(await this._search(request, signal))
|
|
63
|
+
}
|
|
64
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goodandready/dsh-subscriptions",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.42",
|
|
4
4
|
"description": "Use ChatGPT Codex, Claude, Grok, Antigravity, Kimi, GLM, Cursor, Kiro, Copilot, Qwen, ERNIE, Spark, JetBrains, Perplexity, Replit, and Cody subscriptions as DeepSeek Harness LLM providers via OAuth.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|