@pure01fx/dsh-openai-codex-auth 0.13.2 → 0.13.4

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 CHANGED
@@ -2,6 +2,14 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.13.4
6
+
7
+ - Extends the replacement meter to supply the UI context-pressure projection. Estimated occupancy includes native encrypted replay while measured input remains unchanged; per-session request routes, model-window matching, and compatible sample selection isolate conversations and model switches. Projection checkpoints recompute policy-dependent views on restore, and ownership changes rebuild the single pressure registration.
8
+
9
+ ## 0.13.3
10
+
11
+ - 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.
12
+
5
13
  ## 0.13.2
6
14
 
7
15
  - 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.
package/README.md CHANGED
@@ -253,13 +253,45 @@ $DSH_HOME/openai-codex-auth.json
253
253
 
254
254
  `path` 的优先级高于 `dshHome`。
255
255
 
256
+ ### 关闭或恢复计量服务替代
257
+
258
+ 计量服务的选择由组合节点控制,目前不是 `openai-codex-auth` 的 `config` 开关。要保留 Codex Provider、OAuth 和 UI 功能,同时恢复 DSH 默认计量器,将以下覆盖项放到 profile 最后应用的补丁中(在本包补丁之后):
259
+
260
+ ```yaml
261
+ - id: token-meter
262
+ disabled: false
263
+
264
+ - id: codex-token-meter
265
+ disabled: true
266
+ ```
267
+
268
+ 两项必须同时设置,确保只有一个 `ctx.tokenMeter`;关闭替代后,本包的隐藏推理保留预算修正及 UI 预计占用修正都会停用。`nativeAdapter: false` 只切换 Provider 行为,不会恢复 base 的计量节点。自定义 profile 如果使用不同节点 ID,应替换上面的 ID。
269
+
270
+ 保存覆盖配置后重新启动该 profile,使服务选择在启动时生效,避免在线切换过程中出现两个计量服务同时挂载。
271
+
272
+ 要重新启用本包计量器,反向覆盖这两项并重新启动 profile:
273
+
274
+ ```yaml
275
+ - id: token-meter
276
+ disabled: true
277
+
278
+ - id: codex-token-meter
279
+ disabled: false
280
+ ```
281
+
256
282
  ### 原生 Codex transport 与组合边界
257
283
 
258
284
  本包跟踪的上游 Codex repository、commit、release 与 Catalog client version 只在 `src/upstream.ts` 维护;升级上游时先更新该文件,再刷新 fixtures、生成产物和兼容测试,避免多个散落 hash 漂移。
259
285
 
260
286
  从 0.6 开始,Native Adapter 是插件默认 Provider,并持有生产 route `openai-codex`。旧预览会话确实需要时,可显式设置 `nativeCompatibilityRoute: true`,让同一个 adapter 同时注册 `openai-codex-native` 兼容 route;启用兼容 route 时两者仍原子注册,任一 route 已被占用都会整体失败。默认只暴露生产 route。
261
287
 
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。
288
+ 本包提供 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。
289
+
290
+ `cordis.patch.yml` 会禁用 base bundle 的 `token-meter` 节点,并挂载本包 `/token-meter` 入口作为唯一的 `ctx.tokenMeter`;无需修改 DSH 源码。自定义组合应禁用自己的默认计量服务,再挂载同一入口,不能同时运行两个计量服务。服务复用宿主的文本、图片、文件计价及用量、组成投影,只对源 route 与目标 route 属于同一个活跃 Native Codex adapter 的历史补充加密推理价格;`nativeAdapter: false` 和其他模型沿用宿主价格。
291
+
292
+ 加密推理计量优先使用原始响应的 `usage.reasoningTokens`,每次响应计一次;缺少推理细分时以 `outputTokens` 作为生成推理的保守上界,连用量也没有时才用解码密文的字节数作体积估算。这些都是重放占用估计,不是精确的服务端输入计费。计量会同时修正节点 `tokens`、`surfaceTokens`、完整锚点与有符号的 `surfaceDeltaTokens`,服务端已计入的推理不会再加一次,压缩移除历史时会扣减隐藏占用;`heuristicTokens`、持久消息和服务端用量保持原语义。UI 的 `contextPressure` 由本包注册一个纯投影,复用同一隐藏推理定价函数;`pressureTokens` 仍是最近服务端实测输入,`projectedTokens` 是预计下一次请求占用,适用的加密推理会计入增长和压缩扣减。每个对话根据已提交的 `request/header` 独立选择路由,稳定的其他模型对话沿用原投影规则;模型或请求信封改变后重新估算,并在匹配的 `request/context` 到达前不显示旧模型窗口。
293
+
294
+ UI 投影的缓存保存与当前路由策略无关的每来源价格和采样总量,读取旧格式缓存会完整回放;同格式缓存读取时重新计算展示值,因此重启、分支恢复及 Native 路由归属变化不会沿用旧展示缓存。无法确认兼容性的跨 route 用量样本会使用完整估算,实测输入字段不变。路由归属变化会重建投影;宿主注册接口不主动推送空闲对话,现有订阅在下一次会话事件或快照读取时看到新值。尚未提交为请求信封的模型选择不属于该日志投影的输入。
263
295
 
