@vincemakes/kiso-runtime 0.40.2 → 0.40.3
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/dist/agent.js +6 -2
- package/dist/provider/metadata.d.ts +42 -0
- package/dist/provider/metadata.js +64 -0
- package/dist/run.js +5 -0
- package/dist/session.d.ts +13 -0
- package/dist/session.js +54 -8
- package/dist/window-learner.d.ts +26 -0
- package/dist/window-learner.js +40 -0
- package/package.json +6 -6
package/dist/agent.js
CHANGED
|
@@ -87,6 +87,7 @@ export class AgentRuntime {
|
|
|
87
87
|
});
|
|
88
88
|
let restored = null;
|
|
89
89
|
let profilePending = false;
|
|
90
|
+
let newSession = null;
|
|
90
91
|
let driftAcknowledgement = null;
|
|
91
92
|
if (meta.kind === "ok") {
|
|
92
93
|
const drift = assessProfileDrift(meta.profile, {
|
|
@@ -134,8 +135,10 @@ export class AgentRuntime {
|
|
|
134
135
|
}
|
|
135
136
|
}
|
|
136
137
|
else if (log.all.length === 0) {
|
|
137
|
-
// a NEW session:
|
|
138
|
-
|
|
138
|
+
// a NEW session. DC-60: nothing of it reaches the disk before its
|
|
139
|
+
// first durable event — revision 1 (with the workspace it opened in)
|
|
140
|
+
// lands WITH that event, still before it (session.ts persist).
|
|
141
|
+
newSession = { workspace: candidate.workspace };
|
|
139
142
|
}
|
|
140
143
|
else {
|
|
141
144
|
// legacy (pre-XP log, no sidecar): generation absence is not
|
|
@@ -154,6 +157,7 @@ export class AgentRuntime {
|
|
|
154
157
|
: {}),
|
|
155
158
|
...(restored !== null ? { reasoning: restored.reasoning } : {}),
|
|
156
159
|
...(profilePending ? { profilePending: true } : {}),
|
|
160
|
+
...(newSession !== null ? { newSession } : {}),
|
|
157
161
|
...(this.#definition.profileName !== undefined ? { profileName: this.#definition.profileName } : {}),
|
|
158
162
|
...(driftAcknowledgement !== null ? { driftAcknowledgement } : {}),
|
|
159
163
|
...(this.#definition.systemPrompt !== undefined ? { systemPrompt: this.#definition.systemPrompt } : {}),
|
|
@@ -126,6 +126,48 @@ export interface ModelMetadataEntry {
|
|
|
126
126
|
* one matches any endpoint. Unknown model → null, never a default.
|
|
127
127
|
*/
|
|
128
128
|
export declare function lookupModelMetadata(model: string, endpoint?: string): ModelMetadataEntry | null;
|
|
129
|
+
/** CW-1: the aliases, for the gate that holds each one to a dated source
|
|
130
|
+
* and a registered target. */
|
|
131
|
+
export declare function modelAliases(): Readonly<Record<string, {
|
|
132
|
+
readonly is: string;
|
|
133
|
+
readonly asOf: string;
|
|
134
|
+
readonly source: string;
|
|
135
|
+
}>>;
|
|
136
|
+
/** CW-1: a model id reduced to the weights it names — `deepseek/DeepSeek-V4.1-Flash`
|
|
137
|
+
* and `deepseek-v4.1-flash` are both `deepseek-flash`. A variant suffix
|
|
138
|
+
* (`:free`) is NOT folded: a variant may serve a smaller window. */
|
|
139
|
+
export declare function modelIdentity(model: string): string;
|
|
140
|
+
/** Where a resolved window came from — the source a display names. */
|
|
141
|
+
export type ContextWindowSource = "route" | "upstream" | "model";
|
|
142
|
+
export interface ResolvedContextWindow {
|
|
143
|
+
readonly tokens: number;
|
|
144
|
+
readonly source: ContextWindowSource;
|
|
145
|
+
/** the registry row's model id the figure was read from */
|
|
146
|
+
readonly from: string;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* CW-1 (owner, 2026-09-23) — the context window for a route the registry
|
|
150
|
+
* has no row for.
|
|
151
|
+
*
|
|
152
|
+
* Rows are keyed by (model, endpoint) because a route can answer
|
|
153
|
+
* differently from the vendor — gpt-5.5 is 1,050,000 at the first-party
|
|
154
|
+
* API and 272,000 at the subscription backend. The same key made every
|
|
155
|
+
* forwarder, relay and gateway a miss: `deepseek-v4.1-flash` behind a local
|
|
156
|
+
* forwarder to a model gateway read `ctx ?`, and the tiers compacted a 1M model
|
|
157
|
+
* against the CLI's 200K fallback. A window belongs to the weights; a
|
|
158
|
+
* route can only cap it lower. In order:
|
|
159
|
+
*
|
|
160
|
+
* route — this model's row at this endpoint (lookupModelMetadata);
|
|
161
|
+
* upstream — its row at the endpoint a forwarder names as its upstream;
|
|
162
|
+
* model — every row of the same model identity, at any endpoint;
|
|
163
|
+
* where rows disagree, the SMALLEST — the one figure no row
|
|
164
|
+
* contradicts.
|
|
165
|
+
*
|
|
166
|
+
* A null window falls through; after the last step, null. Only the window
|
|
167
|
+
* resolves this way: price, reasoning wire fields and max output are
|
|
168
|
+
* facts about a route and stay on lookupModelMetadata.
|
|
169
|
+
*/
|
|
170
|
+
export declare function lookupContextWindow(model: string, endpoint?: string, upstream?: string): ResolvedContextWindow | null;
|
|
129
171
|
/**
|
|
130
172
|
* XP-1 §4.2 — native-only resolution. The rules, in order:
|
|
131
173
|
* default/default → NO wire fields (the byte-identity anchor — a
|
|
@@ -455,6 +455,70 @@ export function lookupModelMetadata(model, endpoint) {
|
|
|
455
455
|
}
|
|
456
456
|
return null;
|
|
457
457
|
}
|
|
458
|
+
/**
|
|
459
|
+
* CW-1 — names one set of weights answers to across routes, each a dated
|
|
460
|
+
* vendor statement. The key is the FOLDED name a route serves (vendor
|
|
461
|
+
* prefix off, lower case); the value names the registry row.
|
|
462
|
+
*/
|
|
463
|
+
const MODEL_ALIASES = {
|
|
464
|
+
// The pricing table: `deepseek-flash` IS DeepSeek-V4.1-Flash. Gateways
|
|
465
|
+
// serve it by the version name (`deepseek-v4.1-flash`).
|
|
466
|
+
"deepseek-v4.1-flash": { is: "deepseek-flash", asOf: "2026-09-17", source: "https://api-docs.deepseek.com/quick_start/pricing/" },
|
|
467
|
+
};
|
|
468
|
+
/** CW-1: the aliases, for the gate that holds each one to a dated source
|
|
469
|
+
* and a registered target. */
|
|
470
|
+
export function modelAliases() {
|
|
471
|
+
return MODEL_ALIASES;
|
|
472
|
+
}
|
|
473
|
+
/** CW-1: a model id reduced to the weights it names — `deepseek/DeepSeek-V4.1-Flash`
|
|
474
|
+
* and `deepseek-v4.1-flash` are both `deepseek-flash`. A variant suffix
|
|
475
|
+
* (`:free`) is NOT folded: a variant may serve a smaller window. */
|
|
476
|
+
export function modelIdentity(model) {
|
|
477
|
+
const folded = model.slice(model.lastIndexOf("/") + 1).toLowerCase();
|
|
478
|
+
return MODEL_ALIASES[folded]?.is ?? folded;
|
|
479
|
+
}
|
|
480
|
+
/**
|
|
481
|
+
* CW-1 (owner, 2026-09-23) — the context window for a route the registry
|
|
482
|
+
* has no row for.
|
|
483
|
+
*
|
|
484
|
+
* Rows are keyed by (model, endpoint) because a route can answer
|
|
485
|
+
* differently from the vendor — gpt-5.5 is 1,050,000 at the first-party
|
|
486
|
+
* API and 272,000 at the subscription backend. The same key made every
|
|
487
|
+
* forwarder, relay and gateway a miss: `deepseek-v4.1-flash` behind a local
|
|
488
|
+
* forwarder to a model gateway read `ctx ?`, and the tiers compacted a 1M model
|
|
489
|
+
* against the CLI's 200K fallback. A window belongs to the weights; a
|
|
490
|
+
* route can only cap it lower. In order:
|
|
491
|
+
*
|
|
492
|
+
* route — this model's row at this endpoint (lookupModelMetadata);
|
|
493
|
+
* upstream — its row at the endpoint a forwarder names as its upstream;
|
|
494
|
+
* model — every row of the same model identity, at any endpoint;
|
|
495
|
+
* where rows disagree, the SMALLEST — the one figure no row
|
|
496
|
+
* contradicts.
|
|
497
|
+
*
|
|
498
|
+
* A null window falls through; after the last step, null. Only the window
|
|
499
|
+
* resolves this way: price, reasoning wire fields and max output are
|
|
500
|
+
* facts about a route and stay on lookupModelMetadata.
|
|
501
|
+
*/
|
|
502
|
+
export function lookupContextWindow(model, endpoint, upstream) {
|
|
503
|
+
const route = lookupModelMetadata(model, endpoint);
|
|
504
|
+
if (route !== null && route.capabilities.contextWindow !== null)
|
|
505
|
+
return { tokens: route.capabilities.contextWindow, source: "route", from: route.model };
|
|
506
|
+
if (upstream !== undefined) {
|
|
507
|
+
const up = lookupModelMetadata(model, upstream);
|
|
508
|
+
if (up !== null && up.capabilities.contextWindow !== null)
|
|
509
|
+
return { tokens: up.capabilities.contextWindow, source: "upstream", from: up.model };
|
|
510
|
+
}
|
|
511
|
+
const identity = modelIdentity(model);
|
|
512
|
+
let found = null;
|
|
513
|
+
for (const entry of ENTRIES) {
|
|
514
|
+
const tokens = entry.capabilities.contextWindow;
|
|
515
|
+
if (tokens === null || modelIdentity(entry.model) !== identity)
|
|
516
|
+
continue;
|
|
517
|
+
if (found === null || tokens < found.tokens)
|
|
518
|
+
found = { tokens, source: "model", from: entry.model };
|
|
519
|
+
}
|
|
520
|
+
return found;
|
|
521
|
+
}
|
|
458
522
|
function originOf(endpoint) {
|
|
459
523
|
try {
|
|
460
524
|
return new URL(endpoint).origin;
|
package/dist/run.js
CHANGED
|
@@ -73,6 +73,11 @@ export class Run {
|
|
|
73
73
|
// here, before any log or disk mutation.
|
|
74
74
|
this.#session.ensureHealthy();
|
|
75
75
|
this.#session.beginRun(this);
|
|
76
|
+
// DC-60: a resume of a session with no durable event has nothing to
|
|
77
|
+
// recover — and writes nothing (no trace header, no summary row) for
|
|
78
|
+
// a session that never began. The CLI runs one at every start.
|
|
79
|
+
if (this.#resume && this.#session.log.all.length === 0)
|
|
80
|
+
return;
|
|
76
81
|
const log = this.#session.log;
|
|
77
82
|
// The static prompt parts — computed ONCE, before the tracer, and
|
|
78
83
|
// reused for both the composed string (below) and the rent ledger
|
package/dist/session.d.ts
CHANGED
|
@@ -501,6 +501,14 @@ export interface ContextPolicy {
|
|
|
501
501
|
* caller's own windowTokens counts as stated. Only a stated window
|
|
502
502
|
* arms the overflow belt. */
|
|
503
503
|
readonly statedWindow?: () => number | null;
|
|
504
|
+
/** CW-1 batch 2: told when an endpoint's refusal states its cap — the
|
|
505
|
+
* binding it was learned for rides along. The tiers have already
|
|
506
|
+
* taken it (only ever down); the caller keeps it for later sessions. */
|
|
507
|
+
readonly onWindowLearned?: (learned: {
|
|
508
|
+
readonly tokens: number;
|
|
509
|
+
readonly model: string;
|
|
510
|
+
readonly baseUrl?: string;
|
|
511
|
+
}) => void;
|
|
504
512
|
};
|
|
505
513
|
}
|
|
506
514
|
export interface SessionConfig {
|
|
@@ -521,6 +529,11 @@ export interface SessionConfig {
|
|
|
521
529
|
readonly reasoning?: import("./provider/metadata.js").ReasoningSetting;
|
|
522
530
|
/** XP-1 internal: a legacy session's deferred revision-1 write. */
|
|
523
531
|
readonly profilePending?: true;
|
|
532
|
+
/** DC-60 internal: a NEW session — nothing is on disk yet; its revision 1
|
|
533
|
+
* (with the workspace it opened in) lands with its first durable event. */
|
|
534
|
+
readonly newSession?: {
|
|
535
|
+
readonly workspace: string | null;
|
|
536
|
+
};
|
|
524
537
|
/** 0.40.0: the config profile name in force — recorded per revision. */
|
|
525
538
|
readonly profileName?: string;
|
|
526
539
|
/** 0.40.0: set when this open acknowledged material drift. */
|
package/dist/session.js
CHANGED
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
import { EventLog, projectMessages, SUMMARY_FRAMING, } from "@vincemakes/kiso-core";
|
|
31
31
|
import { executionLedger } from "./ledger.js";
|
|
32
32
|
import { overflowBelt } from "./overflow-belt.js";
|
|
33
|
+
import { windowLearner } from "./window-learner.js";
|
|
33
34
|
import { assessTasks } from "./task-assessment.js";
|
|
34
35
|
/** TV-1A — the session-level evidence policy: the PURE projection defaults
|
|
35
36
|
* to ∅ (never inventing evidence); the session names the one built-in
|
|
@@ -126,6 +127,8 @@ export class AgentSession {
|
|
|
126
127
|
// XP-1: a legacy session records revision 1 at the next explicit
|
|
127
128
|
// selection or first request — never eagerly at open.
|
|
128
129
|
#profilePending;
|
|
130
|
+
/** DC-60: set until a NEW session's first durable event lands. */
|
|
131
|
+
#newSession;
|
|
129
132
|
/** 0.40.0: the config profile name of the live binding (configuration:
|
|
130
133
|
* each revision records the name in force when it is written). */
|
|
131
134
|
#profileName;
|
|
@@ -199,6 +202,7 @@ export class AgentSession {
|
|
|
199
202
|
// CTX-1: starts as the startup policy, then follows the binding.
|
|
200
203
|
this.#microcompact = config.microcompact;
|
|
201
204
|
this.#profilePending = config.profilePending === true;
|
|
205
|
+
this.#newSession = config.newSession;
|
|
202
206
|
this.#profileName = config.profileName ?? null;
|
|
203
207
|
this.driftAcknowledgement = config.driftAcknowledgement ?? null;
|
|
204
208
|
}
|
|
@@ -241,6 +245,13 @@ export class AgentSession {
|
|
|
241
245
|
async persist(runId, event) {
|
|
242
246
|
this.ensureHealthy();
|
|
243
247
|
try {
|
|
248
|
+
// DC-60: a NEW session's revision 1 lands with its first durable
|
|
249
|
+
// event and BEFORE it — a log line never exists without its profile.
|
|
250
|
+
if (this.#newSession !== undefined) {
|
|
251
|
+
const { workspace } = this.#newSession;
|
|
252
|
+
this.#newSession = undefined;
|
|
253
|
+
this.#writeProfileRevision(workspace);
|
|
254
|
+
}
|
|
244
255
|
await this.#store.append(this.id, runId, event);
|
|
245
256
|
if (event.type === "usage" && event.known)
|
|
246
257
|
this.#lastUsageAt = Date.now();
|
|
@@ -342,8 +353,9 @@ export class AgentSession {
|
|
|
342
353
|
* abort is never answered with the second call. Throws when both fail.
|
|
343
354
|
*/
|
|
344
355
|
async #checkpointCall(args) {
|
|
356
|
+
const learned = this.#learning(this.#adapter);
|
|
345
357
|
const common = {
|
|
346
|
-
adapter:
|
|
358
|
+
adapter: learned,
|
|
347
359
|
model: this.#model,
|
|
348
360
|
maxOutputTokens: args.budget,
|
|
349
361
|
...(args.signal !== undefined ? { signal: args.signal } : {}),
|
|
@@ -357,7 +369,7 @@ export class AgentSession {
|
|
|
357
369
|
// The in-band call carries the run's whole context, so the overflow
|
|
358
370
|
// belt applies to it; the serialised fallback below is small, and
|
|
359
371
|
// its failures keep their own classification.
|
|
360
|
-
const inBandAdapter = overflowBelt(
|
|
372
|
+
const inBandAdapter = overflowBelt(learned, () => this.overflowMeasure());
|
|
361
373
|
const result = await summarizeConversation({ ...common, adapter: inBandAdapter, messages: args.messages, inBand: args.inBand, ...(args.reasoning !== undefined ? { reasoning: args.reasoning } : {}) });
|
|
362
374
|
return { result, path: "in-band" };
|
|
363
375
|
}
|
|
@@ -499,6 +511,26 @@ export class AgentSession {
|
|
|
499
511
|
setMicrocompactThreshold(thresholdTokens) {
|
|
500
512
|
this.#microcompact = { thresholdTokens };
|
|
501
513
|
}
|
|
514
|
+
/** CW-1 batch 2: the adapter as a run or a summary uses it — a refusal
|
|
515
|
+
* that states a cap is learned for the binding in force NOW, the one
|
|
516
|
+
* this adapter belongs to (a /model mid-run must not receive it). */
|
|
517
|
+
#learning(adapter) {
|
|
518
|
+
const at = { model: this.#model, baseUrl: this.#baseUrl };
|
|
519
|
+
return windowLearner(adapter, (tokens) => this.#learnWindow(tokens, at));
|
|
520
|
+
}
|
|
521
|
+
/** CW-1 batch 2: a route stated its cap. The in-run tiers take it at once
|
|
522
|
+
* — the kernel's one overflow compaction runs next and must aim at the
|
|
523
|
+
* real window, not past it — and only ever DOWN; the caller is told, to
|
|
524
|
+
* keep it for the next session and to say so. */
|
|
525
|
+
#learnWindow(tokens, at) {
|
|
526
|
+
const tiers = this.#config.contextPolicy?.tiers;
|
|
527
|
+
if (at.model === this.#model && at.baseUrl === this.#baseUrl) {
|
|
528
|
+
const current = this.#tiersWindow ?? tiers?.windowTokens;
|
|
529
|
+
if (current === undefined || tokens < current)
|
|
530
|
+
this.#tiersWindow = tokens;
|
|
531
|
+
}
|
|
532
|
+
tiers?.onWindowLearned?.({ tokens, model: at.model, ...(at.baseUrl !== undefined ? { baseUrl: at.baseUrl } : {}) });
|
|
533
|
+
}
|
|
502
534
|
/** ADR-0055 Amendment 2 (decision 3): the overflow belt's measure at send
|
|
503
535
|
* time — null unless a window is STATED and a bill anchors the context. */
|
|
504
536
|
overflowMeasure() {
|
|
@@ -570,6 +602,10 @@ export class AgentSession {
|
|
|
570
602
|
* recovery, projection or a request reads it.
|
|
571
603
|
*/
|
|
572
604
|
recordSummary(open) {
|
|
605
|
+
// DC-60: a session with no durable event has no row — writing one made
|
|
606
|
+
// a session that never began appear in the list.
|
|
607
|
+
if (this.log.all.length === 0)
|
|
608
|
+
return;
|
|
573
609
|
try {
|
|
574
610
|
const prior = readProfile(this.#store.root, this.id);
|
|
575
611
|
writeSummary(this.#store.root, this.id, summarizeEvents(this.log.all, {
|
|
@@ -585,8 +621,18 @@ export class AgentSession {
|
|
|
585
621
|
}
|
|
586
622
|
}
|
|
587
623
|
/** XP-1: record the live binding as the next durable profile revision
|
|
588
|
-
* (read-modify-write under the session's single-writer ownership).
|
|
624
|
+
* (read-modify-write under the session's single-writer ownership).
|
|
625
|
+
* DC-60: a NEW session keeps the binding in memory until its first
|
|
626
|
+
* durable event, which records it (persist). */
|
|
589
627
|
#recordProfile() {
|
|
628
|
+
if (this.#newSession !== undefined)
|
|
629
|
+
return;
|
|
630
|
+
this.#writeProfileRevision(null);
|
|
631
|
+
}
|
|
632
|
+
/** The next profile revision from the live binding. `startWorkspace` is
|
|
633
|
+
* the workspace a NEW session opened in; with no prior revision and no
|
|
634
|
+
* start (a legacy session's first revision) the start is unknown: null. */
|
|
635
|
+
#writeProfileRevision(startWorkspace) {
|
|
590
636
|
const prior = readProfile(this.#store.root, this.id);
|
|
591
637
|
const revision = prior.kind === "ok" ? prior.profile.revision + 1 : 1;
|
|
592
638
|
writeProfile(this.#store.root, this.id, buildProfile({
|
|
@@ -595,9 +641,9 @@ export class AgentSession {
|
|
|
595
641
|
provider: this.#continuationScope ?? null,
|
|
596
642
|
profileName: this.#profileName,
|
|
597
643
|
// 0.40.0: history is CARRIED from the prior revision, never
|
|
598
|
-
// re-derived from this process. With no prior
|
|
599
|
-
//
|
|
600
|
-
workspace: prior.kind === "ok" ? prior.profile.workspace :
|
|
644
|
+
// re-derived from this process. With no prior, a NEW session's
|
|
645
|
+
// start is the workspace it opened in; a legacy one's is unknown.
|
|
646
|
+
workspace: prior.kind === "ok" ? prior.profile.workspace : startWorkspace,
|
|
601
647
|
reasoning: this.#reasoning,
|
|
602
648
|
...(this.#config.systemPrompt !== undefined ? { systemPrompt: this.#config.systemPrompt } : {}),
|
|
603
649
|
registry: this.#config.registry,
|
|
@@ -696,7 +742,7 @@ export class AgentSession {
|
|
|
696
742
|
this.ensureHealthy();
|
|
697
743
|
if (this.#profilePending)
|
|
698
744
|
this.#recordProfile(); // XP-1: legacy revision 1, before the first request
|
|
699
|
-
return new Run(this.#store, this.#adapter, this.#effectiveConfig(), this, input, options?.signal, false, options?.source, options?.via);
|
|
745
|
+
return new Run(this.#store, this.#learning(this.#adapter), this.#effectiveConfig(), this, input, options?.signal, false, options?.source, options?.via);
|
|
700
746
|
}
|
|
701
747
|
/**
|
|
702
748
|
* Continue the interrupted run (Area 2): apply durable decisions,
|
|
@@ -706,7 +752,7 @@ export class AgentSession {
|
|
|
706
752
|
*/
|
|
707
753
|
resume() {
|
|
708
754
|
this.ensureHealthy();
|
|
709
|
-
return new Run(this.#store, this.#adapter, this.#effectiveConfig(), this, undefined, undefined, true);
|
|
755
|
+
return new Run(this.#store, this.#learning(this.#adapter), this.#effectiveConfig(), this, undefined, undefined, true);
|
|
710
756
|
}
|
|
711
757
|
/**
|
|
712
758
|
* /compact (ADR-0044): compress the older conversation with a model
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CW-1 batch 2 — a route's real window, read from its own refusal.
|
|
3
|
+
*
|
|
4
|
+
* Batch 1 gives a registered model a window at any endpoint, and at an
|
|
5
|
+
* endpoint no row names that window is the model's — a vendor statement
|
|
6
|
+
* about the weights, which a relay may cap lower. The one source that is
|
|
7
|
+
* right for ANY route is the route itself, and a route that refuses an
|
|
8
|
+
* oversize request usually states its cap. d4fc (2026-09-22) died on:
|
|
9
|
+
*
|
|
10
|
+
* This model's maximum context length is 1048576 tokens. However, you
|
|
11
|
+
* requested 131072 output tokens and your prompt contains at least
|
|
12
|
+
* 917505 input tokens ...
|
|
13
|
+
*
|
|
14
|
+
* The kernel already compacts once and retries once on context_overflow;
|
|
15
|
+
* what it lacked is the figure, so the compaction and every request after
|
|
16
|
+
* it aimed at the wrong window. This wrapper reads the figure off the
|
|
17
|
+
* refusal and hands it on, then rethrows the error untouched — it changes
|
|
18
|
+
* no classification and no retry.
|
|
19
|
+
*/
|
|
20
|
+
import type { Adapter } from "@vincemakes/kiso-core";
|
|
21
|
+
/** The cap a refusal states, or null — anything that does not say one in
|
|
22
|
+
* the two wordings above is null, never a guess. */
|
|
23
|
+
export declare function overflowCap(message: string): number | null;
|
|
24
|
+
/** Wraps an adapter so a refusal that states a cap is reported once, and
|
|
25
|
+
* then thrown on exactly as it came. A caller's abort passes untouched. */
|
|
26
|
+
export declare function windowLearner(adapter: Adapter, onCap: (tokens: number) => void): Adapter;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/** The two wordings the providers kiso speaks to use for the cap:
|
|
2
|
+
* OpenAI-shaped and vLLM-shaped servers (and gateways in front of them)
|
|
3
|
+
* say `maximum context length is N tokens`; Anthropic-shaped ones say
|
|
4
|
+
* `prompt is too long: A tokens > N maximum`. */
|
|
5
|
+
const CAP_PATTERNS = [/maximum context length is\s+(\d[\d,_]*)\s*tokens/i, /prompt is too long:\s*\d[\d,_]*\s*tokens\s*>\s*(\d[\d,_]*)\s*maximum/i];
|
|
6
|
+
/** A window below this, or above the second bound, is not a window a
|
|
7
|
+
* refusal would state — a figure outside them is a misread, never a cap. */
|
|
8
|
+
const MIN_CAP = 1_000;
|
|
9
|
+
const MAX_CAP = 100_000_000;
|
|
10
|
+
/** The cap a refusal states, or null — anything that does not say one in
|
|
11
|
+
* the two wordings above is null, never a guess. */
|
|
12
|
+
export function overflowCap(message) {
|
|
13
|
+
for (const re of CAP_PATTERNS) {
|
|
14
|
+
const m = re.exec(message);
|
|
15
|
+
if (m === null)
|
|
16
|
+
continue;
|
|
17
|
+
const n = Number(m[1].replace(/[,_]/g, ""));
|
|
18
|
+
return Number.isSafeInteger(n) && n >= MIN_CAP && n <= MAX_CAP ? n : null;
|
|
19
|
+
}
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
/** Wraps an adapter so a refusal that states a cap is reported once, and
|
|
23
|
+
* then thrown on exactly as it came. A caller's abort passes untouched. */
|
|
24
|
+
export function windowLearner(adapter, onCap) {
|
|
25
|
+
return { stream: (options) => learning(adapter, options, onCap) };
|
|
26
|
+
}
|
|
27
|
+
async function* learning(adapter, options, onCap) {
|
|
28
|
+
try {
|
|
29
|
+
yield* adapter.stream(options);
|
|
30
|
+
}
|
|
31
|
+
catch (err) {
|
|
32
|
+
if (options.signal?.aborted !== true) {
|
|
33
|
+
const message = typeof err === "object" && err !== null && typeof err.message === "string" ? err.message : null;
|
|
34
|
+
const cap = message === null ? null : overflowCap(message);
|
|
35
|
+
if (cap !== null)
|
|
36
|
+
onCap(cap);
|
|
37
|
+
}
|
|
38
|
+
throw err;
|
|
39
|
+
}
|
|
40
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vincemakes/kiso-runtime",
|
|
3
|
-
"version": "0.40.
|
|
3
|
+
"version": "0.40.3",
|
|
4
4
|
"description": "kiso runtime \u2014 durable multi-turn agent sessions: AgentDefinition, AgentRuntime, AgentSession, Run, append-only JSONL store.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -25,12 +25,12 @@
|
|
|
25
25
|
"test": "vitest run"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@vincemakes/kiso-core": "0.40.
|
|
28
|
+
"@vincemakes/kiso-core": "0.40.3"
|
|
29
29
|
},
|
|
30
30
|
"peerDependencies": {
|
|
31
|
-
"@vincemakes/kiso-provider-anthropic": "0.40.
|
|
32
|
-
"@vincemakes/kiso-provider-openai": "0.40.
|
|
33
|
-
"@vincemakes/kiso-provider-openai-responses": "0.40.
|
|
31
|
+
"@vincemakes/kiso-provider-anthropic": "0.40.3",
|
|
32
|
+
"@vincemakes/kiso-provider-openai": "0.40.3",
|
|
33
|
+
"@vincemakes/kiso-provider-openai-responses": "0.40.3"
|
|
34
34
|
},
|
|
35
35
|
"peerDependenciesMeta": {
|
|
36
36
|
"@vincemakes/kiso-provider-anthropic": {
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
}
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
|
-
"@vincemakes/kiso-evals": "0.40.
|
|
47
|
+
"@vincemakes/kiso-evals": "0.40.3",
|
|
48
48
|
"@types/node": "^26.1.2",
|
|
49
49
|
"typescript": "^5.7.2",
|
|
50
50
|
"vitest": "^3.0.0"
|