@pure01fx/dsh-openai-codex-auth 0.13.1 → 0.13.3
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 +9 -0
- package/README.md +5 -1
- package/cordis.patch.yml +5 -0
- package/lib/index.js +2 -2
- package/lib/native-adapter.d.ts +17 -1
- package/lib/native-adapter.js +43 -0
- package/lib/native-http.js +12 -3
- package/lib/native-websocket.js +11 -15
- package/lib/replay.d.ts +11 -1
- package/lib/replay.js +23 -0
- package/lib/responses.d.ts +13 -0
- package/lib/responses.js +24 -2
- package/lib/token-meter.d.ts +26 -0
- package/lib/token-meter.js +135 -0
- package/package.json +16 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.13.3
|
|
6
|
+
|
|
7
|
+
- Supplies a replacement token-meter service through the package composition patch, so Codex encrypted reasoning participates in retained-history pricing and pressure reductions after compaction without changing Harness source. Reuses ordinary host pricing, preserves fixed heuristic projections, and corrects usage anchors without double-counting reasoning output. Preserves an explicitly reported zero reasoning count so it does not trigger the missing-detail fallback.
|
|
8
|
+
|
|
9
|
+
## 0.13.2
|
|
10
|
+
|
|
11
|
+
- 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.
|
|
12
|
+
- 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.
|
|
13
|
+
|
|
5
14
|
## 0.13.1
|
|
6
15
|
|
|
7
16
|
- Serves a concurrent or pool-saturated request over HTTP/SSE instead of failing the turn.
|
package/README.md
CHANGED
|
@@ -259,7 +259,11 @@ $DSH_HOME/openai-codex-auth.json
|
|
|
259
259
|
|
|
260
260
|
从 0.6 开始,Native Adapter 是插件默认 Provider,并持有生产 route `openai-codex`。旧预览会话确实需要时,可显式设置 `nativeCompatibilityRoute: true`,让同一个 adapter 同时注册 `openai-codex-native` 兼容 route;启用兼容 route 时两者仍原子注册,任一 route 已被占用都会整体失败。默认只暴露生产 route。
|
|
261
261
|
|
|
262
|
-
|
|
262
|
+
本包提供 Codex Provider 和计量服务替代入口,不发布具体 Profile。整合包必须在挂载本插件前释放已有的 `openai-codex` route,例如从自己的 `llm-pi-ai` provider map 中移除该项;本包不会清空未知的 sibling providers。只想复用 OAuth/UI 并继续使用外部 Provider 的组合可以显式设置 `nativeAdapter: false`。从 pi-ai 切到 Native 时,旧 foreign replay 会降级为可见持久历史;反向切回 pi-ai 后,已产生 Native replay 的进行中 session 不保证可续跑,应新建 session。
|
|
263
|
+
|
|
264
|
+
`cordis.patch.yml` 会禁用 base bundle 的 `token-meter` 节点,并挂载本包 `/token-meter` 入口作为唯一的 `ctx.tokenMeter`;无需修改 DSH 源码。自定义组合应禁用自己的默认计量服务,再挂载同一入口,不能同时运行两个计量服务。服务复用宿主的文本、图片、文件和投影实现,只对源 route 与目标 route 属于同一个活跃 Native Codex adapter 的历史补充加密推理价格;`nativeAdapter: false` 和其他模型沿用宿主价格。
|
|
265
|
+
|
|
266
|
+
加密推理计量优先使用原始响应的 `usage.reasoningTokens`,每次响应计一次;缺少推理细分时以 `outputTokens` 作为生成推理的保守上界,连用量也没有时才用解码密文的字节数作体积估算。这些都是重放占用估计,不是精确的服务端输入计费。计量会同时修正节点 `tokens`、`surfaceTokens`、完整锚点与有符号的 `surfaceDeltaTokens`,服务端已计入的推理不会再加一次,压缩移除历史时会扣减隐藏占用;`heuristicTokens`、持久消息和服务端用量保持原语义。宿主 UI 的固定启发式投影仍由原实现提供,不包含本包的隐藏推理估计。
|
|
263
267
|
|
|
264
268
|
上游 Native Codex Responses 请求没有输出 token 上限字段,因此普通会话若显式设置 `maxTokens` 仍会在 Provider I/O 前失败。DSH 的压缩与会话标题辅助调用会固定携带 `maxTokens`,并分别通过 `purpose: compaction` 与 `purpose: session-title` 标识;本适配器允许这两类 hint 通过,但不会把它序列化为 `max_output_tokens`,实际输出长度仍由 Native Codex 决定。
|
|
265
269
|
|
package/cordis.patch.yml
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# Native Codex is the package default. Integration bundles own any removal or
|
|
2
2
|
# replacement of an existing openai-codex route before mounting this plugin.
|
|
3
|
+
# Replace the service provider without renaming the base token-meter row.
|
|
4
|
+
- id: token-meter
|
|
5
|
+
disabled: true
|
|
3
6
|
- insert:
|
|
4
7
|
- id: openai-codex-auth
|
|
5
8
|
name: '@pure01fx/dsh-openai-codex-auth'
|
|
9
|
+
- id: codex-token-meter
|
|
10
|
+
name: '@pure01fx/dsh-openai-codex-auth/token-meter'
|
package/lib/index.js
CHANGED
|
@@ -9,7 +9,7 @@ import { createHash, randomBytes } from 'node:crypto';
|
|
|
9
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
|
-
import { CODEX_PROVIDER, NATIVE_CODEX_PROVIDER, NativeCodexAdapter, } from './native-adapter.js';
|
|
12
|
+
import { CODEX_PROVIDER, NATIVE_CODEX_PROVIDER, NativeCodexAdapter, registerNativeCodexAdapter, } from './native-adapter.js';
|
|
13
13
|
export { CODEX_PROVIDER, NATIVE_CODEX_PROVIDER } from './native-adapter.js';
|
|
14
14
|
import { NativeCodexCatalog } from './catalog.js';
|
|
15
15
|
import { NativeCodexHttpTransport } from './native-http.js';
|
|
@@ -795,7 +795,7 @@ export class OpenAICodexAuth extends Service {
|
|
|
795
795
|
ctx.effect(() => () => { transport.dispose(); }, 'openai-codex-auth: dispose native WebSockets');
|
|
796
796
|
}
|
|
797
797
|
ctx.inject(['llm'], (llmCtx) => {
|
|
798
|
-
llmCtx
|
|
798
|
+
registerNativeCodexAdapter(llmCtx, [
|
|
799
799
|
CODEX_PROVIDER,
|
|
800
800
|
...(this.routeConfig.nativeCompatibilityRoute ? [NATIVE_CODEX_PROVIDER] : []),
|
|
801
801
|
], new NativeCodexAdapter(catalog, transport));
|
package/lib/native-adapter.d.ts
CHANGED
|
@@ -1,10 +1,26 @@
|
|
|
1
1
|
/** Native Codex adapter with live catalog and HTTP/WebSocket transport delegation. */
|
|
2
|
-
import { LlmAdapter, type GenerateOptions, type LlmModelInfo, type LlmProviderInfo, type LlmResolvedModelInfo, type ResolvedRetryPolicy, type StreamChunk } from '@deepseek-ai/dsh-llm';
|
|
2
|
+
import { LlmAdapter, type GenerateOptions, type LlmModelInfo, type LlmRuntime, type LlmProviderInfo, type LlmResolvedModelInfo, type ResolvedRetryPolicy, type StreamChunk } from '@deepseek-ai/dsh-llm';
|
|
3
|
+
import { type Context } from '@deepseek-ai/cordis';
|
|
3
4
|
import type { NativeCodexModel, NativeCodexModelCatalog } from './catalog.js';
|
|
4
5
|
/** Production provider route owned by the package native Codex adapter after M6 cutover. */
|
|
5
6
|
export declare const CODEX_PROVIDER = "openai-codex";
|
|
6
7
|
/** Compatibility route retained for sessions created during the native preview. */
|
|
7
8
|
export declare const NATIVE_CODEX_PROVIDER = "openai-codex-native";
|
|
9
|
+
/**
|
|
10
|
+
* Register native routes and their replay-pricing ownership in the same Cordis scope.
|
|
11
|
+
* @param ctx - scope owning the native adapter registration.
|
|
12
|
+
* @param providers - canonical and optional compatibility routes for this instance.
|
|
13
|
+
* @param adapter - native adapter that will replay its own continuation state.
|
|
14
|
+
*/
|
|
15
|
+
export declare function registerNativeCodexAdapter(ctx: Context, providers: string[], adapter: NativeCodexAdapter): void;
|
|
16
|
+
/**
|
|
17
|
+
* Match the runtime's same-adapter ownership rule for native continuation replay.
|
|
18
|
+
* @param runtime - current global LLM service instance.
|
|
19
|
+
* @param sourceProvider - provider that produced the historical assistant message.
|
|
20
|
+
* @param targetProvider - provider preparing the measured request.
|
|
21
|
+
* @returns whether both routes are currently owned by the same native adapter.
|
|
22
|
+
*/
|
|
23
|
+
export declare function canReplayNativeCodex(runtime: LlmRuntime, sourceProvider: string, targetProvider: string): boolean;
|
|
8
24
|
export declare const CODEX_FAST_ALIAS_SUFFIX = "-fast";
|
|
9
25
|
export declare const CODEX_FAST_SERVICE_TIER = "priority";
|
|
10
26
|
/** Pure network establishment failure; retried without consuming the finite stream budget. */
|
package/lib/native-adapter.js
CHANGED
|
@@ -1,10 +1,53 @@
|
|
|
1
1
|
/** Native Codex adapter with live catalog and HTTP/WebSocket transport delegation. */
|
|
2
2
|
import { LlmAdapter, LlmError, ReasoningEffortId, } from '@deepseek-ai/dsh-llm';
|
|
3
|
+
import { symbols } from '@deepseek-ai/cordis';
|
|
3
4
|
/** Production provider route owned by the package native Codex adapter after M6 cutover. */
|
|
4
5
|
export const CODEX_PROVIDER = 'openai-codex';
|
|
5
6
|
/** Compatibility route retained for sessions created during the native preview. */
|
|
6
7
|
export const NATIVE_CODEX_PROVIDER = 'openai-codex-native';
|
|
7
8
|
const OWNED_CODEX_PROVIDERS = new Set([CODEX_PROVIDER, NATIVE_CODEX_PROVIDER]);
|
|
9
|
+
const replayOwners = new WeakMap();
|
|
10
|
+
// Cordis returns scope-traced views; use the original service for ownership identity.
|
|
11
|
+
function runtimeKey(runtime) {
|
|
12
|
+
return Reflect.get(runtime, symbols.original) ?? runtime;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Register native routes and their replay-pricing ownership in the same Cordis scope.
|
|
16
|
+
* @param ctx - scope owning the native adapter registration.
|
|
17
|
+
* @param providers - canonical and optional compatibility routes for this instance.
|
|
18
|
+
* @param adapter - native adapter that will replay its own continuation state.
|
|
19
|
+
*/
|
|
20
|
+
export function registerNativeCodexAdapter(ctx, providers, adapter) {
|
|
21
|
+
const runtime = ctx.get('llm');
|
|
22
|
+
if (runtime === undefined)
|
|
23
|
+
throw new Error('native Codex registration requires llm');
|
|
24
|
+
ctx.llm.registerAdapter(providers, adapter);
|
|
25
|
+
ctx.effect(() => {
|
|
26
|
+
let owners = replayOwners.get(runtimeKey(runtime));
|
|
27
|
+
if (owners === undefined)
|
|
28
|
+
replayOwners.set(runtimeKey(runtime), owners = new Map());
|
|
29
|
+
for (const provider of providers)
|
|
30
|
+
owners.set(provider, adapter);
|
|
31
|
+
return () => {
|
|
32
|
+
for (const provider of providers) {
|
|
33
|
+
if (owners.get(provider) === adapter)
|
|
34
|
+
owners.delete(provider);
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Match the runtime's same-adapter ownership rule for native continuation replay.
|
|
41
|
+
* @param runtime - current global LLM service instance.
|
|
42
|
+
* @param sourceProvider - provider that produced the historical assistant message.
|
|
43
|
+
* @param targetProvider - provider preparing the measured request.
|
|
44
|
+
* @returns whether both routes are currently owned by the same native adapter.
|
|
45
|
+
*/
|
|
46
|
+
export function canReplayNativeCodex(runtime, sourceProvider, targetProvider) {
|
|
47
|
+
const owners = replayOwners.get(runtimeKey(runtime));
|
|
48
|
+
const adapter = owners?.get(targetProvider);
|
|
49
|
+
return adapter !== undefined && owners?.get(sourceProvider) === adapter;
|
|
50
|
+
}
|
|
8
51
|
export const CODEX_FAST_ALIAS_SUFFIX = '-fast';
|
|
9
52
|
export const CODEX_FAST_SERVICE_TIER = 'priority';
|
|
10
53
|
/** Pure network establishment failure; retried without consuming the finite stream budget. */
|
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,21 +191,17 @@ function eventFailure(event) {
|
|
|
191
191
|
}
|
|
192
192
|
return retryError('native Codex WebSocket request was rejected', 'INVALID_REQUEST', facts);
|
|
193
193
|
}
|
|
194
|
-
/**
|
|
195
|
-
|
|
196
|
-
* see why the socket refused a request. The provider's free-form text stays in
|
|
197
|
-
* the log; it never becomes the provider-neutral error message.
|
|
198
|
-
*/
|
|
199
|
-
function rejectionDiagnostic(event) {
|
|
194
|
+
/** Adapt one raw socket failure event to the shared operator-facing rejection line. */
|
|
195
|
+
function socketRejectionDiagnostic(event) {
|
|
200
196
|
const error = typeof event.error === 'object' && event.error !== null
|
|
201
197
|
? event.error : event;
|
|
202
|
-
const
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
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
|
+
});
|
|
209
205
|
}
|
|
210
206
|
/** Prefer WebSocket v2; once safe retries exhaust, keep that DSH session on HTTP. */
|
|
211
207
|
export class NativeCodexWebSocketTransport {
|
|
@@ -416,7 +412,7 @@ export class NativeCodexWebSocketTransport {
|
|
|
416
412
|
? rawEvent.headers : undefined), this.options.onRateLimits, this.options.warn);
|
|
417
413
|
const wrapped = eventFailure(rawEvent);
|
|
418
414
|
if (wrapped !== undefined) {
|
|
419
|
-
this.options.warn?.(
|
|
415
|
+
this.options.warn?.(socketRejectionDiagnostic(rawEvent));
|
|
420
416
|
throw wrapped;
|
|
421
417
|
}
|
|
422
418
|
if (entry.turnState === undefined) {
|
package/lib/replay.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Versioned Codex Responses continuation state. */
|
|
2
|
-
import { type ContentBlock } from '@deepseek-ai/dsh-llm';
|
|
2
|
+
import { type AssistantProviderMetadata, type ContentBlock, type TokenUsage } from '@deepseek-ai/dsh-llm';
|
|
3
3
|
export declare const NATIVE_CODEX_REPLAY_KIND = "openai-codex-native.responses-replay";
|
|
4
4
|
export declare const NATIVE_CODEX_REPLAY_VERSION = 1;
|
|
5
5
|
export type CodexMessagePhase = 'commentary' | 'partial_answer' | 'final_answer';
|
|
@@ -36,6 +36,16 @@ export interface NativeCodexReplaySource {
|
|
|
36
36
|
export declare function replayableItemId(value: string | undefined): string | undefined;
|
|
37
37
|
/** True only for legacy raw state or an rc.2 envelope emitted by this package. */
|
|
38
38
|
export declare function hasNativeCodexReplayKind(value: unknown): boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Estimate hidden reasoning only when the native continuation replays encrypted items.
|
|
41
|
+
* A response's reasoning usage is charged once, not once per reasoning item.
|
|
42
|
+
* Without reasoning details, output usage bounds the generated reasoning; without
|
|
43
|
+
* usage, decoded ciphertext bytes are a size heuristic, not exact token billing.
|
|
44
|
+
* @param source - original provider/model and native continuation state.
|
|
45
|
+
* @param usage - original response usage, when available in the durable event.
|
|
46
|
+
* @returns additional hidden replay tokens; zero for non-native or unencrypted state.
|
|
47
|
+
*/
|
|
48
|
+
export declare function estimateNativeCodexReplayTokens(source: AssistantProviderMetadata, usage?: TokenUsage): number;
|
|
39
49
|
/** Attempt-local accumulator for completed replay descriptors. */
|
|
40
50
|
export declare class NativeCodexReplayCapture {
|
|
41
51
|
private readonly provider;
|
package/lib/replay.js
CHANGED
|
@@ -125,6 +125,29 @@ function parseState(value) {
|
|
|
125
125
|
items,
|
|
126
126
|
};
|
|
127
127
|
}
|
|
128
|
+
/**
|
|
129
|
+
* Estimate hidden reasoning only when the native continuation replays encrypted items.
|
|
130
|
+
* A response's reasoning usage is charged once, not once per reasoning item.
|
|
131
|
+
* Without reasoning details, output usage bounds the generated reasoning; without
|
|
132
|
+
* usage, decoded ciphertext bytes are a size heuristic, not exact token billing.
|
|
133
|
+
* @param source - original provider/model and native continuation state.
|
|
134
|
+
* @param usage - original response usage, when available in the durable event.
|
|
135
|
+
* @returns additional hidden replay tokens; zero for non-native or unencrypted state.
|
|
136
|
+
*/
|
|
137
|
+
export function estimateNativeCodexReplayTokens(source, usage) {
|
|
138
|
+
if (!hasNativeCodexReplayKind(source.replayState))
|
|
139
|
+
return 0;
|
|
140
|
+
const state = parseState(source.replayState);
|
|
141
|
+
if (state.provider !== source.provider || state.model !== source.model) {
|
|
142
|
+
throw failure('native Codex replay identity does not match its assistant message');
|
|
143
|
+
}
|
|
144
|
+
const encrypted = state.items.flatMap(item => item.type === 'reasoning' && item.encryptedContent !== undefined ? [item.encryptedContent] : []);
|
|
145
|
+
if (encrypted.length === 0)
|
|
146
|
+
return 0;
|
|
147
|
+
if (usage !== undefined)
|
|
148
|
+
return usage.reasoningTokens ?? usage.outputTokens;
|
|
149
|
+
return encrypted.reduce((total, content) => total + Buffer.byteLength(content, 'base64'), 0);
|
|
150
|
+
}
|
|
128
151
|
/** Attempt-local accumulator for completed replay descriptors. */
|
|
129
152
|
export class NativeCodexReplayCapture {
|
|
130
153
|
provider;
|
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
|
}
|
|
@@ -284,7 +289,7 @@ export function mapResponsesUsage(usage) {
|
|
|
284
289
|
inputTokens: uncached, outputTokens: output,
|
|
285
290
|
...cached === 0 ? {} : { cacheReadTokens: cached },
|
|
286
291
|
...written === 0 ? {} : { cacheWriteTokens: written },
|
|
287
|
-
...
|
|
292
|
+
...usage.output_tokens_details?.reasoning_tokens === undefined ? {} : { reasoningTokens: reasoning },
|
|
288
293
|
};
|
|
289
294
|
}
|
|
290
295
|
function retryDelay(message) {
|
|
@@ -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;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/** Codex hidden-replay pricing layered over the host's ordinary token meter. */
|
|
2
|
+
import { TokenMeter } from '@deepseek-ai/dsh-token-meter';
|
|
3
|
+
import type { TokenMeasurement } from '@deepseek-ai/dsh-token-meter';
|
|
4
|
+
import type { EpochHeader, Session } from '@deepseek-ai/dsh-session';
|
|
5
|
+
/**
|
|
6
|
+
* Replacement tokenMeter service that charges native Codex encrypted reasoning.
|
|
7
|
+
* Ordinary prices and projections come from the host meter. Hidden prices apply
|
|
8
|
+
* only between routes currently owned by the same native adapter; fixed
|
|
9
|
+
* heuristicTokens stay unchanged. Provider usage is not charged a second time.
|
|
10
|
+
*/
|
|
11
|
+
export declare class CodexTokenMeter extends TokenMeter {
|
|
12
|
+
private readonly hiddenFolds;
|
|
13
|
+
/**
|
|
14
|
+
* Return a detached measurement including opaque native replay in node prices,
|
|
15
|
+
* surface totals, and signed anchor deltas. Recheck usage conservatism against
|
|
16
|
+
* the corrected full anchor before reusing the provider sample.
|
|
17
|
+
* @param session - session to price through its current durable tail.
|
|
18
|
+
* @param requestHeader - optional effective routed request envelope.
|
|
19
|
+
* @returns immutable request pressure and positional message prices.
|
|
20
|
+
*/
|
|
21
|
+
measure(session: Session, requestHeader?: EpochHeader): TokenMeasurement;
|
|
22
|
+
/** Track only replay metadata and surface positions; the host validates each event first. */
|
|
23
|
+
private syncHidden;
|
|
24
|
+
private foldHidden;
|
|
25
|
+
}
|
|
26
|
+
export default CodexTokenMeter;
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/** Codex hidden-replay pricing layered over the host's ordinary token meter. */
|
|
2
|
+
import { TokenMeter } from '@deepseek-ai/dsh-token-meter';
|
|
3
|
+
import { estimateToolsTokens } from '@deepseek-ai/dsh-token-meter/estimate';
|
|
4
|
+
import { assembleAssistantStream } from '@deepseek-ai/dsh-llm';
|
|
5
|
+
import { canonicalHeader, deriveEventMessage, headerEquals, isSurfaceEvent, SessionSeq, } from '@deepseek-ai/dsh-session';
|
|
6
|
+
import { canReplayNativeCodex } from './native-adapter.js';
|
|
7
|
+
import { estimateNativeCodexReplayTokens } from './replay.js';
|
|
8
|
+
function replayPrice(message, usage) {
|
|
9
|
+
if (message?.role !== 'assistant' || message.source.kind !== 'model')
|
|
10
|
+
return undefined;
|
|
11
|
+
return { source: message.source, usage };
|
|
12
|
+
}
|
|
13
|
+
function sameHeader(a, b) {
|
|
14
|
+
return a === undefined || b === undefined ? a === b : headerEquals(a, b);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Replacement tokenMeter service that charges native Codex encrypted reasoning.
|
|
18
|
+
* Ordinary prices and projections come from the host meter. Hidden prices apply
|
|
19
|
+
* only between routes currently owned by the same native adapter; fixed
|
|
20
|
+
* heuristicTokens stay unchanged. Provider usage is not charged a second time.
|
|
21
|
+
*/
|
|
22
|
+
export class CodexTokenMeter extends TokenMeter {
|
|
23
|
+
hiddenFolds = new WeakMap();
|
|
24
|
+
/**
|
|
25
|
+
* Return a detached measurement including opaque native replay in node prices,
|
|
26
|
+
* surface totals, and signed anchor deltas. Recheck usage conservatism against
|
|
27
|
+
* the corrected full anchor before reusing the provider sample.
|
|
28
|
+
* @param session - session to price through its current durable tail.
|
|
29
|
+
* @param requestHeader - optional effective routed request envelope.
|
|
30
|
+
* @returns immutable request pressure and positional message prices.
|
|
31
|
+
*/
|
|
32
|
+
measure(session, requestHeader) {
|
|
33
|
+
const base = super.measure(session, requestHeader);
|
|
34
|
+
const state = this.syncHidden(session, base.logRevision);
|
|
35
|
+
const header = requestHeader === undefined ? state.header : canonicalHeader(requestHeader);
|
|
36
|
+
const runtime = this.ctx.get('llm');
|
|
37
|
+
const provider = header?.config?.provider;
|
|
38
|
+
if (runtime === undefined || provider === undefined)
|
|
39
|
+
return base;
|
|
40
|
+
const price = (replay) => {
|
|
41
|
+
if (replay === undefined || !canReplayNativeCodex(runtime, replay.source.provider, provider))
|
|
42
|
+
return 0;
|
|
43
|
+
return replay.tokens ??= estimateNativeCodexReplayTokens(replay.source, replay.usage);
|
|
44
|
+
};
|
|
45
|
+
const current = new Map(state.nodes.map(node => [node.seq, price(node.replay)]));
|
|
46
|
+
const hiddenCurrent = base.nodes.reduce((total, node) => total + (current.get(node.seq) ?? 0), 0);
|
|
47
|
+
const anchor = state.anchor;
|
|
48
|
+
const matched = anchor !== undefined && sameHeader(anchor.header, header);
|
|
49
|
+
const hiddenAnchor = matched
|
|
50
|
+
? anchor.nodes.reduce((total, node) => total + price(node.replay), 0) + price(anchor.assistant)
|
|
51
|
+
: 0;
|
|
52
|
+
if (hiddenCurrent === 0 && hiddenAnchor === 0)
|
|
53
|
+
return base;
|
|
54
|
+
let baseline = base.baseline;
|
|
55
|
+
let surfaceDeltaTokens = base.surfaceDeltaTokens;
|
|
56
|
+
if (matched) {
|
|
57
|
+
surfaceDeltaTokens += hiddenCurrent - hiddenAnchor;
|
|
58
|
+
if (base.baseline.kind === 'usage') {
|
|
59
|
+
const estimatedAnchor = estimateToolsTokens(header)
|
|
60
|
+
+ base.surfaceTokens - base.surfaceDeltaTokens + hiddenAnchor;
|
|
61
|
+
if (base.baseline.tokens < estimatedAnchor) {
|
|
62
|
+
baseline = Object.freeze({ kind: 'estimated', tokens: estimatedAnchor });
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
else if (base.baseline.kind === 'estimated') {
|
|
66
|
+
baseline = Object.freeze({ kind: 'estimated', tokens: base.baseline.tokens + hiddenAnchor });
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
else if (base.baseline.kind === 'estimated') {
|
|
70
|
+
baseline = Object.freeze({ kind: 'estimated', tokens: base.baseline.tokens + hiddenCurrent });
|
|
71
|
+
}
|
|
72
|
+
const nodes = Object.freeze(base.nodes.map(node => Object.freeze({
|
|
73
|
+
...node, tokens: node.tokens + (current.get(node.seq) ?? 0),
|
|
74
|
+
})));
|
|
75
|
+
return Object.freeze({
|
|
76
|
+
...base,
|
|
77
|
+
baseline,
|
|
78
|
+
surfaceDeltaTokens,
|
|
79
|
+
totalTokens: Math.max(0, baseline.tokens + surfaceDeltaTokens),
|
|
80
|
+
surfaceTokens: base.surfaceTokens + hiddenCurrent,
|
|
81
|
+
nodes,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
/** Track only replay metadata and surface positions; the host validates each event first. */
|
|
85
|
+
syncHidden(session, revision) {
|
|
86
|
+
let state = this.hiddenFolds.get(session);
|
|
87
|
+
if (state === undefined) {
|
|
88
|
+
state = { consumed: 0, header: undefined, nodes: [], anchor: undefined };
|
|
89
|
+
this.hiddenFolds.set(session, state);
|
|
90
|
+
}
|
|
91
|
+
while (state.consumed < revision) {
|
|
92
|
+
const event = session.eventAt(SessionSeq(state.consumed));
|
|
93
|
+
if (event === undefined)
|
|
94
|
+
throw new Error('Codex token meter: missing consumed session event');
|
|
95
|
+
this.foldHidden(state, event);
|
|
96
|
+
state.consumed++;
|
|
97
|
+
}
|
|
98
|
+
return state;
|
|
99
|
+
}
|
|
100
|
+
foldHidden(state, event) {
|
|
101
|
+
const header = event.type === 'request/header' ? canonicalHeader(event.data.header) : state.header;
|
|
102
|
+
const node = isSurfaceEvent(event) ? {
|
|
103
|
+
seq: event.seq,
|
|
104
|
+
replay: replayPrice(deriveEventMessage(event), event.type === 'assistant/message' ? event.data.usage : undefined),
|
|
105
|
+
} : undefined;
|
|
106
|
+
let anchor = state.anchor;
|
|
107
|
+
if (event.type === 'assistant/message') {
|
|
108
|
+
const route = header?.config;
|
|
109
|
+
const original = event.data.usage !== undefined && route !== undefined
|
|
110
|
+
? { source: {
|
|
111
|
+
provider: route.provider,
|
|
112
|
+
model: route.model,
|
|
113
|
+
replayState: assembleAssistantStream(event.data.stream).replayState,
|
|
114
|
+
}, usage: event.data.usage }
|
|
115
|
+
: node?.replay;
|
|
116
|
+
anchor = { header, nodes: state.nodes.slice(), assistant: original };
|
|
117
|
+
}
|
|
118
|
+
if (node !== undefined && isSurfaceEvent(event)) {
|
|
119
|
+
if (event.surfaceOp === 'append') {
|
|
120
|
+
state.nodes.push(node);
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
const { startSeq, endSeq } = event.surfaceOp;
|
|
124
|
+
const first = state.nodes.findIndex(item => item.seq === startSeq);
|
|
125
|
+
const last = state.nodes.findIndex(item => item.seq === endSeq);
|
|
126
|
+
if (first < 0 || last < first)
|
|
127
|
+
throw new Error('Codex token meter: invalid surface replacement');
|
|
128
|
+
state.nodes.splice(first, last - first + 1, node);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
state.header = header;
|
|
132
|
+
state.anchor = anchor;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
export default CodexTokenMeter;
|
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.3",
|
|
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": {
|
|
@@ -27,6 +27,10 @@
|
|
|
27
27
|
"types": "./lib/index.d.ts",
|
|
28
28
|
"default": "./lib/index.js"
|
|
29
29
|
},
|
|
30
|
+
"./token-meter": {
|
|
31
|
+
"types": "./lib/token-meter.d.ts",
|
|
32
|
+
"default": "./lib/token-meter.js"
|
|
33
|
+
},
|
|
30
34
|
"./client": "./client.js",
|
|
31
35
|
"./locale/*.json": "./locale/*.json",
|
|
32
36
|
"./package.json": "./package.json"
|
|
@@ -55,6 +59,8 @@
|
|
|
55
59
|
"lib/responses.d.ts",
|
|
56
60
|
"lib/replay.js",
|
|
57
61
|
"lib/replay.d.ts",
|
|
62
|
+
"lib/token-meter.js",
|
|
63
|
+
"lib/token-meter.d.ts",
|
|
58
64
|
"lib/retry-after.js",
|
|
59
65
|
"lib/retry-after.d.ts",
|
|
60
66
|
"lib/rate-limits.js",
|
|
@@ -116,12 +122,14 @@
|
|
|
116
122
|
}
|
|
117
123
|
},
|
|
118
124
|
"peerDependencies": {
|
|
125
|
+
"@deepseek-ai/dsh-session": "0.2.0-rc.2",
|
|
119
126
|
"@deepseek-ai/cordis": "~4.0.4",
|
|
120
127
|
"@deepseek-ai/dsh-attachment": "0.2.0-rc.2",
|
|
121
128
|
"@deepseek-ai/dsh-credentials": "0.2.0-rc.2",
|
|
122
129
|
"@deepseek-ai/dsh-fs": "0.2.0-rc.2",
|
|
123
130
|
"@deepseek-ai/dsh-host-webserver": "0.2.0-rc.2",
|
|
124
131
|
"@deepseek-ai/dsh-llm": "0.2.0-rc.2",
|
|
132
|
+
"@deepseek-ai/dsh-token-meter": "0.2.0-rc.2",
|
|
125
133
|
"@deepseek-ai/dsh-sandbox": "0.2.0-rc.2",
|
|
126
134
|
"@deepseek-ai/dsh-sandbox-policy": "0.2.0-rc.2",
|
|
127
135
|
"@deepseek-ai/dsh-shell": "0.2.0-rc.2",
|
|
@@ -138,7 +146,13 @@
|
|
|
138
146
|
},
|
|
139
147
|
"devDependencies": {
|
|
140
148
|
"@deepseek-ai/cordis": "~4.0.4",
|
|
149
|
+
"@deepseek-ai/cordis-plugin-loader": "1.0.5",
|
|
150
|
+
"@deepseek-ai/cordis-plugin-include": "1.0.9",
|
|
151
|
+
"js-yaml": "^4.1.0",
|
|
152
|
+
"@types/js-yaml": "^4.0.9",
|
|
141
153
|
"@deepseek-ai/dsh-agent": "0.2.0-rc.2",
|
|
154
|
+
"@deepseek-ai/dsh-compaction-basic": "0.2.0-rc.2",
|
|
155
|
+
"@deepseek-ai/dsh-session-projection": "0.2.0-rc.2",
|
|
142
156
|
"@deepseek-ai/dsh-attachment": "0.2.0-rc.2",
|
|
143
157
|
"@deepseek-ai/dsh-attachment-local": "0.2.0-rc.2",
|
|
144
158
|
"@deepseek-ai/dsh-client-store": "0.2.0-rc.2",
|
|
@@ -147,6 +161,7 @@
|
|
|
147
161
|
"@deepseek-ai/dsh-fs": "0.2.0-rc.2",
|
|
148
162
|
"@deepseek-ai/dsh-host-webserver": "0.2.0-rc.2",
|
|
149
163
|
"@deepseek-ai/dsh-llm": "0.2.0-rc.2",
|
|
164
|
+
"@deepseek-ai/dsh-token-meter": "0.2.0-rc.2",
|
|
150
165
|
"@deepseek-ai/dsh-sandbox": "0.2.0-rc.2",
|
|
151
166
|
"@deepseek-ai/dsh-sandbox-policy": "0.2.0-rc.2",
|
|
152
167
|
"@deepseek-ai/dsh-scope": "0.2.0-rc.2",
|