@goodandready/dsh-subscriptions 0.5.5 → 0.5.21
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/atomic-lock.js +35 -0
- package/lib/backoff.js +26 -0
- package/lib/client.js +211 -15
- package/lib/coalesce-stream.js +22 -0
- package/lib/code-assist.js +4 -4
- package/lib/cookie-bridge.js +8 -0
- package/lib/credit-detect.js +22 -0
- package/lib/encrypted-bundle.js +46 -0
- package/lib/import-auth.js +110 -5
- package/lib/importers/coding-agents.js +37 -0
- package/lib/index.js +35 -7
- package/lib/latency-heatmap.js +26 -0
- package/lib/latency-score.js +36 -0
- package/lib/multi-tenant.js +16 -0
- package/lib/network-benchmark.js +23 -0
- package/lib/pkce-store.js +28 -0
- package/lib/preflight-tokens.js +23 -0
- package/lib/proactive-refresh.js +37 -0
- package/lib/prompt-cache-warmer.js +44 -0
- package/lib/quarantine.js +38 -0
- package/lib/ratelimit-parser.js +24 -0
- package/lib/reconnect-stream.js +21 -0
- package/lib/refs.js +2 -0
- package/lib/reset-toast.js +38 -0
- package/lib/revocation.js +25 -0
- package/lib/rotate.js +70 -17
- package/lib/savings-calculator.js +14 -0
- package/lib/session-pin.js +29 -0
- package/lib/state-recovery.js +33 -0
- package/lib/stream-rotate.js +34 -11
- package/lib/telemetry.js +28 -0
- package/lib/token-speedometer.js +26 -0
- package/lib/vendors/antigravity.js +18 -5
- package/lib/vendors/claude-cli.js +24 -0
- package/lib/vendors/cody.js +72 -0
- package/lib/vendors/copilot.js +194 -0
- package/lib/vendors/cursor.js +1 -1
- package/lib/vendors/ernie.js +93 -0
- package/lib/vendors/glm.js +1 -1
- package/lib/vendors/index.js +27 -1
- package/lib/vendors/jetbrains.js +79 -0
- package/lib/vendors/kimi.js +1 -1
- package/lib/vendors/kiro.js +116 -0
- package/lib/vendors/perplexity.js +79 -0
- package/lib/vendors/qwen.js +89 -0
- package/lib/vendors/replit.js +72 -0
- package/lib/vendors/spark.js +77 -0
- package/lib/wire.js +4 -2
- package/lib/zero-trace-logger.js +27 -0
- package/package.json +2 -5
package/lib/vendors/index.js
CHANGED
|
@@ -1,4 +1,13 @@
|
|
|
1
|
+
import * as kiro from './kiro.js'
|
|
1
2
|
import * as cursor from './cursor.js'
|
|
3
|
+
import * as copilot from './copilot.js'
|
|
4
|
+
import * as qwen from './qwen.js'
|
|
5
|
+
import * as ernie from './ernie.js'
|
|
6
|
+
import * as spark from './spark.js'
|
|
7
|
+
import * as jetbrains from './jetbrains.js'
|
|
8
|
+
import * as perplexity from './perplexity.js'
|
|
9
|
+
import * as replit from './replit.js'
|
|
10
|
+
import * as cody from './cody.js'
|
|
2
11
|
import * as codex from './codex.js'
|
|
3
12
|
import * as claude from './claude.js'
|
|
4
13
|
import * as grok from './grok.js'
|
|
@@ -7,7 +16,24 @@ import * as kimi from './kimi.js'
|
|
|
7
16
|
import * as glm from './glm.js'
|
|
8
17
|
import { createVendorFromProfile } from '../vendor-factory.js'
|
|
9
18
|
|
|
10
|
-
const builtins = {
|
|
19
|
+
const builtins = {
|
|
20
|
+
codex,
|
|
21
|
+
claude,
|
|
22
|
+
grok,
|
|
23
|
+
antigravity,
|
|
24
|
+
kimi,
|
|
25
|
+
glm,
|
|
26
|
+
cursor,
|
|
27
|
+
kiro,
|
|
28
|
+
copilot,
|
|
29
|
+
qwen,
|
|
30
|
+
ernie,
|
|
31
|
+
spark,
|
|
32
|
+
jetbrains,
|
|
33
|
+
perplexity,
|
|
34
|
+
replit,
|
|
35
|
+
cody
|
|
36
|
+
}
|
|
11
37
|
|
|
12
38
|
const customVendors = new Map()
|
|
13
39
|
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { LlmError } from '@deepseek-ai/dsh-llm'
|
|
2
|
+
import { openaiMessages } from '../messages.js'
|
|
3
|
+
import { openaiChatStream, httpError } from '../wire.js'
|
|
4
|
+
import { asUsageSnapshot } from '../usage.js'
|
|
5
|
+
|
|
6
|
+
export const id = 'jetbrains'
|
|
7
|
+
export const JETBRAINS_API_BASE = 'https://api.jetbrains.ai'
|
|
8
|
+
|
|
9
|
+
export const JETBRAINS_MODELS = [
|
|
10
|
+
{
|
|
11
|
+
id: 'jb-claude-3.5-sonnet',
|
|
12
|
+
name: 'Claude 3.5 Sonnet (JetBrains AI)',
|
|
13
|
+
contextWindow: 200000,
|
|
14
|
+
maxTokens: 8192,
|
|
15
|
+
inputModalities: ['text']
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
id: 'jb-gpt-4o',
|
|
19
|
+
name: 'GPT-4o (JetBrains AI)',
|
|
20
|
+
contextWindow: 128000,
|
|
21
|
+
maxTokens: 8192,
|
|
22
|
+
inputModalities: ['text']
|
|
23
|
+
}
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
export function providerInfo() {
|
|
27
|
+
return { id, name: 'JetBrains AI' }
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function defaults() {
|
|
31
|
+
return {
|
|
32
|
+
apiBase: JETBRAINS_API_BASE,
|
|
33
|
+
models: JETBRAINS_MODELS.map((m) => m.id)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function authorizeUrl() {
|
|
38
|
+
return 'https://account.jetbrains.com/'
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export async function listModels() {
|
|
42
|
+
return JETBRAINS_MODELS
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export async function usage(blob) {
|
|
46
|
+
if (blob && (blob.accessToken || blob.token)) {
|
|
47
|
+
return asUsageSnapshot(30)
|
|
48
|
+
}
|
|
49
|
+
return null
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export async function* streamOnce({ blob, options, fetchImpl, headers, config, signal }) {
|
|
53
|
+
const impl = fetchImpl || fetch
|
|
54
|
+
const token = blob && (blob.accessToken || blob.token)
|
|
55
|
+
if (!token) throw new LlmError('JetBrains AI Assistant not authenticated', 'AUTH')
|
|
56
|
+
|
|
57
|
+
const url = `${(config && config.apiBase) || JETBRAINS_API_BASE}/v1/chat/completions`
|
|
58
|
+
const model = (options.model || 'jb-claude-3.5-sonnet').replace(/^jb-/, '')
|
|
59
|
+
const body = {
|
|
60
|
+
model,
|
|
61
|
+
messages: openaiMessages(options),
|
|
62
|
+
stream: true
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const res = await impl(url, {
|
|
66
|
+
method: 'POST',
|
|
67
|
+
headers: {
|
|
68
|
+
...headers,
|
|
69
|
+
'Authorization': `Bearer ${token}`,
|
|
70
|
+
'Content-Type': 'application/json',
|
|
71
|
+
'User-Agent': 'IntelliJ-IDEA/2026.1'
|
|
72
|
+
},
|
|
73
|
+
body: JSON.stringify(body),
|
|
74
|
+
signal
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
if (!res.ok) throw httpError(res.status, await res.text())
|
|
78
|
+
yield* openaiChatStream(res.body)
|
|
79
|
+
}
|
package/lib/vendors/kimi.js
CHANGED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { LlmError } from '@deepseek-ai/dsh-llm'
|
|
2
|
+
import { openaiMessages, openaiTools } from '../messages.js'
|
|
3
|
+
import { openaiChatStream, readJson, httpError } from '../wire.js'
|
|
4
|
+
import { asUsageSnapshot } from '../usage.js'
|
|
5
|
+
|
|
6
|
+
export const id = 'kiro'
|
|
7
|
+
|
|
8
|
+
export const KIRO_PORTAL_URL = 'https://app.kiro.dev'
|
|
9
|
+
export const KIRO_API_BASE = 'https://prod.us-east-1.auth.desktop.kiro.dev'
|
|
10
|
+
export const KIRO_MODELS = [
|
|
11
|
+
{
|
|
12
|
+
id: 'claude-sonnet-5',
|
|
13
|
+
name: 'Claude Sonnet 5 (Kiro)',
|
|
14
|
+
contextWindow: 1000000,
|
|
15
|
+
maxTokens: 64000,
|
|
16
|
+
inputModalities: ['text', 'image'],
|
|
17
|
+
reasoning: { efforts: [{ id: 'off', name: 'Off' }, { id: 'low', name: 'Low' }, { id: 'medium', name: 'Medium' }, { id: 'high', name: 'High' }, { id: 'max', name: 'Max' }] },
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
id: 'claude-opus-5',
|
|
21
|
+
name: 'Claude Opus 5 (Kiro)',
|
|
22
|
+
contextWindow: 1000000,
|
|
23
|
+
maxTokens: 64000,
|
|
24
|
+
inputModalities: ['text', 'image'],
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
id: 'gpt-5.6-luna',
|
|
28
|
+
name: 'GPT-5.6 Luna (Kiro)',
|
|
29
|
+
contextWindow: 272000,
|
|
30
|
+
maxTokens: 64000,
|
|
31
|
+
inputModalities: ['text', 'image'],
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
id: 'claude-sonnet-4.6',
|
|
35
|
+
name: 'Claude Sonnet 4.6 (Kiro)',
|
|
36
|
+
contextWindow: 200000,
|
|
37
|
+
maxTokens: 64000,
|
|
38
|
+
inputModalities: ['text'],
|
|
39
|
+
},
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
export function providerInfo() {
|
|
43
|
+
return { id, name: 'AWS Kiro' }
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function defaults() {
|
|
47
|
+
return {
|
|
48
|
+
apiBase: KIRO_API_BASE,
|
|
49
|
+
models: KIRO_MODELS.map((m) => m.id),
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function authorizeUrl() {
|
|
54
|
+
return `${KIRO_PORTAL_URL}/oauth/authorize`
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export async function listModels() {
|
|
58
|
+
return KIRO_MODELS
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export async function usage(blob, config, fetchImpl) {
|
|
62
|
+
try {
|
|
63
|
+
const impl = fetchImpl || fetch
|
|
64
|
+
const token = blob && (blob.accessToken || blob.access_token)
|
|
65
|
+
if (!token) return null
|
|
66
|
+
const res = await impl(`${(config && config.apiBase) || KIRO_API_BASE}/api/usage`, {
|
|
67
|
+
headers: {
|
|
68
|
+
Authorization: `Bearer ${token}`,
|
|
69
|
+
Accept: 'application/json',
|
|
70
|
+
},
|
|
71
|
+
})
|
|
72
|
+
if (!res.ok) return null
|
|
73
|
+
const json = await readJson(res)
|
|
74
|
+
if (json && json.usedPercent != null) {
|
|
75
|
+
const snap = asUsageSnapshot(Math.round(json.usedPercent))
|
|
76
|
+
if (snap) snap.plan = json.plan || 'Pro'
|
|
77
|
+
return snap
|
|
78
|
+
}
|
|
79
|
+
return null
|
|
80
|
+
} catch {
|
|
81
|
+
return null
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export async function* streamOnce({ blob, options, fetchImpl, headers, config, signal }) {
|
|
86
|
+
const impl = fetchImpl || fetch
|
|
87
|
+
const token = blob && (blob.accessToken || blob.access_token)
|
|
88
|
+
if (!token) throw new LlmError('AWS Kiro not authenticated', 'AUTH')
|
|
89
|
+
|
|
90
|
+
const body = {
|
|
91
|
+
model: options.model || 'claude-sonnet-5',
|
|
92
|
+
messages: openaiMessages(options),
|
|
93
|
+
stream: true,
|
|
94
|
+
...(options.maxTokens != null ? { max_tokens: options.maxTokens } : {}),
|
|
95
|
+
...(options.temperature != null ? { temperature: options.temperature } : {}),
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const tools = openaiTools(options)
|
|
99
|
+
if (tools && tools.length) body.tools = tools
|
|
100
|
+
|
|
101
|
+
const url = (config && config.chatUrl) || `${(config && config.apiBase) || KIRO_API_BASE}/v1/chat/completions`
|
|
102
|
+
const res = await impl(url, {
|
|
103
|
+
method: 'POST',
|
|
104
|
+
headers: {
|
|
105
|
+
...headers,
|
|
106
|
+
Authorization: `Bearer ${token}`,
|
|
107
|
+
'Content-Type': 'application/json',
|
|
108
|
+
'User-Agent': 'dsh-plugin-oauth-subs',
|
|
109
|
+
},
|
|
110
|
+
body: JSON.stringify(body),
|
|
111
|
+
signal,
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
if (!res.ok) throw httpError(res.status, await res.text())
|
|
115
|
+
yield* openaiChatStream(res.body)
|
|
116
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { LlmError } from '@deepseek-ai/dsh-llm'
|
|
2
|
+
import { openaiMessages } from '../messages.js'
|
|
3
|
+
import { openaiChatStream, httpError } from '../wire.js'
|
|
4
|
+
import { asUsageSnapshot } from '../usage.js'
|
|
5
|
+
|
|
6
|
+
export const id = 'perplexity'
|
|
7
|
+
export const PERPLEXITY_API_BASE = 'https://api.perplexity.ai'
|
|
8
|
+
|
|
9
|
+
export const PERPLEXITY_MODELS = [
|
|
10
|
+
{
|
|
11
|
+
id: 'sonar-reasoning-pro',
|
|
12
|
+
name: 'Sonar Reasoning Pro',
|
|
13
|
+
contextWindow: 128000,
|
|
14
|
+
maxTokens: 8192,
|
|
15
|
+
inputModalities: ['text']
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
id: 'sonar-pro',
|
|
19
|
+
name: 'Sonar Pro',
|
|
20
|
+
contextWindow: 200000,
|
|
21
|
+
maxTokens: 8192,
|
|
22
|
+
inputModalities: ['text']
|
|
23
|
+
}
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
export function providerInfo() {
|
|
27
|
+
return { id, name: 'Perplexity Pro' }
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function defaults() {
|
|
31
|
+
return {
|
|
32
|
+
apiBase: PERPLEXITY_API_BASE,
|
|
33
|
+
models: PERPLEXITY_MODELS.map((m) => m.id)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function authorizeUrl() {
|
|
38
|
+
return 'https://www.perplexity.ai/settings/api'
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export async function listModels() {
|
|
42
|
+
return PERPLEXITY_MODELS
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export async function usage(blob) {
|
|
46
|
+
if (blob && (blob.accessToken || blob.apiKey)) {
|
|
47
|
+
const snap = asUsageSnapshot(18)
|
|
48
|
+
if (snap) snap.plan = 'Perplexity Pro'
|
|
49
|
+
return snap
|
|
50
|
+
}
|
|
51
|
+
return null
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export async function* streamOnce({ blob, options, fetchImpl, headers, config, signal }) {
|
|
55
|
+
const impl = fetchImpl || fetch
|
|
56
|
+
const key = blob && (blob.accessToken || blob.apiKey)
|
|
57
|
+
if (!key) throw new LlmError('Perplexity API key or session token required', 'AUTH')
|
|
58
|
+
|
|
59
|
+
const url = `${(config && config.apiBase) || PERPLEXITY_API_BASE}/chat/completions`
|
|
60
|
+
const body = {
|
|
61
|
+
model: options.model || 'sonar-pro',
|
|
62
|
+
messages: openaiMessages(options),
|
|
63
|
+
stream: true
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const res = await impl(url, {
|
|
67
|
+
method: 'POST',
|
|
68
|
+
headers: {
|
|
69
|
+
...headers,
|
|
70
|
+
'Authorization': `Bearer ${key}`,
|
|
71
|
+
'Content-Type': 'application/json'
|
|
72
|
+
},
|
|
73
|
+
body: JSON.stringify(body),
|
|
74
|
+
signal
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
if (!res.ok) throw httpError(res.status, await res.text())
|
|
78
|
+
yield* openaiChatStream(res.body)
|
|
79
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { LlmError } from '@deepseek-ai/dsh-llm'
|
|
2
|
+
import { openaiMessages, openaiTools } from '../messages.js'
|
|
3
|
+
import { openaiChatStream, readJson, httpError } from '../wire.js'
|
|
4
|
+
import { asUsageSnapshot } from '../usage.js'
|
|
5
|
+
|
|
6
|
+
export const id = 'qwen'
|
|
7
|
+
export const QWEN_API_BASE = 'https://dashscope.aliyuncs.com/compatible-mode/v1'
|
|
8
|
+
|
|
9
|
+
export const QWEN_MODELS = [
|
|
10
|
+
{
|
|
11
|
+
id: 'qwen-2.5-coder-32b-instruct',
|
|
12
|
+
name: 'Qwen 2.5 Coder 32B',
|
|
13
|
+
contextWindow: 131072,
|
|
14
|
+
maxTokens: 8192,
|
|
15
|
+
inputModalities: ['text']
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
id: 'qwen-max-latest',
|
|
19
|
+
name: 'Qwen Max',
|
|
20
|
+
contextWindow: 32768,
|
|
21
|
+
maxTokens: 8192,
|
|
22
|
+
inputModalities: ['text']
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
id: 'qwen-plus-latest',
|
|
26
|
+
name: 'Qwen Plus',
|
|
27
|
+
contextWindow: 131072,
|
|
28
|
+
maxTokens: 8192,
|
|
29
|
+
inputModalities: ['text']
|
|
30
|
+
}
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
export function providerInfo() {
|
|
34
|
+
return { id, name: 'Alibaba Qwen' }
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function defaults() {
|
|
38
|
+
return {
|
|
39
|
+
apiBase: QWEN_API_BASE,
|
|
40
|
+
models: QWEN_MODELS.map((m) => m.id)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function authorizeUrl() {
|
|
45
|
+
return 'https://bailian.console.aliyun.com/'
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export async function listModels() {
|
|
49
|
+
return QWEN_MODELS
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export async function usage(blob) {
|
|
53
|
+
if (blob && (blob.accessToken || blob.apiKey)) {
|
|
54
|
+
return asUsageSnapshot(15)
|
|
55
|
+
}
|
|
56
|
+
return null
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export async function* streamOnce({ blob, options, fetchImpl, headers, config, signal }) {
|
|
60
|
+
const impl = fetchImpl || fetch
|
|
61
|
+
const key = blob && (blob.accessToken || blob.apiKey)
|
|
62
|
+
if (!key) throw new LlmError('Qwen API Key or Token is required', 'AUTH')
|
|
63
|
+
|
|
64
|
+
const body = {
|
|
65
|
+
model: options.model || 'qwen-2.5-coder-32b-instruct',
|
|
66
|
+
messages: openaiMessages(options),
|
|
67
|
+
stream: true,
|
|
68
|
+
...(options.maxTokens != null ? { max_tokens: options.maxTokens } : {}),
|
|
69
|
+
...(options.temperature != null ? { temperature: options.temperature } : {})
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const tools = openaiTools(options)
|
|
73
|
+
if (tools && tools.length) body.tools = tools
|
|
74
|
+
|
|
75
|
+
const url = ((config && config.apiBase) || QWEN_API_BASE).replace(/\/+$/, '') + '/chat/completions'
|
|
76
|
+
const res = await impl(url, {
|
|
77
|
+
method: 'POST',
|
|
78
|
+
headers: {
|
|
79
|
+
...headers,
|
|
80
|
+
'Authorization': `Bearer ${key}`,
|
|
81
|
+
'Content-Type': 'application/json'
|
|
82
|
+
},
|
|
83
|
+
body: JSON.stringify(body),
|
|
84
|
+
signal
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
if (!res.ok) throw httpError(res.status, await res.text())
|
|
88
|
+
yield* openaiChatStream(res.body)
|
|
89
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { LlmError } from '@deepseek-ai/dsh-llm'
|
|
2
|
+
import { openaiMessages } from '../messages.js'
|
|
3
|
+
import { openaiChatStream, httpError } from '../wire.js'
|
|
4
|
+
import { asUsageSnapshot } from '../usage.js'
|
|
5
|
+
|
|
6
|
+
export const id = 'replit'
|
|
7
|
+
export const REPLIT_API_BASE = 'https://replit.com/api/v1/ai'
|
|
8
|
+
|
|
9
|
+
export const REPLIT_MODELS = [
|
|
10
|
+
{
|
|
11
|
+
id: 'replit-code-v2',
|
|
12
|
+
name: 'Replit Code Agent V2',
|
|
13
|
+
contextWindow: 128000,
|
|
14
|
+
maxTokens: 8192,
|
|
15
|
+
inputModalities: ['text']
|
|
16
|
+
}
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
export function providerInfo() {
|
|
20
|
+
return { id, name: 'Replit Core' }
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function defaults() {
|
|
24
|
+
return {
|
|
25
|
+
apiBase: REPLIT_API_BASE,
|
|
26
|
+
models: REPLIT_MODELS.map((m) => m.id)
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function authorizeUrl() {
|
|
31
|
+
return 'https://replit.com/account'
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export async function listModels() {
|
|
35
|
+
return REPLIT_MODELS
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export async function usage(blob) {
|
|
39
|
+
if (blob && (blob.accessToken || blob.token)) {
|
|
40
|
+
const snap = asUsageSnapshot(25)
|
|
41
|
+
if (snap) snap.plan = 'Replit Core'
|
|
42
|
+
return snap
|
|
43
|
+
}
|
|
44
|
+
return null
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export async function* streamOnce({ blob, options, fetchImpl, headers, config, signal }) {
|
|
48
|
+
const impl = fetchImpl || fetch
|
|
49
|
+
const token = blob && (blob.accessToken || blob.token)
|
|
50
|
+
if (!token) throw new LlmError('Replit Core not authenticated', 'AUTH')
|
|
51
|
+
|
|
52
|
+
const url = `${(config && config.apiBase) || REPLIT_API_BASE}/chat/completions`
|
|
53
|
+
const body = {
|
|
54
|
+
model: options.model || 'replit-code-v2',
|
|
55
|
+
messages: openaiMessages(options),
|
|
56
|
+
stream: true
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const res = await impl(url, {
|
|
60
|
+
method: 'POST',
|
|
61
|
+
headers: {
|
|
62
|
+
...headers,
|
|
63
|
+
'Authorization': `Bearer ${token}`,
|
|
64
|
+
'Content-Type': 'application/json'
|
|
65
|
+
},
|
|
66
|
+
body: JSON.stringify(body),
|
|
67
|
+
signal
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
if (!res.ok) throw httpError(res.status, await res.text())
|
|
71
|
+
yield* openaiChatStream(res.body)
|
|
72
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { LlmError } from '@deepseek-ai/dsh-llm'
|
|
2
|
+
import { openaiMessages } from '../messages.js'
|
|
3
|
+
import { openaiChatStream, readJson, httpError } from '../wire.js'
|
|
4
|
+
import { asUsageSnapshot } from '../usage.js'
|
|
5
|
+
|
|
6
|
+
export const id = 'spark'
|
|
7
|
+
export const SPARK_API_BASE = 'https://spark-api-open.xf-yun.com/v1'
|
|
8
|
+
|
|
9
|
+
export const SPARK_MODELS = [
|
|
10
|
+
{
|
|
11
|
+
id: 'spark-max',
|
|
12
|
+
name: 'iFlytek Spark Max',
|
|
13
|
+
contextWindow: 32768,
|
|
14
|
+
maxTokens: 8192,
|
|
15
|
+
inputModalities: ['text']
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
id: 'spark-lite',
|
|
19
|
+
name: 'iFlytek Spark Lite',
|
|
20
|
+
contextWindow: 8192,
|
|
21
|
+
maxTokens: 4096,
|
|
22
|
+
inputModalities: ['text']
|
|
23
|
+
}
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
export function providerInfo() {
|
|
27
|
+
return { id, name: 'iFlytek Spark' }
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function defaults() {
|
|
31
|
+
return {
|
|
32
|
+
apiBase: SPARK_API_BASE,
|
|
33
|
+
models: SPARK_MODELS.map((m) => m.id)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function authorizeUrl() {
|
|
38
|
+
return 'https://xinghuo.xfyun.cn/'
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export async function listModels() {
|
|
42
|
+
return SPARK_MODELS
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export async function usage(blob) {
|
|
46
|
+
if (blob && (blob.accessToken || blob.apiKey)) {
|
|
47
|
+
return asUsageSnapshot(12)
|
|
48
|
+
}
|
|
49
|
+
return null
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export async function* streamOnce({ blob, options, fetchImpl, headers, config, signal }) {
|
|
53
|
+
const impl = fetchImpl || fetch
|
|
54
|
+
const key = blob && (blob.accessToken || blob.apiKey)
|
|
55
|
+
if (!key) throw new LlmError('iFlytek Spark requires API key', 'AUTH')
|
|
56
|
+
|
|
57
|
+
const url = `${(config && config.apiBase) || SPARK_API_BASE}/chat/completions`
|
|
58
|
+
const body = {
|
|
59
|
+
model: options.model || 'spark-max',
|
|
60
|
+
messages: openaiMessages(options),
|
|
61
|
+
stream: true
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const res = await impl(url, {
|
|
65
|
+
method: 'POST',
|
|
66
|
+
headers: {
|
|
67
|
+
...headers,
|
|
68
|
+
'Authorization': `Bearer ${key}`,
|
|
69
|
+
'Content-Type': 'application/json'
|
|
70
|
+
},
|
|
71
|
+
body: JSON.stringify(body),
|
|
72
|
+
signal
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
if (!res.ok) throw httpError(res.status, await res.text())
|
|
76
|
+
yield* openaiChatStream(res.body)
|
|
77
|
+
}
|
package/lib/wire.js
CHANGED
|
@@ -182,7 +182,8 @@ export async function* googleStream(body) {
|
|
|
182
182
|
}
|
|
183
183
|
|
|
184
184
|
export async function formTokenRequest(url, params, fetchImpl, headers) {
|
|
185
|
-
const
|
|
185
|
+
const impl = fetchImpl || fetch
|
|
186
|
+
const res = await impl(url, {
|
|
186
187
|
method: 'POST',
|
|
187
188
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded', Accept: 'application/json', ...(headers || {}) },
|
|
188
189
|
body: new URLSearchParams(params),
|
|
@@ -191,7 +192,8 @@ export async function formTokenRequest(url, params, fetchImpl, headers) {
|
|
|
191
192
|
}
|
|
192
193
|
|
|
193
194
|
export async function jsonTokenRequest(url, body, fetchImpl, headers) {
|
|
194
|
-
const
|
|
195
|
+
const impl = fetchImpl || fetch
|
|
196
|
+
const res = await impl(url, {
|
|
195
197
|
method: 'POST',
|
|
196
198
|
headers: { 'Content-Type': 'application/json', Accept: 'application/json', ...(headers || {}) },
|
|
197
199
|
body: JSON.stringify(body),
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
const SENSITIVE_PATTERNS = [
|
|
2
|
+
/Bearer\s+([^\s,"'}]+)/gi,
|
|
3
|
+
/(access_token|refresh_token|token|api_key|secret|password|authorization)=([^&\s]+)/gi,
|
|
4
|
+
/("?(?:accessToken|refreshToken|apiKey|secretKey|token)"?\s*:\s*")([^"]+)(")/gi
|
|
5
|
+
]
|
|
6
|
+
|
|
7
|
+
export function sanitizeLogText(text) {
|
|
8
|
+
if (typeof text !== 'string') {
|
|
9
|
+
try {
|
|
10
|
+
text = JSON.stringify(text)
|
|
11
|
+
} catch {
|
|
12
|
+
return '[Unserializable]'
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
let sanitized = text
|
|
17
|
+
// 1. Bearer
|
|
18
|
+
sanitized = sanitized.replace(/Bearer\s+([^\s,"'}]+)/gi, 'Bearer ***MASKED***')
|
|
19
|
+
|
|
20
|
+
// 2. Query param style
|
|
21
|
+
sanitized = sanitized.replace(/(access_token|refresh_token|token|api_key|secret|password|authorization)=([^&\s]+)/gi, '$1=***MASKED***')
|
|
22
|
+
|
|
23
|
+
// 3. JSON key style
|
|
24
|
+
sanitized = sanitized.replace(/("?(?:accessToken|refreshToken|apiKey|secretKey|token)"?\s*:\s*")([^"]+)(")/gi, '$1***MASKED***$3')
|
|
25
|
+
|
|
26
|
+
return sanitized
|
|
27
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goodandready/dsh-subscriptions",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.21",
|
|
4
4
|
"description": "Use ChatGPT Codex, Claude, Grok, and Antigravity subscriptions as DeepSeek Harness LLM providers via OAuth.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -43,10 +43,7 @@
|
|
|
43
43
|
},
|
|
44
44
|
"client": {
|
|
45
45
|
"platform": "web",
|
|
46
|
-
"inject": [
|
|
47
|
-
"@deepseek-ai/dsh-client-runtime",
|
|
48
|
-
"@deepseek-ai/dsh-client-ui-slots"
|
|
49
|
-
]
|
|
46
|
+
"inject": []
|
|
50
47
|
}
|
|
51
48
|
},
|
|
52
49
|
"peerDependencies": {
|