@goodandready/dsh-subscriptions 0.6.39 → 0.6.41
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/lib/accounts.js +72 -47
- package/lib/adapter-manager.js +5 -3
- package/lib/adapter.js +94 -34
- package/lib/client.js +64 -11
- package/lib/history.js +12 -0
- package/lib/images.js +37 -14
- package/lib/index.js +7 -3
- package/lib/responses-stream.js +1 -1
- package/lib/rotate.js +2 -3
- package/lib/routes/proxy.js +1 -1
- package/lib/runaway-guard.js +8 -1
- package/lib/sse.js +25 -7
- package/lib/stream-rotate.js +12 -1
- package/lib/tools-normalizer.js +54 -27
- package/lib/vendor-factory.js +3 -3
- package/lib/vendors/cody.js +1 -4
- package/lib/vendors/copilot.js +8 -4
- package/lib/vendors/ernie.js +1 -5
- package/lib/vendors/jetbrains.js +1 -5
- package/lib/vendors/perplexity.js +1 -4
- package/lib/vendors/qwen.js +1 -5
- package/lib/vendors/replit.js +1 -4
- package/lib/vendors/spark.js +1 -5
- package/lib/wire.js +103 -14
- package/package.json +1 -1
package/lib/runaway-guard.js
CHANGED
|
@@ -79,7 +79,7 @@ export async function* withRunawayGuard(stream, options = {}) {
|
|
|
79
79
|
for await (const chunk of stream) {
|
|
80
80
|
if (chunk && chunk.type === 'finish') {
|
|
81
81
|
yield chunk
|
|
82
|
-
|
|
82
|
+
continue
|
|
83
83
|
}
|
|
84
84
|
|
|
85
85
|
const text = (chunk && typeof chunk === 'object')
|
|
@@ -88,6 +88,13 @@ export async function* withRunawayGuard(stream, options = {}) {
|
|
|
88
88
|
|
|
89
89
|
if (text && (chunk.type === 'text-delta' || chunk.type === 'reasoning-delta' || !chunk.type)) {
|
|
90
90
|
if (detector.feed(text)) {
|
|
91
|
+
if (options && typeof options === 'object') {
|
|
92
|
+
options.tripped = true
|
|
93
|
+
options.trippedReason = detector.getReason()
|
|
94
|
+
if (typeof options.onRunaway === 'function') {
|
|
95
|
+
options.onRunaway(detector.getReason())
|
|
96
|
+
}
|
|
97
|
+
}
|
|
91
98
|
yield {
|
|
92
99
|
type: 'finish',
|
|
93
100
|
reason: {
|
package/lib/sse.js
CHANGED
|
@@ -2,7 +2,10 @@ export async function* iterateSse(body, { idleTimeoutMs = 60000, signal } = {})
|
|
|
2
2
|
const reader = body && typeof body.getReader === 'function' ? body.getReader() : null
|
|
3
3
|
const decoder = new TextDecoder()
|
|
4
4
|
let buffer = ''
|
|
5
|
+
let terminated = false
|
|
6
|
+
|
|
5
7
|
async function* fromText(chunk) {
|
|
8
|
+
if (terminated) return
|
|
6
9
|
buffer += chunk
|
|
7
10
|
let sep
|
|
8
11
|
while ((sep = buffer.search(/\r?\n\r?\n/)) >= 0) {
|
|
@@ -10,12 +13,16 @@ export async function* iterateSse(body, { idleTimeoutMs = 60000, signal } = {})
|
|
|
10
13
|
buffer = buffer.slice(sep).replace(/^\r?\n\r?\n/, '')
|
|
11
14
|
const dataLines = []
|
|
12
15
|
for (const line of raw.split(/\r?\n/)) {
|
|
13
|
-
if (line.startsWith(
|
|
16
|
+
if (line.startsWith(':')) continue // SSE comments / keep-alive pings
|
|
14
17
|
if (line.startsWith('data:')) dataLines.push(line.slice(5).trimStart())
|
|
15
18
|
}
|
|
16
19
|
if (!dataLines.length) continue
|
|
17
20
|
const data = dataLines.join('\n')
|
|
18
|
-
if (data === '[DONE]')
|
|
21
|
+
if (data === '[DONE]') {
|
|
22
|
+
terminated = true
|
|
23
|
+
yield '[DONE]'
|
|
24
|
+
return
|
|
25
|
+
}
|
|
19
26
|
yield data
|
|
20
27
|
}
|
|
21
28
|
}
|
|
@@ -36,7 +43,7 @@ export async function* iterateSse(body, { idleTimeoutMs = 60000, signal } = {})
|
|
|
36
43
|
reject(err)
|
|
37
44
|
}, idleTimeoutMs)
|
|
38
45
|
})
|
|
39
|
-
|
|
46
|
+
|
|
40
47
|
let abortPromise
|
|
41
48
|
let abortHandler
|
|
42
49
|
if (signal) {
|
|
@@ -64,12 +71,15 @@ export async function* iterateSse(body, { idleTimeoutMs = 60000, signal } = {})
|
|
|
64
71
|
|
|
65
72
|
if (reader) {
|
|
66
73
|
try {
|
|
67
|
-
while (
|
|
74
|
+
while (!terminated) {
|
|
68
75
|
const { done, value } = await readWithTimeout(reader)
|
|
69
76
|
if (done) break
|
|
70
77
|
yield* fromText(decoder.decode(value, { stream: true }))
|
|
78
|
+
if (terminated) break
|
|
79
|
+
}
|
|
80
|
+
if (!terminated) {
|
|
81
|
+
yield* fromText(decoder.decode())
|
|
71
82
|
}
|
|
72
|
-
yield* fromText(decoder.decode())
|
|
73
83
|
} finally {
|
|
74
84
|
if (typeof reader.cancel === 'function') await reader.cancel().catch(() => {})
|
|
75
85
|
}
|
|
@@ -77,11 +87,19 @@ export async function* iterateSse(body, { idleTimeoutMs = 60000, signal } = {})
|
|
|
77
87
|
}
|
|
78
88
|
if (body && typeof body[Symbol.asyncIterator] === 'function') {
|
|
79
89
|
for await (const chunk of body) {
|
|
80
|
-
if (signal && signal.aborted)
|
|
90
|
+
if (signal && signal.aborted) {
|
|
91
|
+
const err = new Error('stream aborted by client')
|
|
92
|
+
err.name = 'AbortError'
|
|
93
|
+
throw err
|
|
94
|
+
}
|
|
95
|
+
if (terminated) break
|
|
81
96
|
const text = typeof chunk === 'string' ? chunk : decoder.decode(chunk, { stream: true })
|
|
82
97
|
yield* fromText(text)
|
|
98
|
+
if (terminated) break
|
|
99
|
+
}
|
|
100
|
+
if (!terminated) {
|
|
101
|
+
yield* fromText(decoder.decode())
|
|
83
102
|
}
|
|
84
|
-
yield* fromText(decoder.decode())
|
|
85
103
|
return
|
|
86
104
|
}
|
|
87
105
|
const text = typeof body === 'string' ? body : await new Response(body).text()
|
package/lib/stream-rotate.js
CHANGED
|
@@ -53,7 +53,18 @@ export async function* streamWithRotation({
|
|
|
53
53
|
|
|
54
54
|
while (true) {
|
|
55
55
|
try {
|
|
56
|
-
const
|
|
56
|
+
const runawayState = options && options._runawayState ? options._runawayState : { tripped: false, reason: null }
|
|
57
|
+
const runawayOpts = {
|
|
58
|
+
...(options && options.runawayGuard),
|
|
59
|
+
onRunaway: (reason) => {
|
|
60
|
+
runawayState.tripped = true
|
|
61
|
+
runawayState.reason = reason
|
|
62
|
+
},
|
|
63
|
+
}
|
|
64
|
+
const guardedStream = withRunawayGuard(
|
|
65
|
+
streamOnce(account, { ...options, _runawayState: runawayState }),
|
|
66
|
+
runawayOpts
|
|
67
|
+
)
|
|
57
68
|
for await (const chunk of guardedStream) {
|
|
58
69
|
firstChunkDelivered = true
|
|
59
70
|
yield chunk
|
package/lib/tools-normalizer.js
CHANGED
|
@@ -1,40 +1,44 @@
|
|
|
1
1
|
export function sanitizeToolName(name) {
|
|
2
|
-
if (typeof name !==
|
|
3
|
-
const sanitized = name.trim().replace(/[^a-zA-Z0-9_-]/g,
|
|
4
|
-
return sanitized ||
|
|
2
|
+
if (typeof name !== "string" || !name.trim()) return "tool_fn"
|
|
3
|
+
const sanitized = name.trim().replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 64)
|
|
4
|
+
return sanitized || "tool_fn"
|
|
5
5
|
}
|
|
6
6
|
|
|
7
7
|
export function sanitizeDescription(desc) {
|
|
8
|
-
if (typeof desc ===
|
|
9
|
-
if (desc == null) return
|
|
8
|
+
if (typeof desc === "string") return desc.trim()
|
|
9
|
+
if (desc == null) return ""
|
|
10
10
|
return String(desc).trim()
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
export function normalizeJsonSchema(schema, depth = 0) {
|
|
14
|
-
if (depth > 12) return { type:
|
|
15
|
-
if (!schema || typeof schema !==
|
|
16
|
-
return { type:
|
|
14
|
+
if (depth > 12) return { type: "string" }
|
|
15
|
+
if (!schema || typeof schema !== "object") {
|
|
16
|
+
return { type: "object" }
|
|
17
17
|
}
|
|
18
18
|
if (Array.isArray(schema)) {
|
|
19
|
-
return normalizeJsonSchema(schema[0] || { type:
|
|
19
|
+
return normalizeJsonSchema(schema[0] || { type: "object" }, depth + 1)
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
const out = {}
|
|
23
23
|
|
|
24
|
+
if (schema.title) out.title = String(schema.title)
|
|
25
|
+
|
|
24
26
|
let type = schema.type
|
|
25
27
|
let nullable = false
|
|
26
28
|
if (Array.isArray(type)) {
|
|
27
|
-
nullable = type.includes(
|
|
28
|
-
type = type.find((t) => t && t !==
|
|
29
|
+
nullable = type.includes("null")
|
|
30
|
+
type = type.find((t) => t && t !== "null") || "string"
|
|
29
31
|
}
|
|
30
|
-
if (typeof type ===
|
|
32
|
+
if (typeof type === "string") {
|
|
31
33
|
out.type = type.toLowerCase()
|
|
32
34
|
} else if (schema.properties) {
|
|
33
|
-
out.type =
|
|
35
|
+
out.type = "object"
|
|
34
36
|
} else if (schema.items) {
|
|
35
|
-
out.type =
|
|
37
|
+
out.type = "array"
|
|
38
|
+
} else if (schema.anyOf || schema.oneOf || schema.allOf) {
|
|
39
|
+
// Schema combinator without explicit type
|
|
36
40
|
} else {
|
|
37
|
-
out.type = depth === 0 ?
|
|
41
|
+
out.type = depth === 0 ? "object" : "string"
|
|
38
42
|
}
|
|
39
43
|
|
|
40
44
|
if (nullable) {
|
|
@@ -45,51 +49,74 @@ export function normalizeJsonSchema(schema, depth = 0) {
|
|
|
45
49
|
out.description = String(schema.description)
|
|
46
50
|
}
|
|
47
51
|
|
|
48
|
-
if (
|
|
49
|
-
|
|
52
|
+
if (schema.default !== undefined) {
|
|
53
|
+
out.default = schema.default
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (out.type === "object" || (!out.type && (schema.properties || schema.required || schema.additionalProperties !== undefined))) {
|
|
57
|
+
if (schema.properties && typeof schema.properties === "object" && !Array.isArray(schema.properties)) {
|
|
50
58
|
out.properties = {}
|
|
51
59
|
for (const [key, val] of Object.entries(schema.properties)) {
|
|
52
|
-
if (typeof key ===
|
|
60
|
+
if (typeof key === "string" && key) {
|
|
53
61
|
out.properties[key] = normalizeJsonSchema(val, depth + 1)
|
|
54
62
|
}
|
|
55
63
|
}
|
|
56
64
|
}
|
|
57
65
|
|
|
58
66
|
if (Array.isArray(schema.required)) {
|
|
59
|
-
const propKeys = new Set(Object.keys(out.properties))
|
|
67
|
+
const propKeys = out.properties ? new Set(Object.keys(out.properties)) : null
|
|
60
68
|
const reqs = schema.required
|
|
61
|
-
.filter((k) => typeof k ===
|
|
62
|
-
.filter((k) => propKeys.size === 0 || propKeys.has(k))
|
|
69
|
+
.filter((k) => typeof k === "string" && k.length > 0)
|
|
70
|
+
.filter((k) => !propKeys || propKeys.size === 0 || propKeys.has(k))
|
|
63
71
|
if (reqs.length > 0) {
|
|
64
72
|
out.required = [...new Set(reqs)]
|
|
65
73
|
}
|
|
66
74
|
}
|
|
67
75
|
|
|
68
|
-
if (typeof schema.additionalProperties ===
|
|
76
|
+
if (typeof schema.additionalProperties === "boolean") {
|
|
69
77
|
out.additionalProperties = schema.additionalProperties
|
|
78
|
+
} else if (typeof schema.additionalProperties === "object" && schema.additionalProperties !== null) {
|
|
79
|
+
out.additionalProperties = normalizeJsonSchema(schema.additionalProperties, depth + 1)
|
|
70
80
|
}
|
|
71
|
-
} else if (out.type ===
|
|
81
|
+
} else if (out.type === "array" || (!out.type && schema.items)) {
|
|
72
82
|
if (schema.items) {
|
|
73
83
|
out.items = normalizeJsonSchema(
|
|
74
84
|
Array.isArray(schema.items) ? schema.items[0] : schema.items,
|
|
75
85
|
depth + 1,
|
|
76
86
|
)
|
|
77
87
|
} else {
|
|
78
|
-
out.items = { type:
|
|
88
|
+
out.items = { type: "string" }
|
|
79
89
|
}
|
|
80
90
|
}
|
|
81
91
|
|
|
92
|
+
if (Array.isArray(schema.anyOf)) {
|
|
93
|
+
out.anyOf = schema.anyOf.map((s) => normalizeJsonSchema(s, depth + 1))
|
|
94
|
+
}
|
|
95
|
+
if (Array.isArray(schema.oneOf)) {
|
|
96
|
+
out.oneOf = schema.oneOf.map((s) => normalizeJsonSchema(s, depth + 1))
|
|
97
|
+
}
|
|
98
|
+
if (Array.isArray(schema.allOf)) {
|
|
99
|
+
out.allOf = schema.allOf.map((s) => normalizeJsonSchema(s, depth + 1))
|
|
100
|
+
}
|
|
101
|
+
|
|
82
102
|
if (Array.isArray(schema.enum) && schema.enum.length > 0) {
|
|
83
103
|
const cleaned = [...new Set(
|
|
84
104
|
schema.enum
|
|
85
|
-
.
|
|
86
|
-
.
|
|
105
|
+
.filter((v) => v !== undefined && v !== null)
|
|
106
|
+
.map((v) => (typeof v === "string" ? v.trim() : v))
|
|
107
|
+
.filter((v) => (typeof v === "string" ? v.length > 0 : true))
|
|
87
108
|
)]
|
|
88
109
|
if (cleaned.length > 0) {
|
|
89
110
|
out.enum = cleaned
|
|
90
111
|
}
|
|
91
112
|
}
|
|
92
113
|
|
|
114
|
+
if (typeof schema.pattern === "string") out.pattern = schema.pattern
|
|
115
|
+
if (typeof schema.minimum === "number") out.minimum = schema.minimum
|
|
116
|
+
if (typeof schema.maximum === "number") out.maximum = schema.maximum
|
|
117
|
+
if (typeof schema.minLength === "number") out.minLength = schema.minLength
|
|
118
|
+
if (typeof schema.maxLength === "number") out.maxLength = schema.maxLength
|
|
119
|
+
|
|
93
120
|
return out
|
|
94
121
|
}
|
|
95
122
|
|
|
@@ -98,7 +125,7 @@ export function normalizeTools(tools) {
|
|
|
98
125
|
const validTools = []
|
|
99
126
|
|
|
100
127
|
for (const tool of tools) {
|
|
101
|
-
if (!tool || typeof tool !==
|
|
128
|
+
if (!tool || typeof tool !== "object") continue
|
|
102
129
|
const rawName = tool.name || (tool.function && tool.function.name)
|
|
103
130
|
if (!rawName) continue
|
|
104
131
|
|
package/lib/vendor-factory.js
CHANGED
|
@@ -13,8 +13,8 @@ import { codexResponsesBody, modelCatalog } from "./messages.js"
|
|
|
13
13
|
import { formTokenRequest, jsonTokenRequest, codexResponsesStream, readJson, tokenBlobFromOAuth, httpError } from "./wire.js"
|
|
14
14
|
import { emailFromToken } from "./jwt.js"
|
|
15
15
|
|
|
16
|
-
function defaultModels(models) {
|
|
17
|
-
return modelCatalog(
|
|
16
|
+
function defaultModels(providerId, models) {
|
|
17
|
+
return modelCatalog(providerId, (models || []).map((row) => (typeof row === "string" ? { id: row, name: row } : row)))
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
/**
|
|
@@ -84,7 +84,7 @@ export function createVendorFromProfile(profile) {
|
|
|
84
84
|
return tokenBlobFromOAuth(json, { label: blob.label, email: blob.email })
|
|
85
85
|
},
|
|
86
86
|
async listModels(blob, cfg, fetchImpl) {
|
|
87
|
-
const catalog = defaultModels(cfg.models || profile.models || [])
|
|
87
|
+
const catalog = defaultModels(id, cfg.models || profile.models || [])
|
|
88
88
|
if (!profile.modelsPath) return catalog
|
|
89
89
|
try {
|
|
90
90
|
const res = await fetchWithTimeout(fetchImpl || fetch, `${base}${profile.modelsPath}`, {
|
package/lib/vendors/cody.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { LlmError } from '@deepseek-ai/dsh-llm'
|
|
2
2
|
import { openaiMessages } from '../messages.js'
|
|
3
3
|
import { openaiChatStream, httpError } from '../wire.js'
|
|
4
|
-
import { asUsageSnapshot } from '../usage.js'
|
|
5
4
|
|
|
6
5
|
export const id = 'cody'
|
|
7
6
|
const CODY_API_BASE = 'https://sourcegraph.com/.api'
|
|
@@ -42,9 +41,7 @@ export async function listModels() {
|
|
|
42
41
|
|
|
43
42
|
export async function usage(blob) {
|
|
44
43
|
if (blob && (blob.accessToken || blob.token)) {
|
|
45
|
-
|
|
46
|
-
if (snap) snap.plan = 'Cody Pro'
|
|
47
|
-
return snap
|
|
44
|
+
return { usedPercent: null, plan: 'Cody Pro', timestamp: Date.now() }
|
|
48
45
|
}
|
|
49
46
|
return null
|
|
50
47
|
}
|
package/lib/vendors/copilot.js
CHANGED
|
@@ -2,7 +2,6 @@ import { LlmError } from "@deepseek-ai/dsh-llm"
|
|
|
2
2
|
import { openaiMessages, openaiTools, modelCatalog } from "../messages.js"
|
|
3
3
|
import { fetchWithTimeout } from "../http.js"
|
|
4
4
|
import { openaiChatStream, readJson, httpError } from "../wire.js"
|
|
5
|
-
import { asUsageSnapshot } from "../usage.js"
|
|
6
5
|
|
|
7
6
|
export const id = "copilot"
|
|
8
7
|
|
|
@@ -268,9 +267,14 @@ export async function usage(blob, config, fetchImpl) {
|
|
|
268
267
|
})
|
|
269
268
|
if (!res.ok) return null
|
|
270
269
|
const user = await readJson(res)
|
|
271
|
-
const
|
|
272
|
-
|
|
273
|
-
|
|
270
|
+
const usedPercent = user && typeof user.used_percent === "number" && Number.isFinite(user.used_percent)
|
|
271
|
+
? user.used_percent
|
|
272
|
+
: null
|
|
273
|
+
return {
|
|
274
|
+
usedPercent,
|
|
275
|
+
plan: user && user.plan ? (user.plan.name || String(user.plan)) : "Copilot",
|
|
276
|
+
timestamp: Date.now(),
|
|
277
|
+
}
|
|
274
278
|
} catch {
|
|
275
279
|
return null
|
|
276
280
|
}
|
package/lib/vendors/ernie.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { LlmError } from '@deepseek-ai/dsh-llm'
|
|
2
2
|
import { openaiMessages } from '../messages.js'
|
|
3
3
|
import { openaiChatStream, readJson, httpError } from '../wire.js'
|
|
4
|
-
import { asUsageSnapshot } from '../usage.js'
|
|
5
4
|
|
|
6
5
|
export const id = 'ernie'
|
|
7
6
|
export const ERNIE_OAUTH_URL = 'https://aip.baidubce.com/oauth/2.0/token'
|
|
@@ -51,10 +50,7 @@ async function refreshErnieToken(apiKey, secretKey, fetchImpl) {
|
|
|
51
50
|
return readJson(res)
|
|
52
51
|
}
|
|
53
52
|
|
|
54
|
-
export async function usage(
|
|
55
|
-
if (blob && (blob.accessToken || blob.apiKey)) {
|
|
56
|
-
return asUsageSnapshot(20)
|
|
57
|
-
}
|
|
53
|
+
export async function usage() {
|
|
58
54
|
return null
|
|
59
55
|
}
|
|
60
56
|
|
package/lib/vendors/jetbrains.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { LlmError } from '@deepseek-ai/dsh-llm'
|
|
2
2
|
import { openaiMessages } from '../messages.js'
|
|
3
3
|
import { openaiChatStream, httpError } from '../wire.js'
|
|
4
|
-
import { asUsageSnapshot } from '../usage.js'
|
|
5
4
|
|
|
6
5
|
export const id = 'jetbrains'
|
|
7
6
|
const JETBRAINS_API_BASE = 'https://api.jetbrains.ai'
|
|
@@ -42,10 +41,7 @@ export async function listModels() {
|
|
|
42
41
|
return JETBRAINS_MODELS
|
|
43
42
|
}
|
|
44
43
|
|
|
45
|
-
export async function usage(
|
|
46
|
-
if (blob && (blob.accessToken || blob.token)) {
|
|
47
|
-
return asUsageSnapshot(30)
|
|
48
|
-
}
|
|
44
|
+
export async function usage() {
|
|
49
45
|
return null
|
|
50
46
|
}
|
|
51
47
|
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { LlmError } from '@deepseek-ai/dsh-llm'
|
|
2
2
|
import { openaiMessages } from '../messages.js'
|
|
3
3
|
import { openaiChatStream, httpError } from '../wire.js'
|
|
4
|
-
import { asUsageSnapshot } from '../usage.js'
|
|
5
4
|
|
|
6
5
|
export const id = 'perplexity'
|
|
7
6
|
const PERPLEXITY_API_BASE = 'https://api.perplexity.ai'
|
|
@@ -44,9 +43,7 @@ export async function listModels() {
|
|
|
44
43
|
|
|
45
44
|
export async function usage(blob) {
|
|
46
45
|
if (blob && (blob.accessToken || blob.apiKey)) {
|
|
47
|
-
|
|
48
|
-
if (snap) snap.plan = 'Perplexity Pro'
|
|
49
|
-
return snap
|
|
46
|
+
return { usedPercent: null, plan: 'Perplexity Pro', timestamp: Date.now() }
|
|
50
47
|
}
|
|
51
48
|
return null
|
|
52
49
|
}
|
package/lib/vendors/qwen.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { LlmError } from '@deepseek-ai/dsh-llm'
|
|
2
2
|
import { openaiMessages, openaiTools } from '../messages.js'
|
|
3
3
|
import { openaiChatStream, httpError } from '../wire.js'
|
|
4
|
-
import { asUsageSnapshot } from '../usage.js'
|
|
5
4
|
|
|
6
5
|
export const id = 'qwen'
|
|
7
6
|
const QWEN_API_BASE = 'https://dashscope.aliyuncs.com/compatible-mode/v1'
|
|
@@ -49,10 +48,7 @@ export async function listModels() {
|
|
|
49
48
|
return QWEN_MODELS
|
|
50
49
|
}
|
|
51
50
|
|
|
52
|
-
export async function usage(
|
|
53
|
-
if (blob && (blob.accessToken || blob.apiKey)) {
|
|
54
|
-
return asUsageSnapshot(15)
|
|
55
|
-
}
|
|
51
|
+
export async function usage() {
|
|
56
52
|
return null
|
|
57
53
|
}
|
|
58
54
|
|
package/lib/vendors/replit.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { LlmError } from '@deepseek-ai/dsh-llm'
|
|
2
2
|
import { openaiMessages } from '../messages.js'
|
|
3
3
|
import { openaiChatStream, httpError } from '../wire.js'
|
|
4
|
-
import { asUsageSnapshot } from '../usage.js'
|
|
5
4
|
|
|
6
5
|
export const id = 'replit'
|
|
7
6
|
const REPLIT_API_BASE = 'https://replit.com/api/v1/ai'
|
|
@@ -37,9 +36,7 @@ export async function listModels() {
|
|
|
37
36
|
|
|
38
37
|
export async function usage(blob) {
|
|
39
38
|
if (blob && (blob.accessToken || blob.token)) {
|
|
40
|
-
|
|
41
|
-
if (snap) snap.plan = 'Replit Core'
|
|
42
|
-
return snap
|
|
39
|
+
return { usedPercent: null, plan: 'Replit Core', timestamp: Date.now() }
|
|
43
40
|
}
|
|
44
41
|
return null
|
|
45
42
|
}
|
package/lib/vendors/spark.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { LlmError } from '@deepseek-ai/dsh-llm'
|
|
2
2
|
import { openaiMessages } from '../messages.js'
|
|
3
3
|
import { openaiChatStream, httpError } from '../wire.js'
|
|
4
|
-
import { asUsageSnapshot } from '../usage.js'
|
|
5
4
|
|
|
6
5
|
export const id = 'spark'
|
|
7
6
|
const SPARK_API_BASE = 'https://spark-api-open.xf-yun.com/v1'
|
|
@@ -42,10 +41,7 @@ export async function listModels() {
|
|
|
42
41
|
return SPARK_MODELS
|
|
43
42
|
}
|
|
44
43
|
|
|
45
|
-
export async function usage(
|
|
46
|
-
if (blob && (blob.accessToken || blob.apiKey)) {
|
|
47
|
-
return asUsageSnapshot(12)
|
|
48
|
-
}
|
|
44
|
+
export async function usage() {
|
|
49
45
|
return null
|
|
50
46
|
}
|
|
51
47
|
|
package/lib/wire.js
CHANGED
|
@@ -162,7 +162,12 @@ export async function* openaiChatStream(body) {
|
|
|
162
162
|
let textBlock = null
|
|
163
163
|
const tools = new Map()
|
|
164
164
|
let finish = { kind: 'stop' }
|
|
165
|
+
let sawTerminal = false
|
|
165
166
|
for await (const data of iterateSse(body)) {
|
|
167
|
+
if (data === '[DONE]') {
|
|
168
|
+
sawTerminal = true
|
|
169
|
+
break
|
|
170
|
+
}
|
|
166
171
|
const json = jsonSse(data)
|
|
167
172
|
if (!json) continue
|
|
168
173
|
const choice = (json.choices && json.choices[0]) || {}
|
|
@@ -196,9 +201,19 @@ export async function* openaiChatStream(body) {
|
|
|
196
201
|
}
|
|
197
202
|
}
|
|
198
203
|
if (json.usage) yield { type: 'usage', usage: json.usage }
|
|
199
|
-
if (choice.finish_reason
|
|
200
|
-
|
|
204
|
+
if (choice.finish_reason) {
|
|
205
|
+
sawTerminal = true
|
|
206
|
+
if (choice.finish_reason === 'length') finish = { kind: 'max-tokens' }
|
|
207
|
+
else if (choice.finish_reason === 'tool_calls' || choice.finish_reason === 'function_call') finish = { kind: 'tool-calls' }
|
|
208
|
+
else if (choice.finish_reason === 'content_filter') finish = { kind: 'error', failure: { code: 'CONTENT_FILTER', message: 'content filter triggered' } }
|
|
209
|
+
else if (choice.finish_reason === 'stop') finish = { kind: 'stop' }
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
if (!sawTerminal && (textBlock !== null || tools.size > 0)) {
|
|
213
|
+
yield { type: 'finish', reason: { kind: 'error', failure: { code: 'INCOMPLETE', message: 'stream terminated unexpectedly before finish_reason or terminal marker' } } }
|
|
214
|
+
return
|
|
201
215
|
}
|
|
216
|
+
if (finish.kind === 'stop' && tools.size > 0) finish = { kind: 'tool-calls' }
|
|
202
217
|
yield { type: 'finish', reason: finish }
|
|
203
218
|
}
|
|
204
219
|
|
|
@@ -208,10 +223,20 @@ export async function* anthropicStream(body) {
|
|
|
208
223
|
let index = 0
|
|
209
224
|
let textIndex = 0
|
|
210
225
|
const toolCallIds = new Map()
|
|
226
|
+
let finish = { kind: 'stop' }
|
|
227
|
+
let sawTerminal = false
|
|
228
|
+
let usageState = null
|
|
229
|
+
let yieldedUsage = false
|
|
211
230
|
for await (const data of iterateSse(body)) {
|
|
231
|
+
if (data === '[DONE]') {
|
|
232
|
+
sawTerminal = true
|
|
233
|
+
break
|
|
234
|
+
}
|
|
212
235
|
const json = jsonSse(data)
|
|
213
236
|
if (!json) continue
|
|
214
|
-
if (json.type === '
|
|
237
|
+
if (json.type === 'message_start' && json.message && json.message.usage) {
|
|
238
|
+
usageState = { ...(usageState || {}), ...json.message.usage }
|
|
239
|
+
} else if (json.type === 'content_block_start') {
|
|
215
240
|
const block = json.content_block || {}
|
|
216
241
|
if (block.type === 'text') {
|
|
217
242
|
textIndex = json.index ?? index++
|
|
@@ -243,17 +268,54 @@ export async function* anthropicStream(body) {
|
|
|
243
268
|
argumentsDelta: delta.partial_json,
|
|
244
269
|
}
|
|
245
270
|
}
|
|
246
|
-
} else if (json.type === 'message_delta'
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
}
|
|
271
|
+
} else if (json.type === 'message_delta') {
|
|
272
|
+
sawTerminal = true
|
|
273
|
+
if (json.delta && json.delta.stop_reason) {
|
|
274
|
+
const sr = json.delta.stop_reason
|
|
275
|
+
if (sr === 'tool_use') finish = { kind: 'tool-calls' }
|
|
276
|
+
else if (sr === 'max_tokens') finish = { kind: 'max-tokens' }
|
|
277
|
+
else finish = { kind: 'stop' }
|
|
278
|
+
}
|
|
279
|
+
if (json.usage) {
|
|
280
|
+
usageState = { ...(usageState || {}), ...json.usage }
|
|
281
|
+
}
|
|
282
|
+
if (usageState) {
|
|
283
|
+
const norm = toTokenUsage(usageState)
|
|
284
|
+
yieldedUsage = true
|
|
285
|
+
yield {
|
|
286
|
+
type: 'usage',
|
|
287
|
+
usage: {
|
|
288
|
+
...usageState,
|
|
289
|
+
...(norm || {}),
|
|
290
|
+
input: norm ? norm.inputTokens : (Number(usageState.input_tokens ?? usageState.input ?? 0) || 0),
|
|
291
|
+
output: norm ? norm.outputTokens : (Number(usageState.output_tokens ?? usageState.output ?? 0) || 0),
|
|
292
|
+
},
|
|
293
|
+
}
|
|
253
294
|
}
|
|
295
|
+
} else if (json.type === 'error') {
|
|
296
|
+
sawTerminal = true
|
|
297
|
+
const err = json.error || {}
|
|
298
|
+
finish = { kind: 'error', failure: { code: String(err.type || 'ANTHROPIC_ERROR'), message: String(err.message || 'anthropic error') } }
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
if (usageState && !yieldedUsage) {
|
|
302
|
+
const norm = toTokenUsage(usageState)
|
|
303
|
+
yield {
|
|
304
|
+
type: 'usage',
|
|
305
|
+
usage: {
|
|
306
|
+
...usageState,
|
|
307
|
+
...(norm || {}),
|
|
308
|
+
input: norm ? norm.inputTokens : (Number(usageState.input_tokens ?? usageState.input ?? 0) || 0),
|
|
309
|
+
output: norm ? norm.outputTokens : (Number(usageState.output_tokens ?? usageState.output ?? 0) || 0),
|
|
310
|
+
},
|
|
254
311
|
}
|
|
255
312
|
}
|
|
256
|
-
|
|
313
|
+
if (!sawTerminal && (index > 0 || textIndex > 0 || toolCallIds.size > 0)) {
|
|
314
|
+
yield { type: 'finish', reason: { kind: 'error', failure: { code: 'INCOMPLETE', message: 'stream terminated before message_delta' } } }
|
|
315
|
+
return
|
|
316
|
+
}
|
|
317
|
+
if (finish.kind === 'stop' && toolCallIds.size > 0) finish = { kind: 'tool-calls' }
|
|
318
|
+
yield { type: 'finish', reason: finish }
|
|
257
319
|
}
|
|
258
320
|
|
|
259
321
|
export async function* googleStream(body) {
|
|
@@ -261,11 +323,27 @@ export async function* googleStream(body) {
|
|
|
261
323
|
let started = false
|
|
262
324
|
let callCount = 0
|
|
263
325
|
const functionCallIndexes = new Map()
|
|
326
|
+
let finish = { kind: 'stop' }
|
|
327
|
+
let sawTerminal = false
|
|
264
328
|
for await (const data of iterateSse(body)) {
|
|
329
|
+
if (data === '[DONE]') {
|
|
330
|
+
sawTerminal = true
|
|
331
|
+
break
|
|
332
|
+
}
|
|
265
333
|
const json = jsonSse(data)
|
|
266
334
|
const wrapped = json && json.response ? json.response : json
|
|
267
335
|
if (!wrapped) continue
|
|
268
336
|
const cand = wrapped.candidates && wrapped.candidates[0]
|
|
337
|
+
if (cand && cand.finishReason) {
|
|
338
|
+
sawTerminal = true
|
|
339
|
+
const fr = cand.finishReason
|
|
340
|
+
if (fr === 'MAX_TOKENS') finish = { kind: 'max-tokens' }
|
|
341
|
+
else if (fr === 'SAFETY' || fr === 'RECITATION' || fr === 'BLOCKLIST' || fr === 'PROHIBITED_CONTENT' || fr === 'SPII') {
|
|
342
|
+
finish = { kind: 'error', failure: { code: fr, message: cand.finishMessage || fr } }
|
|
343
|
+
} else if (fr === 'STOP') {
|
|
344
|
+
finish = { kind: 'stop' }
|
|
345
|
+
}
|
|
346
|
+
}
|
|
269
347
|
const parts = cand && cand.content && Array.isArray(cand.content.parts) ? cand.content.parts : []
|
|
270
348
|
for (const part of parts) {
|
|
271
349
|
if (part.text) {
|
|
@@ -299,16 +377,27 @@ export async function* googleStream(body) {
|
|
|
299
377
|
}
|
|
300
378
|
}
|
|
301
379
|
if (wrapped.usageMetadata) {
|
|
380
|
+
const u = wrapped.usageMetadata
|
|
381
|
+
const norm = toTokenUsage(u)
|
|
302
382
|
yield {
|
|
303
383
|
type: 'usage',
|
|
304
384
|
usage: {
|
|
305
|
-
|
|
306
|
-
|
|
385
|
+
...u,
|
|
386
|
+
...(norm || {}),
|
|
387
|
+
input: norm ? norm.inputTokens : (Number(u.promptTokenCount ?? 0) || 0),
|
|
388
|
+
output: norm ? norm.outputTokens : (Number(u.candidatesTokenCount ?? 0) || 0),
|
|
307
389
|
},
|
|
308
390
|
}
|
|
309
391
|
}
|
|
310
392
|
}
|
|
311
|
-
|
|
393
|
+
if (!sawTerminal && (started || callCount > 0)) {
|
|
394
|
+
yield { type: 'finish', reason: { kind: 'error', failure: { code: 'INCOMPLETE', message: 'stream terminated before finishReason' } } }
|
|
395
|
+
return
|
|
396
|
+
}
|
|
397
|
+
if (finish.kind === 'stop' && (functionCallIndexes.size > 0 || callCount > 0)) {
|
|
398
|
+
finish = { kind: 'tool-calls' }
|
|
399
|
+
}
|
|
400
|
+
yield { type: 'finish', reason: finish }
|
|
312
401
|
}
|
|
313
402
|
|
|
314
403
|
export async function formTokenRequest(url, params, fetchImpl, headers, { timeoutMs = 25000 } = {}) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goodandready/dsh-subscriptions",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.41",
|
|
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",
|