@vincemakes/kiso-runtime 0.50.1 → 0.51.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/dist/agent.js +6 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/provider/metadata.d.ts +17 -0
- package/dist/provider/metadata.js +148 -35
- package/dist/provider/openrouter-rows.d.ts +7 -0
- package/dist/provider/openrouter-rows.js +14 -0
- package/dist/session.d.ts +12 -0
- package/dist/session.js +8 -1
- package/dist/summarize.d.ts +54 -2
- package/dist/summarize.js +116 -5
- package/package.json +6 -6
package/dist/agent.js
CHANGED
|
@@ -15,13 +15,18 @@ import { resolveContinuationScope } from "./provider/manifest.js";
|
|
|
15
15
|
import { assessProfileDrift, buildProfile, profilePath, readProfile, writeProfile } from "./profile.js";
|
|
16
16
|
import { EventLog, ToolRegistry } from "@vincemakes/kiso-core";
|
|
17
17
|
import { AgentSession } from "./session.js";
|
|
18
|
+
import { snapshotCheckpointSections } from "./summarize.js";
|
|
18
19
|
/** @deprecated the canonical name is `Agent` (root export, 1.1.0); this alias is removed in the next major. */
|
|
19
20
|
export class AgentRuntime {
|
|
20
21
|
#definition;
|
|
22
|
+
/** 0.51.0: the context policy sessions receive — `checkpointSections` validated and copied at construction (a host that mutates its array afterwards changes nothing); every other field stays the host's own reference, as before. */
|
|
23
|
+
#contextPolicy;
|
|
21
24
|
#registry;
|
|
22
25
|
#adapterPromise;
|
|
23
26
|
constructor(definition) {
|
|
24
27
|
this.#definition = definition;
|
|
28
|
+
const sections = snapshotCheckpointSections(definition.contextPolicy?.checkpointSections);
|
|
29
|
+
this.#contextPolicy = sections === undefined || definition.contextPolicy === undefined ? definition.contextPolicy : { ...definition.contextPolicy, checkpointSections: sections };
|
|
25
30
|
this.#registry = new ToolRegistry();
|
|
26
31
|
for (const tool of definition.tools)
|
|
27
32
|
this.#registry.register(tool);
|
|
@@ -178,7 +183,7 @@ export class AgentRuntime {
|
|
|
178
183
|
...(this.#definition.maxTokens !== undefined ? { maxTokens: this.#definition.maxTokens } : {}),
|
|
179
184
|
...(this.#definition.temperature !== undefined ? { temperature: this.#definition.temperature } : {}),
|
|
180
185
|
...(this.#definition.microcompact !== undefined ? { microcompact: this.#definition.microcompact } : {}),
|
|
181
|
-
...(this.#
|
|
186
|
+
...(this.#contextPolicy !== undefined ? { contextPolicy: this.#contextPolicy } : {}),
|
|
182
187
|
...(this.#definition.maxRetries !== undefined ? { maxRetries: this.#definition.maxRetries } : {}),
|
|
183
188
|
...(this.#definition.streamIdleMs !== undefined ? { streamIdleMs: this.#definition.streamIdleMs } : {}),
|
|
184
189
|
...(this.#definition.toolRules !== undefined ? { toolRules: this.#definition.toolRules } : {}),
|
package/dist/index.d.ts
CHANGED
|
@@ -24,6 +24,8 @@ export { AgentSession, AgentSession as Session } from "./session.js";
|
|
|
24
24
|
export type { SideQueryOptions } from "./session.js";
|
|
25
25
|
export { PoisonedSessionError, ResumeBlockedError } from "./session.js";
|
|
26
26
|
export type { ApprovalRequest, CompactInfo, ContextPolicy, SessionConfig, SummarizeResult } from "./session.js";
|
|
27
|
+
export { DEFAULT_CHECKPOINT_SECTIONS } from "./summarize.js";
|
|
28
|
+
export type { CheckpointSection } from "./summarize.js";
|
|
27
29
|
export { Run, RunClosedError } from "./run.js";
|
|
28
30
|
export { SessionStore, StaleWriterError, StoreCorruptionError } from "./store.js";
|
|
29
31
|
export type { Event, SessionMeta, StoreRecord } from "./store.js";
|
package/dist/index.js
CHANGED
|
@@ -23,6 +23,8 @@ export { AgentRuntime, AgentRuntime as Agent, createAgent } from "./agent.js";
|
|
|
23
23
|
// session
|
|
24
24
|
export { AgentSession, AgentSession as Session } from "./session.js";
|
|
25
25
|
export { PoisonedSessionError, ResumeBlockedError } from "./session.js";
|
|
26
|
+
// 0.51.0: host-defined checkpoint sections (the type and the coding outline as data; the prompt machinery stays behind ./internal)
|
|
27
|
+
export { DEFAULT_CHECKPOINT_SECTIONS } from "./summarize.js";
|
|
26
28
|
// run
|
|
27
29
|
export { Run, RunClosedError } from "./run.js";
|
|
28
30
|
// store
|
|
@@ -76,6 +76,15 @@ export interface ReasoningCapabilities {
|
|
|
76
76
|
readonly levels: readonly Exclude<ReasoningEffort, "default">[];
|
|
77
77
|
readonly default: Exclude<ReasoningEffort, "default"> | null;
|
|
78
78
|
readonly wire: string;
|
|
79
|
+
/** a route that SPELLS a level differently on the wire (Command Code
|
|
80
|
+
* takes `off` for no reasoning, and refuses `none`): the level a
|
|
81
|
+
* person picks stays the native name, the wire carries the route's
|
|
82
|
+
* word. Absent = every level goes as named. */
|
|
83
|
+
readonly values?: Readonly<Partial<Record<Exclude<ReasoningEffort, "default">, string>>>;
|
|
84
|
+
/** the levels came from the USER's profile, not from a dated source:
|
|
85
|
+
* a claim of theirs, shown as such (the profile's `contextWindow` is
|
|
86
|
+
* the same kind of claim). Absent on every registry row. */
|
|
87
|
+
readonly declared?: true;
|
|
79
88
|
} | null;
|
|
80
89
|
/** invalid combinations the provider documents (e.g. thinking-disabled
|
|
81
90
|
* at certain efforts). */
|
|
@@ -120,12 +129,20 @@ export interface ModelMetadataEntry {
|
|
|
120
129
|
readonly capabilitiesSource?: string | null;
|
|
121
130
|
readonly pricing: ModelPricing | null;
|
|
122
131
|
}
|
|
132
|
+
/** Every row of the table, for the gate that holds the whole table to its
|
|
133
|
+
* contract (shape, dates, sources, unique keys). Internal; read-only. */
|
|
134
|
+
export declare function modelMetadataRows(): readonly ModelMetadataEntry[];
|
|
123
135
|
/**
|
|
124
136
|
* Look a model up. `endpoint` narrows: an entry WITH an endpoint only
|
|
125
137
|
* matches when the caller's endpoint origin equals it; an entry without
|
|
126
138
|
* one matches any endpoint. Unknown model → null, never a default.
|
|
127
139
|
*/
|
|
128
140
|
export declare function lookupModelMetadata(model: string, endpoint?: string): ModelMetadataEntry | null;
|
|
141
|
+
export declare function declareEffort(model: string, endpoint: string | undefined, effort: Omit<NonNullable<ReasoningCapabilities["effort"]>, "declared" | "wire"> & {
|
|
142
|
+
readonly wire?: string;
|
|
143
|
+
}): void;
|
|
144
|
+
/** For tests and a config reload: forget every declaration. */
|
|
145
|
+
export declare function clearDeclaredEfforts(): void;
|
|
129
146
|
/** CW-1: the aliases, for the gate that holds each one to a dated source
|
|
130
147
|
* and a registered target. */
|
|
131
148
|
export declare function modelAliases(): Readonly<Record<string, {
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
* Lives under runtime/internal — the curated root surface (44 names)
|
|
19
19
|
* does not move; core is untouched.
|
|
20
20
|
*/
|
|
21
|
+
import { OPENROUTER_ROWS } from "./openrouter-rows.js";
|
|
21
22
|
/**
|
|
22
23
|
* REG-1 — the OFF-PEAK rates, read from the vendor's pricing table on
|
|
23
24
|
* 2026-09-17. The table carries TWO sets: off-peak and peak, the peak
|
|
@@ -63,12 +64,16 @@ const ANTHROPIC_PRICING_SOURCE = "https://platform.claude.com/docs/en/about-clau
|
|
|
63
64
|
const ANTHROPIC_MODELS_SOURCE = "https://platform.claude.com/docs/en/models/overview";
|
|
64
65
|
const ANTHROPIC_EFFORT_SOURCE = "https://platform.claude.com/docs/en/build-with-claude/effort";
|
|
65
66
|
const ANTHROPIC_ASOF = "2026-09-07";
|
|
66
|
-
|
|
67
|
+
/** The 5.5 line (Opus 5.5, Sonnet 5.5, Haiku 5.5): the models overview, the
|
|
68
|
+
* effort page, the thinking table and the pricing page, all read 2026-10-17. */
|
|
69
|
+
const ANTHROPIC_ASOF_55 = "2026-10-17";
|
|
70
|
+
const ANTHROPIC_THINKING_SOURCE = "https://platform.claude.com/docs/en/build-with-claude/thinking-troubleshooting";
|
|
71
|
+
const anthropicPricing = (inputPerM, outputPerM, cacheReadPerM, cacheWritePerM, asOf = ANTHROPIC_ASOF) => ({
|
|
67
72
|
inputPerM,
|
|
68
73
|
outputPerM,
|
|
69
74
|
cacheReadPerM,
|
|
70
75
|
cacheWritePerM,
|
|
71
|
-
asOf
|
|
76
|
+
asOf,
|
|
72
77
|
source: ANTHROPIC_PRICING_SOURCE,
|
|
73
78
|
});
|
|
74
79
|
/** The 5-line's effort control: five native levels, default high, wire
|
|
@@ -77,24 +82,24 @@ const anthropicPricing = (inputPerM, outputPerM, cacheReadPerM, cacheWritePerM)
|
|
|
77
82
|
* gives the modes per model. */
|
|
78
83
|
const FIVE_LEVELS = { levels: ["low", "medium", "high", "xhigh", "max"], default: "high", wire: "output_config.effort" };
|
|
79
84
|
function anthropicLine() {
|
|
80
|
-
const row = (model, capabilities, pricing) => ({
|
|
85
|
+
const row = (model, capabilities, pricing, asOf = ANTHROPIC_ASOF) => ({
|
|
81
86
|
model,
|
|
82
87
|
providerId: "anthropic",
|
|
83
88
|
capabilities,
|
|
84
|
-
capabilitiesAsOf:
|
|
89
|
+
capabilitiesAsOf: asOf,
|
|
85
90
|
capabilitiesSource: ANTHROPIC_MODELS_SOURCE,
|
|
86
91
|
pricing,
|
|
87
92
|
});
|
|
88
|
-
const fiveLine = (thinkingModes, forbidden) => ({
|
|
93
|
+
const fiveLine = (thinkingModes, forbidden, opts = {}) => ({
|
|
89
94
|
contextWindow: 1_000_000,
|
|
90
95
|
maxOutputTokens: 128_000,
|
|
91
96
|
promptCaching: "explicit",
|
|
92
97
|
reasoning: {
|
|
93
98
|
emitsThinkingStream: true,
|
|
94
99
|
thinking: { modes: thinkingModes, default: "adaptive" },
|
|
95
|
-
effort: FIVE_LEVELS,
|
|
100
|
+
effort: opts.effortDefault === undefined ? FIVE_LEVELS : { ...FIVE_LEVELS, default: opts.effortDefault },
|
|
96
101
|
...(forbidden !== undefined ? { forbidden } : {}),
|
|
97
|
-
asOf: ANTHROPIC_ASOF,
|
|
102
|
+
asOf: opts.asOf ?? ANTHROPIC_ASOF,
|
|
98
103
|
source: ANTHROPIC_EFFORT_SOURCE,
|
|
99
104
|
},
|
|
100
105
|
inputModalities: ["text", "image"],
|
|
@@ -121,6 +126,30 @@ function anthropicLine() {
|
|
|
121
126
|
// Haiku 4.5: the pinned id and its alias (the registry matches exact ids).
|
|
122
127
|
row("claude-haiku-4-5-20251001", haiku, haikuPricing),
|
|
123
128
|
row("claude-haiku-4-5", haiku, haikuPricing),
|
|
129
|
+
// The 5.5 line (read 2026-10-17; every model id is its own pinned
|
|
130
|
+
// snapshot, so there is no dated alias). All three: 1M window, 128K
|
|
131
|
+
// output, five native effort levels, wire output_config.effort.
|
|
132
|
+
//
|
|
133
|
+
// Opus 5.5: adaptive ALWAYS on ("disabled" is a 400 at every level);
|
|
134
|
+
// the default effort is MEDIUM, one below Opus 5's. Cache hits cost 5%
|
|
135
|
+
// of input.
|
|
136
|
+
row("claude-opus-5-5", fiveLine(["adaptive"], undefined, { effortDefault: "medium", asOf: ANTHROPIC_ASOF_55 }), anthropicPricing(4, 20, 0.2, 5, ANTHROPIC_ASOF_55), ANTHROPIC_ASOF_55),
|
|
137
|
+
// Sonnet 5.5: adaptive, on by default. "disabled" is a 400 at every
|
|
138
|
+
// level; the vendor's own off switch is the new thinking type
|
|
139
|
+
// `between_tools` (accepted only here, only at effort high or below),
|
|
140
|
+
// which the registry's thinking vocabulary does not have, so it is
|
|
141
|
+
// not offered. Default effort high. Cache hits cost 5% of input.
|
|
142
|
+
row("claude-sonnet-5-5", fiveLine(["adaptive"], undefined, { asOf: ANTHROPIC_ASOF_55 }), anthropicPricing(2, 10, 0.1, 2.5, ANTHROPIC_ASOF_55), ANTHROPIC_ASOF_55),
|
|
143
|
+
// Haiku 5.5: adaptive, on by default; "disabled" accepted at effort
|
|
144
|
+
// high or below, a 400 with xhigh or max (the forbidden pair). Default
|
|
145
|
+
// effort medium. PRICE: the vendor prices by PROMPT LENGTH. The row
|
|
146
|
+
// carries the rates for prompts up to 100,000 tokens; a prompt over
|
|
147
|
+
// that pays $0.50 in / $2.50 out / $0.05 cache read / $0.625 cache
|
|
148
|
+
// write, five times the row. ModelPricing has one set of rates, so a
|
|
149
|
+
// bill computed from this row for a long prompt is a fifth of the
|
|
150
|
+
// truth: the one-rate-against-two-rates shape the DeepSeek peak
|
|
151
|
+
// rates already have.
|
|
152
|
+
row("claude-haiku-5-5", fiveLine(["adaptive", "disabled"], [{ thinking: "disabled", effort: "xhigh" }, { thinking: "disabled", effort: "max" }], { effortDefault: "medium", asOf: ANTHROPIC_ASOF_55 }), anthropicPricing(0.1, 0.5, 0.01, 0.125, ANTHROPIC_ASOF_55), ANTHROPIC_ASOF_55),
|
|
124
153
|
];
|
|
125
154
|
}
|
|
126
155
|
const OPENAI_MODELS_ASOF = "2026-09-08";
|
|
@@ -141,7 +170,7 @@ const openaiRow = (model, effortDefault, rate, page, opts = {}) => ({
|
|
|
141
170
|
}, inputModalities: null },
|
|
142
171
|
capabilitiesAsOf: opts.asOf ?? OPENAI_MODELS_ASOF,
|
|
143
172
|
capabilitiesSource: page,
|
|
144
|
-
pricing: { ...rate, cacheWritePerM: 0, asOf: opts.asOf ?? OPENAI_MODELS_ASOF, source: page },
|
|
173
|
+
pricing: { ...rate, cacheWritePerM: rate.cacheWritePerM ?? 0, asOf: opts.asOf ?? OPENAI_MODELS_ASOF, source: page },
|
|
145
174
|
});
|
|
146
175
|
/** The subscription backend's row for the same id: the presets' four
|
|
147
176
|
* levels (no `none`), the presets' context window, no price, and a cache
|
|
@@ -171,12 +200,24 @@ const ULTRA_LADDER = ["low", "medium", "high", "xhigh", "max", "ultra"];
|
|
|
171
200
|
* The owner's `sol` profile moved to `gpt-6.1-sol` on 2026-10-08, and with
|
|
172
201
|
* no row kiso sent it no effort: the remembered `high` never reached the
|
|
173
202
|
* wire and no reasoning summary came back. The id answered on the
|
|
174
|
-
* subscription backend that night (the 0.47.3 live sessions).
|
|
175
|
-
* first-party row
|
|
203
|
+
* subscription backend that night (the 0.47.3 live sessions). The
|
|
204
|
+
* first-party row was added 2026-10-17 (OPENAI_MODELS_ASOF_4). */
|
|
176
205
|
const OPENAI_MODELS_ASOF_3 = "2026-10-09";
|
|
177
206
|
const CHATGPT_PRESETS_SOURCE_3 = "https://github.com/openai/codex/blob/d63a9b8344cfe58bc78bbe319b560378fc8756ef/codex-rs/models-manager/models.json";
|
|
207
|
+
/** The first-party pages for the GPT-6.1 / GPT-6 sol and luna rows, read
|
|
208
|
+
* 2026-10-17 (the model page is the source for the levels, the window, the
|
|
209
|
+
* output cap and the price alike, as for every first-party row). */
|
|
210
|
+
const OPENAI_MODELS_ASOF_4 = "2026-10-17";
|
|
178
211
|
/** The v1 table. Nulls outnumber numbers ON PURPOSE: only values with a
|
|
179
212
|
* named source enter; everything else waits for one. */
|
|
213
|
+
/** Command Code: what the gateway ACCEPTS (probe, 2026-10-17) intersected with
|
|
214
|
+
* each model's native levels; `none` is spelled `off` on this route. */
|
|
215
|
+
const COMMAND_CODE_EFFORT_SOURCE = "probe 2026-10-17: POST https://api.commandcode.ai/provider/v1/chat/completions with a sentinel reasoning_effort names the legal set; native levels per model: the first-party deepseek-flash row and https://openrouter.ai/api/v1/models";
|
|
216
|
+
const COMMAND_CODE_EFFORT = {
|
|
217
|
+
"deepseek/deepseek-v4.1-flash": { levels: ["none", "low", "high", "max"], default: "high", wire: "reasoning_effort", values: { none: "off" } },
|
|
218
|
+
"z-ai/glm-5.3-flash": { levels: ["low", "high", "max"], default: null, wire: "reasoning_effort" },
|
|
219
|
+
"xai/grok-4.7": { levels: ["low", "medium", "high", "xhigh"], default: null, wire: "reasoning_effort" },
|
|
220
|
+
};
|
|
180
221
|
const ENTRIES = [
|
|
181
222
|
{
|
|
182
223
|
model: "deepseek-chat",
|
|
@@ -418,43 +459,98 @@ const ENTRIES = [
|
|
|
418
459
|
chatgptRow("gpt-6-astra", { levels: ULTRA_LADDER, default: "low", source: CHATGPT_PRESETS_SOURCE_2, asOf: OPENAI_MODELS_ASOF_2 }),
|
|
419
460
|
chatgptRow("gpt-5.6-sol", { levels: ULTRA_LADDER, default: "low", source: CHATGPT_PRESETS_SOURCE_2, asOf: OPENAI_MODELS_ASOF_2 }),
|
|
420
461
|
chatgptRow("gpt-6.1-sol", { levels: ULTRA_LADDER, default: "low", source: CHATGPT_PRESETS_SOURCE_3, asOf: OPENAI_MODELS_ASOF_3 }),
|
|
421
|
-
//
|
|
422
|
-
//
|
|
423
|
-
//
|
|
424
|
-
//
|
|
425
|
-
//
|
|
426
|
-
//
|
|
427
|
-
//
|
|
428
|
-
//
|
|
429
|
-
//
|
|
430
|
-
//
|
|
431
|
-
{
|
|
432
|
-
|
|
433
|
-
|
|
462
|
+
// 2026-10-17: the first-party rows the 0.48.0 note said were missing
|
|
463
|
+
// ("no model page was read for it"). gpt-6.1-sol: five levels, and the
|
|
464
|
+
// page says `none` and `minimal` are not supported; default medium.
|
|
465
|
+
// gpt-6-sol and gpt-6-luna: `none` through `max`, default medium.
|
|
466
|
+
// PRICE: each page also lists a LONG-PROMPT tier: a prompt over 272,000
|
|
467
|
+
// input tokens pays 2x the input and cache rates and 1.5x the output
|
|
468
|
+
// rate for the whole request. The rows carry the standard rates; a bill
|
|
469
|
+
// computed from them for a long prompt is low (the one-rate-against-
|
|
470
|
+
// two-rates shape the DeepSeek peak rates already have). Cache writes
|
|
471
|
+
// are priced on these pages and carried (sol $2.50, luna $0.125).
|
|
472
|
+
openaiRow("gpt-6.1-sol", "medium", { inputPerM: 2, outputPerM: 10, cacheReadPerM: 0.1, cacheWritePerM: 2.5 }, "https://developers.openai.com/api/docs/models/gpt-6.1-sol", { levels: ["low", "medium", "high", "xhigh", "max"], asOf: OPENAI_MODELS_ASOF_4 }),
|
|
473
|
+
openaiRow("gpt-6-sol", "medium", { inputPerM: 2, outputPerM: 10, cacheReadPerM: 0.2, cacheWritePerM: 2.5 }, "https://developers.openai.com/api/docs/models/gpt-6-sol", { levels: ["none", "low", "medium", "high", "xhigh", "max"], asOf: OPENAI_MODELS_ASOF_4 }),
|
|
474
|
+
openaiRow("gpt-6-luna", "medium", { inputPerM: 0.1, outputPerM: 0.5, cacheReadPerM: 0.01, cacheWritePerM: 0.125 }, "https://developers.openai.com/api/docs/models/gpt-6-luna", { levels: ["none", "low", "medium", "high", "xhigh", "max"], asOf: OPENAI_MODELS_ASOF_4 }),
|
|
475
|
+
// The OpenRouter route (origin https://openrouter.ai): generated from
|
|
476
|
+
// OpenRouter's own model list by scripts/registry-or-rows.mjs and kept in
|
|
477
|
+
// openrouter-rows.ts, dated 2026-10-17. It replaces the hand-written
|
|
478
|
+
// z-ai/glm-5.3-flash row of 2026-09-09 (roadmap R3.2 §1a step 1), which
|
|
479
|
+
// the list has since moved past: the efforts are now max / high / low.
|
|
480
|
+
...OPENROUTER_ROWS,
|
|
481
|
+
// The Command Code gateway (origin https://api.commandcode.ai), read by a
|
|
482
|
+
// PROBE on 2026-10-17: a chat completion with a nonsense reasoning_effort
|
|
483
|
+
// and max_tokens 1 is refused before generation and the 400 names the
|
|
484
|
+
// legal set, `off | low | medium | high | xhigh | max`. That enum belongs
|
|
485
|
+
// to the gateway, not to a model: the same list came back for every model
|
|
486
|
+
// probed. So a row here offers the INTERSECTION of that set with the
|
|
487
|
+
// model's own native levels. `none` goes on the wire as `off`: the gateway
|
|
488
|
+
// refuses `none` (400, probed the same day) and accepts `off` (200). Each
|
|
489
|
+
// model's native levels: deepseek from the first-party deepseek-flash row
|
|
490
|
+
// (live-probed 2026-09-14); glm and grok from OpenRouter's list, which is
|
|
491
|
+
// a router's statement and is named as such in `source`. Mimo has no
|
|
492
|
+
// stated efforts anywhere, so it has no row: unknown stays unknown. No
|
|
493
|
+
// default, window, output cap or price is claimed here: the window falls
|
|
494
|
+
// to the model's identity row, and the gateway's plan has no per-token
|
|
495
|
+
// price.
|
|
496
|
+
...["deepseek/deepseek-v4.1-flash", "z-ai/glm-5.3-flash", "xai/grok-4.7"].map((model) => ({
|
|
497
|
+
model,
|
|
498
|
+
endpoint: "https://api.commandcode.ai",
|
|
434
499
|
capabilities: {
|
|
435
|
-
contextWindow:
|
|
436
|
-
maxOutputTokens:
|
|
437
|
-
promptCaching:
|
|
500
|
+
contextWindow: null,
|
|
501
|
+
maxOutputTokens: null,
|
|
502
|
+
promptCaching: null,
|
|
438
503
|
reasoning: {
|
|
439
|
-
emitsThinkingStream:
|
|
504
|
+
emitsThinkingStream: null,
|
|
440
505
|
thinking: null,
|
|
441
|
-
effort:
|
|
442
|
-
asOf: "2026-
|
|
443
|
-
source:
|
|
506
|
+
effort: COMMAND_CODE_EFFORT[model],
|
|
507
|
+
asOf: "2026-10-17",
|
|
508
|
+
source: COMMAND_CODE_EFFORT_SOURCE,
|
|
444
509
|
},
|
|
445
510
|
inputModalities: null,
|
|
446
511
|
},
|
|
447
|
-
capabilitiesAsOf: "2026-
|
|
448
|
-
capabilitiesSource:
|
|
449
|
-
pricing:
|
|
450
|
-
},
|
|
512
|
+
capabilitiesAsOf: "2026-10-17",
|
|
513
|
+
capabilitiesSource: COMMAND_CODE_EFFORT_SOURCE,
|
|
514
|
+
pricing: null,
|
|
515
|
+
})),
|
|
451
516
|
];
|
|
517
|
+
/** Every row of the table, for the gate that holds the whole table to its
|
|
518
|
+
* contract (shape, dates, sources, unique keys). Internal; read-only. */
|
|
519
|
+
export function modelMetadataRows() {
|
|
520
|
+
return ENTRIES;
|
|
521
|
+
}
|
|
452
522
|
/**
|
|
453
523
|
* Look a model up. `endpoint` narrows: an entry WITH an endpoint only
|
|
454
524
|
* matches when the caller's endpoint origin equals it; an entry without
|
|
455
525
|
* one matches any endpoint. Unknown model → null, never a default.
|
|
456
526
|
*/
|
|
457
527
|
export function lookupModelMetadata(model, endpoint) {
|
|
528
|
+
const base = lookupRegistryRow(model, endpoint);
|
|
529
|
+
const declared = DECLARED_EFFORT.get(declarationKey(model, endpoint));
|
|
530
|
+
if (declared === undefined)
|
|
531
|
+
return base;
|
|
532
|
+
// a profile's declaration wins over the registry's effort for that
|
|
533
|
+
// (model, endpoint); the rest of the row (window, price, thinking modes)
|
|
534
|
+
// stays the registry's, and a model the registry does not know becomes a
|
|
535
|
+
// row that claims nothing but the declared levels
|
|
536
|
+
const reasoning = {
|
|
537
|
+
emitsThinkingStream: base?.capabilities.reasoning?.emitsThinkingStream ?? null,
|
|
538
|
+
thinking: base?.capabilities.reasoning?.thinking ?? null,
|
|
539
|
+
effort: declared,
|
|
540
|
+
...(base?.capabilities.reasoning?.forbidden !== undefined ? { forbidden: base.capabilities.reasoning.forbidden } : {}),
|
|
541
|
+
asOf: null,
|
|
542
|
+
source: "declared in the profile",
|
|
543
|
+
};
|
|
544
|
+
if (base !== null)
|
|
545
|
+
return { ...base, capabilities: { ...base.capabilities, reasoning } };
|
|
546
|
+
return {
|
|
547
|
+
model,
|
|
548
|
+
...(endpoint !== undefined ? { endpoint: originOf(endpoint) } : {}),
|
|
549
|
+
capabilities: { contextWindow: null, maxOutputTokens: null, promptCaching: null, reasoning, inputModalities: null },
|
|
550
|
+
pricing: null,
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
function lookupRegistryRow(model, endpoint) {
|
|
458
554
|
for (const entry of ENTRIES) {
|
|
459
555
|
if (entry.model !== model)
|
|
460
556
|
continue;
|
|
@@ -464,6 +560,18 @@ export function lookupModelMetadata(model, endpoint) {
|
|
|
464
560
|
}
|
|
465
561
|
return null;
|
|
466
562
|
}
|
|
563
|
+
/** A profile's own effort levels (the CLI reads them from the user's config
|
|
564
|
+
* at startup). Keyed by (model, endpoint origin); a later declaration for
|
|
565
|
+
* the same key replaces the earlier. Process-wide, like the table. */
|
|
566
|
+
const DECLARED_EFFORT = new Map();
|
|
567
|
+
const declarationKey = (model, endpoint) => `${model}@${endpoint === undefined ? "" : originOf(endpoint)}`;
|
|
568
|
+
export function declareEffort(model, endpoint, effort) {
|
|
569
|
+
DECLARED_EFFORT.set(declarationKey(model, endpoint), { ...effort, wire: effort.wire ?? "reasoning_effort", declared: true });
|
|
570
|
+
}
|
|
571
|
+
/** For tests and a config reload: forget every declaration. */
|
|
572
|
+
export function clearDeclaredEfforts() {
|
|
573
|
+
DECLARED_EFFORT.clear();
|
|
574
|
+
}
|
|
467
575
|
/**
|
|
468
576
|
* CW-1 — names one set of weights answers to across routes, each a dated
|
|
469
577
|
* vendor statement. The key is the FOLDED name a route serves (vendor
|
|
@@ -473,6 +581,11 @@ const MODEL_ALIASES = {
|
|
|
473
581
|
// The pricing table: `deepseek-flash` IS DeepSeek-V4.1-Flash. Gateways
|
|
474
582
|
// serve it by the version name (`deepseek-v4.1-flash`).
|
|
475
583
|
"deepseek-v4.1-flash": { is: "deepseek-flash", asOf: "2026-09-17", source: "https://api-docs.deepseek.com/quick_start/pricing/" },
|
|
584
|
+
// OpenRouter spells the 5.5 line with a dot; the vendor's ids carry dashes
|
|
585
|
+
// (the models overview, read 2026-10-17). Same weights.
|
|
586
|
+
"claude-opus-5.5": { is: "claude-opus-5-5", asOf: "2026-10-17", source: "https://platform.claude.com/docs/en/models/overview" },
|
|
587
|
+
"claude-sonnet-5.5": { is: "claude-sonnet-5-5", asOf: "2026-10-17", source: "https://platform.claude.com/docs/en/models/overview" },
|
|
588
|
+
"claude-haiku-5.5": { is: "claude-haiku-5-5", asOf: "2026-10-17", source: "https://platform.claude.com/docs/en/models/overview" },
|
|
476
589
|
};
|
|
477
590
|
/** CW-1: the aliases, for the gate that holds each one to a dated source
|
|
478
591
|
* and a registered target. */
|
|
@@ -565,7 +678,7 @@ export function resolveReasoning(model, setting, endpoint) {
|
|
|
565
678
|
const levels = r.effort === null ? "none known" : r.effort.levels.join("/");
|
|
566
679
|
return { ok: false, reason: `${model} does not support effort "${setting.effort}" (native: ${levels})` };
|
|
567
680
|
}
|
|
568
|
-
wire.effort = setting.effort;
|
|
681
|
+
wire.effort = r.effort.values?.[setting.effort] ?? setting.effort;
|
|
569
682
|
}
|
|
570
683
|
for (const f of r.forbidden ?? []) {
|
|
571
684
|
if (f.thinking === setting.thinking && f.effort === setting.effort) {
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GENERATED by scripts/registry-or-rows.mjs from https://openrouter.ai/api/v1/models on 2026-10-17.
|
|
3
|
+
* Do not edit by hand: run the script and commit the diff. The rules for what
|
|
4
|
+
* a row says (and does not) are in the script's header.
|
|
5
|
+
*/
|
|
6
|
+
import type { ModelMetadataEntry } from "./metadata.js";
|
|
7
|
+
export declare const OPENROUTER_ROWS: readonly ModelMetadataEntry[];
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export const OPENROUTER_ROWS = [
|
|
2
|
+
{ "model": "anthropic/claude-opus-5.5", "endpoint": "https://openrouter.ai", "capabilities": { "contextWindow": 1000000, "maxOutputTokens": 128000, "promptCaching": null, "reasoning": { "emitsThinkingStream": null, "thinking": null, "effort": { "levels": ["low", "medium", "high", "xhigh", "max"], "default": null, "wire": "reasoning_effort" }, "asOf": "2026-10-17", "source": "https://openrouter.ai/api/v1/models" }, "inputModalities": ["text", "image", "file"] }, "capabilitiesAsOf": "2026-10-17", "capabilitiesSource": "https://openrouter.ai/api/v1/models", "pricing": null },
|
|
3
|
+
{ "model": "anthropic/claude-sonnet-5.5", "endpoint": "https://openrouter.ai", "capabilities": { "contextWindow": 1000000, "maxOutputTokens": 128000, "promptCaching": null, "reasoning": { "emitsThinkingStream": null, "thinking": null, "effort": { "levels": ["low", "medium", "high", "xhigh", "max"], "default": null, "wire": "reasoning_effort" }, "asOf": "2026-10-17", "source": "https://openrouter.ai/api/v1/models" }, "inputModalities": ["text", "image", "file"] }, "capabilitiesAsOf": "2026-10-17", "capabilitiesSource": "https://openrouter.ai/api/v1/models", "pricing": null },
|
|
4
|
+
{ "model": "anthropic/claude-haiku-5.5", "endpoint": "https://openrouter.ai", "capabilities": { "contextWindow": 1000000, "maxOutputTokens": 128000, "promptCaching": null, "reasoning": { "emitsThinkingStream": null, "thinking": null, "effort": { "levels": ["none", "low", "medium", "high", "xhigh", "max"], "default": null, "wire": "reasoning_effort" }, "asOf": "2026-10-17", "source": "https://openrouter.ai/api/v1/models" }, "inputModalities": ["text", "image", "file"] }, "capabilitiesAsOf": "2026-10-17", "capabilitiesSource": "https://openrouter.ai/api/v1/models", "pricing": null },
|
|
5
|
+
{ "model": "openai/gpt-6.1-sol", "endpoint": "https://openrouter.ai", "capabilities": { "contextWindow": 1050000, "maxOutputTokens": 128000, "promptCaching": null, "reasoning": { "emitsThinkingStream": null, "thinking": null, "effort": { "levels": ["low", "medium", "high", "xhigh", "max"], "default": null, "wire": "reasoning_effort" }, "asOf": "2026-10-17", "source": "https://openrouter.ai/api/v1/models" }, "inputModalities": ["file", "image", "text"] }, "capabilitiesAsOf": "2026-10-17", "capabilitiesSource": "https://openrouter.ai/api/v1/models", "pricing": null },
|
|
6
|
+
{ "model": "openai/gpt-6-sol", "endpoint": "https://openrouter.ai", "capabilities": { "contextWindow": 1050000, "maxOutputTokens": 128000, "promptCaching": null, "reasoning": { "emitsThinkingStream": null, "thinking": null, "effort": { "levels": ["none", "low", "medium", "high", "xhigh", "max"], "default": null, "wire": "reasoning_effort" }, "asOf": "2026-10-17", "source": "https://openrouter.ai/api/v1/models" }, "inputModalities": ["file", "image", "text"] }, "capabilitiesAsOf": "2026-10-17", "capabilitiesSource": "https://openrouter.ai/api/v1/models", "pricing": null },
|
|
7
|
+
{ "model": "openai/gpt-6-luna", "endpoint": "https://openrouter.ai", "capabilities": { "contextWindow": 1050000, "maxOutputTokens": 128000, "promptCaching": null, "reasoning": { "emitsThinkingStream": null, "thinking": null, "effort": { "levels": ["none", "low", "medium", "high", "xhigh", "max"], "default": null, "wire": "reasoning_effort" }, "asOf": "2026-10-17", "source": "https://openrouter.ai/api/v1/models" }, "inputModalities": ["file", "image", "text"] }, "capabilitiesAsOf": "2026-10-17", "capabilitiesSource": "https://openrouter.ai/api/v1/models", "pricing": null },
|
|
8
|
+
{ "model": "deepseek/deepseek-v4.1-flash", "endpoint": "https://openrouter.ai", "capabilities": { "contextWindow": 1048576, "maxOutputTokens": null, "promptCaching": null, "reasoning": { "emitsThinkingStream": null, "thinking": null, "effort": { "levels": ["none", "low", "high", "max"], "default": null, "wire": "reasoning_effort" }, "asOf": "2026-10-17", "source": "https://openrouter.ai/api/v1/models" }, "inputModalities": ["text", "image"] }, "capabilitiesAsOf": "2026-10-17", "capabilitiesSource": "https://openrouter.ai/api/v1/models", "pricing": null },
|
|
9
|
+
{ "model": "x-ai/grok-4.7", "endpoint": "https://openrouter.ai", "capabilities": { "contextWindow": 500000, "maxOutputTokens": null, "promptCaching": null, "reasoning": { "emitsThinkingStream": null, "thinking": null, "effort": { "levels": ["low", "medium", "high", "xhigh"], "default": null, "wire": "reasoning_effort" }, "asOf": "2026-10-17", "source": "https://openrouter.ai/api/v1/models" }, "inputModalities": ["text", "image", "file"] }, "capabilitiesAsOf": "2026-10-17", "capabilitiesSource": "https://openrouter.ai/api/v1/models", "pricing": null },
|
|
10
|
+
{ "model": "z-ai/glm-5.3-flash", "endpoint": "https://openrouter.ai", "capabilities": { "contextWindow": 1048576, "maxOutputTokens": null, "promptCaching": "automatic", "reasoning": { "emitsThinkingStream": true, "thinking": null, "effort": { "levels": ["low", "high", "max"], "default": null, "wire": "reasoning_effort" }, "asOf": "2026-10-17", "source": "https://openrouter.ai/api/v1/models" }, "inputModalities": ["text", "image", "video"] }, "capabilitiesAsOf": "2026-10-17", "capabilitiesSource": "https://openrouter.ai/api/v1/models", "pricing": null },
|
|
11
|
+
{ "model": "moonshotai/kimi-k3", "endpoint": "https://openrouter.ai", "capabilities": { "contextWindow": 1048576, "maxOutputTokens": null, "promptCaching": null, "reasoning": { "emitsThinkingStream": null, "thinking": null, "effort": { "levels": ["none", "low", "high", "max"], "default": null, "wire": "reasoning_effort" }, "asOf": "2026-10-17", "source": "https://openrouter.ai/api/v1/models" }, "inputModalities": ["text", "image", "video"] }, "capabilitiesAsOf": "2026-10-17", "capabilitiesSource": "https://openrouter.ai/api/v1/models", "pricing": null },
|
|
12
|
+
{ "model": "xiaomi/mimo-v2.6-flash", "endpoint": "https://openrouter.ai", "capabilities": { "contextWindow": 1050000, "maxOutputTokens": 131072, "promptCaching": null, "reasoning": null, "inputModalities": ["text", "image", "video", "audio"] }, "capabilitiesAsOf": "2026-10-17", "capabilitiesSource": "https://openrouter.ai/api/v1/models", "pricing": null },
|
|
13
|
+
{ "model": "google/gemini-4-argon", "endpoint": "https://openrouter.ai", "capabilities": { "contextWindow": 1048576, "maxOutputTokens": 65536, "promptCaching": null, "reasoning": { "emitsThinkingStream": null, "thinking": null, "effort": { "levels": ["low", "medium", "high"], "default": null, "wire": "reasoning_effort" }, "asOf": "2026-10-17", "source": "https://openrouter.ai/api/v1/models" }, "inputModalities": ["audio", "file", "image", "text", "video"] }, "capabilitiesAsOf": "2026-10-17", "capabilitiesSource": "https://openrouter.ai/api/v1/models", "pricing": null },
|
|
14
|
+
];
|
package/dist/session.d.ts
CHANGED
|
@@ -31,6 +31,7 @@ import { EventLog, type AbortSignalLike, type Adapter, type Event, type KisoExte
|
|
|
31
31
|
import { type OverflowMeasure } from "./overflow-belt.js";
|
|
32
32
|
import { type ContinuationScope } from "@vincemakes/kiso-core";
|
|
33
33
|
import { type ReasoningSetting } from "./provider/metadata.js";
|
|
34
|
+
import type { CheckpointSection } from "./summarize.js";
|
|
34
35
|
import { type SessionStore, type StoreRecord } from "./store.js";
|
|
35
36
|
import { Run } from "./run.js";
|
|
36
37
|
import { type TaskDeliveryOptions } from "./tasks/delivery.js";
|
|
@@ -432,6 +433,17 @@ export declare class AgentSession {
|
|
|
432
433
|
* /compact affordance point.
|
|
433
434
|
*/
|
|
434
435
|
export interface ContextPolicy {
|
|
436
|
+
/**
|
|
437
|
+
* 0.51.0 — the sections of every checkpoint this session writes (the run-start
|
|
438
|
+
* auto-summary, the in-run tiers, the manual compact). Static data: a list of
|
|
439
|
+
* `{ heading, instruction }`, rendered in place of the coding outline; EVERY
|
|
440
|
+
* listed heading must appear as its own `## <heading>` line or the checkpoint
|
|
441
|
+
* is rejected (the usual route: serialized retry, then the breaker). Absent:
|
|
442
|
+
* the coding outline of 0.50.0, byte for byte. Validated and copied when the
|
|
443
|
+
* agent or session is constructed; extend the default with
|
|
444
|
+
* `[...DEFAULT_CHECKPOINT_SECTIONS, { heading, instruction }]`.
|
|
445
|
+
*/
|
|
446
|
+
readonly checkpointSections?: readonly CheckpointSection[];
|
|
435
447
|
/**
|
|
436
448
|
* A — the auto-summary: at run start, when the projected context
|
|
437
449
|
* crosses the trigger AND enough uncovered rounds exist, the
|
package/dist/session.js
CHANGED
|
@@ -35,7 +35,7 @@ import { denialResult } from "@vincemakes/kiso-core";
|
|
|
35
35
|
import { buildProfile, readProfile, writeProfile, writeSummary } from "./profile.js";
|
|
36
36
|
import { summarizeEvents } from "./session-summary.js";
|
|
37
37
|
import { lookupModelMetadata, resolveReasoning } from "./provider/metadata.js";
|
|
38
|
-
import { DROP_PLACEHOLDER, estimateSummarySavings, KEEP_RECENT_ROUNDS, KEEP_TOKENS_DEFAULT, lastSummaryPoint, MAX_SUMMARY_FAILURES, policyTriggerFromWindow, serializeCovered, MANUAL_SUMMARY_BUDGET, SUMMARY_MAX_OUTPUT, summarizeConversation, SummaryBudgetExhausted, summaryBoundarySeq, } from "./summarize.js";
|
|
38
|
+
import { DROP_PLACEHOLDER, estimateSummarySavings, KEEP_RECENT_ROUNDS, KEEP_TOKENS_DEFAULT, lastSummaryPoint, MAX_SUMMARY_FAILURES, policyTriggerFromWindow, serializeCovered, MANUAL_SUMMARY_BUDGET, SUMMARY_MAX_OUTPUT, resolveCheckpoint, snapshotCheckpointSections, summarizeConversation, SummaryBudgetExhausted, summaryBoundarySeq, } from "./summarize.js";
|
|
39
39
|
import { canonicalizeUsageForModel } from "./usage/canonical.js";
|
|
40
40
|
import { contextAnchor } from "./context-anchor.js";
|
|
41
41
|
import { appendFileSync, mkdirSync } from "node:fs";
|
|
@@ -102,6 +102,8 @@ export class AgentSession {
|
|
|
102
102
|
// constructor, setAdapter, and setModelBinding are the only writers).
|
|
103
103
|
#adapter;
|
|
104
104
|
#config;
|
|
105
|
+
/** 0.51.0: the checkpoint every summary call is built from — resolved ONCE at construction from a snapshot of `contextPolicy.checkpointSections`; the raw host array is never read again. */
|
|
106
|
+
#checkpoint;
|
|
105
107
|
// PH-1a (finding PH-F8, P0): the LIVE model binding. #config froze the
|
|
106
108
|
// startup model/provider, so a /model switch replaced the adapter while
|
|
107
109
|
// every later run kept sending the OLD model id and canonicalizing
|
|
@@ -193,6 +195,10 @@ export class AgentSession {
|
|
|
193
195
|
}
|
|
194
196
|
const composedHooks = composeHooks(config.hooks, config.extensions ?? []);
|
|
195
197
|
this.#config = composedHooks === undefined ? config : { ...config, hooks: composedHooks };
|
|
198
|
+
// 0.51.0: validate and snapshot the host's checkpoint sections here, at
|
|
199
|
+
// construction (a direct `new AgentSession` included); an invalid list throws.
|
|
200
|
+
const sections = snapshotCheckpointSections(config.contextPolicy?.checkpointSections);
|
|
201
|
+
this.#checkpoint = sections === undefined ? undefined : resolveCheckpoint(sections);
|
|
196
202
|
this.#model = config.model;
|
|
197
203
|
this.#provider = config.provider;
|
|
198
204
|
this.#baseUrl = config.baseUrl;
|
|
@@ -415,6 +421,7 @@ export class AgentSession {
|
|
|
415
421
|
await this.#config.hooks?.onRetry?.(info, {});
|
|
416
422
|
},
|
|
417
423
|
...(args.onProgress !== undefined ? { onProgress: args.onProgress } : {}),
|
|
424
|
+
...(this.#checkpoint !== undefined ? { checkpoint: this.#checkpoint } : {}),
|
|
418
425
|
};
|
|
419
426
|
try {
|
|
420
427
|
// The in-band call carries the run's whole context, so the overflow
|
package/dist/summarize.d.ts
CHANGED
|
@@ -159,8 +159,57 @@ export declare const DSML_MARKERS: readonly ["<tool_call", "<tool_use", "<invoke
|
|
|
159
159
|
* guards the trust-critical tail). */
|
|
160
160
|
export declare const REQUIRED_SECTIONS: readonly ["## Current work", "## Next steps"];
|
|
161
161
|
/** null = pass; an error string = reject. Empty text is the existing
|
|
162
|
-
* no-text rule's domain, reported here too (defense in depth).
|
|
163
|
-
|
|
162
|
+
* no-text rule's domain, reported here too (defense in depth).
|
|
163
|
+
* `headings` (a host's `checkpointSections`, canonical form): when given,
|
|
164
|
+
* EVERY listed heading must appear as its own line (`## <heading>`,
|
|
165
|
+
* trimmed) instead of the two default tail sections matched by substring —
|
|
166
|
+
* `## Story` is a substring of `## Storyboard`, so the custom path cannot
|
|
167
|
+
* match that way. */
|
|
168
|
+
export declare function validateSummary(text: string, headings?: readonly string[]): string | null;
|
|
169
|
+
/**
|
|
170
|
+
* 0.51.0 — host-defined checkpoint sections (plan-checkpoint-sections rev 2.1;
|
|
171
|
+
* ADR-0055 Amendment). Kiso owns the mechanics of a checkpoint — when it is
|
|
172
|
+
* written, its boundary, its validation, its retries; the host owns what the
|
|
173
|
+
* checkpoint records. The list is static data: a callback could read time,
|
|
174
|
+
* state or spend, and two requests from one configuration would differ.
|
|
175
|
+
*/
|
|
176
|
+
export interface CheckpointSection {
|
|
177
|
+
/** One line, rendered as `## <heading>`. */
|
|
178
|
+
readonly heading: string;
|
|
179
|
+
/** What the section must hold; rendered under the heading. */
|
|
180
|
+
readonly instruction: string;
|
|
181
|
+
}
|
|
182
|
+
/** The seven sections of the coding outline, as data. Deep-frozen: a host
|
|
183
|
+
* extends it with `[...DEFAULT_CHECKPOINT_SECTIONS, { heading, instruction }]`
|
|
184
|
+
* and cannot rewrite the shared default. */
|
|
185
|
+
export declare const DEFAULT_CHECKPOINT_SECTIONS: readonly CheckpointSection[];
|
|
186
|
+
/** The kiso-owned rule the CUSTOM path adds: strict every-section validation
|
|
187
|
+
* must not push the model to invent content for a section with nothing in it.
|
|
188
|
+
* The default path does not carry it — its bytes stay 0.50.0's. */
|
|
189
|
+
export declare const CHECKPOINT_NONE_RECORDED_RULE = "Every listed section must appear. If the covered conversation holds nothing for a section, write that none is recorded; never invent content to fill it.";
|
|
190
|
+
/** The custom-path checkpoint prompt for `sections` (canonical form). */
|
|
191
|
+
export declare function renderCheckpointPrompt(sections: readonly CheckpointSection[]): string;
|
|
192
|
+
/** The rendered sections block's cap, UTF-8 bytes. */
|
|
193
|
+
export declare const MAX_CHECKPOINT_SECTIONS_BYTES = 8192;
|
|
194
|
+
/**
|
|
195
|
+
* Validate and snapshot a host's list: a deep copy, trimmed, frozen at both
|
|
196
|
+
* levels, or `undefined` for an absent list. Throws
|
|
197
|
+
* `Invalid contextPolicy.checkpointSections: …` — fail fast, never truncate.
|
|
198
|
+
* The snapshot is taken at construction (the runtime's, and a direct
|
|
199
|
+
* session's); the raw host array is never read again.
|
|
200
|
+
*/
|
|
201
|
+
export declare function snapshotCheckpointSections(input: readonly CheckpointSection[] | undefined): readonly CheckpointSection[] | undefined;
|
|
202
|
+
/** What one summary call is built from: the system prompt of the serialized
|
|
203
|
+
* form, the instruction of the in-band form, and the headings validation
|
|
204
|
+
* requires (`null` = the default two-section substring rule). */
|
|
205
|
+
export interface ResolvedCheckpoint {
|
|
206
|
+
readonly prompt: string;
|
|
207
|
+
readonly inBand: string;
|
|
208
|
+
readonly headings: readonly string[] | null;
|
|
209
|
+
}
|
|
210
|
+
/** Absent sections return the EXISTING constants (the path stays byte-identical
|
|
211
|
+
* to 0.50.0), not a rebuild of them. `sections` must already be a snapshot. */
|
|
212
|
+
export declare function resolveCheckpoint(sections?: readonly CheckpointSection[]): ResolvedCheckpoint;
|
|
164
213
|
export interface SummarizeConversationOptions {
|
|
165
214
|
readonly adapter: Adapter;
|
|
166
215
|
readonly model: string;
|
|
@@ -196,6 +245,9 @@ export interface SummarizeConversationOptions {
|
|
|
196
245
|
* the final usage arrive. Observation only: a throw is swallowed, and
|
|
197
246
|
* the request is identical with or without it. */
|
|
198
247
|
readonly onProgress?: (progress: SummaryProgress) => void;
|
|
248
|
+
/** 0.51.0: the checkpoint the call is built from (`resolveCheckpoint`).
|
|
249
|
+
* Absent, the coding outline of 0.50.0, byte for byte. */
|
|
250
|
+
readonly checkpoint?: ResolvedCheckpoint;
|
|
199
251
|
}
|
|
200
252
|
/**
|
|
201
253
|
* 0.40.0 — the summary call's progress against its output budget.
|
package/dist/summarize.js
CHANGED
|
@@ -243,8 +243,13 @@ export const DSML_MARKERS = ["<tool_call", "<tool_use", "<invoke", "tool_calls",
|
|
|
243
243
|
* guards the trust-critical tail). */
|
|
244
244
|
export const REQUIRED_SECTIONS = ["## Current work", "## Next steps"];
|
|
245
245
|
/** null = pass; an error string = reject. Empty text is the existing
|
|
246
|
-
* no-text rule's domain, reported here too (defense in depth).
|
|
247
|
-
|
|
246
|
+
* no-text rule's domain, reported here too (defense in depth).
|
|
247
|
+
* `headings` (a host's `checkpointSections`, canonical form): when given,
|
|
248
|
+
* EVERY listed heading must appear as its own line (`## <heading>`,
|
|
249
|
+
* trimmed) instead of the two default tail sections matched by substring —
|
|
250
|
+
* `## Story` is a substring of `## Storyboard`, so the custom path cannot
|
|
251
|
+
* match that way. */
|
|
252
|
+
export function validateSummary(text, headings) {
|
|
248
253
|
const trimmed = text.trim();
|
|
249
254
|
if (trimmed === "")
|
|
250
255
|
return "the summary is empty";
|
|
@@ -253,12 +258,117 @@ export function validateSummary(text) {
|
|
|
253
258
|
if (lower.includes(marker))
|
|
254
259
|
return `the summary carries a tool-call marker (${marker}) — reject`;
|
|
255
260
|
}
|
|
261
|
+
if (headings !== undefined) {
|
|
262
|
+
const lines = new Set(trimmed.split(/\r?\n/).map((l) => l.trim()));
|
|
263
|
+
for (const h of headings) {
|
|
264
|
+
if (!lines.has(`## ${h}`))
|
|
265
|
+
return `the summary is missing the required section ## ${h} — a truncated or incomplete checkpoint`;
|
|
266
|
+
}
|
|
267
|
+
return null;
|
|
268
|
+
}
|
|
256
269
|
for (const section of REQUIRED_SECTIONS) {
|
|
257
270
|
if (!trimmed.includes(section))
|
|
258
271
|
return `the summary is missing the required section ${section} — a truncated or incomplete checkpoint`;
|
|
259
272
|
}
|
|
260
273
|
return null;
|
|
261
274
|
}
|
|
275
|
+
/** The seven sections of the coding outline, as data. Deep-frozen: a host
|
|
276
|
+
* extends it with `[...DEFAULT_CHECKPOINT_SECTIONS, { heading, instruction }]`
|
|
277
|
+
* and cannot rewrite the shared default. */
|
|
278
|
+
export const DEFAULT_CHECKPOINT_SECTIONS = Object.freeze([
|
|
279
|
+
{ heading: "Goal", instruction: "The user's goal and the acceptance criterion, in one or two sentences." },
|
|
280
|
+
{ heading: "Constraints", instruction: "The constraints, requirements, and rulings the work must honor." },
|
|
281
|
+
{
|
|
282
|
+
heading: "User requests",
|
|
283
|
+
instruction: `Every user request of the task — those an earlier checkpoint lists and
|
|
284
|
+
those in the covered range — enumerated one by one, each with what it
|
|
285
|
+
asked for and what was done about it.`,
|
|
286
|
+
},
|
|
287
|
+
{
|
|
288
|
+
heading: "Files and changes",
|
|
289
|
+
instruction: `Every file touched — exact paths, what changed, and why. Include the
|
|
290
|
+
precise code-level changes later turns may need to continue.`,
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
heading: "Errors and fixes",
|
|
294
|
+
instruction: `Every error encountered and its resolution; commands run and their
|
|
295
|
+
outcomes.`,
|
|
296
|
+
},
|
|
297
|
+
{
|
|
298
|
+
heading: "Current work",
|
|
299
|
+
instruction: `The current state of the work — what is done, what is not. Quote the
|
|
300
|
+
current task's criterion VERBATIM if one exists.`,
|
|
301
|
+
},
|
|
302
|
+
{ heading: "Next steps", instruction: "The concrete next steps, in order." },
|
|
303
|
+
].map((x) => Object.freeze(x)));
|
|
304
|
+
/** The kiso-owned rule the CUSTOM path adds: strict every-section validation
|
|
305
|
+
* must not push the model to invent content for a section with nothing in it.
|
|
306
|
+
* The default path does not carry it — its bytes stay 0.50.0's. */
|
|
307
|
+
export const CHECKPOINT_NONE_RECORDED_RULE = "Every listed section must appear. If the covered conversation holds nothing for a section, write that none is recorded; never invent content to fill it.";
|
|
308
|
+
const SECTIONS_LEAD = "Produce the checkpoint with exactly these sections, in this order:\n\n";
|
|
309
|
+
// The frame is cut from the constant itself — the host's sections replace only
|
|
310
|
+
// the block between these two marks, never a copy of the frame.
|
|
311
|
+
const FRAME_HEAD = SUMMARY_PROMPT.slice(0, SUMMARY_PROMPT.indexOf(SECTIONS_LEAD) + SECTIONS_LEAD.length);
|
|
312
|
+
const FRAME_TAIL = SUMMARY_PROMPT.slice(SUMMARY_PROMPT.indexOf("Preserve concrete identifiers VERBATIM"));
|
|
313
|
+
const renderSectionBlocks = (sections) => sections.map((x) => `## ${x.heading}\n${x.instruction}`).join("\n\n");
|
|
314
|
+
/** The custom-path checkpoint prompt for `sections` (canonical form). */
|
|
315
|
+
export function renderCheckpointPrompt(sections) {
|
|
316
|
+
return `${FRAME_HEAD}${renderSectionBlocks(sections)}\n\n${CHECKPOINT_NONE_RECORDED_RULE}\n\n${FRAME_TAIL}`;
|
|
317
|
+
}
|
|
318
|
+
const MAX_SECTIONS = 16;
|
|
319
|
+
const MAX_HEADING_CHARS = 80;
|
|
320
|
+
/** The rendered sections block's cap, UTF-8 bytes. */
|
|
321
|
+
export const MAX_CHECKPOINT_SECTIONS_BYTES = 8192;
|
|
322
|
+
/**
|
|
323
|
+
* Validate and snapshot a host's list: a deep copy, trimmed, frozen at both
|
|
324
|
+
* levels, or `undefined` for an absent list. Throws
|
|
325
|
+
* `Invalid contextPolicy.checkpointSections: …` — fail fast, never truncate.
|
|
326
|
+
* The snapshot is taken at construction (the runtime's, and a direct
|
|
327
|
+
* session's); the raw host array is never read again.
|
|
328
|
+
*/
|
|
329
|
+
export function snapshotCheckpointSections(input) {
|
|
330
|
+
if (input === undefined)
|
|
331
|
+
return undefined;
|
|
332
|
+
const fail = (why) => {
|
|
333
|
+
throw new Error(`Invalid contextPolicy.checkpointSections: ${why}`);
|
|
334
|
+
};
|
|
335
|
+
if (!Array.isArray(input))
|
|
336
|
+
return fail("it must be an array");
|
|
337
|
+
if (input.length < 1 || input.length > MAX_SECTIONS)
|
|
338
|
+
return fail(`it must hold 1 to ${MAX_SECTIONS} sections, got ${input.length}`);
|
|
339
|
+
const seen = new Set();
|
|
340
|
+
const out = input.map((x, i) => {
|
|
341
|
+
const heading = typeof x?.heading === "string" ? x.heading.trim() : "";
|
|
342
|
+
const instruction = typeof x?.instruction === "string" ? x.instruction.trim() : "";
|
|
343
|
+
if (heading === "")
|
|
344
|
+
return fail(`section ${i + 1} has an empty heading`);
|
|
345
|
+
if (/[\r\n]/.test(heading))
|
|
346
|
+
return fail(`section ${i + 1}: the heading must be one line`);
|
|
347
|
+
if (heading.length > MAX_HEADING_CHARS)
|
|
348
|
+
return fail(`section ${i + 1}: the heading is ${heading.length} characters, the cap is ${MAX_HEADING_CHARS}`);
|
|
349
|
+
if (heading.startsWith("#"))
|
|
350
|
+
return fail(`section ${i + 1}: the heading must not start with "#" (kiso adds "## ")`);
|
|
351
|
+
const key = heading.toLowerCase();
|
|
352
|
+
if (seen.has(key))
|
|
353
|
+
return fail(`section ${i + 1}: the heading "${heading}" repeats an earlier one`);
|
|
354
|
+
seen.add(key);
|
|
355
|
+
if (instruction === "")
|
|
356
|
+
return fail(`section ${i + 1} ("${heading}") has an empty instruction`);
|
|
357
|
+
return Object.freeze({ heading, instruction });
|
|
358
|
+
});
|
|
359
|
+
const bytes = Buffer.byteLength(renderSectionBlocks(out), "utf8");
|
|
360
|
+
if (bytes > MAX_CHECKPOINT_SECTIONS_BYTES)
|
|
361
|
+
fail(`the rendered sections are ${bytes} bytes, the cap is ${MAX_CHECKPOINT_SECTIONS_BYTES}`);
|
|
362
|
+
return Object.freeze(out);
|
|
363
|
+
}
|
|
364
|
+
/** Absent sections return the EXISTING constants (the path stays byte-identical
|
|
365
|
+
* to 0.50.0), not a rebuild of them. `sections` must already be a snapshot. */
|
|
366
|
+
export function resolveCheckpoint(sections) {
|
|
367
|
+
if (sections === undefined)
|
|
368
|
+
return { prompt: SUMMARY_PROMPT, inBand: SUMMARY_IN_BAND, headings: null };
|
|
369
|
+
const prompt = renderCheckpointPrompt(sections);
|
|
370
|
+
return { prompt, inBand: `${SUMMARY_IN_BAND.slice(0, SUMMARY_IN_BAND.length - SUMMARY_PROMPT.length)}${prompt}`, headings: sections.map((x) => x.heading) };
|
|
371
|
+
}
|
|
262
372
|
/** A thrown value the adapter CLASSIFIED — `{ code, retryable, message }`
|
|
263
373
|
* (ADR-0005 rule 1). The core's own guard is private, and this asks a
|
|
264
374
|
* narrower question than that one: is this a failure someone already
|
|
@@ -385,12 +495,13 @@ async function summaryAttempt(options) {
|
|
|
385
495
|
const budget = options.maxOutputTokens ?? null;
|
|
386
496
|
report({ produced: 0, budget, reasoningUnseen: false, reported: false });
|
|
387
497
|
const inBand = options.inBand;
|
|
498
|
+
const cp = options.checkpoint;
|
|
388
499
|
for await (const ev of adapter.stream({
|
|
389
500
|
model,
|
|
390
501
|
...(inBand === undefined
|
|
391
|
-
? { messages, systemPrompt: SUMMARY_PROMPT }
|
|
502
|
+
? { messages, systemPrompt: cp?.prompt ?? SUMMARY_PROMPT }
|
|
392
503
|
: {
|
|
393
|
-
messages: [...messages, { role: "user", content: inBand.focus === undefined ? SUMMARY_IN_BAND : `${SUMMARY_IN_BAND}\n\nFocus the summary on: ${inBand.focus}` }],
|
|
504
|
+
messages: [...messages, { role: "user", content: inBand.focus === undefined ? (cp?.inBand ?? SUMMARY_IN_BAND) : `${cp?.inBand ?? SUMMARY_IN_BAND}\n\nFocus the summary on: ${inBand.focus}` }],
|
|
394
505
|
...(inBand.systemPrompt !== undefined ? { systemPrompt: inBand.systemPrompt } : {}),
|
|
395
506
|
tools: inBand.tools,
|
|
396
507
|
}),
|
|
@@ -453,7 +564,7 @@ async function summaryAttempt(options) {
|
|
|
453
564
|
}
|
|
454
565
|
// E6 (b): a non-checkpoint summary is an honest failure — throw, the
|
|
455
566
|
// caller reports it, nothing is persisted (the auto-T5-1 regression).
|
|
456
|
-
const invalid = validateSummary(trimmed);
|
|
567
|
+
const invalid = validateSummary(trimmed, cp?.headings ?? undefined);
|
|
457
568
|
if (invalid !== null) {
|
|
458
569
|
throw new Error(`the summary call produced an invalid summary: ${invalid}`);
|
|
459
570
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vincemakes/kiso-runtime",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.51.0",
|
|
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.
|
|
28
|
+
"@vincemakes/kiso-core": "0.51.0"
|
|
29
29
|
},
|
|
30
30
|
"peerDependencies": {
|
|
31
|
-
"@vincemakes/kiso-provider-anthropic": "0.
|
|
32
|
-
"@vincemakes/kiso-provider-openai": "0.
|
|
33
|
-
"@vincemakes/kiso-provider-openai-responses": "0.
|
|
31
|
+
"@vincemakes/kiso-provider-anthropic": "0.51.0",
|
|
32
|
+
"@vincemakes/kiso-provider-openai": "0.51.0",
|
|
33
|
+
"@vincemakes/kiso-provider-openai-responses": "0.51.0"
|
|
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.
|
|
47
|
+
"@vincemakes/kiso-evals": "0.51.0",
|
|
48
48
|
"@types/node": "^26.1.2",
|
|
49
49
|
"typescript": "^5.7.2",
|
|
50
50
|
"vitest": "^3.0.0"
|