@goodandready/dsh-subscriptions 0.5.6 → 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 +74 -7
- 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/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 +26 -1
- package/lib/vendors/jetbrains.js +79 -0
- package/lib/vendors/kimi.js +1 -1
- 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
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { readFile, writeFile } from 'node:fs/promises'
|
|
2
|
+
import { join } from 'node:path'
|
|
3
|
+
import { homedir } from 'node:os'
|
|
4
|
+
|
|
5
|
+
const SNAPSHOT_FILE = join(homedir(), '.dsh', 'subscriptions-snapshot.json')
|
|
6
|
+
|
|
7
|
+
export async function persistPoolSnapshot(accounts) {
|
|
8
|
+
try {
|
|
9
|
+
const data = JSON.stringify({
|
|
10
|
+
savedAt: Date.now(),
|
|
11
|
+
accounts: (accounts || []).map((a) => ({
|
|
12
|
+
ref: a.ref || a.id,
|
|
13
|
+
cooldownUntil: a.cooldownUntil || 0,
|
|
14
|
+
quota: a.quota || null,
|
|
15
|
+
status: a.status || 'active'
|
|
16
|
+
}))
|
|
17
|
+
}, null, 2)
|
|
18
|
+
await writeFile(SNAPSHOT_FILE, data, 'utf8')
|
|
19
|
+
return true
|
|
20
|
+
} catch {
|
|
21
|
+
return false
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export async function recoverPoolSnapshot() {
|
|
26
|
+
try {
|
|
27
|
+
const text = await readFile(SNAPSHOT_FILE, 'utf8')
|
|
28
|
+
const json = JSON.parse(text)
|
|
29
|
+
return json && json.accounts ? json.accounts : []
|
|
30
|
+
} catch {
|
|
31
|
+
return []
|
|
32
|
+
}
|
|
33
|
+
}
|
package/lib/stream-rotate.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { pickAccount, markCooldown, isSwitchableError, modelFamily } from './rotate.js'
|
|
2
|
+
import { putInQuarantine, REASON_RATE_LIMIT } from './quarantine.js'
|
|
2
3
|
|
|
3
4
|
export async function* streamWithRotation({
|
|
4
5
|
accounts,
|
|
@@ -8,36 +9,58 @@ export async function* streamWithRotation({
|
|
|
8
9
|
streamOnce,
|
|
9
10
|
options,
|
|
10
11
|
onCooldown,
|
|
12
|
+
offlineFallback, // #174: optional fallback generator if all accounts exhausted
|
|
11
13
|
}) {
|
|
12
14
|
const pool = (accounts || []).map((account) => ({ ...account }))
|
|
13
15
|
let lastError = null
|
|
14
16
|
const tried = new Set()
|
|
17
|
+
|
|
15
18
|
while (true) {
|
|
16
|
-
const account = pickAccount(pool, nowMs(), {
|
|
17
|
-
|
|
19
|
+
const account = pickAccount(pool, nowMs(), {
|
|
20
|
+
switchAtRemaining,
|
|
21
|
+
family: modelFamily(options && options.provider, options && options.model),
|
|
22
|
+
sessionId: options && options.sessionId,
|
|
23
|
+
tag: options && options.tag,
|
|
24
|
+
vip: options && options.vip,
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
if (!account || tried.has(account.ref || account.id)) {
|
|
28
|
+
if (offlineFallback) {
|
|
29
|
+
// #174 Local Mock Server Offline Fallback
|
|
30
|
+
yield* offlineFallback(options, lastError)
|
|
31
|
+
return
|
|
32
|
+
}
|
|
18
33
|
if (lastError) throw lastError
|
|
19
34
|
const err = new Error('no usable subscription account for this provider')
|
|
20
|
-
err.code = 'AUTH'
|
|
21
|
-
throw err
|
|
22
|
-
}
|
|
23
|
-
if (tried.has(account.ref)) {
|
|
24
|
-
if (lastError) throw lastError
|
|
25
|
-
const err = new Error('all subscription accounts failed')
|
|
26
35
|
err.code = 'RATE_LIMIT'
|
|
27
36
|
throw err
|
|
28
37
|
}
|
|
29
|
-
|
|
38
|
+
|
|
39
|
+
tried.add(account.ref || account.id)
|
|
40
|
+
|
|
30
41
|
try {
|
|
31
|
-
|
|
42
|
+
// In-flight seamless failover (#166): stream generator execution
|
|
43
|
+
let firstChunkDelivered = false
|
|
44
|
+
for await (const chunk of streamOnce(account, options)) {
|
|
45
|
+
firstChunkDelivered = true
|
|
46
|
+
yield chunk
|
|
47
|
+
}
|
|
32
48
|
return
|
|
33
49
|
} catch (err) {
|
|
34
50
|
lastError = err
|
|
35
51
|
if (!isSwitchableError(err)) throw err
|
|
52
|
+
|
|
53
|
+
// Move slot to cooldown and quarantine (#172)
|
|
36
54
|
const cooled = markCooldown(account, nowMs(), cooldownMs, modelFamily(options && options.provider, options && options.model))
|
|
37
55
|
account.cooldownUntil = cooled.cooldownUntil
|
|
38
56
|
if (cooled.cooldownFamilies) account.cooldownFamilies = cooled.cooldownFamilies
|
|
57
|
+
|
|
58
|
+
const quarantined = putInQuarantine(account, REASON_RATE_LIMIT, nowMs())
|
|
59
|
+
account.quarantineUntil = quarantined.quarantineUntil
|
|
60
|
+
account.quarantineReason = quarantined.quarantineReason
|
|
61
|
+
|
|
39
62
|
if (onCooldown) onCooldown(account)
|
|
63
|
+
// Loop continues seamlessly to next slot if no chunk was delivered yet
|
|
40
64
|
}
|
|
41
65
|
}
|
|
42
66
|
}
|
|
43
|
-
|
package/lib/telemetry.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto'
|
|
2
|
+
|
|
3
|
+
const USER_AGENTS = [
|
|
4
|
+
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36',
|
|
5
|
+
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36',
|
|
6
|
+
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36',
|
|
7
|
+
'VSCode/1.98.0 (Windows_NT x64)',
|
|
8
|
+
'Cursor/0.45.6 (Darwin x64)'
|
|
9
|
+
]
|
|
10
|
+
|
|
11
|
+
export function getRandomUserAgent(seed) {
|
|
12
|
+
if (!seed) return USER_AGENTS[0]
|
|
13
|
+
const hash = createHash('md5').update(String(seed)).digest('hex')
|
|
14
|
+
const idx = parseInt(hash.slice(0, 4), 16) % USER_AGENTS.length
|
|
15
|
+
return USER_AGENTS[idx]
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function generateIdeTelemetryHeaders(sessionId, clientType = 'vscode') {
|
|
19
|
+
const sid = sessionId || 'session-' + Math.random().toString(36).slice(2, 10)
|
|
20
|
+
const machineHash = createHash('sha256').update(sid).digest('hex').slice(0, 32)
|
|
21
|
+
return {
|
|
22
|
+
'vscode-sessionid': sid,
|
|
23
|
+
'vscode-machineid': machineHash,
|
|
24
|
+
'editor-version': clientType === 'cursor' ? 'cursor/0.45.6' : 'vscode/1.98.0',
|
|
25
|
+
'editor-plugin-version': 'dsh-sub-client/0.5.8',
|
|
26
|
+
'User-Agent': getRandomUserAgent(sid)
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export class TokenSpeedometer {
|
|
2
|
+
constructor() {
|
|
3
|
+
this.startTime = null
|
|
4
|
+
this.tokenCount = 0
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
recordChunk(chunkText) {
|
|
8
|
+
if (!this.startTime) this.startTime = Date.now()
|
|
9
|
+
// Approximation: 1 token ~ 4 chars
|
|
10
|
+
const tokens = Math.max(1, Math.ceil((chunkText || '').length / 4))
|
|
11
|
+
this.tokenCount += tokens
|
|
12
|
+
return this.getCurrentSpeed()
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
getCurrentSpeed() {
|
|
16
|
+
if (!this.startTime) return 0
|
|
17
|
+
const elapsedSec = (Date.now() - this.startTime) / 1000
|
|
18
|
+
if (elapsedSec <= 0) return 0
|
|
19
|
+
return Math.round((this.tokenCount / elapsedSec) * 10) / 10
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
reset() {
|
|
23
|
+
this.startTime = null
|
|
24
|
+
this.tokenCount = 0
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -32,10 +32,22 @@ export function providerInfo() {
|
|
|
32
32
|
|
|
33
33
|
export function defaults() {
|
|
34
34
|
return {
|
|
35
|
-
clientId: '',
|
|
36
|
-
clientSecret: '',
|
|
37
|
-
redirectUri: '
|
|
38
|
-
models: [
|
|
35
|
+
clientId: '884354919052-36trc1jjb3tguiac32ov6cod268c5blh.apps.googleusercontent.com',
|
|
36
|
+
clientSecret: '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',
|
|
37
|
+
redirectUri: 'http://localhost:8085/oauth/callback',
|
|
38
|
+
models: [
|
|
39
|
+
'gemini-3.8-flash-medium',
|
|
40
|
+
'gemini-3.7-flash-medium',
|
|
41
|
+
'gemini-3.6-flash-medium',
|
|
42
|
+
'gemini-3.1-pro-low',
|
|
43
|
+
'claude-sonnet-4.6-thinking',
|
|
44
|
+
'claude-opus-4.6-thinking',
|
|
45
|
+
'gpt-oss-120b-medium',
|
|
46
|
+
'gemini-3.1-pro-high-vertex',
|
|
47
|
+
'gemini-3-flash',
|
|
48
|
+
'gemini-2.5-pro',
|
|
49
|
+
'gemini-2.5-flash',
|
|
50
|
+
],
|
|
39
51
|
}
|
|
40
52
|
}
|
|
41
53
|
|
|
@@ -60,12 +72,13 @@ function metadataFor(projectId) {
|
|
|
60
72
|
}
|
|
61
73
|
|
|
62
74
|
async function withProject(blob, fetchImpl, saveBlob) {
|
|
75
|
+
const impl = fetchImpl || fetch
|
|
63
76
|
if (blob.projectId && blob.paidTierId) {
|
|
64
77
|
return { ...blob, sessionId: blob.sessionId || randomUUID() }
|
|
65
78
|
}
|
|
66
79
|
try {
|
|
67
80
|
const meta = metadataFor(blob.projectId || '')
|
|
68
|
-
const found = await discoverProject(
|
|
81
|
+
const found = await discoverProject(impl, blob.accessToken, meta, headersFor(blob.projectId || ''))
|
|
69
82
|
const next = {
|
|
70
83
|
...blob,
|
|
71
84
|
projectId: found.projectId || blob.projectId || '',
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises'
|
|
2
|
+
import { homedir } from 'node:os'
|
|
3
|
+
import { join } from 'node:path'
|
|
4
|
+
|
|
5
|
+
export async function readClaudeCredentials() {
|
|
6
|
+
const p = join(homedir(), '.claude', 'credentials.json')
|
|
7
|
+
try {
|
|
8
|
+
const text = await readFile(p, 'utf8')
|
|
9
|
+
const json = JSON.parse(text)
|
|
10
|
+
if (!json) return null
|
|
11
|
+
const oauth = json.claudeAiOauth || json.oauth || json
|
|
12
|
+
if (oauth && (oauth.accessToken || oauth.access_token)) {
|
|
13
|
+
return {
|
|
14
|
+
accessToken: oauth.accessToken || oauth.access_token,
|
|
15
|
+
refreshToken: oauth.refreshToken || oauth.refresh_token || '',
|
|
16
|
+
expiresAt: oauth.expiresAt || (oauth.expires_in ? Date.now() + oauth.expires_in * 1000 : Date.now() + 3600000),
|
|
17
|
+
subscriptionType: oauth.subscriptionType || 'Claude Code'
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return null
|
|
21
|
+
} catch {
|
|
22
|
+
return null
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -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 = 'cody'
|
|
7
|
+
export const CODY_API_BASE = 'https://sourcegraph.com/.api'
|
|
8
|
+
|
|
9
|
+
export const CODY_MODELS = [
|
|
10
|
+
{
|
|
11
|
+
id: 'cody-claude-3.5-sonnet',
|
|
12
|
+
name: 'Claude 3.5 Sonnet (Sourcegraph Cody)',
|
|
13
|
+
contextWindow: 200000,
|
|
14
|
+
maxTokens: 8192,
|
|
15
|
+
inputModalities: ['text']
|
|
16
|
+
}
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
export function providerInfo() {
|
|
20
|
+
return { id, name: 'Sourcegraph Cody' }
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function defaults() {
|
|
24
|
+
return {
|
|
25
|
+
apiBase: CODY_API_BASE,
|
|
26
|
+
models: CODY_MODELS.map((m) => m.id)
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function authorizeUrl() {
|
|
31
|
+
return 'https://sourcegraph.com/user/settings/tokens'
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export async function listModels() {
|
|
35
|
+
return CODY_MODELS
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export async function usage(blob) {
|
|
39
|
+
if (blob && (blob.accessToken || blob.token)) {
|
|
40
|
+
const snap = asUsageSnapshot(15)
|
|
41
|
+
if (snap) snap.plan = 'Cody Pro'
|
|
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('Sourcegraph Cody not authenticated', 'AUTH')
|
|
51
|
+
|
|
52
|
+
const url = `${(config && config.apiBase) || CODY_API_BASE}/chat/completions`
|
|
53
|
+
const body = {
|
|
54
|
+
model: 'claude-3-5-sonnet',
|
|
55
|
+
messages: openaiMessages(options),
|
|
56
|
+
stream: true
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const res = await impl(url, {
|
|
60
|
+
method: 'POST',
|
|
61
|
+
headers: {
|
|
62
|
+
...headers,
|
|
63
|
+
'Authorization': `token ${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,194 @@
|
|
|
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 = "copilot"
|
|
7
|
+
|
|
8
|
+
export const COPILOT_CLIENT_ID = "Iv1.b507a08c87ecfe98"
|
|
9
|
+
export const COPILOT_DEVICE_CODE_URL = "https://github.com/login/device/code"
|
|
10
|
+
export const COPILOT_DEVICE_TOKEN_URL = "https://github.com/login/oauth/access_token"
|
|
11
|
+
export const COPILOT_TOKEN_URL = "https://api.github.com/copilot_internal/v2/token"
|
|
12
|
+
export const COPILOT_API_URL = "https://api.githubcopilot.com/chat/completions"
|
|
13
|
+
export const COPILOT_USER_URL = "https://api.github.com/user"
|
|
14
|
+
|
|
15
|
+
export const COPILOT_MODELS = [
|
|
16
|
+
{
|
|
17
|
+
id: "claude-3.7-sonnet",
|
|
18
|
+
name: "Claude 3.7 Sonnet (Copilot)",
|
|
19
|
+
contextWindow: 200000,
|
|
20
|
+
maxTokens: 64000,
|
|
21
|
+
inputModalities: ["text", "image"],
|
|
22
|
+
reasoning: { efforts: [{ id: "low", name: "Low" }, { id: "medium", name: "Medium" }, { id: "high", name: "High" }] }
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
id: "claude-3.5-sonnet",
|
|
26
|
+
name: "Claude 3.5 Sonnet (Copilot)",
|
|
27
|
+
contextWindow: 200000,
|
|
28
|
+
maxTokens: 64000,
|
|
29
|
+
inputModalities: ["text", "image"]
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
id: "gpt-4o",
|
|
33
|
+
name: "GPT-4o (Copilot)",
|
|
34
|
+
contextWindow: 128000,
|
|
35
|
+
maxTokens: 16384,
|
|
36
|
+
inputModalities: ["text", "image"]
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
id: "o3-mini",
|
|
40
|
+
name: "o3-mini (Copilot)",
|
|
41
|
+
contextWindow: 200000,
|
|
42
|
+
maxTokens: 65536,
|
|
43
|
+
inputModalities: ["text"],
|
|
44
|
+
reasoning: { efforts: [{ id: "low", name: "Low" }, { id: "medium", name: "Medium" }, { id: "high", name: "High" }] }
|
|
45
|
+
}
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
export function providerInfo() {
|
|
49
|
+
return { id, name: "GitHub Copilot" }
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function defaults() {
|
|
53
|
+
return {
|
|
54
|
+
apiBase: COPILOT_API_URL,
|
|
55
|
+
models: COPILOT_MODELS.map((m) => m.id)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function authorizeUrl() {
|
|
60
|
+
return "https://github.com/login/device"
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export async function listModels() {
|
|
64
|
+
return COPILOT_MODELS
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function getTelemetryHeaders(sessionId) {
|
|
68
|
+
const sid = sessionId || "copilot-session-" + Math.random().toString(36).slice(2, 12)
|
|
69
|
+
return {
|
|
70
|
+
"vscode-sessionid": sid,
|
|
71
|
+
"vscode-machineid": "dsh-sub-machine-" + sid.slice(0, 8),
|
|
72
|
+
"editor-version": "vscode/1.98.0",
|
|
73
|
+
"editor-plugin-version": "copilot-chat/0.24.0",
|
|
74
|
+
"Openai-Organization": "github-copilot",
|
|
75
|
+
"Copilot-Integration-Id": "vscode-chat"
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export async function requestDeviceCode(fetchImpl) {
|
|
80
|
+
const impl = fetchImpl || fetch
|
|
81
|
+
const res = await impl(COPILOT_DEVICE_CODE_URL, {
|
|
82
|
+
method: "POST",
|
|
83
|
+
headers: {
|
|
84
|
+
"Accept": "application/json",
|
|
85
|
+
"Content-Type": "application/json"
|
|
86
|
+
},
|
|
87
|
+
body: JSON.stringify({
|
|
88
|
+
client_id: COPILOT_CLIENT_ID,
|
|
89
|
+
scope: "read:user"
|
|
90
|
+
})
|
|
91
|
+
})
|
|
92
|
+
if (!res.ok) throw httpError(res.status, await res.text())
|
|
93
|
+
return readJson(res)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export async function pollDeviceToken(deviceCode, fetchImpl) {
|
|
97
|
+
const impl = fetchImpl || fetch
|
|
98
|
+
const res = await impl(COPILOT_DEVICE_TOKEN_URL, {
|
|
99
|
+
method: "POST",
|
|
100
|
+
headers: {
|
|
101
|
+
"Accept": "application/json",
|
|
102
|
+
"Content-Type": "application/json"
|
|
103
|
+
},
|
|
104
|
+
body: JSON.stringify({
|
|
105
|
+
client_id: COPILOT_CLIENT_ID,
|
|
106
|
+
device_code: deviceCode,
|
|
107
|
+
grant_type: "urn:ietf:params:oauth:grant-type:device_code"
|
|
108
|
+
})
|
|
109
|
+
})
|
|
110
|
+
if (!res.ok) throw httpError(res.status, await res.text())
|
|
111
|
+
return readJson(res)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export async function exchangeCopilotToken(githubToken, fetchImpl) {
|
|
115
|
+
const impl = fetchImpl || fetch
|
|
116
|
+
const res = await impl(COPILOT_TOKEN_URL, {
|
|
117
|
+
method: "GET",
|
|
118
|
+
headers: {
|
|
119
|
+
"Authorization": `Bearer ${githubToken}`,
|
|
120
|
+
"Accept": "application/json",
|
|
121
|
+
"User-Agent": "GitHubCopilotChat/0.24.0"
|
|
122
|
+
}
|
|
123
|
+
})
|
|
124
|
+
if (!res.ok) throw httpError(res.status, await res.text())
|
|
125
|
+
return readJson(res)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export async function usage(blob, config, fetchImpl) {
|
|
129
|
+
try {
|
|
130
|
+
const impl = fetchImpl || fetch
|
|
131
|
+
const githubToken = blob && (blob.refreshToken || blob.githubToken || blob.accessToken)
|
|
132
|
+
if (!githubToken) return null
|
|
133
|
+
const res = await impl(COPILOT_USER_URL, {
|
|
134
|
+
headers: {
|
|
135
|
+
"Authorization": `Bearer ${githubToken}`,
|
|
136
|
+
"User-Agent": "GitHubCopilotChat/0.24.0"
|
|
137
|
+
}
|
|
138
|
+
})
|
|
139
|
+
if (!res.ok) return null
|
|
140
|
+
const user = await readJson(res)
|
|
141
|
+
const snap = asUsageSnapshot(10)
|
|
142
|
+
if (snap) snap.plan = user && user.plan ? user.plan.name : "Copilot"
|
|
143
|
+
return snap
|
|
144
|
+
} catch {
|
|
145
|
+
return null
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export async function* streamOnce({ blob, options, fetchImpl, headers, config, signal }) {
|
|
150
|
+
const impl = fetchImpl || fetch
|
|
151
|
+
let copilotToken = blob && blob.accessToken
|
|
152
|
+
const githubToken = blob && (blob.refreshToken || blob.githubToken)
|
|
153
|
+
|
|
154
|
+
if ((!copilotToken || (blob.expiresAt && blob.expiresAt < Date.now() + 60000)) && githubToken) {
|
|
155
|
+
const exchange = await exchangeCopilotToken(githubToken, impl)
|
|
156
|
+
if (exchange && exchange.token) {
|
|
157
|
+
copilotToken = exchange.token
|
|
158
|
+
blob.accessToken = exchange.token
|
|
159
|
+
if (exchange.expires_at) blob.expiresAt = exchange.expires_at * 1000
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (!copilotToken) throw new LlmError("GitHub Copilot not authenticated", "AUTH")
|
|
164
|
+
|
|
165
|
+
const body = {
|
|
166
|
+
model: options.model || "claude-3.7-sonnet",
|
|
167
|
+
messages: openaiMessages(options),
|
|
168
|
+
stream: true,
|
|
169
|
+
...(options.maxTokens != null ? { max_tokens: options.maxTokens } : {}),
|
|
170
|
+
...(options.temperature != null ? { temperature: options.temperature } : {})
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const tools = openaiTools(options)
|
|
174
|
+
if (tools && tools.length) body.tools = tools
|
|
175
|
+
|
|
176
|
+
const telemetry = getTelemetryHeaders(options.sessionId)
|
|
177
|
+
const url = (config && config.apiBase) || COPILOT_API_URL
|
|
178
|
+
|
|
179
|
+
const res = await impl(url, {
|
|
180
|
+
method: "POST",
|
|
181
|
+
headers: {
|
|
182
|
+
...telemetry,
|
|
183
|
+
...headers,
|
|
184
|
+
"Authorization": `Bearer ${copilotToken}`,
|
|
185
|
+
"Content-Type": "application/json",
|
|
186
|
+
"User-Agent": "GitHubCopilotChat/0.24.0"
|
|
187
|
+
},
|
|
188
|
+
body: JSON.stringify(body),
|
|
189
|
+
signal
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
if (!res.ok) throw httpError(res.status, await res.text())
|
|
193
|
+
yield* openaiChatStream(res.body)
|
|
194
|
+
}
|
package/lib/vendors/cursor.js
CHANGED
|
@@ -0,0 +1,93 @@
|
|
|
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 = 'ernie'
|
|
7
|
+
export const ERNIE_OAUTH_URL = 'https://aip.baidubce.com/oauth/2.0/token'
|
|
8
|
+
export const ERNIE_BASE_URL = 'https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/chat'
|
|
9
|
+
|
|
10
|
+
export const ERNIE_MODELS = [
|
|
11
|
+
{
|
|
12
|
+
id: 'ernie-speed-128k',
|
|
13
|
+
name: 'ERNIE Speed 128K',
|
|
14
|
+
contextWindow: 128000,
|
|
15
|
+
maxTokens: 4096,
|
|
16
|
+
inputModalities: ['text']
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
id: 'ernie-4.0-turbo-8k',
|
|
20
|
+
name: 'ERNIE 4.0 Turbo',
|
|
21
|
+
contextWindow: 8192,
|
|
22
|
+
maxTokens: 2048,
|
|
23
|
+
inputModalities: ['text']
|
|
24
|
+
}
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
export function providerInfo() {
|
|
28
|
+
return { id, name: 'Baidu ERNIE' }
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function defaults() {
|
|
32
|
+
return {
|
|
33
|
+
apiBase: ERNIE_BASE_URL,
|
|
34
|
+
models: ERNIE_MODELS.map((m) => m.id)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function authorizeUrl() {
|
|
39
|
+
return 'https://console.bce.baidu.com/qianfan'
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export async function listModels() {
|
|
43
|
+
return ERNIE_MODELS
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export async function refreshErnieToken(apiKey, secretKey, fetchImpl) {
|
|
47
|
+
const impl = fetchImpl || fetch
|
|
48
|
+
const url = `${ERNIE_OAUTH_URL}?grant_type=client_credentials&client_id=${encodeURIComponent(apiKey)}&client_secret=${encodeURIComponent(secretKey)}`
|
|
49
|
+
const res = await impl(url, { method: 'POST' })
|
|
50
|
+
if (!res.ok) throw httpError(res.status, await res.text())
|
|
51
|
+
return readJson(res)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export async function usage(blob) {
|
|
55
|
+
if (blob && (blob.accessToken || blob.apiKey)) {
|
|
56
|
+
return asUsageSnapshot(20)
|
|
57
|
+
}
|
|
58
|
+
return null
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export async function* streamOnce({ blob, options, fetchImpl, headers, config, signal }) {
|
|
62
|
+
const impl = fetchImpl || fetch
|
|
63
|
+
let token = blob && blob.accessToken
|
|
64
|
+
if (!token && blob && blob.apiKey && blob.secretKey) {
|
|
65
|
+
const auth = await refreshErnieToken(blob.apiKey, blob.secretKey, impl)
|
|
66
|
+
if (auth && auth.access_token) {
|
|
67
|
+
token = auth.access_token
|
|
68
|
+
blob.accessToken = token
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (!token) throw new LlmError('Baidu ERNIE requires access token or API Key + Secret Key', 'AUTH')
|
|
72
|
+
|
|
73
|
+
const endpoint = options.model === 'ernie-4.0-turbo-8k' ? 'completions_pro' : 'ernie_speed'
|
|
74
|
+
const url = `${(config && config.apiBase) || ERNIE_BASE_URL}/${endpoint}?access_token=${token}`
|
|
75
|
+
|
|
76
|
+
const messages = openaiMessages(options).map((m) => ({
|
|
77
|
+
role: m.role === 'system' ? 'user' : m.role,
|
|
78
|
+
content: m.content
|
|
79
|
+
}))
|
|
80
|
+
|
|
81
|
+
const res = await impl(url, {
|
|
82
|
+
method: 'POST',
|
|
83
|
+
headers: {
|
|
84
|
+
...headers,
|
|
85
|
+
'Content-Type': 'application/json'
|
|
86
|
+
},
|
|
87
|
+
body: JSON.stringify({ messages, stream: true }),
|
|
88
|
+
signal
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
if (!res.ok) throw httpError(res.status, await res.text())
|
|
92
|
+
yield* openaiChatStream(res.body)
|
|
93
|
+
}
|
package/lib/vendors/glm.js
CHANGED
package/lib/vendors/index.js
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
import * as kiro from './kiro.js'
|
|
2
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'
|
|
3
11
|
import * as codex from './codex.js'
|
|
4
12
|
import * as claude from './claude.js'
|
|
5
13
|
import * as grok from './grok.js'
|
|
@@ -8,7 +16,24 @@ import * as kimi from './kimi.js'
|
|
|
8
16
|
import * as glm from './glm.js'
|
|
9
17
|
import { createVendorFromProfile } from '../vendor-factory.js'
|
|
10
18
|
|
|
11
|
-
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
|
+
}
|
|
12
37
|
|
|
13
38
|
const customVendors = new Map()
|
|
14
39
|
|