@eddyskywalker/dsh-chatgpt-subscription 0.12.2 → 0.12.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/lib/index.js +166 -20
- package/lib/types/host/kimi-code/adapter.d.ts +37 -2
- package/lib/types/host/kimi-code/adapter.d.ts.map +1 -1
- package/lib/types/host/reasoning-collapse-guard/index.d.ts +42 -10
- package/lib/types/host/reasoning-collapse-guard/index.d.ts.map +1 -1
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -21610,8 +21610,10 @@ function assertStreamComplete$3(state) {
|
|
|
21610
21610
|
* - `INVALID_CREDENTIAL` — a rejected access token fails identically on every
|
|
21611
21611
|
* attempt;
|
|
21612
21612
|
* - `PROVIDER_ERROR` — a 400, a 401 that is really a plan-entitlement refusal,
|
|
21613
|
-
* or a 403 quota limit. Retrying a quota that resets in hours
|
|
21614
|
-
* requests and delays the message the user needs to
|
|
21613
|
+
* or a 403 quota limit. Retrying a quota that resets in hours against the
|
|
21614
|
+
* SAME account only burns requests and delays the message the user needs to
|
|
21615
|
+
* see, so none of it is retried; a 403 limit still rotates to another account
|
|
21616
|
+
* when the pool holds one, which is a routing decision rather than a retry;
|
|
21615
21617
|
* - `ABORTED` — the caller already cancelled.
|
|
21616
21618
|
*
|
|
21617
21619
|
* The DSH normal defaults would apply anyway; stating the values here pins them
|
|
@@ -21695,17 +21697,20 @@ function classifyKimiFailure(status, bodyText) {
|
|
|
21695
21697
|
if (isHttpContextOverflow(status, bodyText)) return {
|
|
21696
21698
|
code: CONTEXT_OVERFLOW_CODE,
|
|
21697
21699
|
retryable: false,
|
|
21700
|
+
accountScoped: false,
|
|
21698
21701
|
message: `${PROVIDER_NAME$3} context window exceeded: ${detail}`
|
|
21699
21702
|
};
|
|
21700
21703
|
if (status === 402) return {
|
|
21701
21704
|
code: "SERVER",
|
|
21702
21705
|
retryable: true,
|
|
21706
|
+
accountScoped: false,
|
|
21703
21707
|
message: `${PROVIDER_NAME$3} could not verify the subscription tier (402). Retrying; if it persists, confirm the membership is active.${detail ? ` ${detail}` : ""}`
|
|
21704
21708
|
};
|
|
21705
21709
|
if (status === 401 || status === 403) {
|
|
21706
21710
|
if (matchesAny$1(detail, ENTITLEMENT_PATTERNS)) return {
|
|
21707
21711
|
code: "PROVIDER_ERROR",
|
|
21708
21712
|
retryable: false,
|
|
21713
|
+
accountScoped: false,
|
|
21709
21714
|
message: `${PROVIDER_NAME$3} refused this request for the current plan: ${detail || "the requested model or context is not included"}. Switch to a model the plan includes, lower the context-window override, or upgrade the subscription.`
|
|
21710
21715
|
};
|
|
21711
21716
|
if (status === 403) {
|
|
@@ -21713,12 +21718,14 @@ function classifyKimiFailure(status, bodyText) {
|
|
|
21713
21718
|
return {
|
|
21714
21719
|
code: "PROVIDER_ERROR",
|
|
21715
21720
|
retryable: false,
|
|
21721
|
+
accountScoped: true,
|
|
21716
21722
|
message: `${PROVIDER_NAME$3} blocked the request on an account limit (403): ${detail || (limitReached ? "the account limit was reached" : "the account refused the request")}. The quota refreshes on its own schedule — check the Kimi Code card in Settings for the reset time.`
|
|
21717
21723
|
};
|
|
21718
21724
|
}
|
|
21719
21725
|
return {
|
|
21720
21726
|
code: "INVALID_CREDENTIAL",
|
|
21721
21727
|
retryable: false,
|
|
21728
|
+
accountScoped: false,
|
|
21722
21729
|
message: `${PROVIDER_NAME$3} rejected the stored credential (401). Sign in again from Settings > Kimi Code.${detail ? ` ${detail}` : ""}`
|
|
21723
21730
|
};
|
|
21724
21731
|
}
|
|
@@ -21726,32 +21733,81 @@ function classifyKimiFailure(status, bodyText) {
|
|
|
21726
21733
|
if (matchesAny$1(detail, QUOTA_EXHAUSTED_PATTERNS$1)) return {
|
|
21727
21734
|
code: "PROVIDER_ERROR",
|
|
21728
21735
|
retryable: false,
|
|
21736
|
+
accountScoped: true,
|
|
21729
21737
|
message: `${PROVIDER_NAME$3} reports the account quota is exhausted: ${detail || "no remaining quota"}. Top up or wait for the window to reset.`
|
|
21730
21738
|
};
|
|
21731
21739
|
return {
|
|
21732
21740
|
code: "RATE_LIMIT",
|
|
21733
21741
|
retryable: true,
|
|
21742
|
+
accountScoped: false,
|
|
21734
21743
|
message: `${PROVIDER_NAME$3} is rate limited or overloaded (429): ${detail || "too many requests"}. Retrying with backoff.`
|
|
21735
21744
|
};
|
|
21736
21745
|
}
|
|
21737
21746
|
if (status >= 500) return {
|
|
21738
21747
|
code: "SERVER",
|
|
21739
21748
|
retryable: true,
|
|
21749
|
+
accountScoped: false,
|
|
21740
21750
|
message: `${PROVIDER_NAME$3} upstream server error (${status}): ${detail || "the model provider is temporarily unavailable"}. Retrying with backoff.`
|
|
21741
21751
|
};
|
|
21742
21752
|
if (status === 400) return {
|
|
21743
21753
|
code: "PROVIDER_ERROR",
|
|
21744
21754
|
retryable: false,
|
|
21755
|
+
accountScoped: false,
|
|
21745
21756
|
message: `${PROVIDER_NAME$3} rejected the request (400): ${detail || "the request was not accepted"}`
|
|
21746
21757
|
};
|
|
21747
21758
|
return {
|
|
21748
21759
|
code: "PROVIDER_ERROR",
|
|
21749
21760
|
retryable: false,
|
|
21761
|
+
accountScoped: false,
|
|
21750
21762
|
message: `${PROVIDER_NAME$3} API error (${status}): ${detail || "No response"}`
|
|
21751
21763
|
};
|
|
21752
21764
|
}
|
|
21765
|
+
/** Numeric reset the service may ship as a field rather than in prose. */
|
|
21766
|
+
const RESET_FIELD_PATTERN = /"?reset[A-Za-z_]*"?\s*[:=]\s*"?(\d{9,16})"?/i;
|
|
21767
|
+
/** ISO instant quoted in prose, trusted only when it names its own zone. */
|
|
21768
|
+
const RESET_INSTANT_PATTERN = /\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}(?::\d{2})?(?:\.\d+)?(?:Z|[+-]\d{2}:?\d{2})/;
|
|
21769
|
+
/** The window a billing-cycle refusal names; nothing shorter describes it. */
|
|
21770
|
+
const BILLING_CYCLE_PATTERN = /usage limit for this billing cycle/i;
|
|
21771
|
+
/**
|
|
21772
|
+
* How long one account stays out of rotation after it reports a spent limit.
|
|
21773
|
+
*
|
|
21774
|
+
* The reset instant is read from the body whenever the service states one — as a
|
|
21775
|
+
* numeric field, or as an instant carrying its own zone — and the length of the
|
|
21776
|
+
* window the service names is used otherwise. The zone is required for that
|
|
21777
|
+
* instant because an unqualified one is ambiguous, and a cooldown computed from
|
|
21778
|
+
* the wrong zone is either hours too long or already over; where the body is
|
|
21779
|
+
* ambiguous the window length is the honest answer.
|
|
21780
|
+
*
|
|
21781
|
+
* Both ends of the range are load-bearing. A reset instant barely in the future
|
|
21782
|
+
* would otherwise read as no cooldown at all and put the account straight back
|
|
21783
|
+
* into rotation to be refused again; and a reset instant already in the past
|
|
21784
|
+
* (a stale cache, a clock skew) falls back to the window length above rather
|
|
21785
|
+
* than to no cooldown at all.
|
|
21786
|
+
*/
|
|
21787
|
+
function accountLimitCooldownMs(bodyText, now = Date.now()) {
|
|
21788
|
+
const field = bodyText.match(RESET_FIELD_PATTERN);
|
|
21789
|
+
const instant = bodyText.match(RESET_INSTANT_PATTERN);
|
|
21790
|
+
const resetsAt = field !== null ? parseTimestamp(Number(field[1])) : instant !== null ? Date.parse(instant[0].replace(" ", "T")) : null;
|
|
21791
|
+
if (resetsAt !== null && Number.isFinite(resetsAt) && resetsAt > now) return Math.min(Math.max(resetsAt - now, POOL_COOLDOWN_MS$2), MAX_ACCOUNT_LIMIT_COOLDOWN_MS);
|
|
21792
|
+
return BILLING_CYCLE_PATTERN.test(bodyText) ? BILLING_CYCLE_COOLDOWN_MS : ACCOUNT_LIMIT_COOLDOWN_MS;
|
|
21793
|
+
}
|
|
21753
21794
|
/** Cooldown one rate-limited account takes when the provider states no delay. */
|
|
21754
21795
|
const POOL_COOLDOWN_MS$2 = 15 * 6e4;
|
|
21796
|
+
/**
|
|
21797
|
+
* Fallback cooldown for an account whose usage window is spent.
|
|
21798
|
+
*
|
|
21799
|
+
* The service states the reset time only sometimes, so the fallback is the
|
|
21800
|
+
* window's own length. The 15 minutes a 429 gets would put the account back
|
|
21801
|
+
* into rotation four times inside the very window that just refused it.
|
|
21802
|
+
*/
|
|
21803
|
+
const ACCOUNT_LIMIT_COOLDOWN_MS = 300 * 6e4;
|
|
21804
|
+
/**
|
|
21805
|
+
* A billing cycle is not a window: a 5-hour cooldown would spend a request every
|
|
21806
|
+
* five hours to learn an answer that changes at most once a month.
|
|
21807
|
+
*/
|
|
21808
|
+
const BILLING_CYCLE_COOLDOWN_MS = 720 * 60 * 6e4;
|
|
21809
|
+
/** Ceiling, so a malformed reset time cannot park an account for years. */
|
|
21810
|
+
const MAX_ACCOUNT_LIMIT_COOLDOWN_MS = BILLING_CYCLE_COOLDOWN_MS;
|
|
21755
21811
|
var KimiCodeAdapter = class extends LlmAdapter {
|
|
21756
21812
|
store;
|
|
21757
21813
|
modelSettings;
|
|
@@ -21936,11 +21992,15 @@ var KimiCodeAdapter = class extends LlmAdapter {
|
|
|
21936
21992
|
if (pool !== null && accountId !== void 0) {
|
|
21937
21993
|
const planScoped = response.status === 429 && matchesAny$1(detail, ENTITLEMENT_PATTERNS);
|
|
21938
21994
|
if (response.status === 429 && !planScoped) {
|
|
21939
|
-
|
|
21995
|
+
const cooldownMs = failure.accountScoped ? accountLimitCooldownMs(detail) : after ?? POOL_COOLDOWN_MS$2;
|
|
21996
|
+
await pool.markCooldown(accountId, cooldownMs, `${PROVIDER_NAME$3} 429`).catch(() => void 0);
|
|
21940
21997
|
if (await pool.hasAnotherAvailableAccount(tried)) continue;
|
|
21941
21998
|
} else if (failure.code === "INVALID_CREDENTIAL") {
|
|
21942
21999
|
await pool.markAuthFailed(accountId, failure.message).catch(() => void 0);
|
|
21943
22000
|
if (await pool.hasAnotherAvailableAccount(tried)) continue;
|
|
22001
|
+
} else if (failure.accountScoped) {
|
|
22002
|
+
await pool.markCooldown(accountId, accountLimitCooldownMs(detail), `${PROVIDER_NAME$3} 403`).catch(() => void 0);
|
|
22003
|
+
if (await pool.hasAnotherAvailableAccount(tried)) continue;
|
|
21944
22004
|
}
|
|
21945
22005
|
}
|
|
21946
22006
|
throw new LlmError(failure.message, failure.code, {
|
|
@@ -43268,6 +43328,16 @@ function installRelayProbe(ctx, options) {
|
|
|
43268
43328
|
* The guard rewrites nothing and appends nothing to the aborted attempt. Its
|
|
43269
43329
|
* only model-visible input is the single resume message it queues afterwards,
|
|
43270
43330
|
* on a fresh turn.
|
|
43331
|
+
*
|
|
43332
|
+
* Agent identity is read from the request, never remembered globally. The loop
|
|
43333
|
+
* stamps every request it builds with its `sessionId`, and `ctx.agents.get`
|
|
43334
|
+
* turns that back into the one Agent that owns the stream. Remembering "the
|
|
43335
|
+
* newest agent this process saw" instead was wrong twice over: a host runs one
|
|
43336
|
+
* Agent per session plus one per subagent, so the newest is usually a different
|
|
43337
|
+
* conversation, and `agent/created` fires once per Agent, so an Agent that
|
|
43338
|
+
* already existed when the guard installed was never reported at all. Both
|
|
43339
|
+
* cases cut the runaway and then resumed nothing, which reads to the user as a
|
|
43340
|
+
* conversation that simply stops mid-answer.
|
|
43271
43341
|
*/
|
|
43272
43342
|
const DEFAULT_GUARD_OPTIONS = {
|
|
43273
43343
|
windowChars: 4096,
|
|
@@ -43372,6 +43442,43 @@ function appendWindow(current, addition, limit) {
|
|
|
43372
43442
|
const combined = current + addition;
|
|
43373
43443
|
return combined.length > limit ? combined.slice(-limit) : combined;
|
|
43374
43444
|
}
|
|
43445
|
+
/** An Agent's session identity as the plain string a request carries. */
|
|
43446
|
+
function agentId(raw) {
|
|
43447
|
+
const id = raw?.id;
|
|
43448
|
+
return typeof id === "string" ? id : void 0;
|
|
43449
|
+
}
|
|
43450
|
+
/**
|
|
43451
|
+
* The host's Agent registry, read defensively.
|
|
43452
|
+
*
|
|
43453
|
+
* A Cordis service that was never injected reads as undefined and a host
|
|
43454
|
+
* without the service at all must not fail this plugin's load, so both the
|
|
43455
|
+
* property read and the later lookup are guarded.
|
|
43456
|
+
*/
|
|
43457
|
+
function agentRegistry(ctx) {
|
|
43458
|
+
try {
|
|
43459
|
+
const registry = ctx.agents;
|
|
43460
|
+
return typeof registry?.get === "function" ? registry : void 0;
|
|
43461
|
+
} catch {
|
|
43462
|
+
return;
|
|
43463
|
+
}
|
|
43464
|
+
}
|
|
43465
|
+
/**
|
|
43466
|
+
* Whether one request is a conversation call the guard may stop and resume.
|
|
43467
|
+
*
|
|
43468
|
+
* The loop marks its own requests, but that marker is reached through a named
|
|
43469
|
+
* export this plugin cannot assume on every generation it supports, so the
|
|
43470
|
+
* guard reads the request's own `purpose` field instead: compaction and
|
|
43471
|
+
* session-title calls are one-shot, own no turn to resume, and a truncated one
|
|
43472
|
+
* is a hard error rather than a runaway. A generation that predates the field
|
|
43473
|
+
* leaves it undefined, which is the pre-existing behaviour.
|
|
43474
|
+
*/
|
|
43475
|
+
function conversationCall(request) {
|
|
43476
|
+
return request.purpose === void 0;
|
|
43477
|
+
}
|
|
43478
|
+
/** Error text for one warn line, without importing a generation-bound helper. */
|
|
43479
|
+
function describeError(error) {
|
|
43480
|
+
return error instanceof Error ? `${error.name}: ${error.message}` : String(error);
|
|
43481
|
+
}
|
|
43375
43482
|
/**
|
|
43376
43483
|
* Install the reasoning-collapse guard on a Context.
|
|
43377
43484
|
*
|
|
@@ -43380,14 +43487,15 @@ function appendWindow(current, addition, limit) {
|
|
|
43380
43487
|
* load.
|
|
43381
43488
|
*
|
|
43382
43489
|
* Resuming needs the live Agent for `cancel` and `steer`, and no supported
|
|
43383
|
-
* generation hands the agent to `llm/stream`.
|
|
43384
|
-
*
|
|
43385
|
-
*
|
|
43386
|
-
* turn
|
|
43490
|
+
* generation hands the agent to `llm/stream`. The request does, though: every
|
|
43491
|
+
* request the loop builds carries its `sessionId`, so the guard resolves the
|
|
43492
|
+
* owning Agent per request through `ctx.agents.get`, falling back to the map
|
|
43493
|
+
* `agent/created` fills. The per-turn break budget stays keyed on the
|
|
43494
|
+
* request's own abort signal, which exists for a whole turn.
|
|
43387
43495
|
*
|
|
43388
43496
|
* @param ctx - plugin context.
|
|
43389
43497
|
* @param options - see {@link GuardOptions}; validated fail-loud.
|
|
43390
|
-
* @returns a disposer removing
|
|
43498
|
+
* @returns a disposer removing every listener, or undefined when the host
|
|
43391
43499
|
* exposes no event bus.
|
|
43392
43500
|
*/
|
|
43393
43501
|
function installReasoningCollapseGuard(ctx, options = {}) {
|
|
@@ -43395,11 +43503,38 @@ function installReasoningCollapseGuard(ctx, options = {}) {
|
|
|
43395
43503
|
if (typeof ctx.on !== "function") return void 0;
|
|
43396
43504
|
const watched = new Set(resolved.includeModels);
|
|
43397
43505
|
const breakers = /* @__PURE__ */ new WeakMap();
|
|
43398
|
-
|
|
43399
|
-
const
|
|
43400
|
-
|
|
43506
|
+
const known = /* @__PURE__ */ new Map();
|
|
43507
|
+
const releaseCreated = ctx.on("agent/created", (payload) => {
|
|
43508
|
+
remember(payload.agent);
|
|
43509
|
+
});
|
|
43510
|
+
const releaseDisposed = ctx.on("agent/disposed", (payload) => {
|
|
43511
|
+
const id = agentId(payload.agent);
|
|
43512
|
+
if (id !== void 0) known.delete(id);
|
|
43401
43513
|
});
|
|
43402
43514
|
const releaseStream = ctx.on("llm/stream", guardStream, { global: true });
|
|
43515
|
+
function remember(raw) {
|
|
43516
|
+
const agent = raw;
|
|
43517
|
+
if (agent === void 0 || agent === null) return;
|
|
43518
|
+
const id = agentId(agent);
|
|
43519
|
+
if (id !== void 0) known.set(id, agent);
|
|
43520
|
+
}
|
|
43521
|
+
/**
|
|
43522
|
+
* The Agent that owns one guarded request, or undefined when this host
|
|
43523
|
+
* cannot name it.
|
|
43524
|
+
*
|
|
43525
|
+
* The registry is authoritative and is read first: it answers for an Agent
|
|
43526
|
+
* created before this guard was installed, which is exactly the Agent an
|
|
43527
|
+
* `agent/created` listener can never report. Falling back to the newest
|
|
43528
|
+
* Agent this process saw would cancel and resume a different conversation.
|
|
43529
|
+
*/
|
|
43530
|
+
function resolveAgent(sessionId) {
|
|
43531
|
+
if (sessionId === void 0) return void 0;
|
|
43532
|
+
try {
|
|
43533
|
+
const registered = agentRegistry(ctx)?.get(sessionId);
|
|
43534
|
+
if (registered !== void 0 && registered !== null) return registered;
|
|
43535
|
+
} catch {}
|
|
43536
|
+
return known.get(sessionId);
|
|
43537
|
+
}
|
|
43403
43538
|
function breakerFor(key) {
|
|
43404
43539
|
let state = breakers.get(key);
|
|
43405
43540
|
if (state === void 0) {
|
|
@@ -43419,27 +43554,36 @@ function installReasoningCollapseGuard(ctx, options = {}) {
|
|
|
43419
43554
|
* steering submitted while a turn is still unwinding; by the time the
|
|
43420
43555
|
* microtask runs the abort has settled, so the wake lands on a fresh turn that
|
|
43421
43556
|
* inherits the kept inbox.
|
|
43557
|
+
*
|
|
43558
|
+
* With no Agent to address, the runaway is still cut — that half is the whole
|
|
43559
|
+
* point — and the log says so, because a break that cannot resume is
|
|
43560
|
+
* indistinguishable from a broken turn to the user.
|
|
43422
43561
|
*/
|
|
43423
|
-
function breakOff(state, score) {
|
|
43562
|
+
function breakOff(state, score, agent, sessionId) {
|
|
43424
43563
|
state.breaksThisTurn++;
|
|
43425
43564
|
state.lastBreakAt = resolved.now();
|
|
43426
|
-
ctx.logger?.warn(`reasoning-collapse-guard: stopped degenerate reasoning (score ${score.toFixed(3)}, break ${state.breaksThisTurn}/${resolved.maxBreaksPerTurn})`);
|
|
43427
|
-
|
|
43428
|
-
agent
|
|
43565
|
+
ctx.logger?.warn(`reasoning-collapse-guard: stopped degenerate reasoning (score ${score.toFixed(3)}, break ${state.breaksThisTurn}/${resolved.maxBreaksPerTurn}, session ${sessionId ?? "unidentified"}${agent === void 0 ? ", no agent to resume" : ""})`);
|
|
43566
|
+
if (agent === void 0) return;
|
|
43567
|
+
agent.cancel?.({
|
|
43429
43568
|
kind: "hook",
|
|
43430
43569
|
reason: "reasoning-collapse-guard"
|
|
43431
43570
|
}, { keepInbox: true });
|
|
43432
|
-
if (agent === void 0 || state.resuming) return;
|
|
43571
|
+
if (agent.steer === void 0 || state.resuming) return;
|
|
43433
43572
|
state.resuming = true;
|
|
43434
43573
|
const text = state.breaksThisTurn >= 2 ? RESUME_HINT_STRICT : RESUME_HINT;
|
|
43435
43574
|
queueMicrotask(() => {
|
|
43436
43575
|
state.resuming = false;
|
|
43437
|
-
|
|
43576
|
+
try {
|
|
43577
|
+
agent.steer?.(createResumeMessage(text));
|
|
43578
|
+
} catch (error) {
|
|
43579
|
+
ctx.logger?.warn(`reasoning-collapse-guard: could not queue the resume: ${describeError(error)}`);
|
|
43580
|
+
}
|
|
43438
43581
|
});
|
|
43439
43582
|
}
|
|
43440
43583
|
function guardStream(request, next) {
|
|
43441
43584
|
const state = breakerFor(request.signal ?? STATIC_KEY);
|
|
43442
|
-
if (state.exhausted || !watchedModel(request.model)) return next();
|
|
43585
|
+
if (state.exhausted || !watchedModel(request.model) || !conversationCall(request)) return next();
|
|
43586
|
+
const agent = resolveAgent(request.sessionId);
|
|
43443
43587
|
async function* guarded() {
|
|
43444
43588
|
let window = "";
|
|
43445
43589
|
for await (const chunk of next()) {
|
|
@@ -43450,7 +43594,7 @@ function installReasoningCollapseGuard(ctx, options = {}) {
|
|
|
43450
43594
|
const score = collapseScore(window, resolved.ngramSize);
|
|
43451
43595
|
if (score >= resolved.threshold) if (state.breaksThisTurn >= resolved.maxBreaksPerTurn) state.exhausted = true;
|
|
43452
43596
|
else {
|
|
43453
|
-
breakOff(state, score);
|
|
43597
|
+
breakOff(state, score, agent, request.sessionId);
|
|
43454
43598
|
return;
|
|
43455
43599
|
}
|
|
43456
43600
|
}
|
|
@@ -43462,7 +43606,9 @@ function installReasoningCollapseGuard(ctx, options = {}) {
|
|
|
43462
43606
|
}
|
|
43463
43607
|
return () => {
|
|
43464
43608
|
releaseListener(releaseStream);
|
|
43465
|
-
releaseListener(
|
|
43609
|
+
releaseListener(releaseCreated);
|
|
43610
|
+
releaseListener(releaseDisposed);
|
|
43611
|
+
known.clear();
|
|
43466
43612
|
};
|
|
43467
43613
|
}
|
|
43468
43614
|
/** Shared key for the rare request that carries no abort signal. */
|
|
@@ -28,8 +28,10 @@ import { KimiCodeAccountPool } from './account-pool.ts';
|
|
|
28
28
|
* - `INVALID_CREDENTIAL` — a rejected access token fails identically on every
|
|
29
29
|
* attempt;
|
|
30
30
|
* - `PROVIDER_ERROR` — a 400, a 401 that is really a plan-entitlement refusal,
|
|
31
|
-
* or a 403 quota limit. Retrying a quota that resets in hours
|
|
32
|
-
* requests and delays the message the user needs to
|
|
31
|
+
* or a 403 quota limit. Retrying a quota that resets in hours against the
|
|
32
|
+
* SAME account only burns requests and delays the message the user needs to
|
|
33
|
+
* see, so none of it is retried; a 403 limit still rotates to another account
|
|
34
|
+
* when the pool holds one, which is a routing decision rather than a retry;
|
|
33
35
|
* - `ABORTED` — the caller already cancelled.
|
|
34
36
|
*
|
|
35
37
|
* The DSH normal defaults would apply anyway; stating the values here pins them
|
|
@@ -63,6 +65,22 @@ export interface KimiFailureClassification {
|
|
|
63
65
|
message: string;
|
|
64
66
|
/** True only when the failure is worth retrying. */
|
|
65
67
|
retryable: boolean;
|
|
68
|
+
/**
|
|
69
|
+
* True when the refusal belongs to THIS account alone, so another pooled
|
|
70
|
+
* account may still serve the same request.
|
|
71
|
+
*
|
|
72
|
+
* A spent usage window is the case that matters, and this service reports it
|
|
73
|
+
* as a 403 — neither a bad credential nor retryable back-pressure, so nothing
|
|
74
|
+
* in the code or the message says "rotate". Asking the same account again
|
|
75
|
+
* cannot help; taking it out of rotation until its window resets lets the next
|
|
76
|
+
* account answer, and each account in the pool carries its own quota.
|
|
77
|
+
*
|
|
78
|
+
* False for every verdict that belongs to the request or to the plan: an
|
|
79
|
+
* entitlement refusal, a malformed body, ordinary overload. Rotating on one of
|
|
80
|
+
* those would spend every account in the pool to learn the same answer from
|
|
81
|
+
* each of them.
|
|
82
|
+
*/
|
|
83
|
+
accountScoped: boolean;
|
|
66
84
|
}
|
|
67
85
|
/** Short, single-line excerpt of one error body, safe to show a user. */
|
|
68
86
|
export declare function summarizeFailureBody(raw: string): string;
|
|
@@ -73,6 +91,23 @@ export declare function summarizeFailureBody(raw: string): string;
|
|
|
73
91
|
* @param bodyText - raw response body, used to separate overloaded statuses.
|
|
74
92
|
*/
|
|
75
93
|
export declare function classifyKimiFailure(status: number, bodyText: string): KimiFailureClassification;
|
|
94
|
+
/**
|
|
95
|
+
* How long one account stays out of rotation after it reports a spent limit.
|
|
96
|
+
*
|
|
97
|
+
* The reset instant is read from the body whenever the service states one — as a
|
|
98
|
+
* numeric field, or as an instant carrying its own zone — and the length of the
|
|
99
|
+
* window the service names is used otherwise. The zone is required for that
|
|
100
|
+
* instant because an unqualified one is ambiguous, and a cooldown computed from
|
|
101
|
+
* the wrong zone is either hours too long or already over; where the body is
|
|
102
|
+
* ambiguous the window length is the honest answer.
|
|
103
|
+
*
|
|
104
|
+
* Both ends of the range are load-bearing. A reset instant barely in the future
|
|
105
|
+
* would otherwise read as no cooldown at all and put the account straight back
|
|
106
|
+
* into rotation to be refused again; and a reset instant already in the past
|
|
107
|
+
* (a stale cache, a clock skew) falls back to the window length above rather
|
|
108
|
+
* than to no cooldown at all.
|
|
109
|
+
*/
|
|
110
|
+
export declare function accountLimitCooldownMs(bodyText: string, now?: number): number;
|
|
76
111
|
export interface KimiCodeAdapterOptions {
|
|
77
112
|
fetchFn?: typeof fetch;
|
|
78
113
|
attachments?: AttachmentImageReader;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../../../../src/host/kimi-code/adapter.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,UAAU,EAEV,iBAAiB,EAEjB,KAAK,eAAe,EACpB,KAAK,YAAY,EACjB,KAAK,eAAe,EACpB,KAAK,oBAAoB,EACzB,KAAK,mBAAmB,EACxB,KAAK,mBAAmB,EACxB,KAAK,WAAW,EACjB,MAAM,sBAAsB,CAAA;AAE7B,OAAO,EAGL,WAAW,EAOX,mBAAmB,EACpB,MAAM,YAAY,CAAA;AACnB,OAAO,EACL,mBAAmB,EACnB,sBAAsB,EAEtB,KAAK,oBAAoB,EAEzB,KAAK,uBAAuB,EAC7B,MAAM,kBAAkB,CAAA;AAGzB,OAAO,EACL,iBAAiB,EACjB,kBAAkB,
|
|
1
|
+
{"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../../../../src/host/kimi-code/adapter.ts"],"names":[],"mappings":"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"}
|
|
@@ -29,11 +29,32 @@
|
|
|
29
29
|
* The guard rewrites nothing and appends nothing to the aborted attempt. Its
|
|
30
30
|
* only model-visible input is the single resume message it queues afterwards,
|
|
31
31
|
* on a fresh turn.
|
|
32
|
+
*
|
|
33
|
+
* Agent identity is read from the request, never remembered globally. The loop
|
|
34
|
+
* stamps every request it builds with its `sessionId`, and `ctx.agents.get`
|
|
35
|
+
* turns that back into the one Agent that owns the stream. Remembering "the
|
|
36
|
+
* newest agent this process saw" instead was wrong twice over: a host runs one
|
|
37
|
+
* Agent per session plus one per subagent, so the newest is usually a different
|
|
38
|
+
* conversation, and `agent/created` fires once per Agent, so an Agent that
|
|
39
|
+
* already existed when the guard installed was never reported at all. Both
|
|
40
|
+
* cases cut the runaway and then resumed nothing, which reads to the user as a
|
|
41
|
+
* conversation that simply stops mid-answer.
|
|
42
|
+
*/
|
|
43
|
+
/**
|
|
44
|
+
* The subset of `llm/stream` options this guard reads.
|
|
45
|
+
*
|
|
46
|
+
* `sessionId` is what binds a stream to the Agent that owns it, and
|
|
47
|
+
* `purpose` tells a conversation call from an auxiliary one (compaction,
|
|
48
|
+
* session title) that owns no turn to resume. Both are plain request fields:
|
|
49
|
+
* the guard adds no generation-bound named import to learn them.
|
|
32
50
|
*/
|
|
33
|
-
/** The subset of `llm/stream` options this guard reads. */
|
|
34
51
|
export interface GuardStreamOptions {
|
|
35
52
|
readonly model?: string | undefined;
|
|
36
53
|
readonly signal?: AbortSignal | undefined;
|
|
54
|
+
/** Stamped by the agent loop on every request it builds. */
|
|
55
|
+
readonly sessionId?: string | undefined;
|
|
56
|
+
/** Absent on an ordinary conversation request; set on an auxiliary call. */
|
|
57
|
+
readonly purpose?: string | undefined;
|
|
37
58
|
}
|
|
38
59
|
/** A chunk as it crosses the stream boundary; only the reasoning delta is read. */
|
|
39
60
|
export interface GuardChunk {
|
|
@@ -42,6 +63,8 @@ export interface GuardChunk {
|
|
|
42
63
|
}
|
|
43
64
|
/** Live Agent methods the guard calls. Optional so an older shape stays loadable. */
|
|
44
65
|
export interface GuardAgentLike {
|
|
66
|
+
/** The Agent's session identity — the same value the loop stamps on requests. */
|
|
67
|
+
readonly id?: unknown;
|
|
45
68
|
cancel?(cause: {
|
|
46
69
|
kind: 'hook';
|
|
47
70
|
reason: string;
|
|
@@ -52,12 +75,12 @@ export interface GuardAgentLike {
|
|
|
52
75
|
}
|
|
53
76
|
/** The `llm/stream` waterfall listener the guard registers. */
|
|
54
77
|
export type GuardStreamListener = (options: GuardStreamOptions, next: () => AsyncIterable<GuardChunk>) => AsyncIterable<GuardChunk>;
|
|
55
|
-
/** The `agent/created` listener the guard registers. */
|
|
56
|
-
export type
|
|
78
|
+
/** The `agent/created` / `agent/disposed` listener the guard registers. */
|
|
79
|
+
export type GuardAgentEventListener = (payload: {
|
|
57
80
|
agent: unknown;
|
|
58
81
|
}) => void;
|
|
59
82
|
/** Either listener {@link ReasoningCollapseContext.on} accepts. */
|
|
60
|
-
export type GuardListener = GuardStreamListener |
|
|
83
|
+
export type GuardListener = GuardStreamListener | GuardAgentEventListener;
|
|
61
84
|
/** Listener options accepted by the harness event bus. */
|
|
62
85
|
export interface GuardListenerOptions {
|
|
63
86
|
/** Receive the event regardless of context filter checks. */
|
|
@@ -68,10 +91,18 @@ export interface GuardListenerOptions {
|
|
|
68
91
|
* every supported harness generation without importing generation-bound types.
|
|
69
92
|
*/
|
|
70
93
|
export interface ReasoningCollapseContext {
|
|
71
|
-
on?: (event: 'llm/stream' | 'agent/created', listener: GuardListener, options?: GuardListenerOptions) => unknown;
|
|
94
|
+
on?: (event: 'llm/stream' | 'agent/created' | 'agent/disposed', listener: GuardListener, options?: GuardListenerOptions) => unknown;
|
|
72
95
|
logger?: {
|
|
73
96
|
warn(message: string): void;
|
|
74
97
|
} | undefined;
|
|
98
|
+
/**
|
|
99
|
+
* The harness Agent registry, read once so a guard installed after an Agent
|
|
100
|
+
* was created can still resolve it. A host without the service degrades to
|
|
101
|
+
* the `agent/created` map rather than failing this plugin's load.
|
|
102
|
+
*/
|
|
103
|
+
readonly agents?: {
|
|
104
|
+
get(id: unknown): unknown;
|
|
105
|
+
} | undefined;
|
|
75
106
|
}
|
|
76
107
|
/** Configuration for {@link installReasoningCollapseGuard}. */
|
|
77
108
|
export interface GuardOptions {
|
|
@@ -131,14 +162,15 @@ export declare const RESUME_HINT_STRICT: string;
|
|
|
131
162
|
* load.
|
|
132
163
|
*
|
|
133
164
|
* Resuming needs the live Agent for `cancel` and `steer`, and no supported
|
|
134
|
-
* generation hands the agent to `llm/stream`.
|
|
135
|
-
*
|
|
136
|
-
*
|
|
137
|
-
* turn
|
|
165
|
+
* generation hands the agent to `llm/stream`. The request does, though: every
|
|
166
|
+
* request the loop builds carries its `sessionId`, so the guard resolves the
|
|
167
|
+
* owning Agent per request through `ctx.agents.get`, falling back to the map
|
|
168
|
+
* `agent/created` fills. The per-turn break budget stays keyed on the
|
|
169
|
+
* request's own abort signal, which exists for a whole turn.
|
|
138
170
|
*
|
|
139
171
|
* @param ctx - plugin context.
|
|
140
172
|
* @param options - see {@link GuardOptions}; validated fail-loud.
|
|
141
|
-
* @returns a disposer removing
|
|
173
|
+
* @returns a disposer removing every listener, or undefined when the host
|
|
142
174
|
* exposes no event bus.
|
|
143
175
|
*/
|
|
144
176
|
export declare function installReasoningCollapseGuard(ctx: ReasoningCollapseContext, options?: GuardOptions): (() => void) | undefined;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/host/reasoning-collapse-guard/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/host/reasoning-collapse-guard/index.ts"],"names":[],"mappings":"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"}
|