@goodandready/dsh-subscriptions 0.6.10 → 0.6.12
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/README.md +17 -0
- package/README.ru.md +17 -0
- package/README.zh.md +17 -0
- package/lib/adapter.js +12 -1
- package/lib/analyze-session.js +3 -3
- package/lib/client.js +7 -1
- package/lib/config-schema.js +2 -0
- package/lib/oauth.js +3 -0
- package/lib/responses-stream.js +3 -3
- package/lib/routes/oauth.js +14 -1
- package/lib/vendors/antigravity.js +3 -0
- package/lib/vendors/glm.js +1 -1
- package/lib/wire.js +96 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -272,6 +272,23 @@ Supports Anthropic's adaptive thinking (`thinking: { type: "adaptive" }`) and re
|
|
|
272
272
|
|
|
273
273
|
---
|
|
274
274
|
|
|
275
|
+
## 🛡️ Token Usage Normalization & Session Projection Fix (Added in v0.6.12)
|
|
276
|
+
|
|
277
|
+
- **Session Projection Fix (GitHub #4)**: Fixed an issue where ChatGPT Codex and other streaming provider responses caused DSH session recovery to fail on page reload with `received NaN, expected number on uncachedInputTokens and outputTokens`.
|
|
278
|
+
- **Canonical `TokenUsage` Normalization**: Introduced `toTokenUsage()` across all streaming pipelines (`codexResponsesStream`, `openaiChatStream`, `anthropicStream`, and `googleStream`) to map raw vendor usage payloads into canonical DSH `TokenUsage` (`inputTokens`, `outputTokens`, `cacheReadTokens`, `cacheWriteTokens`, `reasoningTokens`, `totalTokens`).
|
|
279
|
+
- **Strict Integer Guard**: All token counts are strictly guarded to non-negative finite integers (guaranteeing `0` fallback, never `NaN` or `undefined`). Disjoint uncached token calculation is performed when cached tokens are folded into prompt totals.
|
|
280
|
+
- **Defense in Depth**: Added adapter-level stream validation in `SubscriptionAdapter` to prevent malformed or invalid token usage chunks from ever reaching DSH session persistence.
|
|
281
|
+
|
|
282
|
+
---
|
|
283
|
+
|
|
284
|
+
## 🔒 OAuth Client Validation & Route Updates (Added in v0.6.11)
|
|
285
|
+
|
|
286
|
+
- **OAuth Client ID Guard (GitHub #2)**: uildAuthorizeUrl and ntigravity.authorizeUrl now strictly require a non-empty clientId. If unconfigured, the endpoint returns an explicit HTTP 400 (missing_client_id) and the UI prompts the user to configure ntigravityClientId in plugin settings or use "From CLI" instead of directing the browser to a failing Google OAuth page.
|
|
287
|
+
- **Config Schema Secret Key**: Added ntigravityClientSecret to the plugin configuration schema, allowing convenient entry of private client secrets alongside ntigravityClientId with automatic masking in public endpoints.
|
|
288
|
+
- **Zhipu GLM Console Route Migration (GitHub #3)**: Updated the Zhipu GLM authorization and API key center link to the active console route (https://bigmodel.cn/usercenter/proj-mgmt/apikeys), eliminating 404 navigation errors caused by vendor console restructuring.
|
|
289
|
+
|
|
290
|
+
---
|
|
291
|
+
|
|
275
292
|
## 🛡️ Reliability & Quality Hardening (Added in v0.6.10)
|
|
276
293
|
|
|
277
294
|
- **Structured Error Handling**: Eliminated all empty catch blocks across the plugin runtime via safe `bestEffort` helper logging debug diagnostics.
|
package/README.ru.md
CHANGED
|
@@ -245,6 +245,23 @@ dsh-subscriptions:
|
|
|
245
245
|
|
|
246
246
|
---
|
|
247
247
|
|
|
248
|
+
## 🛡️ Нормализация расхода токенов и защита проекций сессий (Добавлено в v0.6.12)
|
|
249
|
+
|
|
250
|
+
- **Защита от повреждения сессий (GitHub #4)**: Устранена критическая ошибка, из-за которой сессии с моделями ChatGPT Codex повреждались после первого ответа и не открывались после перезагрузки с ошибкой `received NaN, expected number on uncachedInputTokens and outputTokens`.
|
|
251
|
+
- **Каноническая нормализация `TokenUsage`**: Внедрена универсальная функция `toTokenUsage()` во всех потоковых адаптерах (`codexResponsesStream`, `openaiChatStream`, `anthropicStream`, `googleStream`) для приведения вендорских структур к стандартному интерфейсу DSH `TokenUsage` (`inputTokens`, `outputTokens`, `cacheReadTokens`, `cacheWriteTokens`, `reasoningTokens`, `totalTokens`).
|
|
252
|
+
- **Защита от `NaN` и disjoint-расчет**: Все значения токенов гарантированно приводятся к целым неотрицательным числам (с безопасным fallback в `0`, без `NaN` и `undefined`). Для провайдеров, объединяющих кэш с промптом, автоматически вычисляется раздельный объем некэшированного ввода.
|
|
253
|
+
- **Эшелонированная защита адаптера**: Добавлен перехват и валидация чанков расхода токенов на уровне `SubscriptionAdapter`, исключающий попадание некорректных данных в журнал и хранилище сессий DSH.
|
|
254
|
+
|
|
255
|
+
---
|
|
256
|
+
|
|
257
|
+
## 🔒 Защита OAuth Client ID и обновление ссылок (Добавлено в v0.6.11)
|
|
258
|
+
|
|
259
|
+
- **Защита OAuth Client ID (GitHub #2)**: uildAuthorizeUrl и ntigravity.authorizeUrl теперь строго требуют непустой clientId. Если параметр не задан, сервер возвращает явный код ошибки HTTP 400 (missing_client_id), а интерфейс выводит подсказку с предложением указать ntigravityClientId в настройках или нажать «📥 Из CLI», предотвращая отправку битых запросов в Google OAuth.
|
|
260
|
+
- **Секретный ключ в схеме настроек**: В схему Config добавлено поле ntigravityClientSecret для удобного ввода Client Secret рядом с ntigravityClientId с автоматическим маскированием в публичном API.
|
|
261
|
+
- **Актуализация маршрута Zhipu GLM (GitHub #3)**: Ссылка на консоль API-ключей Zhipu GLM обновлена на актуальный адрес (https://bigmodel.cn/usercenter/proj-mgmt/apikeys), устраняя ошибку 404 («页面跑丢啦!!»), вызванную миграцией маршрутов вендора.
|
|
262
|
+
|
|
263
|
+
---
|
|
264
|
+
|
|
248
265
|
## 🛡️ Надежность и стандарты качества (Добавлено в v0.6.10)
|
|
249
266
|
|
|
250
267
|
- **Структурированная обработка ошибок**: Устранены все пустые блоки catch в рантайме плагина с переходом на безопасный хелпер `bestEffort` с диагностическим логированием.
|
package/README.zh.md
CHANGED
|
@@ -167,6 +167,23 @@ dsh plugin --profile web add @goodandready/dsh-subscriptions
|
|
|
167
167
|
|
|
168
168
|
---
|
|
169
169
|
|
|
170
|
+
## 🛡️ Token 用量规范化与会话投影修复 (v0.6.12 新增)
|
|
171
|
+
|
|
172
|
+
- **会话投影崩溃修复 (GitHub #4)**:修复了使用 ChatGPT Codex 及其他流式模型时,首次回复后会话损坏、刷新页面报错 `received NaN, expected number on uncachedInputTokens and outputTokens` 且无法加载的问题。
|
|
173
|
+
- **全格式通用 TokenUsage 转换**:在所有流式传输通道(`codexResponsesStream`、`openaiChatStream`、`anthropicStream`、`googleStream`)中引入统一的 `toTokenUsage()` 转换逻辑,将各服务商的原始用量数据标准化为 DSH 规范的 `TokenUsage` 接口(`inputTokens`、`outputTokens`、`cacheReadTokens`、`cacheWriteTokens`、`reasoningTokens`、`totalTokens`)。
|
|
174
|
+
- **严格非负整数保底与独立缓存计算**:对所有 token 计数实施严格的非负整数约束(保底为 `0`,杜绝 `NaN` 与 `undefined`)。针对将缓存合并计入总 prompt 的供应商自动执行拆分,确保输入 token 数的准确性。
|
|
175
|
+
- **适配器层纵深防御**:在 `SubscriptionAdapter` 中新增用量数据过滤与净化机制,从根源上阻止异常用量流入 DSH 会话持久化与状态恢复流程。
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
## 🔒 OAuth Client ID 校验保护与控制台地址更新 (v0.6.11 新增)
|
|
180
|
+
|
|
181
|
+
- **Google OAuth Client ID 校验保护 (GitHub #2)**:uildAuthorizeUrl 和 ntigravity.authorizeUrl 严格要求非空 clientId。未配置时服务端返回明确的 HTTP 400 (missing_client_id),前端界面提示用户在插件设置中填写 ntigravityClientId 或使用“📥 从 CLI 导入”,避免浏览器跳转至 Google OAuth 400 报错页面。
|
|
182
|
+
- **配置架构新增密钥项**:在插件 Config 架构中新增 ntigravityClientSecret,方便用户在设置中配置客户端密钥,并在公共接口中自动脱敏脱密。
|
|
183
|
+
- **智谱 GLM 控制台地址迁移 (GitHub #3)**:将智谱 GLM 授权与 API Key 获取链接更新为最新控制台有效路由 (https://bigmodel.cn/usercenter/proj-mgmt/apikeys),彻底解决因服务商控制台路由调整导致的 404 错误。
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
170
187
|
## 🛡️ 稳定性与代码质量强化 (v0.6.10 新增)
|
|
171
188
|
|
|
172
189
|
- **结构化异常降级**: 全面移除运行时空 catch 块,统一采用带诊断日志的 `bestEffort` 安全兜底机制。
|
package/lib/adapter.js
CHANGED
|
@@ -6,6 +6,7 @@ import { modelCatalog } from './messages.js'
|
|
|
6
6
|
import { streamWithRotation } from './stream-rotate.js'
|
|
7
7
|
import { pickFetch } from './proxy.js'
|
|
8
8
|
import { quotaSnapshot, parseBody } from './ratelimit.js'
|
|
9
|
+
import { toTokenUsage } from './wire.js'
|
|
9
10
|
|
|
10
11
|
// #94: heuristic for test/preview/beta/legacy model ids.
|
|
11
12
|
function isDeprecatedId(id) {
|
|
@@ -176,7 +177,17 @@ export class SubscriptionAdapter extends LlmAdapter {
|
|
|
176
177
|
throw err
|
|
177
178
|
}
|
|
178
179
|
},
|
|
179
|
-
})) {
|
|
180
|
+
})) {
|
|
181
|
+
if (event && event.type === 'usage') {
|
|
182
|
+
const safeUsage = toTokenUsage(event.usage)
|
|
183
|
+
if (!safeUsage) continue
|
|
184
|
+
yieldedAny = true
|
|
185
|
+
yield { ...event, usage: safeUsage }
|
|
186
|
+
continue
|
|
187
|
+
}
|
|
188
|
+
yieldedAny = true
|
|
189
|
+
yield event
|
|
190
|
+
}
|
|
180
191
|
return
|
|
181
192
|
} catch (err) {
|
|
182
193
|
// #91: when the whole pool is exhausted and nothing has been streamed
|
package/lib/analyze-session.js
CHANGED
|
@@ -16,9 +16,9 @@ export function analyzeSessionEvents(events = []) {
|
|
|
16
16
|
const usage = ev.usage || (ev.type === 'usage' ? ev.usage : null) || (ev.data && ev.data.usage)
|
|
17
17
|
if (!usage) continue
|
|
18
18
|
|
|
19
|
-
const p = Number(usage.prompt_tokens
|
|
20
|
-
const c = Number(usage.prompt_tokens_details?.cached_tokens
|
|
21
|
-
const comp = Number(usage.completion_tokens
|
|
19
|
+
const p = Number(usage.inputTokens ?? usage.prompt_tokens ?? usage.input_tokens ?? usage.promptTokens ?? 0)
|
|
20
|
+
const c = Number(usage.cacheReadTokens ?? usage.prompt_tokens_details?.cached_tokens ?? usage.cache_read_input_tokens ?? usage.cached_tokens ?? usage.cachedTokens ?? 0)
|
|
21
|
+
const comp = Number(usage.outputTokens ?? usage.completion_tokens ?? usage.output_tokens ?? usage.completionTokens ?? 0)
|
|
22
22
|
|
|
23
23
|
if (p <= 0 && comp <= 0) continue
|
|
24
24
|
|
package/lib/client.js
CHANGED
|
@@ -292,7 +292,7 @@ const cssId = 'dsh-subscriptions/settings.module.css'
|
|
|
292
292
|
"3. 粘贴到输入框中并点击 \"保存令牌\"。",
|
|
293
293
|
"4. 若已安装 ZCode CLI (~/.zcode),点击 \"📥 从 CLI 导入\" 即可自动导入。",
|
|
294
294
|
],
|
|
295
|
-
link: "https://
|
|
295
|
+
link: "https://bigmodel.cn/usercenter/proj-mgmt/apikeys",
|
|
296
296
|
},
|
|
297
297
|
grok: {
|
|
298
298
|
title: "xAI Grok",
|
|
@@ -434,6 +434,7 @@ const cssId = 'dsh-subscriptions/settings.module.css'
|
|
|
434
434
|
"fcMinutes": "m",
|
|
435
435
|
"reconnect": "Reconnect",
|
|
436
436
|
"connect": "Connect",
|
|
437
|
+
"missingClientIdAntigravity": "Google OAuth requires a custom Client ID. Please configure antigravityClientId in plugin settings below, or click '📥 From CLI' to import active credentials.",
|
|
437
438
|
"disconnect": "Disconnect",
|
|
438
439
|
"removeSlot": "Remove slot",
|
|
439
440
|
"pastePlaceholder": "Paste redirected URL or code",
|
|
@@ -603,6 +604,7 @@ const cssId = 'dsh-subscriptions/settings.module.css'
|
|
|
603
604
|
"fcMinutes": "分",
|
|
604
605
|
"reconnect": "重新连接",
|
|
605
606
|
"connect": "连接",
|
|
607
|
+
"missingClientIdAntigravity": "Google OAuth 需要自定义 Client ID。请在下方设置中配置 antigravityClientId,或点击‘📥 从 CLI 导入’直接导入本地会话。",
|
|
606
608
|
"disconnect": "断开连接",
|
|
607
609
|
"removeSlot": "移除槽位",
|
|
608
610
|
"pastePlaceholder": "粘贴重定向 URL 或授权代码",
|
|
@@ -985,6 +987,10 @@ const cssId = 'dsh-subscriptions/settings.module.css'
|
|
|
985
987
|
|
|
986
988
|
const connect = async (provider, index) => {
|
|
987
989
|
setErr('')
|
|
990
|
+
if (provider === 'antigravity' && (!draft || !draft.antigravityClientId || !String(draft.antigravityClientId).trim())) {
|
|
991
|
+
setErr(t('missingClientIdAntigravity'))
|
|
992
|
+
return
|
|
993
|
+
}
|
|
988
994
|
const res = await fetch('/dsh-subscriptions/oauth/start?provider=' + encodeURIComponent(provider) + '&index=' + encodeURIComponent(index), { cache: 'no-store' })
|
|
989
995
|
const data = await res.json().catch(() => ({}))
|
|
990
996
|
if (!res.ok) throw new Error((data.error && data.error.message) || ('HTTP ' + res.status))
|
package/lib/config-schema.js
CHANGED
|
@@ -64,6 +64,8 @@ export const Config = z.object({
|
|
|
64
64
|
grokClientVersion: z.string().default('')
|
|
65
65
|
.description('Grok CLI identity version header. Empty uses the built-in default.'),
|
|
66
66
|
antigravityClientId: z.string().default(''),
|
|
67
|
+
antigravityClientSecret: z.string().default('')
|
|
68
|
+
.description('Google Cloud OAuth Client Secret for Antigravity.'),
|
|
67
69
|
antigravityRedirectUri: z.string().default(''),
|
|
68
70
|
customVendors: z.array(z.any()).default([])
|
|
69
71
|
.description('Declarative OpenAI-Responses-compatible providers. See README.'),
|
package/lib/oauth.js
CHANGED
|
@@ -7,6 +7,9 @@ export function buildAuthorizeUrl({
|
|
|
7
7
|
extra,
|
|
8
8
|
scope,
|
|
9
9
|
}) {
|
|
10
|
+
if (!clientId || typeof clientId !== 'string' || !clientId.trim()) {
|
|
11
|
+
throw new Error('Missing required parameter: clientId')
|
|
12
|
+
}
|
|
10
13
|
const url = new URL(authUrl)
|
|
11
14
|
url.searchParams.set('response_type', 'code')
|
|
12
15
|
url.searchParams.set('client_id', clientId)
|
package/lib/responses-stream.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { iterateSse, jsonSse } from './sse.js'
|
|
2
|
-
import { httpError } from './wire.js'
|
|
2
|
+
import { httpError, toTokenUsage } from './wire.js'
|
|
3
3
|
|
|
4
4
|
function responsesFailure(code, message) {
|
|
5
5
|
const text = String(message || code || 'responses failed')
|
|
@@ -132,8 +132,8 @@ export class ResponsesStreamTranslator {
|
|
|
132
132
|
}
|
|
133
133
|
case 'response.completed': {
|
|
134
134
|
this.terminated = true
|
|
135
|
-
const
|
|
136
|
-
if (
|
|
135
|
+
const u = toTokenUsage(event.response?.usage)
|
|
136
|
+
if (u) chunks.push({ type: 'usage', usage: u })
|
|
137
137
|
chunks.push({ type: 'finish', reason: { kind: this.sawToolCall ? 'tool-calls' : 'stop' } })
|
|
138
138
|
return chunks
|
|
139
139
|
}
|
package/lib/routes/oauth.js
CHANGED
|
@@ -51,7 +51,20 @@ export function registerOauthRoutes(ctx, state) {
|
|
|
51
51
|
createdAt: Date.now(),
|
|
52
52
|
})
|
|
53
53
|
const cfg = { ...vendorConfig(provider, live()), redirectUri }
|
|
54
|
-
|
|
54
|
+
let url
|
|
55
|
+
try {
|
|
56
|
+
url = getVendor(provider).authorizeUrl(cfg, pkce)
|
|
57
|
+
} catch (err) {
|
|
58
|
+
pending.delete(pkce.state)
|
|
59
|
+
writeJson(res, 400, {
|
|
60
|
+
ok: false,
|
|
61
|
+
error: {
|
|
62
|
+
code: 'missing_client_id',
|
|
63
|
+
message: err.message || 'OAuth authorization configuration error',
|
|
64
|
+
},
|
|
65
|
+
})
|
|
66
|
+
return
|
|
67
|
+
}
|
|
55
68
|
// #89: if redirect_uri is loopback - start a temporary catch server.
|
|
56
69
|
let autoCatch = false
|
|
57
70
|
if (live().autoLoopback) {
|
|
@@ -52,6 +52,9 @@ export function defaults() {
|
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
export function authorizeUrl(cfg, pkce) {
|
|
55
|
+
if (!cfg || !cfg.clientId || !String(cfg.clientId).trim()) {
|
|
56
|
+
throw new Error('Google OAuth requires a custom Client ID. Please configure antigravityClientId in plugin settings or use "From CLI" to import active credentials.')
|
|
57
|
+
}
|
|
55
58
|
return buildAuthorizeUrl({
|
|
56
59
|
authUrl: AUTH,
|
|
57
60
|
clientId: cfg.clientId,
|
package/lib/vendors/glm.js
CHANGED
package/lib/wire.js
CHANGED
|
@@ -57,6 +57,102 @@ export function tokenBlobFromOAuth(json, extra) {
|
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
function toCount(v) {
|
|
61
|
+
if (typeof v === "number") {
|
|
62
|
+
return Number.isFinite(v) && v >= 0 ? Math.floor(v) : 0
|
|
63
|
+
}
|
|
64
|
+
if (typeof v === "string" && v.trim()) {
|
|
65
|
+
const n = Number(v)
|
|
66
|
+
return Number.isFinite(n) && n >= 0 ? Math.floor(n) : 0
|
|
67
|
+
}
|
|
68
|
+
return 0
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Normalizes vendor usage metadata into canonical DSH TokenUsage format.
|
|
73
|
+
* Guarantees inputTokens and outputTokens are non-negative finite integer numbers (never NaN/undefined).
|
|
74
|
+
* Calculates disjoint uncached input tokens where providers fold cache hits into total prompt tokens.
|
|
75
|
+
* Returns null if raw is not an object or contains no token-related fields.
|
|
76
|
+
*/
|
|
77
|
+
export function toTokenUsage(raw) {
|
|
78
|
+
if (!raw || typeof raw !== "object") return null
|
|
79
|
+
|
|
80
|
+
const hasKnownField = (
|
|
81
|
+
"inputTokens" in raw || "input_tokens" in raw || "promptTokens" in raw || "prompt_tokens" in raw || "input" in raw ||
|
|
82
|
+
"outputTokens" in raw || "output_tokens" in raw || "completionTokens" in raw || "completion_tokens" in raw || "output" in raw ||
|
|
83
|
+
"totalTokens" in raw || "total_tokens" in raw || "totalTokenCount" in raw ||
|
|
84
|
+
"promptTokenCount" in raw || "candidatesTokenCount" in raw
|
|
85
|
+
)
|
|
86
|
+
if (!hasKnownField) return null
|
|
87
|
+
|
|
88
|
+
const rawCached = toCount(
|
|
89
|
+
raw.input_token_details?.cached_tokens ??
|
|
90
|
+
raw.prompt_tokens_details?.cached_tokens ??
|
|
91
|
+
raw.cache_read_input_tokens ??
|
|
92
|
+
raw.cached_tokens ??
|
|
93
|
+
raw.cacheReadTokens ??
|
|
94
|
+
raw.cachedContentTokenCount
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
let inputTokens = 0
|
|
98
|
+
if (raw.inputTokens !== undefined) {
|
|
99
|
+
inputTokens = toCount(raw.inputTokens)
|
|
100
|
+
} else if (raw.prompt_tokens !== undefined || raw.promptTokens !== undefined) {
|
|
101
|
+
const prompt = toCount(raw.prompt_tokens ?? raw.promptTokens)
|
|
102
|
+
inputTokens = rawCached > 0 && prompt >= rawCached ? prompt - rawCached : prompt
|
|
103
|
+
} else if (raw.input_token_details?.cached_tokens !== undefined) {
|
|
104
|
+
const prompt = toCount(raw.input_tokens ?? raw.input)
|
|
105
|
+
inputTokens = rawCached > 0 && prompt >= rawCached ? prompt - rawCached : prompt
|
|
106
|
+
} else if (raw.promptTokenCount !== undefined) {
|
|
107
|
+
const prompt = toCount(raw.promptTokenCount)
|
|
108
|
+
inputTokens = rawCached > 0 && prompt >= rawCached ? prompt - rawCached : prompt
|
|
109
|
+
} else {
|
|
110
|
+
inputTokens = toCount(raw.input_tokens ?? raw.input)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
let outputTokens = 0
|
|
114
|
+
if (raw.outputTokens !== undefined) {
|
|
115
|
+
outputTokens = toCount(raw.outputTokens)
|
|
116
|
+
} else {
|
|
117
|
+
outputTokens = toCount(
|
|
118
|
+
raw.completion_tokens ??
|
|
119
|
+
raw.output_tokens ??
|
|
120
|
+
raw.completionTokens ??
|
|
121
|
+
raw.output ??
|
|
122
|
+
raw.candidatesTokenCount
|
|
123
|
+
)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const cacheWriteTokens = toCount(raw.cacheWriteTokens ?? raw.cache_creation_input_tokens)
|
|
127
|
+
const reasoningTokens = toCount(
|
|
128
|
+
raw.reasoningTokens ??
|
|
129
|
+
raw.output_token_details?.reasoning_tokens ??
|
|
130
|
+
raw.completion_tokens_details?.reasoning_tokens ??
|
|
131
|
+
raw.reasoning_tokens
|
|
132
|
+
)
|
|
133
|
+
const rawTotal = toCount(raw.totalTokens ?? raw.total_tokens ?? raw.totalTokenCount)
|
|
134
|
+
|
|
135
|
+
const out = {
|
|
136
|
+
inputTokens,
|
|
137
|
+
outputTokens,
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (rawCached > 0 || raw.cacheReadTokens !== undefined || raw.cache_read_input_tokens !== undefined || raw.input_token_details?.cached_tokens !== undefined || raw.prompt_tokens_details?.cached_tokens !== undefined || raw.cachedContentTokenCount !== undefined) {
|
|
141
|
+
out.cacheReadTokens = rawCached
|
|
142
|
+
}
|
|
143
|
+
if (cacheWriteTokens > 0 || raw.cacheWriteTokens !== undefined || raw.cache_creation_input_tokens !== undefined) {
|
|
144
|
+
out.cacheWriteTokens = cacheWriteTokens
|
|
145
|
+
}
|
|
146
|
+
if (reasoningTokens > 0 || raw.reasoningTokens !== undefined || raw.output_token_details?.reasoning_tokens !== undefined || raw.completion_tokens_details?.reasoning_tokens !== undefined) {
|
|
147
|
+
out.reasoningTokens = reasoningTokens
|
|
148
|
+
}
|
|
149
|
+
if (rawTotal > 0 || raw.totalTokens !== undefined || raw.total_tokens !== undefined || raw.totalTokenCount !== undefined) {
|
|
150
|
+
out.totalTokens = rawTotal > 0 ? rawTotal : (inputTokens + (out.cacheReadTokens || 0) + (out.cacheWriteTokens || 0) + outputTokens)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return out
|
|
154
|
+
}
|
|
155
|
+
|
|
60
156
|
function openBlock(kind, index) {
|
|
61
157
|
return { kind, index, text: '', id: '', name: '' }
|
|
62
158
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goodandready/dsh-subscriptions",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.12",
|
|
4
4
|
"description": "Use ChatGPT Codex, Claude, Grok, Antigravity, Kimi, GLM, Cursor, Kiro, Copilot, Qwen, ERNIE, Spark, JetBrains, Perplexity, Replit, and Cody subscriptions as DeepSeek Harness LLM providers via OAuth.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|