@goodandready/dsh-subscriptions 0.6.67 → 0.6.69
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 +8 -0
- package/README.ru.md +8 -0
- package/README.zh.md +8 -0
- package/lib/client.js +13 -9
- package/lib/code-assist.js +24 -4
- package/lib/messages.js +7 -1
- package/lib/rotate.js +1 -0
- package/lib/vendors/antigravity.js +78 -26
- package/lib/vendors/grok.js +78 -0
- package/lib/wire.js +6 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -207,6 +207,14 @@ const res = await ctx.subscriptions.request('codex', '/backend-api/codex/images/
|
|
|
207
207
|
* **In-App Toast Alerts (`enableToasts`)**: Lightweight floating toast alerts in the DSH chat interface notifying you when accounts enter quarantine, recover from cooldowns, or exceed quota safety thresholds.
|
|
208
208
|
* **Automated Webhook Integration**: Instant event notifications dispatched via `notifyQuarantineEntered` and `notifyQuarantineReleased`.
|
|
209
209
|
|
|
210
|
+
### 23. 🧠 Antigravity Tiered Models, Tool Sentinel & Stream Resilience (v0.6.69)
|
|
211
|
+
* **Gemini 3 Tool Replay Sentinel (#534)**: Injects `thoughtSignature: 'skip_thought_signature_validator'` on reconstructed tool calls, enabling reliable multi-turn tool calling without Gemini 3 validation failures.
|
|
212
|
+
* **Tiered Flash Models & Thinking Levels (#534)**: Exposes Gemini 3.8 and 3.7 Flash High, Medium, and Low thinking levels with upstream `*-flash-tiered` mapping.
|
|
213
|
+
* **Stream Resilience & Empty Stream Retry (#534)**: Idle SSE timeout (30s), empty response detection, and transient stream retry with automatic account rotation.
|
|
214
|
+
|
|
215
|
+
### 22. ⚡ xAI Grok RFC 8628 Device Authorization Flow (v0.6.68)
|
|
216
|
+
* **RFC 8628 Device Code Authorization (GH #17, #532)**: Native device authorization grant support for xAI Grok. Solves the issue where xAI presents a Grok Build user-code verification page for CLI client IDs without redirecting back to loopback callbacks. Provides direct user code display and automatic token polling.
|
|
217
|
+
|
|
210
218
|
### 21. 🛡️ Per-Account Claude Billing Attribution Isolation (v0.6.67)
|
|
211
219
|
* **Per-Account `cc_turn_index` & `cch` Isolation (#530)**: Isolates Claude Code billing attribution state (`cch` session hash and `cc_turn_index` counter) per subscription account (`accountId`) in a bounded module Map instead of `globalThis`, preventing cross-account telemetry mixing in multi-account pools.
|
|
212
220
|
* **Dynamic `cc_prompt_index` & Kill-Switch**: Automatically derives 0-based `cc_prompt_index` from multi-turn `messages` history and supports suppression via `CLAUDE_CODE_ATTRIBUTION_HEADER=0` or `disableBillingHeader: true`.
|
package/README.ru.md
CHANGED
|
@@ -183,6 +183,14 @@ const res = await ctx.subscriptions.request('codex', '/backend-api/codex/images/
|
|
|
183
183
|
* **Всплывающие уведомления (Toast Alerts)**: Легковесные всплывающие тосты в веб-интерфейсе DSH при событиях карантина (с отображением причины), восстановлении доступа и критическом расходе квоты. Включение управляется параметром `enableToasts` (по умолчанию `true`).
|
|
184
184
|
* **Интеграция с вебхуками**: Автоматическая отправка уведомлений через `notifyQuarantineEntered` и `notifyQuarantineReleased`.
|
|
185
185
|
|
|
186
|
+
### 19. 🧠 Antigravity Tiered-модели, сентинел тулов и устойчивость стримов (v0.6.69)
|
|
187
|
+
* **Сентинел реплея тулов Gemini 3 (#534)**: Автоматическая вставка `thoughtSignature: 'skip_thought_signature_validator'` для реплея истории вызовов тулов без сбоев валидации Google API.
|
|
188
|
+
* **Tiered-модели и уровни мышления (#534)**: Выбор уровней High/Medium/Low для Gemini 3.8 и 3.7 Flash с маппингом в upstream `*-flash-tiered` и `thinkingLevel`.
|
|
189
|
+
* **Устойчивость стримов и авто-ретрай (#534)**: Защита от зависания SSE (таймаут 30с), детекция пустых ответов и авто-ротация аккаунтов по `EMPTY_RESPONSE`/`TIMEOUT`.
|
|
190
|
+
|
|
191
|
+
### 18. ⚡ RFC 8628 Device Code авторизация для xAI Grok (v0.6.68)
|
|
192
|
+
* **Авторизация по коду устройства (GH #17, #532)**: Нативная поддержка RFC 8628 device code flow для xAI Grok. Устраняет проблему, когда xAI для CLI client ID открывает страницу ввода кода для Grok Build без обратного редиректа на loopback-порт. Отображает одноразовый код на карточке и автоматически поллит токены в фоне.
|
|
193
|
+
|
|
186
194
|
### 17. 🛡️ Поаккаунтная изоляция атрибуции биллинга Claude (v0.6.67)
|
|
187
195
|
* **Изоляция `cc_turn_index` и `cch` по `accountId` (#530)**: Состояние атрибуции Claude Code (`cch` и счётчик `cc_turn_index`) изолировано по каждому аккаунту в ограниченной таблице модуля вместо `globalThis`, что исключает смешивание телеметрии между подписками в пуле.
|
|
188
196
|
* **Динамический `cc_prompt_index` и Kill-Switch**: Индекс `cc_prompt_index` автоматически вычисляется по числу пользовательских ходов в `messages`, а отключение заголовка поддерживается через `CLAUDE_CODE_ATTRIBUTION_HEADER=0` или `disableBillingHeader: true`.
|
package/README.zh.md
CHANGED
|
@@ -132,6 +132,14 @@ graph LR
|
|
|
132
132
|
queueTimeoutMs: 30000 # v0.6.62: 队列最大等待超时时间(毫秒)
|
|
133
133
|
* **自动化 Webhook 告警集成**:通过 `notifyQuarantineEntered` 与 `notifyQuarantineReleased` 自动向外部通知系统派发隔离生命周期事件。
|
|
134
134
|
|
|
135
|
+
### Antigravity 分档思考模型、工具回放哨兵与流式自愈 (v0.6.69)
|
|
136
|
+
* **Gemini 3 工具回放哨兵 (#534)**:在重建的工具调用中注入 `thoughtSignature: 'skip_thought_signature_validator'`,彻底解决多轮工具调用历史重放时 Gemini 3 的校验失败问题。
|
|
137
|
+
* **分档 Flash 模型与思考档位 (#534)**:将 `gemini-3.8-flash-tiered` 与 `3.7` 自动解析展开为 High/Medium/Low 档位选择,并在调用时自动组装 `thinkingConfig.thinkingLevel`。
|
|
138
|
+
* **流式空响应自愈与账号轮换 (#534)**:引入 30 秒 SSE 空闲超时保护与空响应检测,在未输出前自动重试一次,失败时通过 `EMPTY_RESPONSE` / `TIMEOUT` 自动轮换至下一个可用账号。
|
|
139
|
+
|
|
140
|
+
### xAI Grok RFC 8628 设备码授权流支持 (v0.6.68)
|
|
141
|
+
* **RFC 8628 设备码授权流 (GH #17, #532)**:为 xAI Grok 新增原生设备码登录授权支持。彻底解决 xAI 针对 CLI 客户端标识返回 Grok Build 验证码页面且不触发回环重定向回调导致的死锁问题。直接在槽位卡片展示用户授权码,并自动在后台轮询兑换会话令牌。
|
|
142
|
+
|
|
135
143
|
### Claude 账户级计费归因隔离与动态轮次索引 (v0.6.67)
|
|
136
144
|
* **按账户隔离 `cc_turn_index` 与 `cch` (#530)**:将 Claude Code 计费归因状态(会话哈希 `cch` 与递增轮次 `cc_turn_index`)按 `accountId` 隔离在有界模块级映射表中,彻底消除 `globalThis` 跨订阅账户状态混用问题。
|
|
137
145
|
* **动态计算 `cc_prompt_index` 与禁用开关**:根据多轮对话消息历史自动计算从 0 开始的 `cc_prompt_index`,并支持通过环境变量 `CLAUDE_CODE_ATTRIBUTION_HEADER=0` 或 `disableBillingHeader: true` 禁用归因头注入。
|
package/lib/client.js
CHANGED
|
@@ -313,16 +313,20 @@ const cssId = 'dsh-subscriptions/settings.module.css'
|
|
|
313
313
|
grok: {
|
|
314
314
|
title: "xAI Grok",
|
|
315
315
|
stepsEn: [
|
|
316
|
-
"1.
|
|
317
|
-
"2.
|
|
318
|
-
"3.
|
|
316
|
+
"1. Click \"Device Login\" (or \"Connect\") on the slot card.",
|
|
317
|
+
"2. A one-time code will appear on the card.",
|
|
318
|
+
"3. Open https://accounts.x.ai/oauth2/device, enter the code, and approve access for your xAI account.",
|
|
319
|
+
"4. The plugin automatically captures tokens and enables Grok models.",
|
|
320
|
+
"5. Or click \"📥 From CLI\" (~/.grok/auth.json) / paste an xAI API Key.",
|
|
319
321
|
],
|
|
320
322
|
stepsZh: [
|
|
321
|
-
"1.
|
|
322
|
-
"2.
|
|
323
|
-
"3.
|
|
323
|
+
"1. 点击卡片上的 \"设备码登录\" (Device Login) 或 \"连接\"。",
|
|
324
|
+
"2. 界面将展示一次性设备授权码。",
|
|
325
|
+
"3. 打开 https://accounts.x.ai/oauth2/device,输入代码,并为您拥有 Grok 订阅的 xAI 账户授权。",
|
|
326
|
+
"4. 授权成功后,插件会自动捕获会话并激活 Grok 官方模型。",
|
|
327
|
+
"5. 或点击 \"📥 从 CLI 导入\" (~/.grok/auth.json) / 直接粘贴 xAI API 密钥。",
|
|
324
328
|
],
|
|
325
|
-
link: "https://x.ai",
|
|
329
|
+
link: "https://accounts.x.ai/oauth2/device",
|
|
326
330
|
},
|
|
327
331
|
kiro: {
|
|
328
332
|
title: "AWS Kiro",
|
|
@@ -1362,7 +1366,7 @@ const cssId = 'dsh-subscriptions/settings.module.css'
|
|
|
1362
1366
|
|
|
1363
1367
|
const connect = async (provider, index) => {
|
|
1364
1368
|
setErr('')
|
|
1365
|
-
if (provider === 'copilot') {
|
|
1369
|
+
if (provider === 'copilot' || provider === 'grok') {
|
|
1366
1370
|
return deviceStartLogin(provider, index).catch((e) => setErr(cleanErrorMessage(e.message || e)))
|
|
1367
1371
|
}
|
|
1368
1372
|
if (provider === 'antigravity' && (!draft || !draft.antigravityClientId || !String(draft.antigravityClientId).trim())) {
|
|
@@ -2253,7 +2257,7 @@ const cssId = 'dsh-subscriptions/settings.module.css'
|
|
|
2253
2257
|
type: 'button', className: 'dsub-mini',
|
|
2254
2258
|
onClick: () => connect(row.slot.provider, row.slot.index).catch((e) => setErr(cleanErrorMessage(e.message || e))),
|
|
2255
2259
|
}, on ? t('reconnect') : t('connect')),
|
|
2256
|
-
((row.slot.provider === 'codex' || row.slot.provider === 'copilot') ? React.createElement('button', {
|
|
2260
|
+
((row.slot.provider === 'codex' || row.slot.provider === 'copilot' || row.slot.provider === 'grok') ? React.createElement('button', {
|
|
2257
2261
|
type: 'button', className: 'dsub-mini',
|
|
2258
2262
|
onClick: () => deviceStartLogin(row.slot.provider, row.slot.index).catch((e) => setErr(cleanErrorMessage(e.message || e))),
|
|
2259
2263
|
}, t('deviceLogin')) : null),
|
package/lib/code-assist.js
CHANGED
|
@@ -156,21 +156,28 @@ function catalogRows(json) {
|
|
|
156
156
|
return []
|
|
157
157
|
}
|
|
158
158
|
|
|
159
|
+
export function antigravityTieredAlias(model) {
|
|
160
|
+
const match = /^gemini-(3\.[78])-flash-(low|medium|high)$/.exec(model || '')
|
|
161
|
+
return match ? { model: `gemini-${match[1]}-flash-tiered`, thinkingLevel: match[2] } : null
|
|
162
|
+
}
|
|
163
|
+
|
|
159
164
|
export async function fetchAvailableModels(fetchImpl, token, extraHeaders, fallbackIds, provider) {
|
|
160
165
|
try {
|
|
161
166
|
const json = await postAssist(fetchImpl, 'fetchAvailableModels', token, {}, extraHeaders)
|
|
162
167
|
const out = []
|
|
163
|
-
|
|
168
|
+
const catalog = catalogRows(json)
|
|
169
|
+
const publishedIds = new Set(catalog.map((row) => String(row?.id || '').replace(/^models\//, '')))
|
|
170
|
+
for (const row of catalog) {
|
|
164
171
|
if (!row) continue
|
|
165
172
|
if (typeof row === 'string') {
|
|
166
|
-
if (row && !row.startsWith('chat_')) out.push({ provider, id: row, name: row })
|
|
173
|
+
if (row && !row.startsWith('chat_') && !row.startsWith('tab_')) out.push({ provider, id: row, name: row })
|
|
167
174
|
continue
|
|
168
175
|
}
|
|
169
176
|
if (row.isInternal) continue
|
|
170
177
|
const raw = String(row.id || row.name || row.model || '').replace(/^models\//, '')
|
|
171
|
-
if (!raw || raw.startsWith('chat_')) continue
|
|
178
|
+
if (!raw || raw.startsWith('chat_') || raw.startsWith('tab_')) continue
|
|
172
179
|
const display = row.displayName ?? row.display_name
|
|
173
|
-
if (display === '') continue
|
|
180
|
+
if (display === '' && !raw.endsWith('-tiered')) continue
|
|
174
181
|
out.push({
|
|
175
182
|
provider,
|
|
176
183
|
id: raw,
|
|
@@ -178,6 +185,19 @@ export async function fetchAvailableModels(fetchImpl, token, extraHeaders, fallb
|
|
|
178
185
|
...(row.recommended ? { description: 'Recommended' } : {}),
|
|
179
186
|
...(row.maxTokens ? { contextWindow: row.maxTokens } : {}),
|
|
180
187
|
})
|
|
188
|
+
const tiered = /^gemini-(3\.[78])-flash-tiered$/.exec(raw)
|
|
189
|
+
if (tiered) {
|
|
190
|
+
for (const level of ['high', 'medium', 'low']) {
|
|
191
|
+
const id = `gemini-${tiered[1]}-flash-${level}`
|
|
192
|
+
if (publishedIds.has(id)) continue
|
|
193
|
+
out.push({
|
|
194
|
+
provider,
|
|
195
|
+
id,
|
|
196
|
+
name: `Gemini ${tiered[1]} Flash (${level[0].toUpperCase()}${level.slice(1)})`,
|
|
197
|
+
...(row.maxTokens ? { contextWindow: row.maxTokens } : {}),
|
|
198
|
+
})
|
|
199
|
+
}
|
|
200
|
+
}
|
|
181
201
|
}
|
|
182
202
|
if (out.length) {
|
|
183
203
|
out.sort((a, b) => Number(Boolean(b.description)) - Number(Boolean(a.description)) || a.name.localeCompare(b.name))
|
package/lib/messages.js
CHANGED
|
@@ -478,7 +478,13 @@ export function googleContents(options) {
|
|
|
478
478
|
}))),
|
|
479
479
|
]
|
|
480
480
|
for (const call of calls) {
|
|
481
|
-
|
|
481
|
+
// DSH provider-neutral tool history cannot retain Gemini's opaque thoughtSignature.
|
|
482
|
+
// Gemini 3 requires one on replay after a tool result; Google's documented sentinel
|
|
483
|
+
// permits reconstructed/foreign history.
|
|
484
|
+
parts.push({
|
|
485
|
+
functionCall: { name: call.name, args: safeJson(call.arguments) },
|
|
486
|
+
thoughtSignature: 'skip_thought_signature_validator',
|
|
487
|
+
})
|
|
482
488
|
}
|
|
483
489
|
for (const result of toolResults(message.content)) {
|
|
484
490
|
const name = result.toolName || result.name || 'tool'
|
package/lib/rotate.js
CHANGED
|
@@ -18,6 +18,7 @@ export function isRegionError(err) {
|
|
|
18
18
|
|
|
19
19
|
export function isSwitchableError(err) {
|
|
20
20
|
if (!err || typeof err !== 'object') return false
|
|
21
|
+
if (err.name === 'TimeoutError' || err.code === 'TIMEOUT' || err.code === 'EMPTY_RESPONSE') return true
|
|
21
22
|
if (err.name === 'AbortError' || err.code === 'ABORT_ERR' || err.code === 'ERR_ABORTED' || (err.message && /aborted/i.test(err.message))) return false
|
|
22
23
|
const code = err.code || (err.failure && err.failure.code)
|
|
23
24
|
if (SWITCH_CODES.has(code)) return true
|
|
@@ -2,6 +2,7 @@ import { randomUUID } from 'node:crypto'
|
|
|
2
2
|
import { buildAuthorizeUrl } from '../oauth.js'
|
|
3
3
|
import { googleContents } from '../messages.js'
|
|
4
4
|
import { formTokenRequest, googleStream, throwHttpError, tokenBlobFromOAuth } from '../wire.js'
|
|
5
|
+
import { fetchWithTimeout } from '../http.js'
|
|
5
6
|
import { emailFromToken } from '../jwt.js'
|
|
6
7
|
import {
|
|
7
8
|
CODE_ASSIST_STREAM,
|
|
@@ -9,6 +10,7 @@ import {
|
|
|
9
10
|
antigravityIdentityHeaders,
|
|
10
11
|
discoverProject,
|
|
11
12
|
fetchAvailableModels,
|
|
13
|
+
antigravityTieredAlias,
|
|
12
14
|
retrieveQuotaPercent,
|
|
13
15
|
streamEnvelope,
|
|
14
16
|
assistHeaders,
|
|
@@ -38,14 +40,22 @@ export function defaults() {
|
|
|
38
40
|
clientSecret: '',
|
|
39
41
|
redirectUri: 'http://localhost:8085/oauth/callback',
|
|
40
42
|
models: [
|
|
43
|
+
'gemini-3.8-flash-high',
|
|
41
44
|
'gemini-3.8-flash-medium',
|
|
45
|
+
'gemini-3.8-flash-low',
|
|
46
|
+
'gemini-3.8-flash-tiered',
|
|
47
|
+
'gemini-3.7-flash-high',
|
|
42
48
|
'gemini-3.7-flash-medium',
|
|
49
|
+
'gemini-3.7-flash-low',
|
|
50
|
+
'gemini-3.7-flash-tiered',
|
|
51
|
+
'gemini-3.6-flash-high',
|
|
43
52
|
'gemini-3.6-flash-medium',
|
|
53
|
+
'gemini-3.6-flash-low',
|
|
44
54
|
'gemini-3.1-pro-low',
|
|
45
|
-
'
|
|
46
|
-
'claude-
|
|
55
|
+
'gemini-3.1-pro-high',
|
|
56
|
+
'claude-sonnet-4-6',
|
|
57
|
+
'claude-opus-4-6-thinking',
|
|
47
58
|
'gpt-oss-120b-medium',
|
|
48
|
-
'gemini-3.1-pro-high-vertex',
|
|
49
59
|
'gemini-3-flash',
|
|
50
60
|
'gemini-2.5-pro',
|
|
51
61
|
'gemini-2.5-flash',
|
|
@@ -183,30 +193,72 @@ export async function usage(blob, _cfg, fetchImpl) {
|
|
|
183
193
|
return retrieveQuotaPercent(fetchImpl || fetch, blob.accessToken, headersFor(blob.projectId || ''))
|
|
184
194
|
}
|
|
185
195
|
|
|
196
|
+
const RESPONSE_TIMEOUT_MS = 30000
|
|
197
|
+
|
|
198
|
+
function isTransientFailure(err) {
|
|
199
|
+
return err?.name === 'TimeoutError' || err?.code === 'TIMEOUT' || err?.code === 'EMPTY_RESPONSE' || err instanceof TypeError
|
|
200
|
+
}
|
|
201
|
+
|
|
186
202
|
export async function* streamOnce({ blob, options, fetchImpl, headers, signal, saveBlob }) {
|
|
203
|
+
const impl = fetchImpl || fetch
|
|
187
204
|
// Abort must cover the project pre-flight as well as the stream.
|
|
188
|
-
const preflight = (u, i) =>
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
205
|
+
const preflight = (u, i) => fetchWithTimeout(impl, u, { ...(i || {}), signal }, { timeoutMs: RESPONSE_TIMEOUT_MS })
|
|
206
|
+
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
207
|
+
let emitted = false
|
|
208
|
+
try {
|
|
209
|
+
const ready = await withProject(blob, preflight, saveBlob)
|
|
210
|
+
const tiered = antigravityTieredAlias(options.model)
|
|
211
|
+
const request = googleContents(options)
|
|
212
|
+
if (tiered) {
|
|
213
|
+
if (!request.generationConfig) request.generationConfig = {}
|
|
214
|
+
request.generationConfig.thinkingConfig = /** @type {any} */ ({ thinkingLevel: tiered.thinkingLevel })
|
|
215
|
+
}
|
|
216
|
+
const { envelope, sessionId } = streamEnvelope({
|
|
217
|
+
projectId: ready.projectId,
|
|
218
|
+
model: tiered ? tiered.model : options.model,
|
|
219
|
+
request,
|
|
220
|
+
userAgent: 'antigravity',
|
|
221
|
+
sessionId: ready.sessionId,
|
|
222
|
+
paidTierId: ready.paidTierId,
|
|
223
|
+
})
|
|
224
|
+
const res = await fetchWithTimeout(impl, `${CODE_ASSIST_STREAM}:streamGenerateContent?alt=sse`, {
|
|
225
|
+
method: 'POST',
|
|
226
|
+
headers: {
|
|
227
|
+
...headers,
|
|
228
|
+
...assistHeaders(ready.accessToken, { ...headersFor(ready.projectId || ''), Accept: 'text/event-stream' }),
|
|
229
|
+
},
|
|
230
|
+
body: JSON.stringify(envelope),
|
|
231
|
+
signal,
|
|
232
|
+
}, { timeoutMs: RESPONSE_TIMEOUT_MS })
|
|
233
|
+
if (!res.ok) throwHttpError(res.status, await res.text())
|
|
234
|
+
if (saveBlob && sessionId && sessionId !== ready.sessionId) {
|
|
235
|
+
await saveBlob({ ...ready, sessionId })
|
|
236
|
+
}
|
|
237
|
+
const pending = []
|
|
238
|
+
let hasContent = false
|
|
239
|
+
for await (const event of googleStream(res.body, { signal, idleTimeoutMs: RESPONSE_TIMEOUT_MS })) {
|
|
240
|
+
if (!hasContent) {
|
|
241
|
+
if (event.type === 'text-delta' || event.type === 'tool-call-delta' || event.type === 'reasoning-delta') {
|
|
242
|
+
hasContent = true
|
|
243
|
+
for (const item of pending) { emitted = true; yield item }
|
|
244
|
+
} else if (event.type !== 'finish') {
|
|
245
|
+
pending.push(event)
|
|
246
|
+
continue
|
|
247
|
+
} else {
|
|
248
|
+
continue
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
emitted = true
|
|
252
|
+
yield event
|
|
253
|
+
}
|
|
254
|
+
if (!hasContent) {
|
|
255
|
+
const err = /** @type {any} */ (new Error('Antigravity returned no content'))
|
|
256
|
+
err.code = 'EMPTY_RESPONSE'
|
|
257
|
+
throw err
|
|
258
|
+
}
|
|
259
|
+
return
|
|
260
|
+
} catch (err) {
|
|
261
|
+
if (emitted || signal?.aborted || attempt === 1 || !isTransientFailure(err)) throw err
|
|
262
|
+
}
|
|
210
263
|
}
|
|
211
|
-
yield* googleStream(res.body)
|
|
212
264
|
}
|
package/lib/vendors/grok.js
CHANGED
|
@@ -230,6 +230,84 @@ export async function usage(blob, cfg, fetchImpl) {
|
|
|
230
230
|
}
|
|
231
231
|
}
|
|
232
232
|
|
|
233
|
+
const DEVICE_CODE_URL = 'https://auth.x.ai/oauth2/device/code'
|
|
234
|
+
const DEVICE_AUTH_URL = 'https://accounts.x.ai/oauth2/device'
|
|
235
|
+
|
|
236
|
+
export async function deviceStart(cfg, fetchImpl) {
|
|
237
|
+
const impl = fetchImpl || fetch
|
|
238
|
+
const clientId = (cfg && cfg.clientId) || defaults().clientId
|
|
239
|
+
const params = new URLSearchParams({
|
|
240
|
+
client_id: clientId,
|
|
241
|
+
scope: SCOPE,
|
|
242
|
+
})
|
|
243
|
+
const res = await impl(DEVICE_CODE_URL, {
|
|
244
|
+
method: 'POST',
|
|
245
|
+
headers: {
|
|
246
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
247
|
+
Accept: 'application/json',
|
|
248
|
+
},
|
|
249
|
+
body: params.toString(),
|
|
250
|
+
})
|
|
251
|
+
if (!res.ok) throw httpError(res.status, await res.text().catch(() => ''), 'DEVICE')
|
|
252
|
+
const json = await readJson(res)
|
|
253
|
+
if (!json || typeof json.user_code !== 'string' || typeof json.device_code !== 'string') {
|
|
254
|
+
throw httpError(502, 'xAI device authorization response is incomplete', 'DEVICE')
|
|
255
|
+
}
|
|
256
|
+
return {
|
|
257
|
+
userCode: json.user_code,
|
|
258
|
+
deviceAuthId: json.device_code,
|
|
259
|
+
intervalMs: Math.max(parseInt(json.interval, 10) || 5, 1) * 1000,
|
|
260
|
+
authUrl: json.verification_uri_complete || json.verification_uri || DEVICE_AUTH_URL,
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
export async function devicePoll(cfg, session, fetchImpl) {
|
|
265
|
+
const impl = fetchImpl || fetch
|
|
266
|
+
const clientId = (cfg && cfg.clientId) || defaults().clientId
|
|
267
|
+
const params = new URLSearchParams({
|
|
268
|
+
grant_type: 'urn:ietf:params:oauth:grant-type:device_code',
|
|
269
|
+
client_id: clientId,
|
|
270
|
+
device_code: session.deviceAuthId,
|
|
271
|
+
})
|
|
272
|
+
const res = await impl(TOKEN, {
|
|
273
|
+
method: 'POST',
|
|
274
|
+
headers: {
|
|
275
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
276
|
+
Accept: 'application/json',
|
|
277
|
+
},
|
|
278
|
+
body: params.toString(),
|
|
279
|
+
})
|
|
280
|
+
if (res.status === 400 || res.status === 403) {
|
|
281
|
+
const rawText = await res.text().catch(() => '')
|
|
282
|
+
let errJson = {}
|
|
283
|
+
try {
|
|
284
|
+
errJson = JSON.parse(rawText)
|
|
285
|
+
} catch (err) {
|
|
286
|
+
errJson = { error: String(err && err.message) }
|
|
287
|
+
}
|
|
288
|
+
if (errJson && (errJson.error === 'authorization_pending' || errJson.error === 'slow_down')) {
|
|
289
|
+
return { status: 'pending' }
|
|
290
|
+
}
|
|
291
|
+
if (errJson && errJson.error === 'expired_token') {
|
|
292
|
+
return { status: 'expired' }
|
|
293
|
+
}
|
|
294
|
+
if (errJson && errJson.error) {
|
|
295
|
+
throw httpError(400, errJson.error_description || errJson.error, 'DEVICE')
|
|
296
|
+
}
|
|
297
|
+
throw httpError(res.status, rawText, 'DEVICE')
|
|
298
|
+
}
|
|
299
|
+
if (!res.ok) throw httpError(res.status, await res.text().catch(() => ''), 'DEVICE')
|
|
300
|
+
const json = await readJson(res)
|
|
301
|
+
if (!json || typeof json.access_token !== 'string') {
|
|
302
|
+
throw httpError(502, 'xAI device token response is incomplete', 'DEVICE')
|
|
303
|
+
}
|
|
304
|
+
const blob = tokenBlobFromOAuth(json)
|
|
305
|
+
return {
|
|
306
|
+
status: 'authorized',
|
|
307
|
+
blob: { ...blob, email: blob.email || emailFromToken(blob.accessToken), label: blob.label || 'Grok' },
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
233
311
|
export async function* streamOnce({ blob, options, fetchImpl, headers, config, signal }) {
|
|
234
312
|
const base = (config.baseUrl || defaults().baseUrl).replace(/\/$/, '')
|
|
235
313
|
let body = codexResponsesBody(options, '')
|
package/lib/wire.js
CHANGED
|
@@ -328,14 +328,18 @@ export async function* anthropicStream(body) {
|
|
|
328
328
|
yield { type: 'finish', reason: finish }
|
|
329
329
|
}
|
|
330
330
|
|
|
331
|
-
|
|
331
|
+
/**
|
|
332
|
+
* @param {any} body
|
|
333
|
+
* @param {{ signal?: AbortSignal, idleTimeoutMs?: number }} [options]
|
|
334
|
+
*/
|
|
335
|
+
export async function* googleStream(body, { signal, idleTimeoutMs } = {}) {
|
|
332
336
|
let index = 0
|
|
333
337
|
let started = false
|
|
334
338
|
let callCount = 0
|
|
335
339
|
const functionCallIndexes = new Map()
|
|
336
340
|
let finish = { kind: 'stop' }
|
|
337
341
|
let sawTerminal = false
|
|
338
|
-
for await (const data of iterateSse(body)) {
|
|
342
|
+
for await (const data of iterateSse(body, { signal, idleTimeoutMs })) {
|
|
339
343
|
if (data === '[DONE]') {
|
|
340
344
|
sawTerminal = true
|
|
341
345
|
break
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goodandready/dsh-subscriptions",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.69",
|
|
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",
|