@vincemakes/kiso-runtime 0.31.0 → 0.32.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.d.ts +4 -0
- package/dist/agent.js +1 -0
- package/dist/extensions.js +23 -1
- package/dist/idle-guard.d.ts +38 -0
- package/dist/idle-guard.js +122 -0
- package/dist/provider/metadata.d.ts +13 -4
- package/dist/provider/metadata.js +61 -17
- package/dist/run.js +5 -1
- package/dist/session.d.ts +2 -0
- package/dist/session.js +3 -1
- package/package.json +6 -6
package/dist/agent.d.ts
CHANGED
|
@@ -65,6 +65,10 @@ export interface AgentDefinition {
|
|
|
65
65
|
/** E6: the session context policy (run-start actions, injection-side only). */
|
|
66
66
|
readonly contextPolicy?: import("./session.js").ContextPolicy;
|
|
67
67
|
readonly maxRetries?: number;
|
|
68
|
+
/** LT-1: the stream watchdog — milliseconds between adapter events
|
|
69
|
+
* before the request is aborted and retried (default 120 s; 0 off).
|
|
70
|
+
* Type-only additive; read by the runtime's idle guard. */
|
|
71
|
+
readonly streamIdleMs?: number;
|
|
68
72
|
/** E1: loaded extensions — their tools merge into the registry (a name
|
|
69
73
|
* collision with a built-in is a loud startup error), their hooks
|
|
70
74
|
* compose after the agent's own (the existing come first), their approvals join the
|
package/dist/agent.js
CHANGED
|
@@ -145,6 +145,7 @@ export class AgentRuntime {
|
|
|
145
145
|
...(this.#definition.microcompact !== undefined ? { microcompact: this.#definition.microcompact } : {}),
|
|
146
146
|
...(this.#definition.contextPolicy !== undefined ? { contextPolicy: this.#definition.contextPolicy } : {}),
|
|
147
147
|
...(this.#definition.maxRetries !== undefined ? { maxRetries: this.#definition.maxRetries } : {}),
|
|
148
|
+
...(this.#definition.streamIdleMs !== undefined ? { streamIdleMs: this.#definition.streamIdleMs } : {}),
|
|
148
149
|
...(this.#definition.extensions !== undefined ? { extensions: this.#definition.extensions } : {}),
|
|
149
150
|
};
|
|
150
151
|
return new AgentSession(options.id, log, store, adapter, config);
|
package/dist/extensions.js
CHANGED
|
@@ -28,6 +28,27 @@ export async function loadProjectExtensions(dir, existing = []) {
|
|
|
28
28
|
}
|
|
29
29
|
return projectExts;
|
|
30
30
|
}
|
|
31
|
+
/**
|
|
32
|
+
* §2.5 — the load counter behind the import URL's `?load=` query.
|
|
33
|
+
*
|
|
34
|
+
* Node caches ESM modules by URL forever. Without a distinguishing query
|
|
35
|
+
* a second load of the same directory returns the FIRST load's modules,
|
|
36
|
+
* so `/reload` would report success and change nothing — and a
|
|
37
|
+
* module-level Set held across the boundary would be mutated by one side
|
|
38
|
+
* and read by the other.
|
|
39
|
+
*
|
|
40
|
+
* It is unconditional on purpose. A parameter would put a stale mode
|
|
41
|
+
* back within reach and would move `loadExtensions` in the public
|
|
42
|
+
* surface file for no gain: at startup this is one load and invisible,
|
|
43
|
+
* and on a reload it is the entire point.
|
|
44
|
+
*
|
|
45
|
+
* The cost, stated rather than hidden: Node has no module unloader, so
|
|
46
|
+
* each superseded module object stays allocated and unreachable for the
|
|
47
|
+
* life of the process — one per extension file per reload. The only
|
|
48
|
+
* real resources an extension holds are its MCP servers, and those are
|
|
49
|
+
* torn down by `disposeExtensions`, which whoever loaded them owes.
|
|
50
|
+
*/
|
|
51
|
+
let loadSerial = 0;
|
|
31
52
|
export async function loadExtensions(dir) {
|
|
32
53
|
let files;
|
|
33
54
|
try {
|
|
@@ -39,10 +60,11 @@ export async function loadExtensions(dir) {
|
|
|
39
60
|
throw err;
|
|
40
61
|
}
|
|
41
62
|
const out = [];
|
|
63
|
+
const serial = ++loadSerial; // one nonce per CALL — a directory's files load together
|
|
42
64
|
for (const file of files) {
|
|
43
65
|
let ext;
|
|
44
66
|
try {
|
|
45
|
-
const mod = (await import(pathToFileURL(join(dir, file)).href));
|
|
67
|
+
const mod = (await import(`${pathToFileURL(join(dir, file)).href}?load=${serial}`));
|
|
46
68
|
ext = mod.default;
|
|
47
69
|
if (typeof ext === "function")
|
|
48
70
|
ext = await ext(); // a factory
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LT-1 — the stream watchdog (kiso-doc/kiso-lt1-mini-spec-2026-09-09.md).
|
|
3
|
+
*
|
|
4
|
+
* The third guard beside `truncationGuard` and `traceGuard`: it wraps the
|
|
5
|
+
* adapter's `stream()` so that a request whose events STOP ARRIVING is
|
|
6
|
+
* ended rather than waited on forever. Before this, a model stream that
|
|
7
|
+
* went silent — a hung socket, an upstream that stopped writing without
|
|
8
|
+
* closing — held the run open until a human pressed esc; the 0.31.0
|
|
9
|
+
* review filed it as the one long-task hazard nothing handled.
|
|
10
|
+
*
|
|
11
|
+
* The rule is per EVENT, not per request: a long think that keeps
|
|
12
|
+
* streaming is never tripped, a socket that stops is tripped `idleMs`
|
|
13
|
+
* after its last byte. On a trip the guard aborts the UNDERLYING request
|
|
14
|
+
* through a controller it owns (combined with the caller's signal, so
|
|
15
|
+
* esc still wins) and throws a retryable `network` StructuredError. From
|
|
16
|
+
* there the kernel's existing mid-stream retry does the rest: the
|
|
17
|
+
* attempt is voided (`model_output_abandoned` with this reason), the
|
|
18
|
+
* request is retried with backoff, and when the budget is spent the run
|
|
19
|
+
* ends in the error terminal the CLI prints (OR-5). Nothing new is
|
|
20
|
+
* written to the log; the kernel is untouched.
|
|
21
|
+
*
|
|
22
|
+
* `idleMs` ≤ 0 (or absent) returns the adapter itself: the guard is
|
|
23
|
+
* declared off, not silently ignored.
|
|
24
|
+
*/
|
|
25
|
+
import type { Adapter, StructuredError } from "@vincemakes/kiso-core";
|
|
26
|
+
/** The default: 120 s between events. The ChatGPT backend at a high effort
|
|
27
|
+
* can think without writing (no reasoning summaries are requested), and
|
|
28
|
+
* no measured bound exists for an `ultra` turn — 120 s is the order of
|
|
29
|
+
* magnitude the reference implementation allows a whole request, applied
|
|
30
|
+
* here per event: stricter where it matters, looser where it should be.
|
|
31
|
+
* A knob (`streamIdleMs` on the profile) because the first dogfood may
|
|
32
|
+
* move it. */
|
|
33
|
+
export declare const DEFAULT_STREAM_IDLE_MS = 120000;
|
|
34
|
+
export declare function idleGuard(adapter: Adapter, idleMs: number | undefined): Adapter;
|
|
35
|
+
/** The error the kernel receives on a trip — the same shape the adapters
|
|
36
|
+
* produce for a connection failure, so the retry authority (ADR-0005)
|
|
37
|
+
* treats a stall exactly like a dropped socket. */
|
|
38
|
+
export declare function stalledError(idleMs: number, elapsedMs: number): StructuredError;
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LT-1 — the stream watchdog (kiso-doc/kiso-lt1-mini-spec-2026-09-09.md).
|
|
3
|
+
*
|
|
4
|
+
* The third guard beside `truncationGuard` and `traceGuard`: it wraps the
|
|
5
|
+
* adapter's `stream()` so that a request whose events STOP ARRIVING is
|
|
6
|
+
* ended rather than waited on forever. Before this, a model stream that
|
|
7
|
+
* went silent — a hung socket, an upstream that stopped writing without
|
|
8
|
+
* closing — held the run open until a human pressed esc; the 0.31.0
|
|
9
|
+
* review filed it as the one long-task hazard nothing handled.
|
|
10
|
+
*
|
|
11
|
+
* The rule is per EVENT, not per request: a long think that keeps
|
|
12
|
+
* streaming is never tripped, a socket that stops is tripped `idleMs`
|
|
13
|
+
* after its last byte. On a trip the guard aborts the UNDERLYING request
|
|
14
|
+
* through a controller it owns (combined with the caller's signal, so
|
|
15
|
+
* esc still wins) and throws a retryable `network` StructuredError. From
|
|
16
|
+
* there the kernel's existing mid-stream retry does the rest: the
|
|
17
|
+
* attempt is voided (`model_output_abandoned` with this reason), the
|
|
18
|
+
* request is retried with backoff, and when the budget is spent the run
|
|
19
|
+
* ends in the error terminal the CLI prints (OR-5). Nothing new is
|
|
20
|
+
* written to the log; the kernel is untouched.
|
|
21
|
+
*
|
|
22
|
+
* `idleMs` ≤ 0 (or absent) returns the adapter itself: the guard is
|
|
23
|
+
* declared off, not silently ignored.
|
|
24
|
+
*/
|
|
25
|
+
/** The default: 120 s between events. The ChatGPT backend at a high effort
|
|
26
|
+
* can think without writing (no reasoning summaries are requested), and
|
|
27
|
+
* no measured bound exists for an `ultra` turn — 120 s is the order of
|
|
28
|
+
* magnitude the reference implementation allows a whole request, applied
|
|
29
|
+
* here per event: stricter where it matters, looser where it should be.
|
|
30
|
+
* A knob (`streamIdleMs` on the profile) because the first dogfood may
|
|
31
|
+
* move it. */
|
|
32
|
+
export const DEFAULT_STREAM_IDLE_MS = 120_000;
|
|
33
|
+
export function idleGuard(adapter, idleMs) {
|
|
34
|
+
if (idleMs === undefined || !(idleMs > 0))
|
|
35
|
+
return adapter;
|
|
36
|
+
return {
|
|
37
|
+
stream: (options) => guardStream(adapter, options, idleMs),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
/** The error the kernel receives on a trip — the same shape the adapters
|
|
41
|
+
* produce for a connection failure, so the retry authority (ADR-0005)
|
|
42
|
+
* treats a stall exactly like a dropped socket. */
|
|
43
|
+
export function stalledError(idleMs, elapsedMs) {
|
|
44
|
+
return {
|
|
45
|
+
code: "network",
|
|
46
|
+
retryable: true,
|
|
47
|
+
message: `stream stalled: no event for ${Math.round(idleMs / 1000)}s (${Math.round(elapsedMs / 1000)}s into the request)`,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/** The caller's abort, as the kernel's catch reads it: an error NAMED
|
|
51
|
+
* AbortError with the signal set — `aborted by user`, never a stall. */
|
|
52
|
+
function abortError() {
|
|
53
|
+
const err = new Error("stream aborted by the caller");
|
|
54
|
+
err.name = "AbortError";
|
|
55
|
+
return err;
|
|
56
|
+
}
|
|
57
|
+
async function* guardStream(adapter, options, idleMs) {
|
|
58
|
+
const own = new AbortController();
|
|
59
|
+
const caller = options.signal;
|
|
60
|
+
// the caller's abort (esc) reaches the adapter through OUR controller —
|
|
61
|
+
// one signal goes down — and it also settles OUR wait at once: an
|
|
62
|
+
// adapter that ignores its signal (a stub, a transport that only
|
|
63
|
+
// notices on the next write) must not turn the caller's abort into a
|
|
64
|
+
// stall by keeping the guard waiting until the idle timer fires.
|
|
65
|
+
let rejectOnCallerAbort = () => undefined;
|
|
66
|
+
const callerAborted = new Promise((_, reject) => {
|
|
67
|
+
rejectOnCallerAbort = reject;
|
|
68
|
+
});
|
|
69
|
+
callerAborted.catch(() => undefined); // settled only when raced; never an unhandled rejection
|
|
70
|
+
const onCallerAbort = () => {
|
|
71
|
+
own.abort();
|
|
72
|
+
rejectOnCallerAbort(abortError());
|
|
73
|
+
};
|
|
74
|
+
if (caller?.aborted === true)
|
|
75
|
+
onCallerAbort();
|
|
76
|
+
else
|
|
77
|
+
caller?.addEventListener("abort", onCallerAbort, { once: true });
|
|
78
|
+
const iterator = adapter.stream({ ...options, signal: own.signal })[Symbol.asyncIterator]();
|
|
79
|
+
const started = Date.now();
|
|
80
|
+
let tripped = false;
|
|
81
|
+
try {
|
|
82
|
+
for (;;) {
|
|
83
|
+
let timer;
|
|
84
|
+
const stall = new Promise((_, reject) => {
|
|
85
|
+
timer = setTimeout(() => {
|
|
86
|
+
if (caller?.aborted === true) {
|
|
87
|
+
// the caller gave up first; the silence is theirs, not a stall
|
|
88
|
+
reject(abortError());
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
tripped = true;
|
|
92
|
+
own.abort();
|
|
93
|
+
reject(stalledError(idleMs, Date.now() - started));
|
|
94
|
+
}, idleMs);
|
|
95
|
+
});
|
|
96
|
+
let next;
|
|
97
|
+
try {
|
|
98
|
+
next = await Promise.race([iterator.next(), stall, callerAborted]);
|
|
99
|
+
}
|
|
100
|
+
catch (err) {
|
|
101
|
+
// an abort the CALLER asked for is theirs (the kernel reads its
|
|
102
|
+
// own signal and records `aborted by user`); an abort WE caused
|
|
103
|
+
// is the stall, whatever shape the adapter turned it into
|
|
104
|
+
if (tripped && caller?.aborted !== true)
|
|
105
|
+
throw stalledError(idleMs, Date.now() - started);
|
|
106
|
+
throw err;
|
|
107
|
+
}
|
|
108
|
+
finally {
|
|
109
|
+
clearTimeout(timer);
|
|
110
|
+
}
|
|
111
|
+
if (next.done)
|
|
112
|
+
return;
|
|
113
|
+
yield next.value;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
finally {
|
|
117
|
+
caller?.removeEventListener("abort", onCallerAbort);
|
|
118
|
+
// release the underlying generator whichever way this one ends
|
|
119
|
+
// (a trip, the caller's abort, or the consumer walking away)
|
|
120
|
+
await iterator.return?.().catch(() => undefined);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
@@ -24,8 +24,15 @@ export interface ModelCapabilities {
|
|
|
24
24
|
readonly maxOutputTokens: number | null;
|
|
25
25
|
/** "automatic" — the provider caches without request markup (DeepSeek,
|
|
26
26
|
* OpenAI); "explicit" — the request must place cache_control
|
|
27
|
-
* breakpoints (Anthropic); "none" — no caching;
|
|
28
|
-
|
|
27
|
+
* breakpoints (Anthropic); "none" — no caching; "unobservable" — the
|
|
28
|
+
* endpoint answers a cache figure that carries no information (OR-10,
|
|
29
|
+
* owner 2026-09-09: the ChatGPT backend reports `cached_tokens` 0 to
|
|
30
|
+
* a third-party client on every request — three identical 1,922-token
|
|
31
|
+
* requests, one prompt_cache_key, 0 at every attribution level — so
|
|
32
|
+
* its 0 and "not reported" are one value; a display shows no cache
|
|
33
|
+
* meter for such a row, while the record keeps what the backend
|
|
34
|
+
* said); null = unknown. */
|
|
35
|
+
readonly promptCaching: "none" | "automatic" | "explicit" | "unobservable" | null;
|
|
29
36
|
/** XP-1: the reasoning capability matrix — supersedes the pre-XP-1
|
|
30
37
|
* boolean IN PLACE (zero consumers existed, verified). null = unknown:
|
|
31
38
|
* no mode list, no effort levels, nothing downstream may guess. */
|
|
@@ -43,8 +50,10 @@ export interface ModelCapabilities {
|
|
|
43
50
|
* some model/effort combinations forbid thinking-disabled. */
|
|
44
51
|
export type ThinkingMode = "default" | "adaptive" | "enabled" | "disabled";
|
|
45
52
|
/** `none` is the OpenAI Responses dialect's zero-reasoning level — a NATIVE
|
|
46
|
-
* value a request carries, distinct from "default" (no field sent).
|
|
47
|
-
|
|
53
|
+
* value a request carries, distinct from "default" (no field sent).
|
|
54
|
+
* `ultra` (OR-6) is the vendor CLI presets' top rung for the ChatGPT
|
|
55
|
+
* backend — native THERE only; the first-party model pages stop at `max`. */
|
|
56
|
+
export type ReasoningEffort = "default" | "none" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max" | "ultra";
|
|
48
57
|
export interface ReasoningSetting {
|
|
49
58
|
readonly thinking: ThinkingMode;
|
|
50
59
|
readonly effort: ReasoningEffort;
|
|
@@ -102,42 +102,45 @@ const OPENAI_MODELS_ASOF = "2026-09-08";
|
|
|
102
102
|
* subscription; a first-party model page says nothing about that backend). */
|
|
103
103
|
const CHATGPT_PRESETS_SOURCE = "https://github.com/openai/codex/blob/37f4bb94c9f4e180535a12e3f2c2f93f4a773df0/codex-rs/models-manager/models.json";
|
|
104
104
|
const RESPONSES_WIRE = "reasoning.effort";
|
|
105
|
-
|
|
106
|
-
* levels, the window, the output cap and the price alike (read the same
|
|
107
|
-
* day). The adapter asks for no reasoning summaries (encrypted items
|
|
108
|
-
* only), so no thinking stream is emitted. */
|
|
109
|
-
const openaiRow = (model, effortDefault, rate, page) => ({
|
|
105
|
+
const openaiRow = (model, effortDefault, rate, page, opts = {}) => ({
|
|
110
106
|
model,
|
|
111
107
|
providerId: "openai",
|
|
112
108
|
endpoint: "https://api.openai.com",
|
|
113
109
|
capabilities: { contextWindow: 1_050_000, maxOutputTokens: 128_000, promptCaching: "automatic", reasoning: {
|
|
114
110
|
emitsThinkingStream: false,
|
|
115
111
|
thinking: null,
|
|
116
|
-
effort: { levels: ["none", "low", "medium", "high", "xhigh"], default: effortDefault, wire: RESPONSES_WIRE },
|
|
117
|
-
asOf: OPENAI_MODELS_ASOF,
|
|
112
|
+
effort: { levels: opts.levels ?? ["none", "low", "medium", "high", "xhigh"], default: effortDefault, wire: RESPONSES_WIRE },
|
|
113
|
+
asOf: opts.asOf ?? OPENAI_MODELS_ASOF,
|
|
118
114
|
source: page,
|
|
119
115
|
}, inputModalities: null },
|
|
120
|
-
capabilitiesAsOf: OPENAI_MODELS_ASOF,
|
|
116
|
+
capabilitiesAsOf: opts.asOf ?? OPENAI_MODELS_ASOF,
|
|
121
117
|
capabilitiesSource: page,
|
|
122
|
-
pricing: { ...rate, cacheWritePerM: 0, asOf: OPENAI_MODELS_ASOF, source: page },
|
|
118
|
+
pricing: { ...rate, cacheWritePerM: 0, asOf: opts.asOf ?? OPENAI_MODELS_ASOF, source: page },
|
|
123
119
|
});
|
|
124
120
|
/** The subscription backend's row for the same id: the presets' four
|
|
125
|
-
* levels (no `none`), the presets' context window, no price
|
|
126
|
-
|
|
121
|
+
* levels (no `none`), the presets' context window, no price, and a cache
|
|
122
|
+
* figure no client can observe (OR-10 — see ModelCapabilities). */
|
|
123
|
+
const chatgptRow = (model, opts = {}) => ({
|
|
127
124
|
model,
|
|
128
125
|
providerId: "chatgpt",
|
|
129
126
|
endpoint: "https://chatgpt.com",
|
|
130
|
-
capabilities: { contextWindow: 272_000, maxOutputTokens: null, promptCaching:
|
|
127
|
+
capabilities: { contextWindow: 272_000, maxOutputTokens: null, promptCaching: "unobservable", reasoning: {
|
|
131
128
|
emitsThinkingStream: false,
|
|
132
129
|
thinking: null,
|
|
133
|
-
effort: { levels: ["low", "medium", "high", "xhigh"], default: "medium", wire: RESPONSES_WIRE },
|
|
134
|
-
asOf: OPENAI_MODELS_ASOF,
|
|
135
|
-
source: CHATGPT_PRESETS_SOURCE,
|
|
130
|
+
effort: { levels: opts.levels ?? ["low", "medium", "high", "xhigh"], default: opts.default ?? "medium", wire: RESPONSES_WIRE },
|
|
131
|
+
asOf: opts.asOf ?? OPENAI_MODELS_ASOF,
|
|
132
|
+
source: opts.source ?? CHATGPT_PRESETS_SOURCE,
|
|
136
133
|
}, inputModalities: null },
|
|
137
|
-
capabilitiesAsOf: OPENAI_MODELS_ASOF,
|
|
138
|
-
capabilitiesSource: CHATGPT_PRESETS_SOURCE,
|
|
134
|
+
capabilitiesAsOf: opts.asOf ?? OPENAI_MODELS_ASOF,
|
|
135
|
+
capabilitiesSource: opts.source ?? CHATGPT_PRESETS_SOURCE,
|
|
139
136
|
pricing: null,
|
|
140
137
|
});
|
|
138
|
+
/** OR-6 (2026-09-09): the newer line, read the same day — the model pages for
|
|
139
|
+
* the first-party rows, the presets pinned to a later commit for the
|
|
140
|
+
* subscription rows (both ids answered on the backend that day). */
|
|
141
|
+
const OPENAI_MODELS_ASOF_2 = "2026-09-09";
|
|
142
|
+
const CHATGPT_PRESETS_SOURCE_2 = "https://github.com/openai/codex/blob/634ebc1865c6ac840ed3ba118f040d527bf4b55d/codex-rs/models-manager/models.json";
|
|
143
|
+
const ULTRA_LADDER = ["low", "medium", "high", "xhigh", "max", "ultra"];
|
|
141
144
|
/** The v1 table. Nulls outnumber numbers ON PURPOSE: only values with a
|
|
142
145
|
* named source enter; everything else waits for one. */
|
|
143
146
|
const ENTRIES = [
|
|
@@ -232,6 +235,47 @@ const ENTRIES = [
|
|
|
232
235
|
openaiRow("gpt-5.4", "none", { inputPerM: 2.5, outputPerM: 15, cacheReadPerM: 0.25 }, "https://developers.openai.com/api/docs/models/gpt-5.4"),
|
|
233
236
|
chatgptRow("gpt-5.5"),
|
|
234
237
|
chatgptRow("gpt-5.4"),
|
|
238
|
+
// OR-6 (2026-09-09): gpt-6-astra and gpt-5.6-sol. The astra page names
|
|
239
|
+
// no default, so the first-party default stays null (unknown is null);
|
|
240
|
+
// the presets say `low` at the subscription. `ultra` exists only there.
|
|
241
|
+
// The ordering keeps the first-party row first for endpoint-less
|
|
242
|
+
// callers; the run-side resolver passes the endpoint (45a9df5), so the
|
|
243
|
+
// two rows need not be nested — astra's subscription row has a level
|
|
244
|
+
// (ultra) its first-party row lacks.
|
|
245
|
+
openaiRow("gpt-6-astra", null, { inputPerM: 10, outputPerM: 50, cacheReadPerM: 1 }, "https://developers.openai.com/api/docs/models/gpt-6-astra", { levels: ["low", "medium", "high", "xhigh", "max"], asOf: OPENAI_MODELS_ASOF_2 }),
|
|
246
|
+
openaiRow("gpt-5.6-sol", "medium", { inputPerM: 4, outputPerM: 20, cacheReadPerM: 0.4 }, "https://developers.openai.com/api/docs/models/gpt-5.6-sol", { levels: ["none", "low", "medium", "high", "xhigh", "max"], asOf: OPENAI_MODELS_ASOF_2 }),
|
|
247
|
+
chatgptRow("gpt-6-astra", { levels: ULTRA_LADDER, default: "low", source: CHATGPT_PRESETS_SOURCE_2, asOf: OPENAI_MODELS_ASOF_2 }),
|
|
248
|
+
chatgptRow("gpt-5.6-sol", { levels: ULTRA_LADDER, default: "low", source: CHATGPT_PRESETS_SOURCE_2, asOf: OPENAI_MODELS_ASOF_2 }),
|
|
249
|
+
// The compat table's SECOND row (roadmap R3.2 §1a step 1): GLM 5.3 Flash
|
|
250
|
+
// through OpenRouter, read from OpenRouter's models API on 2026-09-09. The
|
|
251
|
+
// id is OpenRouter's, so the row is keyed to that origin; the
|
|
252
|
+
// continuation scope stamps such a profile `custom` at openrouter.ai
|
|
253
|
+
// (manifest.ts's KNOWN_ORIGINS is deliberately not widened — the scope
|
|
254
|
+
// identity is a durable fact and this row needs only the lookup). The
|
|
255
|
+
// window is the TOP provider's (1,048,576), not the listing's 1,310,720:
|
|
256
|
+
// what a request actually gets. No default effort — the page states
|
|
257
|
+
// none. `reasoning_effort` is in the model's supported parameters; the
|
|
258
|
+
// stream's think arrives as `reasoning` (GLM-F1, the compat adapter).
|
|
259
|
+
{
|
|
260
|
+
model: "z-ai/glm-5.3-flash",
|
|
261
|
+
endpoint: "https://openrouter.ai",
|
|
262
|
+
capabilities: {
|
|
263
|
+
contextWindow: 1_048_576,
|
|
264
|
+
maxOutputTokens: 131_072,
|
|
265
|
+
promptCaching: "automatic",
|
|
266
|
+
reasoning: {
|
|
267
|
+
emitsThinkingStream: true,
|
|
268
|
+
thinking: null,
|
|
269
|
+
effort: { levels: ["low", "medium", "high"], default: null, wire: "reasoning_effort" },
|
|
270
|
+
asOf: "2026-09-09",
|
|
271
|
+
source: "https://openrouter.ai/docs/use-cases/reasoning-tokens",
|
|
272
|
+
},
|
|
273
|
+
inputModalities: null,
|
|
274
|
+
},
|
|
275
|
+
capabilitiesAsOf: "2026-09-09",
|
|
276
|
+
capabilitiesSource: "https://openrouter.ai/api/v1/models",
|
|
277
|
+
pricing: { inputPerM: 0.075, outputPerM: 0.25, cacheReadPerM: 0.015, cacheWritePerM: 0, asOf: "2026-09-09", source: "https://openrouter.ai/api/v1/models" },
|
|
278
|
+
},
|
|
235
279
|
];
|
|
236
280
|
/**
|
|
237
281
|
* Look a model up. `endpoint` narrows: an entry WITH an endpoint only
|
package/dist/run.js
CHANGED
|
@@ -9,6 +9,7 @@ import { resolveReasoning } from "./provider/metadata.js";
|
|
|
9
9
|
import { deriveRecoveryPlan, invocationSeqOf } from "./recovery-plan.js";
|
|
10
10
|
import { composeApprovalChain, composeSystemPrompt, composeToolTable, microcompactFor } from "./compose.js";
|
|
11
11
|
import { truncationGuard } from "./truncation-guard.js";
|
|
12
|
+
import { DEFAULT_STREAM_IDLE_MS, idleGuard } from "./idle-guard.js";
|
|
12
13
|
import { RequestTracer, traceGuard } from "./trace/guard.js";
|
|
13
14
|
import { runtimeVersion } from "./trace/writer.js";
|
|
14
15
|
import { ResumeBlockedError } from "./session.js";
|
|
@@ -121,7 +122,10 @@ export class Run {
|
|
|
121
122
|
const loopConfig = () => ({
|
|
122
123
|
// 0.1.40 (R-C item 3): the truncation guard gates the model
|
|
123
124
|
// stream — a truncated turn's tool batch never executes.
|
|
124
|
-
|
|
125
|
+
// LT-1: the idle guard sits closest to the adapter — a stall is
|
|
126
|
+
// aborted and thrown there, the truncation guard and the tracer see
|
|
127
|
+
// the same events and the same error every other transport failure shows them.
|
|
128
|
+
adapter: traceGuard(tracer, truncationGuard(idleGuard(this.#adapter, this.#config.streamIdleMs ?? DEFAULT_STREAM_IDLE_MS))), // tracer assigned above, before loopConfig
|
|
125
129
|
model: this.#config.model,
|
|
126
130
|
sessionId: this.#session.id, // P3: tools see their session (ToolContext.sessionId)
|
|
127
131
|
...(systemPrompt !== undefined ? { systemPrompt } : {}),
|
package/dist/session.d.ts
CHANGED
|
@@ -414,6 +414,8 @@ export interface SessionConfig {
|
|
|
414
414
|
/** E6: the session context policy (run-start actions, injection-side only). */
|
|
415
415
|
readonly contextPolicy?: ContextPolicy;
|
|
416
416
|
readonly maxRetries?: number;
|
|
417
|
+
/** LT-1: the stream watchdog's idle bound (ms); default 120 s, 0 off. */
|
|
418
|
+
readonly streamIdleMs?: number;
|
|
417
419
|
/**
|
|
418
420
|
* E1: loaded extensions — their tools join the registry (idempotently;
|
|
419
421
|
* a collision with a built-in name was already rejected at agent
|
package/dist/session.js
CHANGED
|
@@ -47,6 +47,7 @@ import { Run } from "./run.js";
|
|
|
47
47
|
// TUI2-R3v2 ③ — the side query rides the SAME tracer the runs ride; that
|
|
48
48
|
// sameness is the whole point (one ledger, one shape, no second path).
|
|
49
49
|
import { RequestTracer, traceGuard } from "./trace/guard.js";
|
|
50
|
+
import { DEFAULT_STREAM_IDLE_MS, idleGuard } from "./idle-guard.js";
|
|
50
51
|
import { runtimeVersion } from "./trace/writer.js";
|
|
51
52
|
/** A session whose disk write was rejected (stale handle) is PERMANENTLY
|
|
52
53
|
* poisoned: its in-memory log no longer matches the disk, so no further
|
|
@@ -338,7 +339,8 @@ export class AgentSession {
|
|
|
338
339
|
rentParts: { base: options.systemPrompt, appends: [] },
|
|
339
340
|
});
|
|
340
341
|
tracer.init();
|
|
341
|
-
|
|
342
|
+
// LT-1: the summary request is a model stream too — the same watchdog
|
|
343
|
+
const guarded = traceGuard(tracer, idleGuard(this.#adapter, this.#config.streamIdleMs ?? DEFAULT_STREAM_IDLE_MS));
|
|
342
344
|
let text = "";
|
|
343
345
|
try {
|
|
344
346
|
for await (const ev of guarded.stream({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vincemakes/kiso-runtime",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.32.0",
|
|
4
4
|
"description": "kiso runtime — 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.32.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.32.0",
|
|
32
|
+
"@vincemakes/kiso-provider-openai": "0.32.0",
|
|
33
|
+
"@vincemakes/kiso-provider-openai-responses": "0.32.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.32.0",
|
|
48
48
|
"@types/node": "^26.1.2",
|
|
49
49
|
"typescript": "^5.7.2",
|
|
50
50
|
"vitest": "^3.0.0"
|