264
296
  上游 Native Codex Responses 请求没有输出 token 上限字段,因此普通会话若显式设置 `maxTokens` 仍会在 Provider I/O 前失败。DSH 的压缩与会话标题辅助调用会固定携带 `maxTokens`,并分别通过 `purpose: compaction` 与 `purpose: session-title` 标识;本适配器允许这两类 hint 通过,但不会把它序列化为 `max_output_tokens`,实际输出长度仍由 Native Codex 决定。
265
297
 
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.llm.registerAdapter([
798
+ registerNativeCodexAdapter(llmCtx, [
799
799
  CODEX_PROVIDER,
800
800
  ...(this.routeConfig.nativeCompatibilityRoute ? [NATIVE_CODEX_PROVIDER] : []),
801
801
  ], new NativeCodexAdapter(catalog, transport));
@@ -1,10 +1,41 @@
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
+ declare module '@deepseek-ai/cordis' {
10
+ interface Events {
11
+ /**
12
+ * Native replay ownership changed.
13
+ * @mode emit
14
+ */
15
+ 'codex/replay-owners-updated'(): void;
16
+ }
17
+ }
18
+ /**
19
+ * Snapshot immutable provider groups for a pure projection's replay policy.
20
+ * @param runtime - current LLM service, or undefined before it is available.
21
+ * @returns sorted groups; each group is owned by one native adapter instance.
22
+ */
23
+ export declare function nativeCodexReplayGroups(runtime: LlmRuntime | undefined): readonly (readonly string[])[];
24
+ /**
25
+ * Register native routes and their replay-pricing ownership in the same Cordis scope.
26
+ * @param ctx - scope owning the native adapter registration.
27
+ * @param providers - canonical and optional compatibility routes for this instance.
28
+ * @param adapter - native adapter that will replay its own continuation state.
29
+ */
30
+ export declare function registerNativeCodexAdapter(ctx: Context, providers: string[], adapter: NativeCodexAdapter): void;
31
+ /**
32
+ * Match the runtime's same-adapter ownership rule for native continuation replay.
33
+ * @param runtime - current global LLM service instance.
34
+ * @param sourceProvider - provider that produced the historical assistant message.
35
+ * @param targetProvider - provider preparing the measured request.
36
+ * @returns whether both routes are currently owned by the same native adapter.
37
+ */
38
+ export declare function canReplayNativeCodex(runtime: LlmRuntime, sourceProvider: string, targetProvider: string): boolean;
8
39
  export declare const CODEX_FAST_ALIAS_SUFFIX = "-fast";
9
40
  export declare const CODEX_FAST_SERVICE_TIER = "priority";
10
41
  /** Pure network establishment failure; retried without consuming the finite stream budget. */
@@ -1,10 +1,72 @@
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
+ /**
11
+ * Snapshot immutable provider groups for a pure projection's replay policy.
12
+ * @param runtime - current LLM service, or undefined before it is available.
13
+ * @returns sorted groups; each group is owned by one native adapter instance.
14
+ */
15
+ export function nativeCodexReplayGroups(runtime) {
16
+ if (runtime === undefined)
17
+ return Object.freeze([]);
18
+ const groups = new Map();
19
+ for (const [provider, adapter] of replayOwners.get(runtimeKey(runtime)) ?? []) {
20
+ const group = groups.get(adapter) ?? [];
21
+ group.push(provider);
22
+ groups.set(adapter, group);
23
+ }
24
+ return Object.freeze([...groups.values()].map(group => Object.freeze(group.sort()))
25
+ .sort((a, b) => JSON.stringify(a).localeCompare(JSON.stringify(b))));
26
+ }
27
+ // Cordis returns scope-traced views; use the original service for ownership identity.
28
+ function runtimeKey(runtime) {
29
+ return Reflect.get(runtime, symbols.original) ?? runtime;
30
+ }
31
+ /**
32
+ * Register native routes and their replay-pricing ownership in the same Cordis scope.
33
+ * @param ctx - scope owning the native adapter registration.
34
+ * @param providers - canonical and optional compatibility routes for this instance.
35
+ * @param adapter - native adapter that will replay its own continuation state.
36
+ */
37
+ export function registerNativeCodexAdapter(ctx, providers, adapter) {
38
+ const runtime = ctx.get('llm');
39
+ if (runtime === undefined)
40
+ throw new Error('native Codex registration requires llm');
41
+ ctx.llm.registerAdapter(providers, adapter);
42
+ ctx.effect(() => {
43
+ let owners = replayOwners.get(runtimeKey(runtime));
44
+ if (owners === undefined)
45
+ replayOwners.set(runtimeKey(runtime), owners = new Map());
46
+ for (const provider of providers)
47
+ owners.set(provider, adapter);
48
+ ctx.emit('codex/replay-owners-updated');
49
+ return () => {
50
+ for (const provider of providers) {
51
+ if (owners.get(provider) === adapter)
52
+ owners.delete(provider);
53
+ }
54
+ ctx.emit('codex/replay-owners-updated');
55
+ };
56
+ });
57
+ }
58
+ /**
59
+ * Match the runtime's same-adapter ownership rule for native continuation replay.
60
+ * @param runtime - current global LLM service instance.
61
+ * @param sourceProvider - provider that produced the historical assistant message.
62
+ * @param targetProvider - provider preparing the measured request.
63
+ * @returns whether both routes are currently owned by the same native adapter.
64
+ */
65
+ export function canReplayNativeCodex(runtime, sourceProvider, targetProvider) {
66
+ const owners = replayOwners.get(runtimeKey(runtime));
67
+ const adapter = owners?.get(targetProvider);
68
+ return adapter !== undefined && owners?.get(sourceProvider) === adapter;
69
+ }
8
70
  export const CODEX_FAST_ALIAS_SUFFIX = '-fast';
9
71
  export const CODEX_FAST_SERVICE_TIER = 'priority';
10
72
  /** Pure network establishment failure; retried without consuming the finite stream budget. */
@@ -0,0 +1,69 @@
1
+ import { type Context } from '@deepseek-ai/cordis';
2
+ import { z } from 'zod';
3
+ import { SessionSeq } from '@deepseek-ai/dsh-session';
4
+ import type { ProjectionDefinition, SessionProjectionMap, SessionProjectionStateMap } from '@deepseek-ai/dsh-session-projection';
5
+ type BaseState = SessionProjectionStateMap['contextPressure'];
6
+ type View = SessionProjectionMap['contextPressure'];
7
+ type PressureDefinition = ProjectionDefinition<'contextPressure'> & {
8
+ wire: NonNullable<ProjectionDefinition<'contextPressure'>['wire']>;
9
+ };
10
+ declare const extraSchema: z.ZodObject<{
11
+ header: z.ZodNullable<z.ZodObject<{
12
+ provider: z.ZodString;
13
+ model: z.ZodString;
14
+ fingerprint: z.ZodString;
15
+ tools: z.ZodNumber;
16
+ }, z.core.$strict>>;
17
+ sample: z.ZodNullable<z.ZodObject<{
18
+ provider: z.ZodString;
19
+ model: z.ZodString;
20
+ fingerprint: z.ZodString;
21
+ tools: z.ZodNumber;
22
+ }, z.core.$strict>>;
23
+ sampleNative: z.ZodNullable<z.ZodBoolean>;
24
+ nativeHistory: z.ZodBoolean;
25
+ context: z.ZodNullable<z.ZodObject<{
26
+ provider: z.ZodString;
27
+ model: z.ZodString;
28
+ }, z.core.$strict>>;
29
+ nodes: z.ZodArray<z.ZodObject<{
30
+ seq: z.ZodPipe<z.ZodNumber, z.ZodTransform<SessionSeq, number>>;
31
+ position: z.ZodPipe<z.ZodNumber, z.ZodTransform<SessionSeq, number>>;
32
+ provider: z.ZodString;
33
+ tokens: z.ZodNumber;
34
+ }, z.core.$strict>>;
35
+ positions: z.ZodArray<z.ZodObject<{
36
+ seq: z.ZodPipe<z.ZodNumber, z.ZodTransform<SessionSeq, number>>;
37
+ position: z.ZodPipe<z.ZodNumber, z.ZodTransform<SessionSeq, number>>;
38
+ }, z.core.$strict>>;
39
+ sampledHidden: z.ZodRecord<z.ZodString, z.ZodNumber>;
40
+ }, z.core.$strict>;
41
+ type Extra = z.infer<typeof extraSchema> & {
42
+ view: View;
43
+ };
44
+ type State = BaseState & {
45
+ codex: Extra;
46
+ };
47
+ /**
48
+ * Withhold only the host pressure registration in an isolated service view.
49
+ * All other registrations use the original caller-traced registry and disposer.
50
+ * @param ctx - scope owning the replacement meter.
51
+ * @returns scoped constructor context and the captured host pressure definition.
52
+ */
53
+ export declare function capturePressureRegistration(ctx: Context): {
54
+ context: Context;
55
+ readonly definition: PressureDefinition;
56
+ register<S extends BaseState>(definition: ProjectionDefinition<'contextPressure', S>): () => void;
57
+ };
58
+ /**
59
+ * Add hidden replay to the existing UI pressure fields using only logged data.
60
+ * Native groups are immutable policy; source prices and sample totals stay
61
+ * policy-independent so checkpoints can be safely restored after ownership changes.
62
+ * @param base - host pressure fold, schema, and wire view.
63
+ * @param groups - provider routes grouped by their active native adapter.
64
+ * @returns one pure pressure unit with a policy-independent checkpoint format.
65
+ */
66
+ export declare function codexPressureProjection(base: PressureDefinition, groups: readonly (readonly string[])[]): ProjectionDefinition<'contextPressure', State> & {
67
+ wire: NonNullable<ProjectionDefinition<'contextPressure', State>['wire']>;
68
+ };
69
+ export {};
@@ -0,0 +1,216 @@
1
+ /** Pure, route-aware UI pressure pricing and scoped host registration forwarding. */
2
+ import { createHash } from 'node:crypto';
3
+ import { symbols } from '@deepseek-ai/cordis';
4
+ import { z } from 'zod';
5
+ import { lastAssistantStreamChunk } from '@deepseek-ai/dsh-llm';
6
+ import { canonicalHeader, deriveEventMessage, isSurfaceEvent, SessionSeq, } from '@deepseek-ai/dsh-session';
7
+ import { estimateToolsTokens } from '@deepseek-ai/dsh-token-meter/estimate';
8
+ import { CODEX_PROVIDER, NATIVE_CODEX_PROVIDER } from './native-adapter.js';
9
+ import { estimateNativeCodexReplayTokens, hasNativeCodexReplayKind } from './replay.js';
10
+ const sequence = z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER).transform(SessionSeq);
11
+ const count = z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER);
12
+ const routeSchema = z.object({ provider: z.string(), model: z.string(), fingerprint: z.string(), tools: count }).strict();
13
+ const extraSchema = z.object({
14
+ header: routeSchema.nullable(),
15
+ sample: routeSchema.nullable(),
16
+ sampleNative: z.boolean().nullable(),
17
+ nativeHistory: z.boolean(),
18
+ context: z.object({ provider: z.string(), model: z.string() }).strict().nullable(),
19
+ nodes: z.array(z.object({ seq: sequence, position: sequence, provider: z.string(), tokens: count }).strict()),
20
+ positions: z.array(z.object({ seq: sequence, position: sequence }).strict()),
21
+ sampledHidden: z.record(z.string(), count),
22
+ }).strict();
23
+ function route(header) {
24
+ const normalized = canonicalHeader(header);
25
+ return {
26
+ provider: normalized.config.provider,
27
+ model: normalized.config.model,
28
+ fingerprint: createHash('sha256').update(JSON.stringify({
29
+ config: {
30
+ provider: normalized.config.provider, model: normalized.config.model,
31
+ reasoningEffort: normalized.config.reasoningEffort, temperature: normalized.config.temperature,
32
+ maxTokens: normalized.config.maxTokens, stop: normalized.config.stop,
33
+ },
34
+ adapterDefaults: {
35
+ reasoningEffort: normalized.adapterDefaults?.reasoningEffort,
36
+ maxTokens: normalized.adapterDefaults?.maxTokens,
37
+ },
38
+ tools: normalized.tools ?? [],
39
+ })).digest('hex'),
40
+ tools: estimateToolsTokens(normalized),
41
+ };
42
+ }
43
+ function sums(nodes) {
44
+ const totals = {};
45
+ for (const node of nodes)
46
+ totals[node.provider] = (totals[node.provider] ?? 0) + node.tokens;
47
+ return totals;
48
+ }
49
+ function sameView(a, b) {
50
+ return a.pressureTokens === b.pressureTokens && a.projectedTokens === b.projectedTokens
51
+ && a.contextWindow === b.contextWindow;
52
+ }
53
+ /**
54
+ * Withhold only the host pressure registration in an isolated service view.
55
+ * All other registrations use the original caller-traced registry and disposer.
56
+ * @param ctx - scope owning the replacement meter.
57
+ * @returns scoped constructor context and the captured host pressure definition.
58
+ */
59
+ export function capturePressureRegistration(ctx) {
60
+ const registry = ctx.sessionProjections;
61
+ const originalRegister = registry.register;
62
+ let captured;
63
+ const register = (definition) => {
64
+ if (definition.key === 'contextPressure') {
65
+ if (definition.wire === undefined)
66
+ throw new Error('host pressure projection has no wire view');
67
+ captured = { ...definition, wire: definition.wire };
68
+ return () => { };
69
+ }
70
+ return Reflect.apply(originalRegister, registry, [definition]);
71
+ };
72
+ const forwarded = new Proxy(registry, {
73
+ get(target, key, receiver) {
74
+ // Suppress another Cordis trace wrapper, which would bypass the register trap.
75
+ if (key === symbols.tracker)
76
+ return undefined;
77
+ if (key === 'register')
78
+ return register;
79
+ return Reflect.get(target, key, receiver);
80
+ },
81
+ });
82
+ const context = ctx.isolate('sessionProjections');
83
+ context.provide('sessionProjections', forwarded);
84
+ return { context, register(definition) {
85
+ return Reflect.apply(originalRegister, registry, [definition]);
86
+ }, get definition() {
87
+ if (captured === undefined)
88
+ throw new Error('host token meter did not register contextPressure');
89
+ return captured;
90
+ } };
91
+ }
92
+ /**
93
+ * Add hidden replay to the existing UI pressure fields using only logged data.
94
+ * Native groups are immutable policy; source prices and sample totals stay
95
+ * policy-independent so checkpoints can be safely restored after ownership changes.
96
+ * @param base - host pressure fold, schema, and wire view.
97
+ * @param groups - provider routes grouped by their active native adapter.
98
+ * @returns one pure pressure unit with a policy-independent checkpoint format.
99
+ */
100
+ export function codexPressureProjection(base, groups) {
101
+ const owners = new Map(groups.flatMap((group, index) => group.map(provider => [provider, index])));
102
+ const hidden = (totals, target) => {
103
+ const owner = owners.get(target);
104
+ if (owner === undefined)
105
+ return 0;
106
+ return Object.entries(totals).reduce((n, [provider, tokens]) => n + (owners.get(provider) === owner ? tokens : 0), 0);
107
+ };
108
+ const view = (state, extra) => {
109
+ const original = base.wire.view(state);
110
+ const header = extra.header;
111
+ if (header === null)
112
+ return original;
113
+ const native = owners.has(header.provider);
114
+ const sampleProvider = extra.sample?.provider;
115
+ const changedEnvelope = extra.sample !== null && extra.sample.fingerprint !== header.fingerprint;
116
+ if (!native && !extra.nativeHistory && extra.sampleNative !== true
117
+ && !owners.has(sampleProvider ?? '') && !changedEnvelope)
118
+ return original;
119
+ const current = hidden(sums(extra.nodes), header.provider);
120
+ const sampled = hidden(extra.sampledHidden, header.provider);
121
+ const sampledSources = Object.entries(extra.sampledHidden).filter(([, tokens]) => tokens > 0);
122
+ const policyCompatible = native
123
+ ? sampledSources.every(([source]) => source === extra.sample?.provider
124
+ && owners.get(source) === owners.get(header.provider))
125
+ : extra.sampleNative === false || sampledSources.length === 0 && extra.sampleNative !== true;
126
+ const compatible = extra.sample?.fingerprint === header.fingerprint
127
+ && (extra.sampleNative === null || extra.sampleNative === native) && policyCompatible
128
+ && state.pressureTokens !== undefined
129
+ && state.sampledSurfaceTokens !== undefined
130
+ && state.pressureTokens >= state.sampledSurfaceTokens + header.tools + sampled;
131
+ const projectedTokens = compatible
132
+ ? Math.max(0, state.pressureTokens + state.surfaceTokens - state.sampledSurfaceTokens + current - sampled)
133
+ : state.surfaceTokens + header.tools + current;
134
+ const { contextWindow: _oldWindow, ...reported } = original;
135
+ return {
136
+ ...reported, projectedTokens,
137
+ ...extra.context?.provider === header.provider && extra.context.model === header.model
138
+ && original.contextWindow !== undefined ? { contextWindow: original.contextWindow } : {},
139
+ };
140
+ };
141
+ const stateSchema = z.record(z.string(), z.unknown()).transform((value, ctx) => {
142
+ const { codex, ...legacy } = value;
143
+ const parsedBase = base.stateSchema.safeParse(legacy);
144
+ const parsedExtra = extraSchema.safeParse(typeof codex === 'object' && codex !== null
145
+ ? Object.fromEntries(Object.entries(codex).filter(([key]) => key !== 'view')) : codex);
146
+ if (!parsedBase.success || !parsedExtra.success) {
147
+ ctx.addIssue({ code: 'custom', message: 'invalid Codex pressure checkpoint' });
148
+ return z.NEVER;
149
+ }
150
+ return { ...parsedBase.data, codex: { ...parsedExtra.data, view: view(parsedBase.data, parsedExtra.data) } };
151
+ });
152
+ return {
153
+ key: 'contextPressure', stateVersion: 6, stateSchema,
154
+ init: (header, inherited) => {
155
+ const initial = base.init(header, inherited);
156
+ const extra = {
157
+ header: null, sample: null, sampleNative: null, nativeHistory: false, context: null,
158
+ nodes: [], positions: [], sampledHidden: {},
159
+ };
160
+ return { ...initial, codex: { ...extra, view: view(initial, extra) } };
161
+ },
162
+ apply: (state, event) => {
163
+ const next = base.apply(state, event);
164
+ let extra = state.codex;
165
+ if (event.type === 'request/header') {
166
+ const nextRoute = route(event.data.header);
167
+ if (nextRoute.fingerprint !== extra.header?.fingerprint)
168
+ extra = { ...extra, header: nextRoute };
169
+ }
170
+ if (event.type === 'request/context')
171
+ extra = { ...extra, context: { provider: event.data.provider, model: event.data.model } };
172
+ if (event.type === 'assistant/message' || event.type === 'assistant/attempt') {
173
+ const usage = event.type === 'assistant/message' && event.data.usage !== undefined
174
+ ? event.data.usage : lastAssistantStreamChunk(event.data.stream, 'usage')?.usage;
175
+ if (usage !== undefined) {
176
+ const finish = lastAssistantStreamChunk(event.data.stream, 'finish');
177
+ const replay = finish?.replayState ?? (event.type === 'assistant/message'
178
+ && event.data.message.source.kind === 'model' ? event.data.message.source.replayState : undefined);
179
+ const native = replay === undefined || replay === null ? null : hasNativeCodexReplayKind(replay);
180
+ extra = { ...extra, sample: extra.header, sampledHidden: sums(extra.nodes), sampleNative: native,
181
+ nativeHistory: extra.nativeHistory || native === true };
182
+ }
183
+ }
184
+ if (isSurfaceEvent(event)) {
185
+ const message = deriveEventMessage(event);
186
+ const source = message?.role === 'assistant' && message.source.kind === 'model' ? message.source : undefined;
187
+ const originalNative = source !== undefined && hasNativeCodexReplayKind(source.replayState)
188
+ && (source.provider === CODEX_PROVIDER || source.provider === NATIVE_CODEX_PROVIDER);
189
+ const price = originalNative ? estimateNativeCodexReplayTokens(source, event.type === 'assistant/message' ? event.data.usage : undefined) : 0;
190
+ let nodes = extra.nodes;
191
+ let positions = extra.positions;
192
+ let position = event.seq;
193
+ if (event.surfaceOp !== 'append') {
194
+ const { startSeq, endSeq } = event.surfaceOp;
195
+ const first = positions.find(node => node.seq === startSeq)?.position ?? startSeq;
196
+ const last = positions.find(node => node.seq === endSeq)?.position ?? endSeq;
197
+ if (last < first)
198
+ throw new Error('Codex pressure: invalid positional replacement');
199
+ position = first;
200
+ nodes = nodes.filter(node => node.position < first || node.position > last);
201
+ positions = [...positions.filter(node => node.position < first || node.position > last), { seq: event.seq, position }];
202
+ }
203
+ if (price > 0 && source !== undefined)
204
+ nodes = [...nodes, { seq: event.seq, position, provider: source.provider, tokens: price }];
205
+ if (nodes !== extra.nodes || positions !== extra.positions || originalNative && !extra.nativeHistory) {
206
+ extra = { ...extra, nodes, positions, nativeHistory: extra.nativeHistory || originalNative };
207
+ }
208
+ }
209
+ if (next === state && extra === state.codex)
210
+ return state;
211
+ const output = view(next, extra);
212
+ return { ...next, codex: { ...extra, view: sameView(output, state.codex.view) ? state.codex.view : output } };
213
+ },
214
+ wire: { viewSchema: base.wire.viewSchema, view: state => state.codex.view },
215
+ };
216
+ }
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.js CHANGED
@@ -289,7 +289,7 @@ export function mapResponsesUsage(usage) {
289
289
  inputTokens: uncached, outputTokens: output,
290
290
  ...cached === 0 ? {} : { cacheReadTokens: cached },
291
291
  ...written === 0 ? {} : { cacheWriteTokens: written },
292
- ...reasoning === 0 ? {} : { reasoningTokens: reasoning },
292
+ ...usage.output_tokens_details?.reasoning_tokens === undefined ? {} : { reasoningTokens: reasoning },
293
293
  };
