dsh-qoder-connect 0.1.2 → 0.1.4
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 +148 -148
- package/lib/bin.js +1 -1
- package/lib/client.js +1154 -1215
- package/lib/index.js +45 -4
- package/lib/{variants-C0iZD8AD.js → variants-CSeJqJpR.js} +75 -6
- package/package.json +2 -2
package/lib/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as QODER_GLOBAL_AUTH_PATH, B as qoderCredentialIdentity, C as probeModel, D as FALLBACK_QODER_MODELS, E as qoderCatalogPath, F as QODER_AUTH_FILENAME, G as qoderPluginDataDir, H as QODER_DATA_DIR_ENV, I as QODER_PAT_ENV_CN, K as qoderStateDir, L as QODER_PAT_ENV_GLOBAL, M as QODER_GLOBAL_STATUS_PATH, N as QODER_PROBE_PATH, O as QoderCatalog, P as QODER_STATUS_PATH, R as QoderCredentialStore, S as PROBE_EFFORT_CANDIDATES, T as QoderCatalogStore, U as QODER_DATA_DIR_NAME, V as qoderOwnAuthPath, W as qoderMachineIdPath, _ as modelInfoOf, a as QODER_HOST_HEARTBEAT_FILENAME, b as createQoderTransport, c as processStartTimeMs, d as writeHostHeartbeat, f as QODER_CONNECT_VERSION, g as kindFromQoderFailure, h as classifyUpstreamError, i as variantFor, j as QODER_GLOBAL_PROBE_PATH, k as QODER_AUTH_PATH, l as qoderHostHeartbeatPath, m as QoderUpstreamClient, n as GLOBAL_VARIANT, o as clearHostHeartbeat, p as KIND_STATUS, r as QODER_VARIANTS, s as isHeartbeatProcessAlive, t as CHINA_VARIANT, u as readHostHeartbeat, v as normalizeCredits, w as randomSentinel, x as getMachineId, y as validateApiKey, z as patTail } from "./variants-
|
|
1
|
+
import { A as QODER_GLOBAL_AUTH_PATH, B as qoderCredentialIdentity, C as probeModel, D as FALLBACK_QODER_MODELS, E as qoderCatalogPath, F as QODER_AUTH_FILENAME, G as qoderPluginDataDir, H as QODER_DATA_DIR_ENV, I as QODER_PAT_ENV_CN, K as qoderStateDir, L as QODER_PAT_ENV_GLOBAL, M as QODER_GLOBAL_STATUS_PATH, N as QODER_PROBE_PATH, O as QoderCatalog, P as QODER_STATUS_PATH, R as QoderCredentialStore, S as PROBE_EFFORT_CANDIDATES, T as QoderCatalogStore, U as QODER_DATA_DIR_NAME, V as qoderOwnAuthPath, W as qoderMachineIdPath, _ as modelInfoOf, a as QODER_HOST_HEARTBEAT_FILENAME, b as createQoderTransport, c as processStartTimeMs, d as writeHostHeartbeat, f as QODER_CONNECT_VERSION, g as kindFromQoderFailure, h as classifyUpstreamError, i as variantFor, j as QODER_GLOBAL_PROBE_PATH, k as QODER_AUTH_PATH, l as qoderHostHeartbeatPath, m as QoderUpstreamClient, n as GLOBAL_VARIANT, o as clearHostHeartbeat, p as KIND_STATUS, r as QODER_VARIANTS, s as isHeartbeatProcessAlive, t as CHINA_VARIANT, u as readHostHeartbeat, v as normalizeCredits, w as randomSentinel, x as getMachineId, y as validateApiKey, z as patTail } from "./variants-CSeJqJpR.js";
|
|
2
2
|
import z from "@deepseek-ai/schemastery";
|
|
3
3
|
import { createHash, randomBytes, timingSafeEqual } from "node:crypto";
|
|
4
4
|
import { dirname, join, resolve } from "node:path";
|
|
@@ -1290,16 +1290,42 @@ function emitJobTokenHint(at, text) {
|
|
|
1290
1290
|
const agent = lastRunningAgent;
|
|
1291
1291
|
if (agent?.session === void 0) return;
|
|
1292
1292
|
lastRefreshAt.set(String(agent.id), at);
|
|
1293
|
+
appendHintRow(agent, "success", text);
|
|
1294
|
+
}
|
|
1295
|
+
/**
|
|
1296
|
+
* Print the failed-heal row into the conversation whose request triggered it.
|
|
1297
|
+
*
|
|
1298
|
+
* The mirror of {@link emitJobTokenHint}: the self-heal ran, exchanged a fresh
|
|
1299
|
+
* job token, retried, and the upstream still refused. Without this row that
|
|
1300
|
+
* outcome was silent — the user saw only the resulting authorization failure
|
|
1301
|
+
* and could not tell that recovery had already been attempted and lost.
|
|
1302
|
+
*
|
|
1303
|
+
* Rate limited by the transport (once per unresolved outage), not here: this
|
|
1304
|
+
* module stays a pure printer so the dedupe policy lives with the state that
|
|
1305
|
+
* knows when an outage ends.
|
|
1306
|
+
*
|
|
1307
|
+
* @param at - When the failed heal was recorded.
|
|
1308
|
+
* @param text - The row's summary line.
|
|
1309
|
+
*/
|
|
1310
|
+
function emitJobTokenRefreshFailedHint(at, text) {
|
|
1311
|
+
const agent = lastRunningAgent;
|
|
1312
|
+
if (agent?.session === void 0) return;
|
|
1313
|
+
appendHintRow(agent, "error", text);
|
|
1314
|
+
}
|
|
1315
|
+
/** Append one log-only command pair carrying `text` as its outcome. */
|
|
1316
|
+
function appendHintRow(agent, kind, text) {
|
|
1317
|
+
const session = agent.session;
|
|
1318
|
+
if (session === void 0) return;
|
|
1293
1319
|
const commandId = `cmd-qoder-hint-${Date.now().toString(36)}-${++hintSeq}`;
|
|
1294
1320
|
try {
|
|
1295
|
-
|
|
1321
|
+
session.append("command/run", {
|
|
1296
1322
|
commandId,
|
|
1297
1323
|
name: JOB_TOKEN_HINT_NAME,
|
|
1298
1324
|
source: { kind: "user" }
|
|
1299
1325
|
});
|
|
1300
|
-
|
|
1326
|
+
session.append("command/done", {
|
|
1301
1327
|
commandId,
|
|
1302
|
-
kind
|
|
1328
|
+
kind,
|
|
1303
1329
|
text
|
|
1304
1330
|
});
|
|
1305
1331
|
} catch {}
|
|
@@ -1475,6 +1501,17 @@ function fallbackFor(_variant) {
|
|
|
1475
1501
|
function jobTokenHintText(at) {
|
|
1476
1502
|
return `jobToken 已自动刷新(${new Date(at).toLocaleTimeString("zh-CN", { hour12: false })})— 旧令牌被上游拒绝,已自动重换并恢复`;
|
|
1477
1503
|
}
|
|
1504
|
+
/**
|
|
1505
|
+
* The failed-heal row's summary line.
|
|
1506
|
+
*
|
|
1507
|
+
* Deliberately does not name a cause: the upstream rejection that survived a
|
|
1508
|
+
* fresh token is not necessarily an authorization problem at all, and the real
|
|
1509
|
+
* reason travels in the failure message itself (see the SSE envelope body).
|
|
1510
|
+
* Claiming "quota" or "revoked" here would be a guess presented as a finding.
|
|
1511
|
+
*/
|
|
1512
|
+
function jobTokenRefreshFailedHintText(at) {
|
|
1513
|
+
return `jobToken 已重换但仍被上游拒绝(${new Date(at).toLocaleTimeString("zh-CN", { hour12: false })})— 自愈未恢复,请查看上方错误详情`;
|
|
1514
|
+
}
|
|
1478
1515
|
/** Build one variant's stores, transport, and probe state. */
|
|
1479
1516
|
function createVariantRuntime(ctx, config, variant, current, identityOf) {
|
|
1480
1517
|
const store = new QoderCredentialStore({
|
|
@@ -1490,6 +1527,10 @@ function createVariantRuntime(ctx, config, variant, current, identityOf) {
|
|
|
1490
1527
|
jobTokenRefreshedAt = info.at;
|
|
1491
1528
|
ctx.logger.warn(`dsh-qoder-connect: ${variant.displayName} job token was auto-refreshed after an upstream rejection`);
|
|
1492
1529
|
emitJobTokenHint(info.at, jobTokenHintText(info.at));
|
|
1530
|
+
},
|
|
1531
|
+
onJobTokenRefreshFailed: (info) => {
|
|
1532
|
+
ctx.logger.warn(`dsh-qoder-connect: ${variant.displayName} job token refresh did not recover the chat (upstream status ${info.status ?? "unknown"})`);
|
|
1533
|
+
emitJobTokenRefreshFailedHint(info.at, jobTokenRefreshFailedHintText(info.at));
|
|
1493
1534
|
}
|
|
1494
1535
|
});
|
|
1495
1536
|
const client = new QoderUpstreamClient({
|
|
@@ -2623,7 +2623,10 @@ function parseEnvelope(rawData) {
|
|
|
2623
2623
|
const envelope = value;
|
|
2624
2624
|
if (envelope.statusCodeValue !== void 0 && typeof envelope.statusCodeValue !== "number") throw malformed("Qoder SSE envelope has an invalid status code.");
|
|
2625
2625
|
if (envelope.statusCodeValue !== void 0 && (!Number.isInteger(envelope.statusCodeValue) || envelope.statusCodeValue < 100 || envelope.statusCodeValue > 599)) throw malformed("Qoder SSE envelope has an invalid status code.");
|
|
2626
|
-
if (envelope.statusCodeValue !== void 0 && envelope.statusCodeValue !== 200)
|
|
2626
|
+
if (envelope.statusCodeValue !== void 0 && envelope.statusCodeValue !== 200) {
|
|
2627
|
+
const status = envelope.statusCodeValue;
|
|
2628
|
+
throw qoderHttpError(envelope.body ? `Qoder service returned upstream error status ${status}: ${envelope.body}` : `Qoder service returned upstream error status ${status}.`, { status });
|
|
2629
|
+
}
|
|
2627
2630
|
if (envelope.body !== void 0 && typeof envelope.body !== "string") throw malformed("Qoder SSE envelope has an invalid model body.");
|
|
2628
2631
|
return envelope;
|
|
2629
2632
|
}
|
|
@@ -3030,6 +3033,18 @@ async function* streamQoderChat(options, model, credentials, messages, dependenc
|
|
|
3030
3033
|
requestController.abort("request complete");
|
|
3031
3034
|
}
|
|
3032
3035
|
}
|
|
3036
|
+
/**
|
|
3037
|
+
* How long a rotated-token notice may wait for an accepting chat.
|
|
3038
|
+
*
|
|
3039
|
+
* The heal's own retry can lose a race with a transient fault and be rescued
|
|
3040
|
+
* by a later attempt, so the notice is deferred until some chat is accepted.
|
|
3041
|
+
* That deferral has to be bounded: an unbounded one let a rotation from
|
|
3042
|
+
* minutes earlier surface as a fresh-looking row whose timestamp named a
|
|
3043
|
+
* moment the user could not connect to the row appearing now. Five minutes
|
|
3044
|
+
* comfortably covers the host's own retry cycle (its backoff caps at 10s)
|
|
3045
|
+
* while keeping the row adjacent to the event it describes.
|
|
3046
|
+
*/
|
|
3047
|
+
const jobTokenNoticeMaxAgeMs = 3e5;
|
|
3033
3048
|
function aborted(message) {
|
|
3034
3049
|
return new QoderLlmError(message, "ABORTED");
|
|
3035
3050
|
}
|
|
@@ -3048,6 +3063,7 @@ var DefaultQoderTransport = class {
|
|
|
3048
3063
|
imageUploader;
|
|
3049
3064
|
preserveThinking;
|
|
3050
3065
|
onJobTokenRefreshed;
|
|
3066
|
+
onJobTokenRefreshFailed;
|
|
3051
3067
|
/**
|
|
3052
3068
|
* Set when the self-heal exchanged a fresh job token, cleared when a chat
|
|
3053
3069
|
* afterwards succeeds. The notice reports "the token was rotated because the
|
|
@@ -3056,6 +3072,16 @@ var DefaultQoderTransport = class {
|
|
|
3056
3072
|
* transient upstream timeout and be rescued by a later attempt.
|
|
3057
3073
|
*/
|
|
3058
3074
|
pendingRefreshAt;
|
|
3075
|
+
/**
|
|
3076
|
+
* Whether the current unresolved heal failure has already been reported.
|
|
3077
|
+
*
|
|
3078
|
+
* One upstream rejection can outlive many host-level retries (an observed
|
|
3079
|
+
* storm ran 59 of them across 75 steps), and the transport re-heals inside
|
|
3080
|
+
* every one of them. Without this latch the failure notice would print once
|
|
3081
|
+
* per retry; with it, the user is told once per outage. Cleared as soon as
|
|
3082
|
+
* any chat is accepted, so the next outage announces itself again.
|
|
3083
|
+
*/
|
|
3084
|
+
refreshFailureAnnounced = false;
|
|
3059
3085
|
constructor(options) {
|
|
3060
3086
|
this.region = options.region;
|
|
3061
3087
|
this.resolvePat = options.resolvePat;
|
|
@@ -3067,6 +3093,7 @@ var DefaultQoderTransport = class {
|
|
|
3067
3093
|
this.attachments = options.attachments;
|
|
3068
3094
|
this.preserveThinking = options.preserveThinking;
|
|
3069
3095
|
this.onJobTokenRefreshed = options.onJobTokenRefreshed;
|
|
3096
|
+
this.onJobTokenRefreshFailed = options.onJobTokenRefreshFailed;
|
|
3070
3097
|
this.auth = new QoderAuthService({
|
|
3071
3098
|
fetch: this.fetchImpl,
|
|
3072
3099
|
logger: this.logger,
|
|
@@ -3107,12 +3134,35 @@ var DefaultQoderTransport = class {
|
|
|
3107
3134
|
if (this.pendingRefreshAt === void 0) return;
|
|
3108
3135
|
const at = this.pendingRefreshAt;
|
|
3109
3136
|
this.pendingRefreshAt = void 0;
|
|
3137
|
+
if (Date.now() - at > jobTokenNoticeMaxAgeMs) return;
|
|
3110
3138
|
this.logger?.warn?.("[Qoder Stream] Job token was auto-refreshed after an upstream rejection; the chat has recovered");
|
|
3111
3139
|
this.onJobTokenRefreshed?.({
|
|
3112
3140
|
region: this.region,
|
|
3113
3141
|
at
|
|
3114
3142
|
});
|
|
3115
3143
|
}
|
|
3144
|
+
/**
|
|
3145
|
+
* Drop a pending rotation notice whose heal did not rescue anything.
|
|
3146
|
+
*
|
|
3147
|
+
* The heal's own retry was rejected too, so "the rotation fixed it" is not
|
|
3148
|
+
* what happened. Leaving the notice pending made a LATER, unrelated chat
|
|
3149
|
+
* acceptance flush it — printing a success row that contradicts the failure
|
|
3150
|
+
* row already shown, stamped with the old rotation time.
|
|
3151
|
+
*/
|
|
3152
|
+
discardPendingRefreshNotice() {
|
|
3153
|
+
this.pendingRefreshAt = void 0;
|
|
3154
|
+
}
|
|
3155
|
+
/**
|
|
3156
|
+
* Note that the upstream accepted a chat, ending any unresolved heal failure.
|
|
3157
|
+
*
|
|
3158
|
+
* `streamChat` calls this the moment its first chunk arrives — the only point
|
|
3159
|
+
* at which "the credential was accepted" is actually known. A rejected chat
|
|
3160
|
+
* throws before that, so this never fires on a failing attempt.
|
|
3161
|
+
*/
|
|
3162
|
+
onChatAccepted() {
|
|
3163
|
+
this.refreshFailureAnnounced = false;
|
|
3164
|
+
this.flushPendingRefreshNotice();
|
|
3165
|
+
}
|
|
3116
3166
|
async discoverModels(signal) {
|
|
3117
3167
|
const pat = await this.requirePat(signal);
|
|
3118
3168
|
const key = opaqueCredentialKey(pat);
|
|
@@ -3178,7 +3228,7 @@ var DefaultQoderTransport = class {
|
|
|
3178
3228
|
const chatCredentials = await this.auth.getCredentials(pat, options.signal);
|
|
3179
3229
|
try {
|
|
3180
3230
|
yield* this.streamChat(options, model, chatCredentials, messages, () => {
|
|
3181
|
-
this.
|
|
3231
|
+
this.onChatAccepted();
|
|
3182
3232
|
});
|
|
3183
3233
|
return;
|
|
3184
3234
|
} catch (error) {
|
|
@@ -3191,7 +3241,7 @@ var DefaultQoderTransport = class {
|
|
|
3191
3241
|
this.pendingRefreshAt = Date.now();
|
|
3192
3242
|
try {
|
|
3193
3243
|
yield* this.streamChat(options, model, refreshed, messages, () => {
|
|
3194
|
-
this.
|
|
3244
|
+
this.onChatAccepted();
|
|
3195
3245
|
});
|
|
3196
3246
|
return;
|
|
3197
3247
|
} catch (error) {
|
|
@@ -3212,6 +3262,16 @@ var DefaultQoderTransport = class {
|
|
|
3212
3262
|
}
|
|
3213
3263
|
}
|
|
3214
3264
|
}
|
|
3265
|
+
this.discardPendingRefreshNotice();
|
|
3266
|
+
if (!this.refreshFailureAnnounced) {
|
|
3267
|
+
this.refreshFailureAnnounced = true;
|
|
3268
|
+
this.logger?.warn?.("[Qoder Stream] Job token refresh did not recover the chat; the upstream still rejects it", { status: lastRejection instanceof LlmError ? lastRejection.failure.status : void 0 });
|
|
3269
|
+
this.onJobTokenRefreshFailed?.({
|
|
3270
|
+
region: this.region,
|
|
3271
|
+
at: Date.now(),
|
|
3272
|
+
...lastRejection instanceof LlmError && lastRejection.failure.status !== void 0 ? { status: lastRejection.failure.status } : {}
|
|
3273
|
+
});
|
|
3274
|
+
}
|
|
3215
3275
|
throw lastRejection;
|
|
3216
3276
|
}
|
|
3217
3277
|
};
|
|
@@ -3618,14 +3678,23 @@ var ChunkEncoder = class {
|
|
|
3618
3678
|
}] })];
|
|
3619
3679
|
}
|
|
3620
3680
|
case "usage": {
|
|
3621
|
-
const
|
|
3681
|
+
const cacheRead = chunk.usage.cacheReadTokens;
|
|
3682
|
+
const cacheWrite = chunk.usage.cacheWriteTokens;
|
|
3683
|
+
const prompt = chunk.usage.inputTokens + (cacheRead ?? 0) + (cacheWrite ?? 0);
|
|
3622
3684
|
const completion = chunk.usage.outputTokens;
|
|
3685
|
+
const reasoning = chunk.usage.reasoningTokens;
|
|
3686
|
+
const details = cacheRead === void 0 && cacheWrite === void 0 ? void 0 : {
|
|
3687
|
+
...cacheRead === void 0 ? {} : { cached_tokens: cacheRead },
|
|
3688
|
+
...cacheWrite === void 0 ? {} : { cache_write_tokens: cacheWrite }
|
|
3689
|
+
};
|
|
3623
3690
|
return [this.frame({
|
|
3624
3691
|
choices: [],
|
|
3625
3692
|
usage: {
|
|
3626
3693
|
prompt_tokens: prompt,
|
|
3627
3694
|
completion_tokens: completion,
|
|
3628
|
-
total_tokens: chunk.usage.totalTokens ?? prompt + completion
|
|
3695
|
+
total_tokens: chunk.usage.totalTokens ?? prompt + completion,
|
|
3696
|
+
...details === void 0 ? {} : { prompt_tokens_details: details },
|
|
3697
|
+
...reasoning === void 0 ? {} : { completion_tokens_details: { reasoning_tokens: reasoning } }
|
|
3629
3698
|
}
|
|
3630
3699
|
})];
|
|
3631
3700
|
}
|
|
@@ -4013,7 +4082,7 @@ const DEFAULT_MAX_TOKENS = 32768;
|
|
|
4013
4082
|
const DEFAULT_CONTEXT_WINDOW = 18e4;
|
|
4014
4083
|
//#endregion
|
|
4015
4084
|
//#region src/version.ts
|
|
4016
|
-
const QODER_CONNECT_VERSION = "0.1.
|
|
4085
|
+
const QODER_CONNECT_VERSION = "0.1.4";
|
|
4017
4086
|
//#endregion
|
|
4018
4087
|
//#region src/host-heartbeat.ts
|
|
4019
4088
|
/**
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "dsh-qoder-connect",
|
|
3
3
|
"displayName": "DSH Qoder Connect",
|
|
4
4
|
"description": "将 Qoder(国内版 / 国际版)的模型接入 DeepSeek Harness — bring Qoder (CN & international) models into DeepSeek Harness.",
|
|
5
|
-
"version": "0.1.
|
|
5
|
+
"version": "0.1.4",
|
|
6
6
|
"author": "corrinehu",
|
|
7
7
|
"keywords": [
|
|
8
8
|
"dsh-plugin",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"scripts": {
|
|
24
24
|
"prepack": "npm run build",
|
|
25
25
|
"build": "tsdown",
|
|
26
|
-
"test": "vitest run",
|
|
26
|
+
"test": "node -r ./tests/sandbox-shim.cjs ./node_modules/vitest/vitest.mjs run",
|
|
27
27
|
"test:qoder": "node --test --test-isolation=none --experimental-strip-types tests/qoder/*.test.ts",
|
|
28
28
|
"typecheck": "tsc -p tsconfig.json && tsc -p tsconfig.client.json",
|
|
29
29
|
"check": "pnpm run typecheck && pnpm run test && pnpm run test:qoder && pnpm run build"
|