@goodandready/dsh-subscriptions 0.6.68 → 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 +5 -0
- package/README.ru.md +5 -0
- package/README.zh.md +5 -0
- 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/wire.js +6 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -207,6 +207,11 @@ 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
|
+
|
|
210
215
|
### 22. ⚡ xAI Grok RFC 8628 Device Authorization Flow (v0.6.68)
|
|
211
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.
|
|
212
217
|
|
package/README.ru.md
CHANGED
|
@@ -183,6 +183,11 @@ 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
|
+
|
|
186
191
|
### 18. ⚡ RFC 8628 Device Code авторизация для xAI Grok (v0.6.68)
|
|
187
192
|
* **Авторизация по коду устройства (GH #17, #532)**: Нативная поддержка RFC 8628 device code flow для xAI Grok. Устраняет проблему, когда xAI для CLI client ID открывает страницу ввода кода для Grok Build без обратного редиректа на loopback-порт. Отображает одноразовый код на карточке и автоматически поллит токены в фоне.
|
|
188
193
|
|
package/README.zh.md
CHANGED
|
@@ -132,6 +132,11 @@ 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
|
+
|
|
135
140
|
### xAI Grok RFC 8628 设备码授权流支持 (v0.6.68)
|
|
136
141
|
* **RFC 8628 设备码授权流 (GH #17, #532)**:为 xAI Grok 新增原生设备码登录授权支持。彻底解决 xAI 针对 CLI 客户端标识返回 Grok Build 验证码页面且不触发回环重定向回调导致的死锁问题。直接在槽位卡片展示用户授权码,并自动在后台轮询兑换会话令牌。
|
|
137
142
|
|
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/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",
|