294
294
  }
295
295
  function retryDelay(message) {
@@ -0,0 +1,30 @@
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, TokenMeterConfig } from '@deepseek-ai/dsh-token-meter';
4
+ import type { Context } from '@deepseek-ai/cordis';
5
+ import type { EpochHeader, Session } from '@deepseek-ai/dsh-session';
6
+ /**
7
+ * Replacement tokenMeter service that charges native Codex encrypted reasoning.
8
+ * Ordinary prices, usage, and breakdown projections come from the host meter.
9
+ * The UI pressure unit adds the same hidden pricing per logged request route. Hidden prices apply
10
+ * only between routes currently owned by the same native adapter; fixed
11
+ * heuristicTokens stay unchanged. Provider usage is not charged a second time.
12
+ */
13
+ export declare class CodexTokenMeter extends TokenMeter {
14
+ static inject: string[];
15
+ private readonly hiddenFolds;
16
+ constructor(ctx: Context, config?: TokenMeterConfig);
17
+ /**
18
+ * Return a detached measurement including opaque native replay in node prices,
19
+ * surface totals, and signed anchor deltas. Recheck usage conservatism against
20
+ * the corrected full anchor before reusing the provider sample.
21
+ * @param session - session to price through its current durable tail.
22
+ * @param requestHeader - optional effective routed request envelope.
23
+ * @returns immutable request pressure and positional message prices.
24
+ */
25
+ measure(session: Session, requestHeader?: EpochHeader): TokenMeasurement;
26
+ /** Track only replay metadata and surface positions; the host validates each event first. */
27
+ private syncHidden;
28
+ private foldHidden;
29
+ }
30
+ export default CodexTokenMeter;
@@ -0,0 +1,156 @@
1
+ /** Codex hidden-replay pricing layered over the host's ordinary token meter. */
2
+ import { TokenMeter } from '@deepseek-ai/dsh-token-meter';
3
+ import { capturePressureRegistration, codexPressureProjection } from './pressure-projection.js';
4
+ import { estimateToolsTokens } from '@deepseek-ai/dsh-token-meter/estimate';
5
+ import { assembleAssistantStream } from '@deepseek-ai/dsh-llm';
6
+ import { canonicalHeader, deriveEventMessage, headerEquals, isSurfaceEvent, SessionSeq, } from '@deepseek-ai/dsh-session';
7
+ import { canReplayNativeCodex, nativeCodexReplayGroups } from './native-adapter.js';
8
+ import { estimateNativeCodexReplayTokens } from './replay.js';
9
+ function replayPrice(message, usage) {
10
+ if (message?.role !== 'assistant' || message.source.kind !== 'model')
11
+ return undefined;
12
+ return { source: message.source, usage };
13
+ }
14
+ function sameHeader(a, b) {
15
+ return a === undefined || b === undefined ? a === b : headerEquals(a, b);
16
+ }
17
+ /**
18
+ * Replacement tokenMeter service that charges native Codex encrypted reasoning.
19
+ * Ordinary prices, usage, and breakdown projections come from the host meter.
20
+ * The UI pressure unit adds the same hidden pricing per logged request route. Hidden prices apply
21
+ * only between routes currently owned by the same native adapter; fixed
22
+ * heuristicTokens stay unchanged. Provider usage is not charged a second time.
23
+ */
24
+ export class CodexTokenMeter extends TokenMeter {
25
+ static inject = ['sessionProjections', 'llm'];
26
+ hiddenFolds = new WeakMap();
27
+ constructor(ctx, config) {
28
+ const capture = capturePressureRegistration(ctx);
29
+ super(capture.context, config);
30
+ let unregister;
31
+ let policy;
32
+ const refresh = () => {
33
+ const groups = nativeCodexReplayGroups(ctx.get('llm'));
34
+ const fingerprint = JSON.stringify(groups);
35
+ if (fingerprint === policy)
36
+ return;
37
+ unregister?.();
38
+ unregister = capture.register(codexPressureProjection(capture.definition, groups));
39
+ policy = fingerprint;
40
+ };
41
+ refresh();
42
+ ctx.on('codex/replay-owners-updated', refresh);
43
+ ctx.effect(() => () => { unregister?.(); });
44
+ }
45
+ /**
46
+ * Return a detached measurement including opaque native replay in node prices,
47
+ * surface totals, and signed anchor deltas. Recheck usage conservatism against
48
+ * the corrected full anchor before reusing the provider sample.
49
+ * @param session - session to price through its current durable tail.
50
+ * @param requestHeader - optional effective routed request envelope.
51
+ * @returns immutable request pressure and positional message prices.
52
+ */
53
+ measure(session, requestHeader) {
54
+ const base = super.measure(session, requestHeader);
55
+ const state = this.syncHidden(session, base.logRevision);
56
+ const header = requestHeader === undefined ? state.header : canonicalHeader(requestHeader);
57
+ const runtime = this.ctx.get('llm');
58
+ const provider = header?.config?.provider;
59
+ if (runtime === undefined || provider === undefined)
60
+ return base;
61
+ const price = (replay) => {
62
+ if (replay === undefined || !canReplayNativeCodex(runtime, replay.source.provider, provider))
63
+ return 0;
64
+ return replay.tokens ??= estimateNativeCodexReplayTokens(replay.source, replay.usage);
65
+ };
66
+ const current = new Map(state.nodes.map(node => [node.seq, price(node.replay)]));
67
+ const hiddenCurrent = base.nodes.reduce((total, node) => total + (current.get(node.seq) ?? 0), 0);
68
+ const anchor = state.anchor;
69
+ const matched = anchor !== undefined && sameHeader(anchor.header, header);
70
+ const hiddenAnchor = matched
71
+ ? anchor.nodes.reduce((total, node) => total + price(node.replay), 0) + price(anchor.assistant)
72
+ : 0;
73
+ if (hiddenCurrent === 0 && hiddenAnchor === 0)
74
+ return base;
75
+ let baseline = base.baseline;
76
+ let surfaceDeltaTokens = base.surfaceDeltaTokens;
77
+ if (matched) {
78
+ surfaceDeltaTokens += hiddenCurrent - hiddenAnchor;
79
+ if (base.baseline.kind === 'usage') {
80
+ const estimatedAnchor = estimateToolsTokens(header)
81
+ + base.surfaceTokens - base.surfaceDeltaTokens + hiddenAnchor;
82
+ if (base.baseline.tokens < estimatedAnchor) {
83
+ baseline = Object.freeze({ kind: 'estimated', tokens: estimatedAnchor });
84
+ }
85
+ }
86
+ else if (base.baseline.kind === 'estimated') {
87
+ baseline = Object.freeze({ kind: 'estimated', tokens: base.baseline.tokens + hiddenAnchor });
88
+ }
89
+ }
90
+ else if (base.baseline.kind === 'estimated') {
91
+ baseline = Object.freeze({ kind: 'estimated', tokens: base.baseline.tokens + hiddenCurrent });
92
+ }
93
+ const nodes = Object.freeze(base.nodes.map(node => Object.freeze({
94
+ ...node, tokens: node.tokens + (current.get(node.seq) ?? 0),
95
+ })));
96
+ return Object.freeze({
97
+ ...base,
98
+ baseline,
99
+ surfaceDeltaTokens,
100
+ totalTokens: Math.max(0, baseline.tokens + surfaceDeltaTokens),
101
+ surfaceTokens: base.surfaceTokens + hiddenCurrent,
102
+ nodes,
103
+ });
104
+ }
105
+ /** Track only replay metadata and surface positions; the host validates each event first. */
106
+ syncHidden(session, revision) {
107
+ let state = this.hiddenFolds.get(session);
108
+ if (state === undefined) {
109
+ state = { consumed: 0, header: undefined, nodes: [], anchor: undefined };
110
+ this.hiddenFolds.set(session, state);
111
+ }
112
+ while (state.consumed < revision) {
113
+ const event = session.eventAt(SessionSeq(state.consumed));
114
+ if (event === undefined)
115
+ throw new Error('Codex token meter: missing consumed session event');
116
+ this.foldHidden(state, event);
117
+ state.consumed++;
118
+ }
119
+ return state;
120
+ }
121
+ foldHidden(state, event) {
122
+ const header = event.type === 'request/header' ? canonicalHeader(event.data.header) : state.header;
123
+ const node = isSurfaceEvent(event) ? {
124
+ seq: event.seq,
125
+ replay: replayPrice(deriveEventMessage(event), event.type === 'assistant/message' ? event.data.usage : undefined),
126
+ } : undefined;
127
+ let anchor = state.anchor;
128
+ if (event.type === 'assistant/message') {
129
+ const route = header?.config;
130
+ const original = event.data.usage !== undefined && route !== undefined
131
+ ? { source: {
132
+ provider: route.provider,
133
+ model: route.model,
134
+ replayState: assembleAssistantStream(event.data.stream).replayState,
135
+ }, usage: event.data.usage }
136
+ : node?.replay;
137
+ anchor = { header, nodes: state.nodes.slice(), assistant: original };
138
+ }
139
+ if (node !== undefined && isSurfaceEvent(event)) {
140
+ if (event.surfaceOp === 'append') {
141
+ state.nodes.push(node);
142
+ }
143
+ else {
144
+ const { startSeq, endSeq } = event.surfaceOp;
145
+ const first = state.nodes.findIndex(item => item.seq === startSeq);
146
+ const last = state.nodes.findIndex(item => item.seq === endSeq);
147
+ if (first < 0 || last < first)
148
+ throw new Error('Codex token meter: invalid surface replacement');
149
+ state.nodes.splice(first, last - first + 1, node);
150
+ }
151
+ }
152
+ state.header = header;
153
+ state.anchor = anchor;
154
+ }
155
+ }
156
+ 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.2",
3
+ "version": "0.13.4",
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,10 @@
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",
64
+ "lib/pressure-projection.js",
65
+ "lib/pressure-projection.d.ts",
58
66
  "lib/retry-after.js",
