pi-provider-qoder-cn 0.2.18 → 0.2.20
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 +8 -0
- package/dist/index.js +90 -40
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -133,6 +133,14 @@ Notes:
|
|
|
133
133
|
agree with the numbers on the same row. The API's own `percentage` field is a
|
|
134
134
|
0-1 fraction (94% arrives as `0.94`) and is only used — scaled by 100 — when a
|
|
135
135
|
bucket comes back without numbers.
|
|
136
|
+
- `Add-on expiry` is only printed when a real check-in grant exists. A pool that
|
|
137
|
+
is a whole stack of daily grant packs (never more packs than the validity
|
|
138
|
+
window holds) is laid out as `Rolling 30d · earliest of N daily 100-credit
|
|
139
|
+
packs ~DATE`. A pool that is not one (a purchased top-up, or a leftover
|
|
140
|
+
remainder) prints only the expiry the campaign actually reported, `DATE ·
|
|
141
|
+
latest grant`, and an account that has never claimed prints no expiry line at
|
|
142
|
+
all, because with no grant there is nothing to date from, and inventing one
|
|
143
|
+
would show dates the API never sent.
|
|
136
144
|
- `Expires never` means the API reported the year-9999 sentinel, i.e. the
|
|
137
145
|
allowance has no expiry date.
|
|
138
146
|
- `Note plan quota is prorated` appears after a mid-cycle plan change.
|
package/dist/index.js
CHANGED
|
@@ -2388,6 +2388,28 @@ function formatQoderStreamError(statusCode, rawBody, now = Date.now(), mode = "c
|
|
|
2388
2388
|
`- Next step: run /login qoder. You can also refresh credentials by exporting QODER_PERSONAL_ACCESS_TOKEN (PAT).`
|
|
2389
2389
|
].join("\n");
|
|
2390
2390
|
}
|
|
2391
|
+
if (code === "103" || /duplicate request/i.test(message)) {
|
|
2392
|
+
return isCn ? [
|
|
2393
|
+
`[Qoder \u91CD\u590D\u8BF7\u6C42] \u7F51\u5173\u628A\u8FD9\u6B21\u8BF7\u6C42\u5224\u5B9A\u4E3A\u91CD\u653E\u5E76\u62D2\u7EDD (\u9519\u8BEF\u7801 103 Duplicate request)`,
|
|
2394
|
+
`- \u539F\u56E0\uFF1A\u540C\u4E00\u4E2A\u5DF2\u7B7E\u540D\u7684\u8BF7\u6C42\u88AB\u518D\u6B21\u63D0\u4EA4\uFF08COSY Authorization/\u65F6\u95F4\u6233/\u7B7E\u540D \u672A\u5237\u65B0\uFF09\uFF0C\u5E76\u975E\u989D\u5EA6\u6216\u51ED\u8BC1\u95EE\u9898\u3002`,
|
|
2395
|
+
`- \u89E3\u51B3\u5EFA\u8BAE\uFF1A\u5BA2\u6237\u7AEF\u5E94\u4E3A\u6BCF\u6B21\u91CD\u8BD5\u91CD\u65B0\u7B7E\u540D\uFF1B\u82E5\u4ECD\u590D\u73B0\uFF0C\u8BF7\u7528 /model \u5207\u6362 Provider \u7A0D\u540E\u91CD\u8BD5\u3002`
|
|
2396
|
+
].join("\n") : [
|
|
2397
|
+
`[Qoder duplicate request] The gateway rejected this request as a replay (code 103 Duplicate request)`,
|
|
2398
|
+
`- Reason: an already-signed request (same COSY Authorization/timestamp/signature) was submitted again. This is a transient server error, not a quota or credential problem.`,
|
|
2399
|
+
`- Next step: every retry must be re-signed; if it keeps repeating, switch provider with /model and try again shortly.`
|
|
2400
|
+
].join("\n");
|
|
2401
|
+
}
|
|
2402
|
+
if (code === "10605" || /"isQueued"\s*:\s*true|isQueued/.test(message)) {
|
|
2403
|
+
return isCn ? [
|
|
2404
|
+
`[Qoder CN \u4E0A\u6E38\u6392\u961F] \u6A21\u578B\u7F51\u5173\u5F53\u524D\u5BB9\u91CF\u5DF2\u6EE1\uFF0C\u8BF7\u6C42\u8FDB\u5165\u7B49\u5F85\u961F\u5217 (\u9519\u8BEF\u7801 10605)`,
|
|
2405
|
+
`- \u8BF4\u660E\uFF1A\u51ED\u8BC1\u4E0E\u989D\u5EA6\u5747\u6B63\u5E38\uFF0C\u7EAF\u7CB9\u662F\u4E0A\u6E38\u6392\u961F\uFF1B\u5DF2\u6309\u7F51\u5173\u7ED9\u51FA\u7684 Retry-After \u91CD\u8BD5\u4ECD\u672A\u653E\u884C\u3002`,
|
|
2406
|
+
`- \u89E3\u51B3\u5EFA\u8BAE\uFF1A\u7A0D\u540E\u91CD\u8BD5\uFF0C\u6216\u7528 /model \u5207\u6362\u5230\u5176\u4ED6 Provider\uFF08\u5982\u5176\u4ED6\u6A21\u578B/\u6E20\u9053\uFF09\u3002`
|
|
2407
|
+
].join("\n") : [
|
|
2408
|
+
`[Qoder upstream queue] The model gateway is at capacity and parked this request (code 10605 isQueued)`,
|
|
2409
|
+
`- Meaning: credentials and quota are fine \u2014 this is pure upstream queuing; the gateway's own Retry-After wait did not clear it.`,
|
|
2410
|
+
`- Next step: retry shortly, or switch provider with /model while the queue drains.`
|
|
2411
|
+
].join("\n");
|
|
2412
|
+
}
|
|
2391
2413
|
if (rawBody === void 0 || rawBody === null) {
|
|
2392
2414
|
return `Upstream status ${statusCode}`;
|
|
2393
2415
|
}
|
|
@@ -2402,30 +2424,39 @@ function isCredentialExpiredError(error) {
|
|
|
2402
2424
|
|
|
2403
2425
|
// src/commands/usage.ts
|
|
2404
2426
|
init_region();
|
|
2405
|
-
|
|
2427
|
+
var DEFAULT_GRANT_CREDITS = 100;
|
|
2428
|
+
var DEFAULT_GRANT_VALIDITY_DAYS = 30;
|
|
2429
|
+
function positiveOr(value, fallback) {
|
|
2430
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : fallback;
|
|
2431
|
+
}
|
|
2432
|
+
function estimateRollingAddOnExpiry(addOn, grant, now = Date.now()) {
|
|
2406
2433
|
if (!addOn || !addOn.total || addOn.total <= 0 || !addOn.remaining || addOn.remaining <= 0) {
|
|
2407
2434
|
return null;
|
|
2408
2435
|
}
|
|
2409
|
-
const
|
|
2410
|
-
const
|
|
2411
|
-
|
|
2436
|
+
const shape = typeof grant === "number" ? { expiryMs: grant } : grant ?? {};
|
|
2437
|
+
const packSize = positiveOr(shape.amount, DEFAULT_GRANT_CREDITS);
|
|
2438
|
+
const validityDays = positiveOr(shape.validityDays, DEFAULT_GRANT_VALIDITY_DAYS);
|
|
2439
|
+
const anchorMs = shape.expiryMs;
|
|
2440
|
+
if (typeof anchorMs !== "number" || !Number.isFinite(anchorMs) || anchorMs <= now) return null;
|
|
2441
|
+
if (addOn.total % packSize !== 0) return null;
|
|
2442
|
+
const totalPacks = Math.round(addOn.total / packSize);
|
|
2443
|
+
if (totalPacks < 1 || totalPacks > validityDays) return null;
|
|
2412
2444
|
const used = Math.max(0, addOn.used ?? 0);
|
|
2413
2445
|
const consumedPacks = Math.min(totalPacks - 1, Math.floor(used / packSize));
|
|
2414
|
-
const
|
|
2415
|
-
const
|
|
2416
|
-
const anchorExpiryMs = latestExpiryMs && Number.isFinite(latestExpiryMs) ? latestExpiryMs : now + 30 * 864e5;
|
|
2417
|
-
const earliestMs = anchorExpiryMs - daysAgoClaimed * 864e5;
|
|
2446
|
+
const daysAgoClaimed = totalPacks - 1 - consumedPacks;
|
|
2447
|
+
const earliestMs = anchorMs - daysAgoClaimed * 864e5;
|
|
2418
2448
|
const daysRemaining = Math.max(0, Math.round((earliestMs - now) / 864e5));
|
|
2419
2449
|
const earliestRemaining = Math.min(addOn.remaining, packSize - used % packSize);
|
|
2420
2450
|
const d = new Date(earliestMs);
|
|
2421
|
-
const earliestDate = d.toISOString().slice(0, 10);
|
|
2422
2451
|
return {
|
|
2423
|
-
earliestDate,
|
|
2452
|
+
earliestDate: d.toISOString().slice(0, 10),
|
|
2424
2453
|
earliestMs,
|
|
2425
2454
|
earliestRemaining,
|
|
2426
2455
|
totalPacks,
|
|
2427
2456
|
consumedPacks,
|
|
2428
|
-
daysRemaining
|
|
2457
|
+
daysRemaining,
|
|
2458
|
+
packSize,
|
|
2459
|
+
validityDays
|
|
2429
2460
|
};
|
|
2430
2461
|
}
|
|
2431
2462
|
var ANSI2 = {
|
|
@@ -2607,14 +2638,17 @@ function formatQoderUsage(raw, mode, now = Date.now(), options = {}) {
|
|
|
2607
2638
|
}
|
|
2608
2639
|
const addOn = pickBucket2(raw, "addOnQuota");
|
|
2609
2640
|
if (showAddOnQuota && (addOn?.remaining ?? 0) > 0 && checkin) {
|
|
2610
|
-
const
|
|
2611
|
-
const
|
|
2641
|
+
const expiryMs = checkin.expiresAt ? Date.parse(checkin.expiresAt) : void 0;
|
|
2642
|
+
const grant = { expiryMs, amount: checkin.amount, validityDays: checkin.validityDays };
|
|
2643
|
+
const estimate = estimateRollingAddOnExpiry(addOn, grant, now);
|
|
2612
2644
|
if (estimate) {
|
|
2613
|
-
const
|
|
2645
|
+
const expiryText = estimate.totalPacks > 1 ? `earliest of ${estimate.totalPacks} daily ${estimate.packSize}-credit packs ~${estimate.earliestDate} (in ~${estimate.daysRemaining}d, ~${formatAmount(estimate.earliestRemaining)} credits)` : `${estimate.earliestDate} (in ${estimate.daysRemaining}d)`;
|
|
2614
2646
|
notes.push({
|
|
2615
2647
|
label: "Add-on expiry",
|
|
2616
|
-
value: `Rolling
|
|
2648
|
+
value: `Rolling ${estimate.validityDays}d \xB7 ${expiryText}`
|
|
2617
2649
|
});
|
|
2650
|
+
} else if (typeof expiryMs === "number" && Number.isFinite(expiryMs) && expiryMs > now) {
|
|
2651
|
+
notes.push({ label: "Add-on expiry", value: `${formatResetTime(expiryMs, now)} \xB7 latest grant` });
|
|
2618
2652
|
}
|
|
2619
2653
|
}
|
|
2620
2654
|
notes.push({ label: "Expires", value: formatResetTime(raw?.expiresAt, now) });
|
|
@@ -3176,10 +3210,12 @@ function stableHash(prefix, ...inputs) {
|
|
|
3176
3210
|
}
|
|
3177
3211
|
return hash.digest("hex").slice(0, 16);
|
|
3178
3212
|
}
|
|
3179
|
-
function stableChatRecordID(model, messages, tools, maxTokens) {
|
|
3213
|
+
function stableChatRecordID(model, messages, tools, maxTokens, salt) {
|
|
3180
3214
|
const hash = crypto3.createHash("sha256");
|
|
3181
3215
|
hash.update("qoder-record");
|
|
3182
3216
|
hash.update("\0");
|
|
3217
|
+
hash.update(salt);
|
|
3218
|
+
hash.update("\0");
|
|
3183
3219
|
hash.update(model);
|
|
3184
3220
|
for (const msg of messages) {
|
|
3185
3221
|
if (msg?.role) {
|
|
@@ -3360,7 +3396,13 @@ function streamQoder(model, context, options) {
|
|
|
3360
3396
|
maxTokens = options.maxTokens;
|
|
3361
3397
|
}
|
|
3362
3398
|
const toolsRaw = resolved.tools.length > 0 ? transformTools(resolved.tools) : void 0;
|
|
3363
|
-
const recordID = stableChatRecordID(
|
|
3399
|
+
const recordID = stableChatRecordID(
|
|
3400
|
+
qoderModel,
|
|
3401
|
+
normalizedMessages,
|
|
3402
|
+
toolsRaw,
|
|
3403
|
+
maxTokens,
|
|
3404
|
+
`${sessionID}:${userID}`
|
|
3405
|
+
);
|
|
3364
3406
|
const requestedLevel = options?.reasoning;
|
|
3365
3407
|
const clamped = requestedLevel ? clampThinkingLevel(model, requestedLevel) : void 0;
|
|
3366
3408
|
const reasoningLevel = clamped === "off" ? void 0 : clamped;
|
|
@@ -3375,7 +3417,9 @@ function streamQoder(model, context, options) {
|
|
|
3375
3417
|
} else {
|
|
3376
3418
|
parameters.enable_thinking = false;
|
|
3377
3419
|
}
|
|
3378
|
-
const
|
|
3420
|
+
const modelSource = modelConfig.source || "system";
|
|
3421
|
+
const chatURL = getQoderChatURL(providerMode);
|
|
3422
|
+
const buildRequestBody = (attempt) => ({
|
|
3379
3423
|
request_id: crypto3.randomUUID(),
|
|
3380
3424
|
request_set_id: recordID,
|
|
3381
3425
|
chat_record_id: recordID,
|
|
@@ -3383,7 +3427,7 @@ function streamQoder(model, context, options) {
|
|
|
3383
3427
|
stream: true,
|
|
3384
3428
|
chat_task: "FREE_INPUT",
|
|
3385
3429
|
is_reply: true,
|
|
3386
|
-
is_retry:
|
|
3430
|
+
is_retry: attempt > 0,
|
|
3387
3431
|
source: 1,
|
|
3388
3432
|
version: "3",
|
|
3389
3433
|
session_type: "qodercli",
|
|
@@ -3424,18 +3468,31 @@ function streamQoder(model, context, options) {
|
|
|
3424
3468
|
name: lastUserText.substring(0, 30),
|
|
3425
3469
|
begin_at: Date.now()
|
|
3426
3470
|
}
|
|
3427
|
-
};
|
|
3428
|
-
const bodyBytes = Buffer.from(JSON.stringify(reqBody));
|
|
3429
|
-
const encodedBytes = qoderEncodeBody(bodyBytes);
|
|
3430
|
-
const chatURL = getQoderChatURL(providerMode);
|
|
3431
|
-
const headers = buildAuthHeaders(encodedBytes, chatURL, {
|
|
3432
|
-
userID,
|
|
3433
|
-
authToken: accessToken,
|
|
3434
|
-
name,
|
|
3435
|
-
email,
|
|
3436
|
-
machineID
|
|
3437
3471
|
});
|
|
3438
|
-
const
|
|
3472
|
+
const buildAttempt = (attempt) => {
|
|
3473
|
+
const encoded = qoderEncodeBody(Buffer.from(JSON.stringify(buildRequestBody(attempt))));
|
|
3474
|
+
return {
|
|
3475
|
+
encoded,
|
|
3476
|
+
headers: {
|
|
3477
|
+
"Content-Type": "application/json",
|
|
3478
|
+
Accept: "text/event-stream",
|
|
3479
|
+
"Cache-Control": "no-cache",
|
|
3480
|
+
"Accept-Encoding": "identity",
|
|
3481
|
+
"X-Model-Key": qoderModel,
|
|
3482
|
+
"X-Model-Source": modelSource,
|
|
3483
|
+
// Re-signed per attempt: the COSY payload carries its own requestId,
|
|
3484
|
+
// a second-granularity timestamp and a signature over the body, and
|
|
3485
|
+
// reusing any of them is what the gateway reads as a replay.
|
|
3486
|
+
...buildAuthHeaders(encoded, chatURL, {
|
|
3487
|
+
userID,
|
|
3488
|
+
authToken: accessToken,
|
|
3489
|
+
name,
|
|
3490
|
+
email,
|
|
3491
|
+
machineID
|
|
3492
|
+
})
|
|
3493
|
+
}
|
|
3494
|
+
};
|
|
3495
|
+
};
|
|
3439
3496
|
let queuedAttempt = 0;
|
|
3440
3497
|
await reader?.cancel().catch(() => {
|
|
3441
3498
|
});
|
|
@@ -3444,18 +3501,11 @@ function streamQoder(model, context, options) {
|
|
|
3444
3501
|
let buffer = "";
|
|
3445
3502
|
let bufferStart = 0;
|
|
3446
3503
|
for (; ; ) {
|
|
3504
|
+
const current = buildAttempt(queuedAttempt);
|
|
3447
3505
|
const response = await fetch(chatURL, {
|
|
3448
3506
|
method: "POST",
|
|
3449
|
-
headers:
|
|
3450
|
-
|
|
3451
|
-
Accept: "text/event-stream",
|
|
3452
|
-
"Cache-Control": "no-cache",
|
|
3453
|
-
"Accept-Encoding": "identity",
|
|
3454
|
-
"X-Model-Key": qoderModel,
|
|
3455
|
-
"X-Model-Source": modelSource,
|
|
3456
|
-
...headers
|
|
3457
|
-
},
|
|
3458
|
-
body: encodedBytes,
|
|
3507
|
+
headers: current.headers,
|
|
3508
|
+
body: current.encoded,
|
|
3459
3509
|
signal: options?.signal
|
|
3460
3510
|
});
|
|
3461
3511
|
if (!response.ok) {
|
package/package.json
CHANGED