@pure01fx/dsh-openai-codex-auth 0.13.0 → 0.13.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/CHANGELOG.md +14 -0
- package/lib/catalog.js +2 -1
- package/lib/index.d.ts +11 -0
- package/lib/index.js +57 -2
- package/lib/native-adapter.d.ts +7 -0
- package/lib/native-adapter.js +23 -3
- package/lib/native-http.js +12 -3
- package/lib/native-websocket.js +36 -6
- package/lib/responses.d.ts +13 -0
- package/lib/responses.js +23 -1
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +3 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.13.2
|
|
6
|
+
|
|
7
|
+
- Stops putting `is_error` on a replayed `function_call_output`. The Responses wire has no such field (Codex reports a failed tool through the output text and keeps its own `success` flag internal), one Lite model answers the unknown item field with a 400 `INVALID_REQUEST`, and because the item is replayed from durable history, a single failed tool call then fails every later request in that session.
|
|
8
|
+
- Logs the provider's own rejection code, message, status and request id when an HTTP/SSE failure cannot be classified into a known code, matching the existing WebSocket diagnostic, so an opaque `INVALID_REQUEST` names its cause in the operator log.
|
|
9
|
+
|
|
10
|
+
## 0.13.1
|
|
11
|
+
|
|
12
|
+
- Serves a concurrent or pool-saturated request over HTTP/SSE instead of failing the turn.
|
|
13
|
+
- Moves a session to HTTP/SSE after a 400-class WebSocket rejection, so one rejected request can no longer fail every later turn in that session.
|
|
14
|
+
- Logs the provider's own rejection code, status and message instead of discarding them.
|
|
15
|
+
- Keeps a Fast alias resolvable when the catalog could not be read and retries one empty catalog read, so a transient catalog outage no longer fails the whole provider group in the model picker; a catalog that answers without Fast still refuses it.
|
|
16
|
+
- Memoizes the managed credential per credential-file signature with one shared resolution, so the model picker's per-model resolutions no longer take a dozen concurrent writer-lock acquisitions on the credential file (measured 2.1 s for 14 concurrent readers against a 2 s lock deadline before this change).
|
|
17
|
+
- Adds `locale/en.json` and `locale/zh.json` so the DSH 0.2.0 plugin panel shows a display name and description instead of the package id.
|
|
18
|
+
|
|
5
19
|
## 0.13.0
|
|
6
20
|
|
|
7
21
|
- Targets DSH 0.2.0-rc.2 with exact SDK peers, development dependencies and runtime helpers; retains the tool-message, shell and provenance migration below.
|
package/lib/catalog.js
CHANGED
|
@@ -265,7 +265,8 @@ export class NativeCodexCatalog {
|
|
|
265
265
|
if (error instanceof LlmError && error.code === 'ABORTED')
|
|
266
266
|
throw error;
|
|
267
267
|
if (!(error instanceof LlmError) || error.code !== 'MISSING_CREDENTIAL') {
|
|
268
|
-
|
|
268
|
+
const code = error instanceof LlmError ? error.code : 'UNKNOWN';
|
|
269
|
+
this.options.warn?.(`native Codex credential is unavailable (${code}); model metadata is advisory`);
|
|
269
270
|
}
|
|
270
271
|
return { models: [] };
|
|
271
272
|
}
|
package/lib/index.d.ts
CHANGED
|
@@ -132,6 +132,8 @@ export declare class OpenAICodexAuth extends Service {
|
|
|
132
132
|
private usageAccountId;
|
|
133
133
|
private responseUsage;
|
|
134
134
|
private credentialAccountId;
|
|
135
|
+
private credentialMemo;
|
|
136
|
+
private credentialResolution;
|
|
135
137
|
private usageError;
|
|
136
138
|
private usageRefresh;
|
|
137
139
|
private usageGeneration;
|
|
@@ -163,6 +165,15 @@ export declare class OpenAICodexAuth extends Service {
|
|
|
163
165
|
private commitCredential;
|
|
164
166
|
/** Return the current managed bearer token, refreshing and migrating it when needed. */
|
|
165
167
|
bearerToken(signal?: AbortSignal): Promise<string | undefined>;
|
|
168
|
+
/**
|
|
169
|
+
* Resolve the managed credential for one native call, reusing a lock-free memo of
|
|
170
|
+
* the file it was read from. The model picker resolves one credential per listed
|
|
171
|
+
* model, so one shared resolution replaces a burst of lock acquisitions on the
|
|
172
|
+
* same credential file.
|
|
173
|
+
*/
|
|
174
|
+
private cachedManagedCredential;
|
|
175
|
+
/** Resolve once for every concurrent caller; never memo a credential the file no longer matches. */
|
|
176
|
+
private resolveManagedCredential;
|
|
166
177
|
private managedCredential;
|
|
167
178
|
private externalNativeCredential;
|
|
168
179
|
private resolveNativeCredential;
|
package/lib/index.js
CHANGED
|
@@ -6,7 +6,7 @@ import { withFileLock, writeFileAtomic } from '@deepseek-ai/dsh-atomic-write';
|
|
|
6
6
|
import { resolveDshHome } from '@deepseek-ai/dsh-home-paths';
|
|
7
7
|
import { createServer } from 'node:http';
|
|
8
8
|
import { createHash, randomBytes } from 'node:crypto';
|
|
9
|
-
import { readFile, unlink } from 'node:fs/promises';
|
|
9
|
+
import { readFile, stat, unlink } from 'node:fs/promises';
|
|
10
10
|
import { join, resolve } from 'node:path';
|
|
11
11
|
import { INVALID_CREDENTIAL_CODE, LlmError } from '@deepseek-ai/dsh-llm';
|
|
12
12
|
import { CODEX_PROVIDER, NATIVE_CODEX_PROVIDER, NativeCodexAdapter, } from './native-adapter.js';
|
|
@@ -256,6 +256,26 @@ async function readCredentialDocument(filename) {
|
|
|
256
256
|
throw error;
|
|
257
257
|
}
|
|
258
258
|
}
|
|
259
|
+
/**
|
|
260
|
+
* Cheap lock-free identity of the credential file. Every writer replaces it
|
|
261
|
+
* atomically, so both parts change and a cached read cannot outlive its file.
|
|
262
|
+
*/
|
|
263
|
+
async function credentialFileSignature(filename) {
|
|
264
|
+
try {
|
|
265
|
+
const info = await stat(filename);
|
|
266
|
+
// An atomic replacement renames a fresh file, so the inode changes with it.
|
|
267
|
+
return `${info.ino}:${info.mtimeMs}:${info.size}`;
|
|
268
|
+
}
|
|
269
|
+
catch (error) {
|
|
270
|
+
if (error.code === 'ENOENT')
|
|
271
|
+
return 'missing';
|
|
272
|
+
throw error;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
/** Whether a managed credential still avoids the pre-refresh window `bearerToken` enforces. */
|
|
276
|
+
function managedCredentialUsable(credential, now = Date.now()) {
|
|
277
|
+
return credential.expires > now + TOKEN_REFRESH_PREEMPT_MS;
|
|
278
|
+
}
|
|
259
279
|
function currentCredential(document) {
|
|
260
280
|
if (document?.currentAccountId === null || document === undefined)
|
|
261
281
|
return undefined;
|
|
@@ -722,6 +742,8 @@ export class OpenAICodexAuth extends Service {
|
|
|
722
742
|
usageAccountId;
|
|
723
743
|
responseUsage;
|
|
724
744
|
credentialAccountId;
|
|
745
|
+
credentialMemo;
|
|
746
|
+
credentialResolution;
|
|
725
747
|
usageError;
|
|
726
748
|
usageRefresh;
|
|
727
749
|
usageGeneration = 0;
|
|
@@ -1036,6 +1058,8 @@ export class OpenAICodexAuth extends Service {
|
|
|
1036
1058
|
throw failure;
|
|
1037
1059
|
}
|
|
1038
1060
|
async commitDocument(document, signal) {
|
|
1061
|
+
// A write may publish or replace the managed account, so no read memo survives it.
|
|
1062
|
+
this.credentialMemo = undefined;
|
|
1039
1063
|
const nextToken = currentCredential(document)?.access;
|
|
1040
1064
|
const previous = await this.ctx.credentials.resolve(TOKEN_REF);
|
|
1041
1065
|
throwIfCancelled(signal);
|
|
@@ -1143,6 +1167,37 @@ export class OpenAICodexAuth extends Service {
|
|
|
1143
1167
|
return access;
|
|
1144
1168
|
}
|
|
1145
1169
|
}
|
|
1170
|
+
/**
|
|
1171
|
+
* Resolve the managed credential for one native call, reusing a lock-free memo of
|
|
1172
|
+
* the file it was read from. The model picker resolves one credential per listed
|
|
1173
|
+
* model, so one shared resolution replaces a burst of lock acquisitions on the
|
|
1174
|
+
* same credential file.
|
|
1175
|
+
*/
|
|
1176
|
+
async cachedManagedCredential(signal) {
|
|
1177
|
+
const signature = await credentialFileSignature(this.filename);
|
|
1178
|
+
const memo = this.credentialMemo;
|
|
1179
|
+
if (memo !== undefined && memo.signature === signature && managedCredentialUsable(memo.credential)) {
|
|
1180
|
+
this.setCredentialAccount(memo.credential.accountId);
|
|
1181
|
+
return memo.credential;
|
|
1182
|
+
}
|
|
1183
|
+
const pending = this.credentialResolution ??= this.resolveManagedCredential(signature);
|
|
1184
|
+
return await waitForSignal(pending, signal);
|
|
1185
|
+
}
|
|
1186
|
+
/** Resolve once for every concurrent caller; never memo a credential the file no longer matches. */
|
|
1187
|
+
resolveManagedCredential(signature) {
|
|
1188
|
+
const resolution = this.managedCredential().then(async (credential) => {
|
|
1189
|
+
if (credential !== undefined && await credentialFileSignature(this.filename) === signature) {
|
|
1190
|
+
this.credentialMemo = { signature, credential };
|
|
1191
|
+
}
|
|
1192
|
+
return credential;
|
|
1193
|
+
});
|
|
1194
|
+
const settled = () => {
|
|
1195
|
+
if (this.credentialResolution === resolution)
|
|
1196
|
+
this.credentialResolution = undefined;
|
|
1197
|
+
};
|
|
1198
|
+
void resolution.then(settled, settled);
|
|
1199
|
+
return resolution;
|
|
1200
|
+
}
|
|
1146
1201
|
async managedCredential(signal) {
|
|
1147
1202
|
while (true) {
|
|
1148
1203
|
const access = await this.bearerToken(signal);
|
|
@@ -1172,7 +1227,7 @@ export class OpenAICodexAuth extends Service {
|
|
|
1172
1227
|
async resolveNativeCredential(signal) {
|
|
1173
1228
|
try {
|
|
1174
1229
|
throwIfCancelled(signal);
|
|
1175
|
-
const managed = await this.
|
|
1230
|
+
const managed = await this.cachedManagedCredential(signal);
|
|
1176
1231
|
if (managed !== undefined) {
|
|
1177
1232
|
throwIfCancelled(signal);
|
|
1178
1233
|
return { accessToken: managed.access, accountId: managed.accountId };
|
package/lib/native-adapter.d.ts
CHANGED
|
@@ -44,6 +44,13 @@ export declare class NativeCodexAdapter extends LlmAdapter {
|
|
|
44
44
|
private assertNotAborted;
|
|
45
45
|
providerInfo(provider: string): LlmProviderInfo;
|
|
46
46
|
providerRetryPolicy(provider: string): ResolvedRetryPolicy;
|
|
47
|
+
/**
|
|
48
|
+
* Read model metadata, retrying once when a fetch returned nothing. The catalog
|
|
49
|
+
* caches nothing on failure, so a momentary outage otherwise empties every model
|
|
50
|
+
* list it touches; metadata is advisory and one extra request is cheaper than a
|
|
51
|
+
* provider that disappears from the picker until the next attempt.
|
|
52
|
+
*/
|
|
53
|
+
private catalogModels;
|
|
47
54
|
listModels(provider: string): Promise<readonly LlmModelInfo[]>;
|
|
48
55
|
resolveModel(provider: string, model: string, signal?: AbortSignal): Promise<LlmResolvedModelInfo>;
|
|
49
56
|
stream(options: GenerateOptions): AsyncIterable<StreamChunk>;
|
package/lib/native-adapter.js
CHANGED
|
@@ -189,9 +189,22 @@ export class NativeCodexAdapter extends LlmAdapter {
|
|
|
189
189
|
this.assertProvider(provider);
|
|
190
190
|
return NATIVE_RETRY_POLICY;
|
|
191
191
|
}
|
|
192
|
+
/**
|
|
193
|
+
* Read model metadata, retrying once when a fetch returned nothing. The catalog
|
|
194
|
+
* caches nothing on failure, so a momentary outage otherwise empties every model
|
|
195
|
+
* list it touches; metadata is advisory and one extra request is cheaper than a
|
|
196
|
+
* provider that disappears from the picker until the next attempt.
|
|
197
|
+
*/
|
|
198
|
+
async catalogModels(signal) {
|
|
199
|
+
const first = await this.catalog?.list(signal) ?? [];
|
|
200
|
+
if (first.length > 0)
|
|
201
|
+
return first;
|
|
202
|
+
this.assertNotAborted(signal);
|
|
203
|
+
return await this.catalog?.list(signal) ?? [];
|
|
204
|
+
}
|
|
192
205
|
async listModels(provider) {
|
|
193
206
|
this.assertProvider(provider);
|
|
194
|
-
const models = await this.
|
|
207
|
+
const models = await this.catalogModels();
|
|
195
208
|
const aliases = fastRoutes(models);
|
|
196
209
|
return models
|
|
197
210
|
.filter(model => model.visibility === 'list')
|
|
@@ -207,7 +220,7 @@ export class NativeCodexAdapter extends LlmAdapter {
|
|
|
207
220
|
async resolveModel(provider, model, signal) {
|
|
208
221
|
this.assertNotAborted(signal);
|
|
209
222
|
this.assertProvider(provider);
|
|
210
|
-
const models = await this.
|
|
223
|
+
const models = await this.catalogModels(signal);
|
|
211
224
|
this.assertNotAborted(signal);
|
|
212
225
|
const exact = models.find(candidate => candidate.slug === model);
|
|
213
226
|
if (exact !== undefined)
|
|
@@ -216,7 +229,14 @@ export class NativeCodexAdapter extends LlmAdapter {
|
|
|
216
229
|
if (fast !== undefined)
|
|
217
230
|
return resolvedModel(provider, fast, true);
|
|
218
231
|
if (model.endsWith(CODEX_FAST_ALIAS_SUFFIX)) {
|
|
219
|
-
|
|
232
|
+
const base = model.slice(0, -CODEX_FAST_ALIAS_SUFFIX.length);
|
|
233
|
+
// Only a catalog that knows the base model can answer this negatively.
|
|
234
|
+
// Without any entry for it the metadata is missing rather than negative, and
|
|
235
|
+
// a caller that resolves every listed model (the picker) must not lose the
|
|
236
|
+
// whole provider group to an advisory fetch.
|
|
237
|
+
if (models.some(candidate => candidate.slug === base)) {
|
|
238
|
+
throw new LlmError('native Codex Fast is not advertised for the selected model', 'FAST_UNSUPPORTED');
|
|
239
|
+
}
|
|
220
240
|
}
|
|
221
241
|
return { provider, id: model, name: model };
|
|
222
242
|
}
|
package/lib/native-http.js
CHANGED
|
@@ -6,7 +6,7 @@ import { nativeCodexAuthorityHash } from './catalog.js';
|
|
|
6
6
|
import { nativeCodexEndpoint } from './endpoint.js';
|
|
7
7
|
import { retryAfterMs, retryError, remainingRetryDelay, sleepRetry, inheritRetryDeadline } from './retry-after.js';
|
|
8
8
|
import { NATIVE_CODEX_CONNECTION_FAILED_CODE, NATIVE_CODEX_STREAM_INTERRUPTED_CODE, isNativeCodexConnectionFailure, } from './native-adapter.js';
|
|
9
|
-
import { boundedCodexTurnState, codexRequestBody, codexResponseTurnState, streamResponses, responsesFailure, } from './responses.js';
|
|
9
|
+
import { boundedCodexTurnState, codexRequestBody, codexResponseTurnState, rejectionDiagnostic, streamResponses, responsesFailure, } from './responses.js';
|
|
10
10
|
import { hasNativeCodexReplayKind } from './replay.js';
|
|
11
11
|
import { parseCodexResponseUsageMetadata, publishCodexResponseUsage, } from './response-usage.js';
|
|
12
12
|
import { parseCodexRateLimitEvent, parseCodexRateLimitHeaders, publishCodexRateLimits, } from './rate-limits.js';
|
|
@@ -246,7 +246,7 @@ function errorFacts(response) {
|
|
|
246
246
|
: { requestId: ProviderRequestId(requestId) },
|
|
247
247
|
};
|
|
248
248
|
}
|
|
249
|
-
async function httpFailure(response, signal, telemetry) {
|
|
249
|
+
async function httpFailure(response, signal, telemetry, warn) {
|
|
250
250
|
const facts = errorFacts(response);
|
|
251
251
|
const advice = retryError('native Codex HTTP retry advice', 'SERVER', facts);
|
|
252
252
|
const fail = (message, code, options) => inheritRetryDeadline(fixedFailure(message, code, options), advice);
|
|
@@ -271,6 +271,15 @@ async function httpFailure(response, signal, telemetry) {
|
|
|
271
271
|
if (response.status >= 500) {
|
|
272
272
|
return fail('native Codex server request failed', 'SERVER', facts);
|
|
273
273
|
}
|
|
274
|
+
// Every classified refusal above names itself. This branch is the only place
|
|
275
|
+
// where the provider's own reason would otherwise be discarded, so record it
|
|
276
|
+
// for the operator without turning it into the provider-neutral message.
|
|
277
|
+
warn?.(rejectionDiagnostic('HTTP', {
|
|
278
|
+
status: response.status,
|
|
279
|
+
...detail.code === undefined ? {} : { code: detail.code },
|
|
280
|
+
...detail.message === undefined ? {} : { message: detail.message },
|
|
281
|
+
...facts?.requestId === undefined ? {} : { requestId: String(facts.requestId) },
|
|
282
|
+
}));
|
|
274
283
|
return fail('native Codex request was rejected', 'INVALID_REQUEST', facts);
|
|
275
284
|
}
|
|
276
285
|
function mappedFailure(error, watchdog, parent) {
|
|
@@ -520,7 +529,7 @@ export class NativeCodexHttpTransport {
|
|
|
520
529
|
}
|
|
521
530
|
}
|
|
522
531
|
if (!response.ok) {
|
|
523
|
-
let failure = await httpFailure(response, watchdog.signal, telemetry);
|
|
532
|
+
let failure = await httpFailure(response, watchdog.signal, telemetry, this.options.warn);
|
|
524
533
|
if (generation.signal?.aborted || watchdog.timedOut()) {
|
|
525
534
|
failure = mappedFailure(failure, watchdog, generation.signal);
|
|
526
535
|
}
|
package/lib/native-websocket.js
CHANGED
|
@@ -9,7 +9,7 @@ import { NativeCodexHttpTransport, } from './native-http.js';
|
|
|
9
9
|
import { replayableItemId } from './replay.js';
|
|
10
10
|
import { parseCodexResponseUsageMetadata, publishCodexResponseUsage, } from './response-usage.js';
|
|
11
11
|
import { parseCodexRateLimitEvent, parseCodexRateLimitHeaders, publishCodexRateLimits, } from './rate-limits.js';
|
|
12
|
-
import { ResponsesStreamTranslator, responsesFailure, codexResponseTurnState, } from './responses.js';
|
|
12
|
+
import { ResponsesStreamTranslator, rejectionDiagnostic, responsesFailure, codexResponseTurnState, } from './responses.js';
|
|
13
13
|
import { NodeNativeCodexWebSocketFactory, } from './native-websocket-socket.js';
|
|
14
14
|
import { NativeCodexWebSocketSessionState } from './native-websocket-session.js';
|
|
15
15
|
const WS_BETA = 'responses_websockets=2026-02-06';
|
|
@@ -191,6 +191,18 @@ function eventFailure(event) {
|
|
|
191
191
|
}
|
|
192
192
|
return retryError('native Codex WebSocket request was rejected', 'INVALID_REQUEST', facts);
|
|
193
193
|
}
|
|
194
|
+
/** Adapt one raw socket failure event to the shared operator-facing rejection line. */
|
|
195
|
+
function socketRejectionDiagnostic(event) {
|
|
196
|
+
const error = typeof event.error === 'object' && event.error !== null
|
|
197
|
+
? event.error : event;
|
|
198
|
+
const requestId = eventHeader(event, 'x-request-id');
|
|
199
|
+
return rejectionDiagnostic('WebSocket', {
|
|
200
|
+
...typeof event.status === 'number' ? { status: event.status } : {},
|
|
201
|
+
...typeof error.code === 'string' ? { code: error.code } : {},
|
|
202
|
+
...typeof error.message === 'string' ? { message: error.message } : {},
|
|
203
|
+
...requestId === undefined ? {} : { requestId },
|
|
204
|
+
});
|
|
205
|
+
}
|
|
194
206
|
/** Prefer WebSocket v2; once safe retries exhaust, keep that DSH session on HTTP. */
|
|
195
207
|
export class NativeCodexWebSocketTransport {
|
|
196
208
|
options;
|
|
@@ -399,8 +411,10 @@ export class NativeCodexWebSocketTransport {
|
|
|
399
411
|
publishCodexRateLimits(accountId, parseCodexRateLimitHeaders(typeof rawEvent.headers === 'object' && rawEvent.headers !== null
|
|
400
412
|
? rawEvent.headers : undefined), this.options.onRateLimits, this.options.warn);
|
|
401
413
|
const wrapped = eventFailure(rawEvent);
|
|
402
|
-
if (wrapped !== undefined)
|
|
414
|
+
if (wrapped !== undefined) {
|
|
415
|
+
this.options.warn?.(socketRejectionDiagnostic(rawEvent));
|
|
403
416
|
throw wrapped;
|
|
417
|
+
}
|
|
404
418
|
if (entry.turnState === undefined) {
|
|
405
419
|
const nextTurnState = codexResponseTurnState(event);
|
|
406
420
|
if (nextTurnState !== undefined)
|
|
@@ -478,10 +492,16 @@ export class NativeCodexWebSocketTransport {
|
|
|
478
492
|
throw failure('native Codex WebSocket transport was disposed', 'DISPOSED');
|
|
479
493
|
const key = sessionKey(generation, prepared.routingId);
|
|
480
494
|
const entry = this.entry(key);
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
495
|
+
// A second request for the same DSH session (the session-title call racing
|
|
496
|
+
// the first step, for example) cannot share one socket chain, and a
|
|
497
|
+
// saturated pool has no socket to lend. Both still have a correct transport
|
|
498
|
+
// over HTTP/SSE, so neither may fail the turn.
|
|
499
|
+
if (entry.busy || this.active.size >= this.maxSessions) {
|
|
500
|
+
this.options.warn?.(entry.busy
|
|
501
|
+
? 'native Codex WebSocket session already has an active request; serving this request over HTTP/SSE'
|
|
502
|
+
: 'native Codex WebSocket active session limit was reached; serving this request over HTTP/SSE');
|
|
503
|
+
yield* this.http.stream(activeGeneration, mode, telemetry);
|
|
504
|
+
return;
|
|
485
505
|
}
|
|
486
506
|
entry.busy = true;
|
|
487
507
|
this.active.set(entry, lifecycle);
|
|
@@ -545,6 +565,16 @@ export class NativeCodexWebSocketTransport {
|
|
|
545
565
|
throw failedStepRetry(failureValue);
|
|
546
566
|
throw failureValue;
|
|
547
567
|
}
|
|
568
|
+
// A rejected request is not a transient socket condition: retrying the
|
|
569
|
+
// socket repeats the rejection. The same payload has a correct
|
|
570
|
+
// transport over HTTP/SSE, so keep this DSH session on HTTP from now on
|
|
571
|
+
// instead of failing every later turn.
|
|
572
|
+
if (failureValue.code === 'INVALID_REQUEST') {
|
|
573
|
+
entry.disabled = true;
|
|
574
|
+
yield* this.http.stream(activeGeneration, fallbackMode(), telemetry);
|
|
575
|
+
requestCompleted = true;
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
548
578
|
// Credential resolution may need the network itself, so wait until it recovers.
|
|
549
579
|
// Once a credential is available, however, a WebSocket-only outage must consume
|
|
550
580
|
// the bounded reconnect budget and then fall back to HTTP/SSE instead of leaving
|
package/lib/responses.d.ts
CHANGED
|
@@ -54,6 +54,19 @@ export interface ResponsesUsage {
|
|
|
54
54
|
export declare function mapResponsesUsage(usage: ResponsesUsage): TokenUsage;
|
|
55
55
|
/** Classify in-band failure data without reflecting provider text. */
|
|
56
56
|
export declare function responsesFailure(code?: string, message?: string, headers?: unknown): LlmError;
|
|
57
|
+
export interface ProviderRejectionFacts {
|
|
58
|
+
status?: number;
|
|
59
|
+
code?: string;
|
|
60
|
+
message?: string;
|
|
61
|
+
requestId?: string;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* One bounded operator-facing line naming the provider's own rejection, so a
|
|
65
|
+
* transport that cannot classify a refusal still records why it happened. The
|
|
66
|
+
* provider's free-form text stays in the log; it never becomes the
|
|
67
|
+
* provider-neutral error message.
|
|
68
|
+
*/
|
|
69
|
+
export declare function rejectionDiagnostic(transport: 'HTTP' | 'WebSocket', facts: ProviderRejectionFacts): string;
|
|
57
70
|
interface ResponsesOutputItem {
|
|
58
71
|
type?: string;
|
|
59
72
|
id?: string;
|
package/lib/responses.js
CHANGED
|
@@ -77,7 +77,12 @@ export function toResponsesInput(messages, system) {
|
|
|
77
77
|
type: 'function_call_output',
|
|
78
78
|
call_id: String(message.toolCallId),
|
|
79
79
|
output: toolOutput(message.content),
|
|
80
|
-
|
|
80
|
+
// The Responses wire has no error flag on a function call output: Codex
|
|
81
|
+
// keeps its own `success` metadata off the wire and reports a failed tool
|
|
82
|
+
// only through the output text. A Lite model rejects the unknown item
|
|
83
|
+
// field with a 400 INVALID_REQUEST, and because the item is replayed from
|
|
84
|
+
// durable history, every later request in that session fails the same way.
|
|
85
|
+
// `message.isError` stays DSH-side provenance for cloud context only.
|
|
81
86
|
});
|
|
82
87
|
continue;
|
|
83
88
|
}
|
|
@@ -326,6 +331,23 @@ export function responsesFailure(code, message, headers) {
|
|
|
326
331
|
...delay === undefined ? {} : { providerRetryAfterMs: delay },
|
|
327
332
|
});
|
|
328
333
|
}
|
|
334
|
+
/**
|
|
335
|
+
* One bounded operator-facing line naming the provider's own rejection, so a
|
|
336
|
+
* transport that cannot classify a refusal still records why it happened. The
|
|
337
|
+
* provider's free-form text stays in the log; it never becomes the
|
|
338
|
+
* provider-neutral error message.
|
|
339
|
+
*/
|
|
340
|
+
export function rejectionDiagnostic(transport, facts) {
|
|
341
|
+
const code = facts.code !== undefined && facts.code.length > 0 ? facts.code : undefined;
|
|
342
|
+
const message = (facts.message ?? '').replace(/\s+/g, ' ').trim().slice(0, 300);
|
|
343
|
+
const status = facts.status === undefined ? '' : ` status=${String(facts.status)}`;
|
|
344
|
+
const requestId = facts.requestId === undefined || facts.requestId.length === 0
|
|
345
|
+
? '' : ` request_id=${facts.requestId}`;
|
|
346
|
+
const detail = [code, message.length === 0 ? undefined : message]
|
|
347
|
+
.filter((part) => part !== undefined).join(': ');
|
|
348
|
+
return `native Codex ${transport} rejected the request${status}${requestId}`
|
|
349
|
+
+ `${detail.length === 0 ? '' : ` (${detail})`}`;
|
|
350
|
+
}
|
|
329
351
|
function eventItemId(event) {
|
|
330
352
|
if (typeof event.item_id === 'string' && event.item_id.length > 0)
|
|
331
353
|
return event.item_id;
|
package/locale/en.json
ADDED
package/locale/zh.json
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pure01fx/dsh-openai-codex-auth",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.2",
|
|
4
4
|
"description": "Native ChatGPT Codex provider, device-code-first login, and same-origin Web integration for DeepSeek Harness",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"publishConfig": {
|
|
@@ -28,9 +28,11 @@
|
|
|
28
28
|
"default": "./lib/index.js"
|
|
29
29
|
},
|
|
30
30
|
"./client": "./client.js",
|
|
31
|
+
"./locale/*.json": "./locale/*.json",
|
|
31
32
|
"./package.json": "./package.json"
|
|
32
33
|
},
|
|
33
34
|
"files": [
|
|
35
|
+
"locale",
|
|
34
36
|
"lib/index.js",
|
|
35
37
|
"lib/index.d.ts",
|
|
36
38
|
"lib/catalog.js",
|