59
67
  "lib/retry-after.d.ts",
60
68
  "lib/rate-limits.js",
@@ -116,12 +124,15 @@
116
124
  }
117
125
  },
118
126
  "peerDependencies": {
127
+ "@deepseek-ai/dsh-session": "0.2.0-rc.2",
128
+ "@deepseek-ai/dsh-session-projection": "0.2.0-rc.2",
119
129
  "@deepseek-ai/cordis": "~4.0.4",
120
130
  "@deepseek-ai/dsh-attachment": "0.2.0-rc.2",
121
131
  "@deepseek-ai/dsh-credentials": "0.2.0-rc.2",
122
132
  "@deepseek-ai/dsh-fs": "0.2.0-rc.2",
123
133
  "@deepseek-ai/dsh-host-webserver": "0.2.0-rc.2",
124
134
  "@deepseek-ai/dsh-llm": "0.2.0-rc.2",
135
+ "@deepseek-ai/dsh-token-meter": "0.2.0-rc.2",
125
136
  "@deepseek-ai/dsh-sandbox": "0.2.0-rc.2",
126
137
  "@deepseek-ai/dsh-sandbox-policy": "0.2.0-rc.2",
127
138
  "@deepseek-ai/dsh-shell": "0.2.0-rc.2",
@@ -134,11 +145,18 @@
134
145
  "https-proxy-agent": "7.0.6",
135
146
  "image-size": "^2.0.2",
136
147
  "proxy-from-env": "1.1.0",
137
- "ws": "8.21.3"
148
+ "ws": "8.21.3",
149
+ "zod": "^4.4.3"
138
150
  },
