@vincemakes/kiso-runtime 0.30.0 → 0.31.1
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 +20 -3
- package/dist/agent.js +22 -1
- package/dist/provider/manifest.d.ts +6 -3
- package/dist/provider/manifest.js +29 -2
- package/dist/provider/metadata.d.ts +14 -3
- package/dist/provider/metadata.js +72 -0
- package/dist/run.js +10 -3
- package/dist/session.d.ts +19 -5
- package/dist/session.js +26 -5
- package/dist/trace/guard.d.ts +3 -0
- package/dist/trace/guard.js +6 -1
- package/package.json +9 -5
package/dist/agent.d.ts
CHANGED
|
@@ -35,10 +35,22 @@ export interface AgentDefinition {
|
|
|
35
35
|
readonly hooks?: HookHost;
|
|
36
36
|
/** Direct adapter injection (tests, faux, custom providers). */
|
|
37
37
|
readonly adapter?: Adapter;
|
|
38
|
-
/** Lazy provider: "anthropic" | "openai-compat"
|
|
39
|
-
|
|
38
|
+
/** Lazy provider: "anthropic" | "openai-compat" | "openai-responses"
|
|
39
|
+
* (imports the peer package). */
|
|
40
|
+
readonly provider?: "anthropic" | "openai-compat" | "openai-responses";
|
|
40
41
|
readonly apiKey?: string;
|
|
41
42
|
readonly baseUrl?: string;
|
|
43
|
+
/** OR-1: the Responses adapter's ChatGPT target — a thunk that yields
|
|
44
|
+
* a FRESH token per request (the CLI's calls the credential store's
|
|
45
|
+
* refresh). Its presence is what selects that target; `apiKey`
|
|
46
|
+
* selects the first-party one. Ignored by the other providers. */
|
|
47
|
+
readonly oauth?: () => Promise<{
|
|
48
|
+
readonly access: string;
|
|
49
|
+
readonly accountId: string;
|
|
50
|
+
}>;
|
|
51
|
+
/** OR-1: the ChatGPT backend's prefix-cache key — the session id, so
|
|
52
|
+
* one session's requests share a cache lane. */
|
|
53
|
+
readonly promptCacheKey?: string;
|
|
42
54
|
/** PH-1c.1: opt-in Anthropic prompt caching (cache_control
|
|
43
55
|
* breakpoints) — OFF by default; the openai-compat path ignores it
|
|
44
56
|
* (that dialect's caching is server-automatic). Type-only additive. */
|
|
@@ -83,8 +95,13 @@ export declare function createAgent(definition: AgentDefinition): AgentRuntime;
|
|
|
83
95
|
* adapter each call; the caller (session.setAdapter) decides when it
|
|
84
96
|
* takes effect.
|
|
85
97
|
*/
|
|
86
|
-
export declare function buildAdapter(provider: "anthropic" | "openai-compat", opts?: {
|
|
98
|
+
export declare function buildAdapter(provider: "anthropic" | "openai-compat" | "openai-responses", opts?: {
|
|
87
99
|
readonly apiKey?: string;
|
|
88
100
|
readonly baseUrl?: string;
|
|
89
101
|
readonly promptCaching?: boolean;
|
|
102
|
+
readonly oauth?: () => Promise<{
|
|
103
|
+
readonly access: string;
|
|
104
|
+
readonly accountId: string;
|
|
105
|
+
}>;
|
|
106
|
+
readonly promptCacheKey?: string;
|
|
90
107
|
}): Promise<Adapter>;
|
package/dist/agent.js
CHANGED
|
@@ -121,6 +121,9 @@ export class AgentRuntime {
|
|
|
121
121
|
const config = {
|
|
122
122
|
model: restored?.model ?? this.#definition.model,
|
|
123
123
|
...(this.#definition.provider !== undefined ? { provider: this.#definition.provider } : {}),
|
|
124
|
+
// OR-1: the endpoint rides with the provider — the cost path and
|
|
125
|
+
// the window lookup key on (model, endpoint), never on the id alone.
|
|
126
|
+
...(this.#definition.baseUrl !== undefined ? { baseUrl: this.#definition.baseUrl } : {}),
|
|
124
127
|
...((restored !== null ? restored.scope : startupScope) !== undefined
|
|
125
128
|
? { continuationScope: (restored !== null ? restored.scope : startupScope) }
|
|
126
129
|
: {}),
|
|
@@ -217,7 +220,25 @@ async function resolveAdapter(definition) {
|
|
|
217
220
|
: {}),
|
|
218
221
|
});
|
|
219
222
|
}
|
|
223
|
+
case "openai-responses": {
|
|
224
|
+
const { createOpenAIResponsesProvider } = await import("@vincemakes/kiso-provider-openai-responses");
|
|
225
|
+
// MG-1 (A5): the adapter's replay identity is the SAME one the
|
|
226
|
+
// run's stamping scope uses, so emit and replay agree — the
|
|
227
|
+
// openai-compat case's rule, one dialect over.
|
|
228
|
+
const scope = resolveContinuationScope("openai-responses", "", definition.baseUrl);
|
|
229
|
+
// The adapter picks its TARGET from these options — an `oauth`
|
|
230
|
+
// thunk is the ChatGPT backend, an `apiKey` is the first-party
|
|
231
|
+
// API — so no separate switch exists to disagree with the
|
|
232
|
+
// credential the CLI resolved.
|
|
233
|
+
return createOpenAIResponsesProvider({
|
|
234
|
+
...(scope !== undefined ? { scope: { providerId: scope.providerId } } : {}),
|
|
235
|
+
...(definition.apiKey !== undefined ? { apiKey: definition.apiKey } : {}),
|
|
236
|
+
...(definition.oauth !== undefined ? { oauth: definition.oauth } : {}),
|
|
237
|
+
...(definition.baseUrl !== undefined ? { baseUrl: definition.baseUrl } : {}),
|
|
238
|
+
...(definition.promptCacheKey !== undefined ? { promptCacheKey: definition.promptCacheKey } : {}),
|
|
239
|
+
});
|
|
240
|
+
}
|
|
220
241
|
default:
|
|
221
|
-
throw new Error("createAgent: pass an `adapter` or a `provider` (\"anthropic\" | \"openai-compat\")");
|
|
242
|
+
throw new Error("createAgent: pass an `adapter` or a `provider` (\"anthropic\" | \"openai-compat\" | \"openai-responses\")");
|
|
222
243
|
}
|
|
223
244
|
}
|
|
@@ -25,11 +25,14 @@ export interface ProviderManifest {
|
|
|
25
25
|
readonly apiIds: readonly string[];
|
|
26
26
|
readonly defaultEndpoint?: string;
|
|
27
27
|
}
|
|
28
|
-
/** The
|
|
28
|
+
/** The six built-in identities. `custom` is the honest bucket for any
|
|
29
29
|
* OpenAI-compatible endpoint the origin table does not recognize —
|
|
30
|
-
* capabilities default to UNKNOWN there, never borrowed from OpenAI.
|
|
30
|
+
* capabilities default to UNKNOWN there, never borrowed from OpenAI.
|
|
31
|
+
* `chatgpt` is a PROVIDER, not a dialect: the subscription backend
|
|
32
|
+
* serves the Responses dialect and nothing else, and it authenticates
|
|
33
|
+
* by OAuth only — there is no API key that reaches it. */
|
|
31
34
|
export declare const BUILTIN_MANIFESTS: readonly ProviderManifest[];
|
|
32
35
|
/** The run's continuation scope, resolved from the live binding. An
|
|
33
36
|
* undefined provider (a directly injected SDK/faux adapter) is an
|
|
34
37
|
* UNSCOPED run: the kernel strips adapter-emitted continuation. */
|
|
35
|
-
export declare function resolveContinuationScope(provider: "anthropic" | "openai-compat" | undefined, model: string, baseUrl?: string): ContinuationScope | undefined;
|
|
38
|
+
export declare function resolveContinuationScope(provider: "anthropic" | "openai-compat" | "openai-responses" | undefined, model: string, baseUrl?: string): ContinuationScope | undefined;
|
|
@@ -11,12 +11,16 @@
|
|
|
11
11
|
*
|
|
12
12
|
* Lives under runtime/internal — the curated root surface does not move.
|
|
13
13
|
*/
|
|
14
|
-
/** The
|
|
14
|
+
/** The six built-in identities. `custom` is the honest bucket for any
|
|
15
15
|
* OpenAI-compatible endpoint the origin table does not recognize —
|
|
16
|
-
* capabilities default to UNKNOWN there, never borrowed from OpenAI.
|
|
16
|
+
* capabilities default to UNKNOWN there, never borrowed from OpenAI.
|
|
17
|
+
* `chatgpt` is a PROVIDER, not a dialect: the subscription backend
|
|
18
|
+
* serves the Responses dialect and nothing else, and it authenticates
|
|
19
|
+
* by OAuth only — there is no API key that reaches it. */
|
|
17
20
|
export const BUILTIN_MANIFESTS = [
|
|
18
21
|
{ id: "anthropic", revision: "2026-08-26.1", authMethods: ["api-key", "none"], apiIds: ["anthropic-messages"], defaultEndpoint: "https://api.anthropic.com" },
|
|
19
22
|
{ id: "openai", revision: "2026-08-26.1", authMethods: ["api-key", "none"], apiIds: ["openai-chat", "openai-responses"], defaultEndpoint: "https://api.openai.com" },
|
|
23
|
+
{ id: "chatgpt", revision: "2026-09-08.1", authMethods: ["oauth"], apiIds: ["openai-responses"], defaultEndpoint: "https://chatgpt.com/backend-api" },
|
|
20
24
|
{ id: "deepseek", revision: "2026-08-26.1", authMethods: ["api-key", "none"], apiIds: ["openai-chat"], defaultEndpoint: "https://api.deepseek.com" },
|
|
21
25
|
{ id: "zai", revision: "2026-08-26.1", authMethods: ["api-key", "none"], apiIds: ["openai-chat"], defaultEndpoint: "https://api.z.ai" },
|
|
22
26
|
{ id: "custom", revision: "2026-08-26.1", authMethods: ["api-key", "none"], apiIds: ["openai-chat"] },
|
|
@@ -31,6 +35,21 @@ const KNOWN_ORIGINS = {
|
|
|
31
35
|
"https://open.bigmodel.cn": "zai",
|
|
32
36
|
"https://api.openai.com": "openai",
|
|
33
37
|
};
|
|
38
|
+
/** The ChatGPT backend's origin. Deliberately NOT in KNOWN_ORIGINS: that
|
|
39
|
+
* table resolves openai-CHAT profiles, and the subscription backend does
|
|
40
|
+
* not speak that dialect — a compat profile pointed at it is broken, not
|
|
41
|
+
* a `chatgpt` binding. */
|
|
42
|
+
const CHATGPT_ORIGIN = "https://chatgpt.com";
|
|
43
|
+
function isChatgptOrigin(baseUrl) {
|
|
44
|
+
if (baseUrl === undefined)
|
|
45
|
+
return false;
|
|
46
|
+
try {
|
|
47
|
+
return new URL(baseUrl).origin === CHATGPT_ORIGIN;
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
34
53
|
/** The run's continuation scope, resolved from the live binding. An
|
|
35
54
|
* undefined provider (a directly injected SDK/faux adapter) is an
|
|
36
55
|
* UNSCOPED run: the kernel strips adapter-emitted continuation. */
|
|
@@ -40,6 +59,14 @@ export function resolveContinuationScope(provider, model, baseUrl) {
|
|
|
40
59
|
if (provider === "anthropic") {
|
|
41
60
|
return { providerId: "anthropic", apiId: "anthropic-messages", modelId: model };
|
|
42
61
|
}
|
|
62
|
+
if (provider === "openai-responses") {
|
|
63
|
+
// Two identities serve this dialect and no others, so there is no
|
|
64
|
+
// `custom` bucket here: the ChatGPT origin, or OpenAI. This is the
|
|
65
|
+
// ONLY place the rule lives — the adapter is HANDED the result
|
|
66
|
+
// (agent.ts's openai-responses case) rather than re-deriving it,
|
|
67
|
+
// so the stamping side and the replay side cannot disagree.
|
|
68
|
+
return { providerId: isChatgptOrigin(baseUrl) ? "chatgpt" : "openai", apiId: "openai-responses", modelId: model };
|
|
69
|
+
}
|
|
43
70
|
if (baseUrl === undefined) {
|
|
44
71
|
return { providerId: "openai", apiId: "openai-chat", modelId: model };
|
|
45
72
|
}
|
|
@@ -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. */
|
|
@@ -42,7 +49,11 @@ export interface ModelCapabilities {
|
|
|
42
49
|
* effort affects whole responses with or without explicit thinking, and
|
|
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
|
|
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";
|
|
46
57
|
export interface ReasoningSetting {
|
|
47
58
|
readonly thinking: ThinkingMode;
|
|
48
59
|
readonly effort: ReasoningEffort;
|
|
@@ -97,6 +97,50 @@ function anthropicLine() {
|
|
|
97
97
|
row("claude-haiku-4-5", haiku, haikuPricing),
|
|
98
98
|
];
|
|
99
99
|
}
|
|
100
|
+
const OPENAI_MODELS_ASOF = "2026-09-08";
|
|
101
|
+
/** the vendor CLI's per-model presets, pinned (the levels it offers a
|
|
102
|
+
* subscription; a first-party model page says nothing about that backend). */
|
|
103
|
+
const CHATGPT_PRESETS_SOURCE = "https://github.com/openai/codex/blob/37f4bb94c9f4e180535a12e3f2c2f93f4a773df0/codex-rs/models-manager/models.json";
|
|
104
|
+
const RESPONSES_WIRE = "reasoning.effort";
|
|
105
|
+
const openaiRow = (model, effortDefault, rate, page, opts = {}) => ({
|
|
106
|
+
model,
|
|
107
|
+
providerId: "openai",
|
|
108
|
+
endpoint: "https://api.openai.com",
|
|
109
|
+
capabilities: { contextWindow: 1_050_000, maxOutputTokens: 128_000, promptCaching: "automatic", reasoning: {
|
|
110
|
+
emitsThinkingStream: false,
|
|
111
|
+
thinking: null,
|
|
112
|
+
effort: { levels: opts.levels ?? ["none", "low", "medium", "high", "xhigh"], default: effortDefault, wire: RESPONSES_WIRE },
|
|
113
|
+
asOf: opts.asOf ?? OPENAI_MODELS_ASOF,
|
|
114
|
+
source: page,
|
|
115
|
+
}, inputModalities: null },
|
|
116
|
+
capabilitiesAsOf: opts.asOf ?? OPENAI_MODELS_ASOF,
|
|
117
|
+
capabilitiesSource: page,
|
|
118
|
+
pricing: { ...rate, cacheWritePerM: 0, asOf: opts.asOf ?? OPENAI_MODELS_ASOF, source: page },
|
|
119
|
+
});
|
|
120
|
+
/** The subscription backend's row for the same id: the presets' four
|
|
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 = {}) => ({
|
|
124
|
+
model,
|
|
125
|
+
providerId: "chatgpt",
|
|
126
|
+
endpoint: "https://chatgpt.com",
|
|
127
|
+
capabilities: { contextWindow: 272_000, maxOutputTokens: null, promptCaching: "unobservable", reasoning: {
|
|
128
|
+
emitsThinkingStream: false,
|
|
129
|
+
thinking: null,
|
|
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,
|
|
133
|
+
}, inputModalities: null },
|
|
134
|
+
capabilitiesAsOf: opts.asOf ?? OPENAI_MODELS_ASOF,
|
|
135
|
+
capabilitiesSource: opts.source ?? CHATGPT_PRESETS_SOURCE,
|
|
136
|
+
pricing: null,
|
|
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"];
|
|
100
144
|
/** The v1 table. Nulls outnumber numbers ON PURPOSE: only values with a
|
|
101
145
|
* named source enter; everything else waits for one. */
|
|
102
146
|
const ENTRIES = [
|
|
@@ -174,6 +218,34 @@ const ENTRIES = [
|
|
|
174
218
|
capabilities: { contextWindow: 128_000, maxOutputTokens: null, promptCaching: "automatic", reasoning: { emitsThinkingStream: false, thinking: null, effort: null, asOf: null, source: null }, inputModalities: null },
|
|
175
219
|
pricing: null,
|
|
176
220
|
},
|
|
221
|
+
// OR-1 (2026-09-08): the Responses-dialect rows. The SAME model id gets
|
|
222
|
+
// TWO rows because it answers differently at each endpoint: the
|
|
223
|
+
// first-party API documents `none` and a per-token price; the
|
|
224
|
+
// subscription backend is paid by the subscription (pricing: null — a
|
|
225
|
+
// first-party rate must never be shown as if it were the bill) and
|
|
226
|
+
// offers the levels the vendor's own CLI presets list, pinned to a
|
|
227
|
+
// commit. The first-party row is listed FIRST on purpose: an
|
|
228
|
+
// endpoint-less lookup (a caller with no baseUrl — a direct-write
|
|
229
|
+
// profile, the faux path) resolves to it, and its levels are a
|
|
230
|
+
// superset of the subscription row's. The run-side resolver passes
|
|
231
|
+
// the binding's endpoint (the second OR-1 review), so this ordering
|
|
232
|
+
// is the endpoint-less FALLBACK, not the guard that keeps a
|
|
233
|
+
// subscription run from carrying a level its backend lacks.
|
|
234
|
+
openaiRow("gpt-5.5", "medium", { inputPerM: 5, outputPerM: 30, cacheReadPerM: 0.5 }, "https://developers.openai.com/api/docs/models/gpt-5.5"),
|
|
235
|
+
openaiRow("gpt-5.4", "none", { inputPerM: 2.5, outputPerM: 15, cacheReadPerM: 0.25 }, "https://developers.openai.com/api/docs/models/gpt-5.4"),
|
|
236
|
+
chatgptRow("gpt-5.5"),
|
|
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 }),
|
|
177
249
|
];
|
|
178
250
|
/**
|
|
179
251
|
* Look a model up. `endpoint` narrows: an entry WITH an endpoint only
|
package/dist/run.js
CHANGED
|
@@ -100,6 +100,8 @@ export class Run {
|
|
|
100
100
|
runId: this.runId,
|
|
101
101
|
provider: this.#config.provider ?? "adapter",
|
|
102
102
|
model: this.#config.model,
|
|
103
|
+
// OR-1: the binding's endpoint — the record's price keys on it.
|
|
104
|
+
...(this.#config.baseUrl !== undefined ? { endpoint: this.#config.baseUrl } : {}),
|
|
103
105
|
adapterVersion: runtimeVersion(),
|
|
104
106
|
log: log.all,
|
|
105
107
|
rentParts,
|
|
@@ -135,7 +137,12 @@ export class Run {
|
|
|
135
137
|
// XP-1: native-only resolution — an unsupported recorded
|
|
136
138
|
// setting REFUSES the run with the reason (no silent
|
|
137
139
|
// downgrade); default/default adds nothing (byte anchor).
|
|
138
|
-
|
|
140
|
+
// OR-1: resolved against the binding's ENDPOINT — the same
|
|
141
|
+
// model id offers different levels at the first-party API
|
|
142
|
+
// and at the subscription backend, and the CLI's /model
|
|
143
|
+
// pre-check is not the runtime's guarantee (a direct
|
|
144
|
+
// setModelBinding or a recorded profile reaches here without it).
|
|
145
|
+
...(this.#config.reasoning !== undefined ? resolveReasoningOrThrow(this.#config.model, this.#config.reasoning, this.#config.baseUrl) : {}),
|
|
139
146
|
// E1: the composed approval chain — the extensions'
|
|
140
147
|
// policies composed into ONE gate (deny > allow > ask).
|
|
141
148
|
...(approvalChain !== undefined ? { approvalPolicy: approvalChain } : {}),
|
|
@@ -787,8 +794,8 @@ export class Run {
|
|
|
787
794
|
}
|
|
788
795
|
/** XP-1: the run-side half of "no silent downgrade" — a setting the
|
|
789
796
|
* matrix refuses becomes an actionable failure, never a quiet default. */
|
|
790
|
-
function resolveReasoningOrThrow(model, setting) {
|
|
791
|
-
const r = resolveReasoning(model, setting);
|
|
797
|
+
function resolveReasoningOrThrow(model, setting, endpoint) {
|
|
798
|
+
const r = resolveReasoning(model, setting, endpoint);
|
|
792
799
|
if (!r.ok)
|
|
793
800
|
throw new Error(`the session's recorded reasoning setting cannot run here: ${r.reason}`);
|
|
794
801
|
return Object.keys(r.wire).length > 0 ? { reasoning: r.wire } : {};
|
package/dist/session.d.ts
CHANGED
|
@@ -130,7 +130,11 @@ export declare class AgentSession {
|
|
|
130
130
|
setModelBinding(binding: {
|
|
131
131
|
readonly adapter: Adapter;
|
|
132
132
|
readonly model: string;
|
|
133
|
-
readonly provider?: "anthropic" | "openai-compat";
|
|
133
|
+
readonly provider?: "anthropic" | "openai-compat" | "openai-responses";
|
|
134
|
+
/** OR-1: the endpoint moves with the adapter too — the cost path
|
|
135
|
+
* and the window lookup key on (model, endpoint); absent = an
|
|
136
|
+
* endpoint-less lookup (the first row for the id). */
|
|
137
|
+
readonly baseUrl?: string;
|
|
134
138
|
/** MG-1 (A5): the run's continuation scope — moves atomically with
|
|
135
139
|
* the adapter (absent = unscoped: the kernel strips envelopes). */
|
|
136
140
|
readonly scope?: ContinuationScope;
|
|
@@ -138,11 +142,15 @@ export declare class AgentSession {
|
|
|
138
142
|
* fresh defaults (a new binding never inherits stale effort). */
|
|
139
143
|
readonly reasoning?: ReasoningSetting;
|
|
140
144
|
}): void;
|
|
141
|
-
/** E2: the adapter identity (
|
|
145
|
+
/** E2: the adapter identity (anthropic / openai-compat / openai-responses) — the route
|
|
142
146
|
* key the canonical consumer (CLI usage, the trace block) keys on. The
|
|
143
147
|
* per-run tracer reads the SAME live binding; one source, one
|
|
144
148
|
* route — the CLI and the trace can never disagree. */
|
|
145
|
-
get provider(): "anthropic" | "openai-compat" | undefined;
|
|
149
|
+
get provider(): "anthropic" | "openai-compat" | "openai-responses" | undefined;
|
|
150
|
+
/** OR-1: the live binding's endpoint — what the CLI hands the cost
|
|
151
|
+
* path next to `provider` and `model`, so the status row's dollar
|
|
152
|
+
* figure and the ledger's can never key on different registry rows. */
|
|
153
|
+
get baseUrl(): string | undefined;
|
|
146
154
|
/** XP-1: the model that will answer the NEXT request — the live
|
|
147
155
|
* binding, restored from the durable profile on open. The status row
|
|
148
156
|
* reads THIS, so the row and the request can never disagree. */
|
|
@@ -376,9 +384,15 @@ export interface ContextPolicy {
|
|
|
376
384
|
}
|
|
377
385
|
export interface SessionConfig {
|
|
378
386
|
readonly model: string;
|
|
379
|
-
/** E1: the adapter identity (
|
|
387
|
+
/** E1: the adapter identity (anthropic / openai-compat / openai-responses) — trace
|
|
380
388
|
* provenance, additive (S1 surface untouched: type-only, optional). */
|
|
381
|
-
readonly provider?: "anthropic" | "openai-compat";
|
|
389
|
+
readonly provider?: "anthropic" | "openai-compat" | "openai-responses";
|
|
390
|
+
/** OR-1: the adapter's endpoint. The registry row for one model id can
|
|
391
|
+
* differ per endpoint (gpt-5.5 is priced at the first-party API and
|
|
392
|
+
* unpriced at the ChatGPT backend), so the cost path and the window
|
|
393
|
+
* lookup key on (model, endpoint). Travels with the live binding,
|
|
394
|
+
* exactly as `provider` does. Type-only, optional. */
|
|
395
|
+
readonly baseUrl?: string;
|
|
382
396
|
/** MG-1 (A5): the run's continuation scope — the kernel stamps it on
|
|
383
397
|
* committed envelopes; absent = unscoped (envelopes stripped). */
|
|
384
398
|
readonly continuationScope?: import("@vincemakes/kiso-core").ContinuationScope;
|
package/dist/session.js
CHANGED
|
@@ -85,6 +85,8 @@ export class AgentSession {
|
|
|
85
85
|
// setAdapter always had).
|
|
86
86
|
#model;
|
|
87
87
|
#provider;
|
|
88
|
+
// OR-1: the fourth passenger — the endpoint moves with the adapter too.
|
|
89
|
+
#baseUrl;
|
|
88
90
|
// MG-1 (A5): travels WITH the adapter, same next-turn semantics.
|
|
89
91
|
#continuationScope;
|
|
90
92
|
// XP-1: the selected axes; resolved per request (next-turn semantics).
|
|
@@ -140,6 +142,7 @@ export class AgentSession {
|
|
|
140
142
|
this.#config = composedHooks === undefined ? config : { ...config, hooks: composedHooks };
|
|
141
143
|
this.#model = config.model;
|
|
142
144
|
this.#provider = config.provider;
|
|
145
|
+
this.#baseUrl = config.baseUrl;
|
|
143
146
|
this.#continuationScope = config.continuationScope;
|
|
144
147
|
this.#reasoning = config.reasoning ?? { thinking: "default", effort: "default" };
|
|
145
148
|
this.#profilePending = config.profilePending === true;
|
|
@@ -149,11 +152,12 @@ export class AgentSession {
|
|
|
149
152
|
* fresh per call so an in-flight run keeps the config it started with
|
|
150
153
|
* — the same boundary setAdapter has always drawn. */
|
|
151
154
|
#effectiveConfig() {
|
|
152
|
-
const { provider: _startup, continuationScope: _startupScope, ...rest } = this.#config;
|
|
155
|
+
const { provider: _startup, baseUrl: _startupUrl, continuationScope: _startupScope, ...rest } = this.#config;
|
|
153
156
|
return {
|
|
154
157
|
...rest,
|
|
155
158
|
model: this.#model,
|
|
156
159
|
...(this.#provider !== undefined ? { provider: this.#provider } : {}),
|
|
160
|
+
...(this.#baseUrl !== undefined ? { baseUrl: this.#baseUrl } : {}),
|
|
157
161
|
...(this.#continuationScope !== undefined ? { continuationScope: this.#continuationScope } : {}),
|
|
158
162
|
reasoning: this.#reasoning,
|
|
159
163
|
};
|
|
@@ -216,19 +220,26 @@ export class AgentSession {
|
|
|
216
220
|
this.#adapter = binding.adapter;
|
|
217
221
|
this.#model = binding.model;
|
|
218
222
|
this.#provider = binding.provider;
|
|
223
|
+
this.#baseUrl = binding.baseUrl;
|
|
219
224
|
this.#continuationScope = binding.scope;
|
|
220
225
|
this.#reasoning = binding.reasoning ?? { thinking: "default", effort: "default" };
|
|
221
226
|
// XP-1: an explicit selection is DURABLE — the setting survives
|
|
222
227
|
// /resume because a revision records it now, not at some later flush.
|
|
223
228
|
this.#recordProfile();
|
|
224
229
|
}
|
|
225
|
-
/** E2: the adapter identity (
|
|
230
|
+
/** E2: the adapter identity (anthropic / openai-compat / openai-responses) — the route
|
|
226
231
|
* key the canonical consumer (CLI usage, the trace block) keys on. The
|
|
227
232
|
* per-run tracer reads the SAME live binding; one source, one
|
|
228
233
|
* route — the CLI and the trace can never disagree. */
|
|
229
234
|
get provider() {
|
|
230
235
|
return this.#provider;
|
|
231
236
|
}
|
|
237
|
+
/** OR-1: the live binding's endpoint — what the CLI hands the cost
|
|
238
|
+
* path next to `provider` and `model`, so the status row's dollar
|
|
239
|
+
* figure and the ledger's can never key on different registry rows. */
|
|
240
|
+
get baseUrl() {
|
|
241
|
+
return this.#baseUrl;
|
|
242
|
+
}
|
|
232
243
|
/** XP-1: the model that will answer the NEXT request — the live
|
|
233
244
|
* binding, restored from the durable profile on open. The status row
|
|
234
245
|
* reads THIS, so the row and the request can never disagree. */
|
|
@@ -314,6 +325,8 @@ export class AgentSession {
|
|
|
314
325
|
runId,
|
|
315
326
|
provider: this.#provider ?? "adapter",
|
|
316
327
|
model: this.#model,
|
|
328
|
+
// OR-1: the side query is priced by the same live endpoint as a run.
|
|
329
|
+
...(this.#baseUrl !== undefined ? { endpoint: this.#baseUrl } : {}),
|
|
317
330
|
adapterVersion: runtimeVersion(),
|
|
318
331
|
purpose: options.purpose,
|
|
319
332
|
// the manifest's seqRange pointers derive from the log, and a side
|
|
@@ -418,6 +431,14 @@ export class AgentSession {
|
|
|
418
431
|
});
|
|
419
432
|
let summary;
|
|
420
433
|
let usage = null;
|
|
434
|
+
// OR-1 (the second review): the summary is priced by the binding that
|
|
435
|
+
// MADE the request. A /model switch during this call — the session's
|
|
436
|
+
// longest single request — moves #model/#baseUrl/#provider under it;
|
|
437
|
+
// reading them after the await priced a subscription summary at the
|
|
438
|
+
// first-party rate. ONE snapshot serves the request and the ledger
|
|
439
|
+
// line, the PH-F8 law (adapter, model, provider move together)
|
|
440
|
+
// applied to the one off-loop call.
|
|
441
|
+
const binding = { adapter: this.#adapter, model: this.#model, baseUrl: this.#baseUrl, provider: this.#provider };
|
|
421
442
|
if (options.drop === true) {
|
|
422
443
|
// E6 — the crux drop arm: mechanical, no model call, the fixed
|
|
423
444
|
// placeholder replaces the covered range (experiment-only).
|
|
@@ -425,8 +446,8 @@ export class AgentSession {
|
|
|
425
446
|
}
|
|
426
447
|
else {
|
|
427
448
|
const call = await summarizeConversation({
|
|
428
|
-
adapter:
|
|
429
|
-
model:
|
|
449
|
+
adapter: binding.adapter,
|
|
450
|
+
model: binding.model,
|
|
430
451
|
// E6 (a): ONE serialized user message — the DSML bug's
|
|
431
452
|
// raw-message array is structurally dead on this path.
|
|
432
453
|
messages: [{ role: "user", content: serializedInput }],
|
|
@@ -460,7 +481,7 @@ export class AgentSession {
|
|
|
460
481
|
// a degraded ledger costs one stderr line, never the summary.
|
|
461
482
|
if (usage !== null) {
|
|
462
483
|
try {
|
|
463
|
-
const canonical = canonicalizeUsageForModel(
|
|
484
|
+
const canonical = canonicalizeUsageForModel(binding.model, binding.baseUrl, binding.provider ?? "adapter", usage);
|
|
464
485
|
const line = JSON.stringify({ kind: "summary", canonical }) + "\n";
|
|
465
486
|
mkdirSync(join(this.#store.root, "traces"), { recursive: true });
|
|
466
487
|
appendFileSync(join(this.#store.root, "traces", `${this.id}.jsonl`), line);
|
package/dist/trace/guard.d.ts
CHANGED
|
@@ -29,6 +29,9 @@ export interface RequestTracerDeps {
|
|
|
29
29
|
runId: string;
|
|
30
30
|
provider: string;
|
|
31
31
|
model: string;
|
|
32
|
+
/** OR-1: the adapter's endpoint — the registry row (and so the price)
|
|
33
|
+
* for one model id can differ per endpoint; absent = endpoint-less. */
|
|
34
|
+
endpoint?: string;
|
|
32
35
|
/** The adapter contract's implementation version (the runtime's own),
|
|
33
36
|
* resolved once at tracer init; null on failure (soft-fail). */
|
|
34
37
|
adapterVersion?: string | null;
|
package/dist/trace/guard.js
CHANGED
|
@@ -33,6 +33,7 @@ export class RequestTracer {
|
|
|
33
33
|
#writer;
|
|
34
34
|
#log;
|
|
35
35
|
#provider;
|
|
36
|
+
#endpoint;
|
|
36
37
|
#runId;
|
|
37
38
|
#adapterVersion;
|
|
38
39
|
#rentParts;
|
|
@@ -43,6 +44,7 @@ export class RequestTracer {
|
|
|
43
44
|
this.#writer = new TraceWriter({ root: deps.root, sessionId: deps.sessionId });
|
|
44
45
|
this.#log = deps.log;
|
|
45
46
|
this.#provider = deps.provider;
|
|
47
|
+
this.#endpoint = deps.endpoint;
|
|
46
48
|
this.#runId = deps.runId;
|
|
47
49
|
this.#adapterVersion = deps.adapterVersion ?? null;
|
|
48
50
|
this.#rentParts = deps.rentParts;
|
|
@@ -208,7 +210,10 @@ export class RequestTracer {
|
|
|
208
210
|
// MODEL via the metadata registry (route-keyed pricing priced an
|
|
209
211
|
// Anthropic run at DeepSeek's rates); the convention still keys on
|
|
210
212
|
// the route. An unpriced model costs null — never a fallback rate.
|
|
211
|
-
|
|
213
|
+
// OR-1: and on the binding's ENDPOINT — the same id is priced at the
|
|
214
|
+
// first-party API and unpriced at the subscription backend; an
|
|
215
|
+
// endpoint-less lookup here priced a subscription run at $5/$30.
|
|
216
|
+
record.canonical = canonicalizeUsageForModel(record.model, this.#endpoint, this.#provider, {
|
|
212
217
|
inputTokens: p.inputTokens,
|
|
213
218
|
outputTokens: p.outputTokens,
|
|
214
219
|
cacheRead: p.cacheRead,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vincemakes/kiso-runtime",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.31.1",
|
|
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,11 +25,12 @@
|
|
|
25
25
|
"test": "vitest run"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@vincemakes/kiso-core": "0.
|
|
28
|
+
"@vincemakes/kiso-core": "0.31.1"
|
|
29
29
|
},
|
|
30
30
|
"peerDependencies": {
|
|
31
|
-
"@vincemakes/kiso-provider-anthropic": "0.
|
|
32
|
-
"@vincemakes/kiso-provider-openai": "0.
|
|
31
|
+
"@vincemakes/kiso-provider-anthropic": "0.31.1",
|
|
32
|
+
"@vincemakes/kiso-provider-openai": "0.31.1",
|
|
33
|
+
"@vincemakes/kiso-provider-openai-responses": "0.31.1"
|
|
33
34
|
},
|
|
34
35
|
"peerDependenciesMeta": {
|
|
35
36
|
"@vincemakes/kiso-provider-anthropic": {
|
|
@@ -37,10 +38,13 @@
|
|
|
37
38
|
},
|
|
38
39
|
"@vincemakes/kiso-provider-openai": {
|
|
39
40
|
"optional": true
|
|
41
|
+
},
|
|
42
|
+
"@vincemakes/kiso-provider-openai-responses": {
|
|
43
|
+
"optional": true
|
|
40
44
|
}
|
|
41
45
|
},
|
|
42
46
|
"devDependencies": {
|
|
43
|
-
"@vincemakes/kiso-evals": "0.
|
|
47
|
+
"@vincemakes/kiso-evals": "0.31.1",
|
|
44
48
|
"@types/node": "^26.1.2",
|
|
45
49
|
"typescript": "^5.7.2",
|
|
46
50
|
"vitest": "^3.0.0"
|