dsh-qoder-connect 0.1.8 → 0.2.0
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/assets/icon.svg +6 -0
- package/lib/bin.js +1 -1
- package/lib/client.js +247 -22
- package/lib/index.d.ts +6 -0
- package/lib/index.js +759 -119
- package/lib/{variants-DE76gF0Y.js → variants-DuEgaUad.js} +183 -61
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +11 -4
|
@@ -362,6 +362,15 @@ function qoderOwnAuthPath(variant) {
|
|
|
362
362
|
/** Plugin-owned status endpoint consumed by its browser half. */
|
|
363
363
|
const QODER_STATUS_PATH = "/plugins/dsh-qoder-connect/status";
|
|
364
364
|
/**
|
|
365
|
+
* Plugin-owned settings endpoint consumed by its browser half.
|
|
366
|
+
*
|
|
367
|
+
* GET answers the whole entry configuration as three layers (value/base/user)
|
|
368
|
+
* plus the write key; POST applies one patch. This is the plugin's own settings
|
|
369
|
+
* surface, replacing writes through the host's settings service — see
|
|
370
|
+
* {@link ./settings-store.ts} for why.
|
|
371
|
+
*/
|
|
372
|
+
const QODER_SETTINGS_FACE_PATH = "/plugins/dsh-qoder-connect/settings";
|
|
373
|
+
/**
|
|
365
374
|
* Plugin-owned probe control endpoint.
|
|
366
375
|
*
|
|
367
376
|
* Separate from the status route because it accepts writes: the status route's
|
|
@@ -844,6 +853,125 @@ async function probeModel(options) {
|
|
|
844
853
|
};
|
|
845
854
|
}
|
|
846
855
|
//#endregion
|
|
856
|
+
//#region src/qoder/errors.ts
|
|
857
|
+
/**
|
|
858
|
+
* DSH-compatible LLM error representation.
|
|
859
|
+
*
|
|
860
|
+
* @module dsh-provider-qoder/qoder/errors
|
|
861
|
+
*/
|
|
862
|
+
var QoderLlmError = class extends LlmError {
|
|
863
|
+
constructor(message, code = "UNKNOWN_ERROR", options) {
|
|
864
|
+
super(message, code, options);
|
|
865
|
+
}
|
|
866
|
+
};
|
|
867
|
+
/** The queue/limit markers Qoder puts in a rejection body (observed 2026-09-26). */
|
|
868
|
+
const QUEUE_BODY_MARKERS = [
|
|
869
|
+
/"code"\s*:\s*"?10605"?/u,
|
|
870
|
+
/"isQueued"\s*:\s*true/u,
|
|
871
|
+
/"queueCount"/u,
|
|
872
|
+
/"serviceAvailable"\s*:\s*false/u
|
|
873
|
+
];
|
|
874
|
+
/**
|
|
875
|
+
* Whether an upstream rejection body is Qoder's **queue/limit** answer rather
|
|
876
|
+
* than an authorization failure.
|
|
877
|
+
*
|
|
878
|
+
* Qoder answers a saturated per-model queue (and a region whose service is not
|
|
879
|
+
* available) with an HTTP 401/403 — or a 200 SSE envelope carrying
|
|
880
|
+
* `statusCodeValue: 403` — whose body says `code: 10605`, `isQueued: true`,
|
|
881
|
+
* `queueCount`, `retryAfterSeconds` and sometimes `serviceAvailable: false`.
|
|
882
|
+
* Observed verbatim on 2026-09-26:
|
|
883
|
+
*
|
|
884
|
+
* {"code":"10605","message":"{\"isQueued\":true,\"modelKey\":\"qfmodel\",
|
|
885
|
+
* \"queueCount\":8887,\"queueType\":\"p3\",\"retryAfterSeconds\":30,
|
|
886
|
+
* \"serviceAvailable\":true,\"waitTime\":274}"}
|
|
887
|
+
*
|
|
888
|
+
* Reading that status as AUTH made the self-heal exchange fresh job tokens
|
|
889
|
+
* against a queue no token can jump, and — because the host retries
|
|
890
|
+
* `RATE_LIMIT` but deliberately not `AUTH` — it also suppressed the automatic
|
|
891
|
+
* retry that would have sailed through the 30-second window. The body is the
|
|
892
|
+
* only place the distinction is visible, so it gets the last say over status.
|
|
893
|
+
*/
|
|
894
|
+
function qoderQueueSignal(body) {
|
|
895
|
+
if (body === void 0 || body === "") return void 0;
|
|
896
|
+
const text = body.replace(/\\/gu, "");
|
|
897
|
+
if (!QUEUE_BODY_MARKERS.some((marker) => marker.test(text))) return void 0;
|
|
898
|
+
const match = /"retryAfterSeconds"\s*:\s*(\d+)/u.exec(text);
|
|
899
|
+
return match === null ? {} : { retryAfterMs: Number(match[1]) * 1e3 };
|
|
900
|
+
}
|
|
901
|
+
/**
|
|
902
|
+
* Replace status digits that read as authorization failure with a neutral
|
|
903
|
+
* word, for messages the upstream used to announce a queue.
|
|
904
|
+
*
|
|
905
|
+
* `dsh-llm-pi-ai`'s error classifier is a text matcher whose first test is
|
|
906
|
+
* `/\b(?:401|403)\b/` — ahead of its 429/rate-limit test — so any message
|
|
907
|
+
* carrying those digits classifies AUTH no matter the structured code
|
|
908
|
+
* travelling beside it. A queue answer quoting the upstream's 403 (in the
|
|
909
|
+
* message prefix, the envelope status, or the body's `"code":"403"`) must not
|
|
910
|
+
* reach that matcher as-is, or the user sees "API 密钥无效" for a throttle
|
|
911
|
+
* (observed verbatim on 2026-09-26 23:30 and again at 23:40).
|
|
912
|
+
*/
|
|
913
|
+
function deauthorizeDigits(message) {
|
|
914
|
+
return message.replace(/\b(?:401|403)\b/gu, "throttled");
|
|
915
|
+
}
|
|
916
|
+
function qoderHttpError(message, response, body) {
|
|
917
|
+
const { status } = response;
|
|
918
|
+
const headerRetryAfterMs = retryAfterMs(response.headers?.get("retry-after") ?? null);
|
|
919
|
+
const requestId = qoderRequestId(response.headers);
|
|
920
|
+
const queue = status === 401 || status === 403 || status === 429 ? qoderQueueSignal(body ?? message) : void 0;
|
|
921
|
+
if (queue !== void 0) {
|
|
922
|
+
const retryAfterMs = queue.retryAfterMs ?? headerRetryAfterMs;
|
|
923
|
+
return new QoderLlmError(deauthorizeDigits(message), "RATE_LIMIT", {
|
|
924
|
+
status,
|
|
925
|
+
...retryAfterMs === void 0 ? {} : { providerRetryAfterMs: retryAfterMs },
|
|
926
|
+
...requestId === void 0 ? {} : { requestId }
|
|
927
|
+
});
|
|
928
|
+
}
|
|
929
|
+
return new QoderLlmError(message, status === 401 || status === 403 ? "AUTH" : status === 408 ? "TIMEOUT" : status === 429 ? "RATE_LIMIT" : status >= 500 && status <= 599 ? "SERVER" : status >= 400 && status <= 499 ? "INVALID_REQUEST" : "PROVIDER_ERROR", {
|
|
930
|
+
status,
|
|
931
|
+
...headerRetryAfterMs === void 0 ? {} : { providerRetryAfterMs: headerRetryAfterMs },
|
|
932
|
+
...requestId === void 0 ? {} : { requestId }
|
|
933
|
+
});
|
|
934
|
+
}
|
|
935
|
+
function qoderRequestId(headers) {
|
|
936
|
+
const normalized = (headers?.get("x-request-id") ?? headers?.get("request-id") ?? headers?.get("x-amzn-requestid"))?.trim();
|
|
937
|
+
return normalized ? ProviderRequestId(normalized) : void 0;
|
|
938
|
+
}
|
|
939
|
+
/**
|
|
940
|
+
* Whether this failure is an upstream authorization rejection worth one
|
|
941
|
+
* re-auth retry.
|
|
942
|
+
*
|
|
943
|
+
* The job token the transport signs requests with is cached in memory; an
|
|
944
|
+
* upstream that invalidates it mid-lifetime (gateway rotation or a fault
|
|
945
|
+
* window) answers HTTP 401/403 before any payload is produced. That state is
|
|
946
|
+
* distinguishable from a genuinely revoked PAT only by trying a fresh
|
|
947
|
+
* exchange, so callers clear their credential cache and retry once before
|
|
948
|
+
* reporting `AUTH` to the user.
|
|
949
|
+
*
|
|
950
|
+
* A `RATE_LIMIT` code is never a re-auth candidate, **even when its status is
|
|
951
|
+
* 401/403**: that is the upstream's queue answer (code 10605 / isQueued /
|
|
952
|
+
* retryAfterSeconds in the body), which keeps the status but says the
|
|
953
|
+
* credential was not the problem. Healing it exchanged job tokens against a
|
|
954
|
+
* queue no token can jump (observed 2026-09-26) and, by consuming the retry,
|
|
955
|
+
* suppressed the rate-limit retry that would have ridden out the window.
|
|
956
|
+
*/
|
|
957
|
+
function isQoderAuthRejection(error) {
|
|
958
|
+
if (!(error instanceof LlmError)) return false;
|
|
959
|
+
if (error.code === "RATE_LIMIT") return false;
|
|
960
|
+
return error.code === "AUTH" || error.failure.status === 401 || error.failure.status === 403;
|
|
961
|
+
}
|
|
962
|
+
function retryAfterMs(value, nowMs = Date.now()) {
|
|
963
|
+
const normalized = value?.trim();
|
|
964
|
+
if (!normalized) return void 0;
|
|
965
|
+
if (/^\d+$/u.test(normalized)) {
|
|
966
|
+
const delayMs = Number(normalized) * 1e3;
|
|
967
|
+
return Number.isFinite(delayMs) && delayMs > 0 ? delayMs : void 0;
|
|
968
|
+
}
|
|
969
|
+
const retryAt = Date.parse(normalized);
|
|
970
|
+
if (Number.isNaN(retryAt)) return void 0;
|
|
971
|
+
const delayMs = retryAt - nowMs;
|
|
972
|
+
return delayMs > 0 ? delayMs : void 0;
|
|
973
|
+
}
|
|
974
|
+
//#endregion
|
|
847
975
|
//#region src/qoder/transport/endpoints.ts
|
|
848
976
|
const qoderRegionEndpoints = {
|
|
849
977
|
global: {
|
|
@@ -906,59 +1034,6 @@ function getQoderClaimCampaignUrl(region = "global", campaignId) {
|
|
|
906
1034
|
return `${openApiUrl}/sash/api/v1/me/campaigns/${encodeURIComponent(campaignId)}/claim`;
|
|
907
1035
|
}
|
|
908
1036
|
//#endregion
|
|
909
|
-
//#region src/qoder/errors.ts
|
|
910
|
-
/**
|
|
911
|
-
* DSH-compatible LLM error representation.
|
|
912
|
-
*
|
|
913
|
-
* @module dsh-provider-qoder/qoder/errors
|
|
914
|
-
*/
|
|
915
|
-
var QoderLlmError = class extends LlmError {
|
|
916
|
-
constructor(message, code = "UNKNOWN_ERROR", options) {
|
|
917
|
-
super(message, code, options);
|
|
918
|
-
}
|
|
919
|
-
};
|
|
920
|
-
function qoderHttpError(message, response) {
|
|
921
|
-
const { status } = response;
|
|
922
|
-
const code = status === 401 || status === 403 ? "AUTH" : status === 408 ? "TIMEOUT" : status === 429 ? "RATE_LIMIT" : status >= 500 && status <= 599 ? "SERVER" : status >= 400 && status <= 499 ? "INVALID_REQUEST" : "PROVIDER_ERROR";
|
|
923
|
-
const providerRetryAfterMs = retryAfterMs(response.headers?.get("retry-after") ?? null);
|
|
924
|
-
const requestId = qoderRequestId(response.headers);
|
|
925
|
-
return new QoderLlmError(message, code, {
|
|
926
|
-
status,
|
|
927
|
-
...providerRetryAfterMs === void 0 ? {} : { providerRetryAfterMs },
|
|
928
|
-
...requestId === void 0 ? {} : { requestId }
|
|
929
|
-
});
|
|
930
|
-
}
|
|
931
|
-
function qoderRequestId(headers) {
|
|
932
|
-
const normalized = (headers?.get("x-request-id") ?? headers?.get("request-id") ?? headers?.get("x-amzn-requestid"))?.trim();
|
|
933
|
-
return normalized ? ProviderRequestId(normalized) : void 0;
|
|
934
|
-
}
|
|
935
|
-
/**
|
|
936
|
-
* Whether this failure is an upstream authorization rejection worth one
|
|
937
|
-
* re-auth retry.
|
|
938
|
-
*
|
|
939
|
-
* The job token the transport signs requests with is cached in memory; an
|
|
940
|
-
* upstream that invalidates it mid-lifetime (gateway rotation or a fault
|
|
941
|
-
* window) answers HTTP 401/403 before any payload is produced. That state is
|
|
942
|
-
* distinguishable from a genuinely revoked PAT only by trying a fresh
|
|
943
|
-
* exchange, so callers clear their credential cache and retry once before
|
|
944
|
-
* reporting `AUTH` to the user.
|
|
945
|
-
*/
|
|
946
|
-
function isQoderAuthRejection(error) {
|
|
947
|
-
return error instanceof LlmError && (error.code === "AUTH" || error.failure.status === 401 || error.failure.status === 403);
|
|
948
|
-
}
|
|
949
|
-
function retryAfterMs(value, nowMs = Date.now()) {
|
|
950
|
-
const normalized = value?.trim();
|
|
951
|
-
if (!normalized) return void 0;
|
|
952
|
-
if (/^\d+$/u.test(normalized)) {
|
|
953
|
-
const delayMs = Number(normalized) * 1e3;
|
|
954
|
-
return Number.isFinite(delayMs) && delayMs > 0 ? delayMs : void 0;
|
|
955
|
-
}
|
|
956
|
-
const retryAt = Date.parse(normalized);
|
|
957
|
-
if (Number.isNaN(retryAt)) return void 0;
|
|
958
|
-
const delayMs = retryAt - nowMs;
|
|
959
|
-
return delayMs > 0 ? delayMs : void 0;
|
|
960
|
-
}
|
|
961
|
-
//#endregion
|
|
962
1037
|
//#region src/qoder/transport/machine-id.ts
|
|
963
1038
|
function defaultMachineIdPaths() {
|
|
964
1039
|
return [join(homedir(), ".qoder", ".auth", "machine_id"), join(homedir(), ".dsh", "qoder", "machine_id")];
|
|
@@ -1285,7 +1360,7 @@ async function openApiJsonRequest(fetchImpl, options) {
|
|
|
1285
1360
|
const text = await readLimitedText(response, response.ok ? defaultMaxJsonBytes : defaultMaxErrorBytes, `Qoder ${options.operation} response`);
|
|
1286
1361
|
if (!response.ok) {
|
|
1287
1362
|
options.logger?.error?.(`[Qoder ${options.operation}] Request failed`, redactLogPayload(text));
|
|
1288
|
-
throw qoderHttpError(`Failed to execute Qoder ${options.operation} with status ${response.status}.`, response);
|
|
1363
|
+
throw qoderHttpError(`Failed to execute Qoder ${options.operation} with status ${response.status}.`, response, text);
|
|
1289
1364
|
}
|
|
1290
1365
|
let data;
|
|
1291
1366
|
try {
|
|
@@ -1323,6 +1398,14 @@ async function waitForFlight(promise, signal) {
|
|
|
1323
1398
|
var QoderAuthService = class {
|
|
1324
1399
|
cache = /* @__PURE__ */ new Map();
|
|
1325
1400
|
inFlight = /* @__PURE__ */ new Map();
|
|
1401
|
+
/**
|
|
1402
|
+
* Self-heal exchanges in flight, keyed like the cache. Deliberately separate
|
|
1403
|
+
* from {@link inFlight}: that flight may be aborted by a departing
|
|
1404
|
+
* concurrent waiter (a quota poll, a catalog sweep) whose abort has nothing
|
|
1405
|
+
* to do with the heal, so healers must not share its controller. Concurrent
|
|
1406
|
+
* heals instead share this flight — see {@link exchangeFresh}.
|
|
1407
|
+
*/
|
|
1408
|
+
healFlights = /* @__PURE__ */ new Map();
|
|
1326
1409
|
fetchImpl;
|
|
1327
1410
|
timeoutMs;
|
|
1328
1411
|
resolveMachineId;
|
|
@@ -1345,10 +1428,31 @@ var QoderAuthService = class {
|
|
|
1345
1428
|
* (the quota poll, the catalog sweep), and a retry that joins it would then
|
|
1346
1429
|
* be cancelled by a path that has nothing to do with the chat. The result
|
|
1347
1430
|
* replaces the cache entry.
|
|
1431
|
+
*
|
|
1432
|
+
* Two heals for the same credential that overlap share one exchange instead
|
|
1433
|
+
* of racing: each racer's `clear` deleted what the previous one had just
|
|
1434
|
+
* cached, and its own `cache.set` overwrote the other's — so the cache could
|
|
1435
|
+
* end up naming a token the losing request was no longer using, and with an
|
|
1436
|
+
* upstream that retires the previous job token on a new exchange the racers
|
|
1437
|
+
* also invalidated each other's credentials (one chat recovering while a
|
|
1438
|
+
* concurrent one failed, exactly the 2026-09-26 22:36 observation). The
|
|
1439
|
+
* shared exchange runs on its own timeout signal, so one healer's abort —
|
|
1440
|
+
* its chat request went away — cannot cancel an exchange other healers, and
|
|
1441
|
+
* the future `getCredentials` callers about to read the refreshed cache, are
|
|
1442
|
+
* waiting on.
|
|
1348
1443
|
*/
|
|
1349
1444
|
async exchangeFresh(pat, signal) {
|
|
1445
|
+
const cacheKey = `${this.region}:${opaqueCredentialKey(pat)}`;
|
|
1446
|
+
const existing = this.healFlights.get(cacheKey);
|
|
1447
|
+
if (existing !== void 0 && !existing.settled) return waitForFlight(existing.promise, signal);
|
|
1350
1448
|
this.clear(pat);
|
|
1351
|
-
|
|
1449
|
+
const created = { settled: false };
|
|
1450
|
+
created.promise = this.exchangeAndResolve(pat, AbortSignal.timeout(this.timeoutMs)).finally(() => {
|
|
1451
|
+
created.settled = true;
|
|
1452
|
+
if (this.healFlights.get(cacheKey) === created) this.healFlights.delete(cacheKey);
|
|
1453
|
+
});
|
|
1454
|
+
this.healFlights.set(cacheKey, created);
|
|
1455
|
+
return waitForFlight(created.promise, signal);
|
|
1352
1456
|
}
|
|
1353
1457
|
async getCredentials(pat, signal) {
|
|
1354
1458
|
if (!pat || typeof pat !== "string") throw new QoderLlmError("Qoder Personal Access Token is missing or invalid. Configure Qoder in the Qoder settings page.", "MISSING_CREDENTIAL");
|
|
@@ -2784,7 +2888,7 @@ function parseEnvelope(rawData) {
|
|
|
2784
2888
|
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.");
|
|
2785
2889
|
if (envelope.statusCodeValue !== void 0 && envelope.statusCodeValue !== 200) {
|
|
2786
2890
|
const status = envelope.statusCodeValue;
|
|
2787
|
-
throw qoderHttpError(envelope.body ? `Qoder service returned upstream error status ${status}: ${envelope.body}` : `Qoder service returned upstream error status ${status}.`, { status });
|
|
2891
|
+
throw qoderHttpError(envelope.body ? `Qoder service returned upstream error status ${status}: ${envelope.body}` : `Qoder service returned upstream error status ${status}.`, { status }, envelope.body);
|
|
2788
2892
|
}
|
|
2789
2893
|
if (envelope.body !== void 0 && typeof envelope.body !== "string") throw malformed("Qoder SSE envelope has an invalid model body.");
|
|
2790
2894
|
return envelope;
|
|
@@ -3119,7 +3223,15 @@ async function* streamQoderChat(options, model, credentials, messages, dependenc
|
|
|
3119
3223
|
...reqId === void 0 ? {} : { requestId: reqId }
|
|
3120
3224
|
});
|
|
3121
3225
|
resetIdleTimer();
|
|
3122
|
-
if (!response.ok)
|
|
3226
|
+
if (!response.ok) {
|
|
3227
|
+
let errorBody;
|
|
3228
|
+
try {
|
|
3229
|
+
errorBody = await readLimitedText(response, defaultMaxErrorBytes, "Qoder chat error response");
|
|
3230
|
+
} catch {
|
|
3231
|
+
errorBody = void 0;
|
|
3232
|
+
}
|
|
3233
|
+
throw qoderHttpError(`Qoder upstream service returned HTTP ${response.status}.`, response, errorBody);
|
|
3234
|
+
}
|
|
3123
3235
|
if (!response.body) throw new QoderLlmError("Qoder response contains no readable body stream.", "EMPTY_RESPONSE");
|
|
3124
3236
|
let firstChunkDurationMs;
|
|
3125
3237
|
let tokenUsage;
|
|
@@ -3407,9 +3519,11 @@ var DefaultQoderTransport = class {
|
|
|
3407
3519
|
this.logger?.warn?.("[Qoder Stream] Chat rejected as unauthorized; exchanging a fresh job token and retrying once", { status: error.failure.status });
|
|
3408
3520
|
}
|
|
3409
3521
|
let lastRejection;
|
|
3522
|
+
let noticeAt;
|
|
3410
3523
|
for (let round = 0; round < 2; round++) {
|
|
3411
3524
|
const refreshed = await this.auth.exchangeFresh(pat, options.signal);
|
|
3412
3525
|
this.pendingRefreshAt = Date.now();
|
|
3526
|
+
noticeAt = this.pendingRefreshAt;
|
|
3413
3527
|
try {
|
|
3414
3528
|
yield* this.streamChat(options, model, refreshed, messages, () => {
|
|
3415
3529
|
this.onChatAccepted();
|
|
@@ -3433,7 +3547,7 @@ var DefaultQoderTransport = class {
|
|
|
3433
3547
|
}
|
|
3434
3548
|
}
|
|
3435
3549
|
}
|
|
3436
|
-
this.discardPendingRefreshNotice();
|
|
3550
|
+
if (this.pendingRefreshAt === noticeAt) this.discardPendingRefreshNotice();
|
|
3437
3551
|
if (!this.refreshFailureAnnounced) {
|
|
3438
3552
|
this.refreshFailureAnnounced = true;
|
|
3439
3553
|
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 });
|
|
@@ -3509,6 +3623,7 @@ function normalizeCredits(credits) {
|
|
|
3509
3623
|
*/
|
|
3510
3624
|
function classifyUpstreamError(status, body) {
|
|
3511
3625
|
const lower = body.toLowerCase();
|
|
3626
|
+
if (qoderQueueSignal(body) !== void 0) return "soft_rate";
|
|
3512
3627
|
if (status === 402) return "quota_exceeded";
|
|
3513
3628
|
if (status === 429) return "soft_rate";
|
|
3514
3629
|
if (status === 401 || status === 403) return "auth";
|
|
@@ -3542,11 +3657,18 @@ function classifyUpstreamError(status, body) {
|
|
|
3542
3657
|
* The transport's taxonomy (`src/qoder/errors.ts`) codes an HTTP 402 as
|
|
3543
3658
|
* `INVALID_REQUEST`, so the status gets the last say for the quota and auth
|
|
3544
3659
|
* families before the code-based defaults apply.
|
|
3660
|
+
*
|
|
3661
|
+
* `RATE_LIMIT` is tested before the auth family because the upstream's queue
|
|
3662
|
+
* answer keeps its 401/403 status: a saturated qfmodel queue reads
|
|
3663
|
+
* `code: RATE_LIMIT, status: 403` (body carries 10605/isQueued/
|
|
3664
|
+
* retryAfterSeconds), and letting the status arm claim it reported a dead
|
|
3665
|
+
* credential — which the host never retries — instead of a throttle it would.
|
|
3545
3666
|
*/
|
|
3546
3667
|
function kindFromQoderFailure(failure) {
|
|
3547
3668
|
if (failure.code === "MISSING_CREDENTIAL") return "missing_credential";
|
|
3669
|
+
if (failure.code === "RATE_LIMIT") return "soft_rate";
|
|
3548
3670
|
if (failure.code === "AUTH" || failure.status === 401 || failure.status === 403) return "auth";
|
|
3549
|
-
if (failure.
|
|
3671
|
+
if (failure.status === 429) return "soft_rate";
|
|
3550
3672
|
if (failure.code === "QUOTA" || failure.status === 402) return "quota_exceeded";
|
|
3551
3673
|
if (failure.code.startsWith("INVALID_") || failure.code.startsWith("UNSUPPORTED_") || failure.code === "ATTACHMENT") return "client";
|
|
3552
3674
|
return "server";
|
|
@@ -4266,7 +4388,7 @@ const DEFAULT_MAX_TOKENS = 32768;
|
|
|
4266
4388
|
const DEFAULT_CONTEXT_WINDOW = 18e4;
|
|
4267
4389
|
//#endregion
|
|
4268
4390
|
//#region src/version.ts
|
|
4269
|
-
const QODER_CONNECT_VERSION = "0.
|
|
4391
|
+
const QODER_CONNECT_VERSION = "0.2.0";
|
|
4270
4392
|
//#endregion
|
|
4271
4393
|
//#region src/host-heartbeat.ts
|
|
4272
4394
|
/**
|
|
@@ -4463,4 +4585,4 @@ function variantFor(id) {
|
|
|
4463
4585
|
return QODER_VARIANTS.find((variant) => variant.id === id);
|
|
4464
4586
|
}
|
|
4465
4587
|
//#endregion
|
|
4466
|
-
export { QODER_GLOBAL_AUTH_PATH as A,
|
|
4588
|
+
export { QODER_GLOBAL_AUTH_PATH as A, patTail as B, probeModel as C, FALLBACK_QODER_MODELS as D, qoderCatalogPath as E, QODER_STATUS_PATH as F, qoderMachineIdPath as G, qoderOwnAuthPath as H, QODER_AUTH_FILENAME as I, qoderPluginDataDir as K, QODER_PAT_ENV_CN as L, QODER_GLOBAL_STATUS_PATH as M, QODER_PROBE_PATH as N, QoderCatalog as O, QODER_SETTINGS_FACE_PATH as P, QODER_PAT_ENV_GLOBAL as R, PROBE_EFFORT_CANDIDATES as S, QoderCatalogStore as T, QODER_DATA_DIR_ENV as U, qoderCredentialIdentity as V, QODER_DATA_DIR_NAME as W, modelInfoOf as _, QODER_HOST_HEARTBEAT_FILENAME as a, createQoderTransport as b, processStartTimeMs as c, writeHostHeartbeat as d, QODER_CONNECT_VERSION as f, kindFromQoderFailure as g, classifyUpstreamError as h, variantFor as i, QODER_GLOBAL_PROBE_PATH as j, QODER_AUTH_PATH as k, qoderHostHeartbeatPath as l, QoderUpstreamClient as m, GLOBAL_VARIANT as n, clearHostHeartbeat as o, KIND_STATUS as p, qoderStateDir as q, QODER_VARIANTS as r, isHeartbeatProcessAlive as s, CHINA_VARIANT as t, readHostHeartbeat as u, normalizeCredits as v, randomSentinel as w, getMachineId as x, validateApiKey as y, QoderCredentialStore as z };
|
package/locale/en.json
ADDED
package/locale/zh.json
ADDED
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.
|
|
5
|
+
"version": "0.2.0",
|
|
6
6
|
"author": "corrinehu",
|
|
7
7
|
"keywords": [
|
|
8
8
|
"dsh-plugin",
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
],
|
|
14
14
|
"type": "module",
|
|
15
15
|
"engines": {
|
|
16
|
+
"dsh": ">=0.1.5-rc.2",
|
|
16
17
|
"node": "^22.19.0 || >=24.0.0"
|
|
17
18
|
},
|
|
18
19
|
"main": "lib/index.js",
|
|
@@ -35,7 +36,8 @@
|
|
|
35
36
|
},
|
|
36
37
|
"./client": "./lib/client.js",
|
|
37
38
|
"./cordis.patch.yml": "./cordis.patch.yml",
|
|
38
|
-
"./package.json": "./package.json"
|
|
39
|
+
"./package.json": "./package.json",
|
|
40
|
+
"./locale/*.json": "./locale/*.json"
|
|
39
41
|
},
|
|
40
42
|
"files": [
|
|
41
43
|
"lib",
|
|
@@ -43,7 +45,8 @@
|
|
|
43
45
|
"cordis.patch.yml",
|
|
44
46
|
"README.md",
|
|
45
47
|
"README.en.md",
|
|
46
|
-
"LICENSE"
|
|
48
|
+
"LICENSE",
|
|
49
|
+
"locale/*.json"
|
|
47
50
|
],
|
|
48
51
|
"license": "MIT",
|
|
49
52
|
"repository": {
|
|
@@ -112,5 +115,9 @@
|
|
|
112
115
|
"tsdown": "^0.22.2",
|
|
113
116
|
"typescript": "^5.9.3",
|
|
114
117
|
"vitest": "^4.1.8"
|
|
115
|
-
}
|
|
118
|
+
},
|
|
119
|
+
"dependencies": {
|
|
120
|
+
"js-yaml": "^4.1.0"
|
|
121
|
+
},
|
|
122
|
+
"icon": "./assets/icon.svg"
|
|
116
123
|
}
|