139
151
  "devDependencies": {
140
152
  "@deepseek-ai/cordis": "~4.0.4",
153
+ "@deepseek-ai/cordis-plugin-loader": "1.0.5",
154
+ "@deepseek-ai/cordis-plugin-include": "1.0.9",
155
+ "js-yaml": "^4.1.0",
156
+ "@types/js-yaml": "^4.0.9",
141
157
  "@deepseek-ai/dsh-agent": "0.2.0-rc.2",
158
+ "@deepseek-ai/dsh-compaction-basic": "0.2.0-rc.2",
159
+ "@deepseek-ai/dsh-session-projection": "0.2.0-rc.2",
142
160
  "@deepseek-ai/dsh-attachment": "0.2.0-rc.2",
143
161
  "@deepseek-ai/dsh-attachment-local": "0.2.0-rc.2",
144
162
  "@deepseek-ai/dsh-client-store": "0.2.0-rc.2",
@@ -147,6 +165,7 @@
147
165
  "@deepseek-ai/dsh-fs": "0.2.0-rc.2",
148
166
  "@deepseek-ai/dsh-host-webserver": "0.2.0-rc.2",
149
167
  "@deepseek-ai/dsh-llm": "0.2.0-rc.2",
168
+ "@deepseek-ai/dsh-token-meter": "0.2.0-rc.2",
150
169
  "@deepseek-ai/dsh-sandbox": "0.2.0-rc.2",
151
170
  "@deepseek-ai/dsh-sandbox-policy": "0.2.0-rc.2",
152
171
  "@deepseek-ai/dsh-scope": "0.2.0-rc.2",