1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
|
/**
* Runtime contracts — the input/output types for `runTurn`.
*
* The kernel's turn loop is a pure function of these inputs. It takes
* messages as a plain input, returns result messages, and touches no DB.
* The implementation lives in the kernel runtime module; these types are
* the contract the session-orchestrator (core) programs against.
*/
import type { ChatMessage } from "./conversation.js";
import type { ToolDispatchPolicy } from "./dispatch.js";
import type { AgentEvent } from "./events.js";
import type { Logger } from "./logging.js";
import type { ProviderContract, ProviderStreamOptions, Usage } from "./provider.js";
import type { ToolContract } from "./tool.js";
/**
* The emitter function the kernel calls to push events outward.
* The session-orchestrator provides this, wiring it to transport + persistence.
*/
export type EventEmitter = (event: AgentEvent) => void;
/**
* Why a turn ended. Known kernel/provider reasons are enumerated for ergonomics;
* the trailing `(string & {})` keeps the type open for provider-specific reasons
* passed through verbatim without losing autocomplete on the known values.
*/
export type FinishReason =
| "stop"
| "tool-calls"
| "length"
| "content-filter"
| "max-steps"
| "error"
| "aborted"
| (string & {});
/**
* Input to `runTurn` — everything the kernel needs to execute one turn.
* All fields are resolved by the session-orchestrator before calling;
* the kernel never reads config or resolves providers/tools itself.
*/
export interface RunTurnInput {
/** The resolved provider to stream from. */
readonly provider: ProviderContract;
/** The conversation history (including system prompt as first message). */
readonly messages: readonly ChatMessage[];
/** The tool set available for this turn (may be empty). */
readonly tools: readonly ToolContract[];
/** How to dispatch tool calls within each step. */
readonly dispatch: ToolDispatchPolicy;
/** The emitter the kernel calls for each outward event. */
readonly emit: EventEmitter;
/**
* Identifiers used to attribute every emitted `AgentEvent`. The kernel does
* not generate these — the session-orchestrator owns turn/conversation identity
* and passes them in, so events are traceable to their conversation.
*/
readonly conversationId: string;
readonly turnId: string;
/**
* Optional per-turn provider options (model, temperature, maxTokens,
* systemPrompt). The orchestrator resolves these; the kernel forwards them
* verbatim to `provider.stream` and never interprets them. A provider may
* also be pre-configured at construction and ignore these.
*/
readonly providerOpts?: ProviderStreamOptions;
/** Cancellation signal for the entire turn. */
readonly signal?: AbortSignal;
/**
* Working directory for this turn's tool execution. The kernel does NOT
* interpret it — it forwards the value verbatim to each `ToolExecuteContext.cwd`
* so tools resolve/contain paths against it. It never enters the model prompt,
* so it does not affect prompt caching. When omitted, tools fall back to their
* own configured/default workdir.
*/
readonly cwd?: string;
/**
* Optional logger for structured span instrumentation. The runtime opens
* turn/step/tool-call spans using this logger. If omitted, no spans are
* emitted (backward-compatible with callers that don't yet pass a logger).
*/
readonly logger?: Logger;
/**
* Optional monotonic-ish clock (milliseconds) for emitting wall-clock timing
* on outward events: per-step `step-complete` (ttft/decode/genTotal), tool
* execution `durationMs` on `tool-result`, and turn `durationMs` on `done`.
* Injected (not ambient) so the runtime stays pure and deterministic in tests.
* If omitted, the runtime emits no such timing (the optional fields stay
* absent) — backward-compatible with callers that don't provide a clock.
*/
readonly now?: () => number;
/**
* Optional. Called by the runtime at the tool-result boundary — after a
* step whose tool calls have all executed, before the next step begins —
* to drain messages to inject alongside the tool results. Whatever it
* returns is appended as user-role messages to the next step's input, so
* a caller can inject mid-turn guidance the model sees with the tool
* results. When omitted or returning an empty array, no injection happens
* (the runtime is unchanged).
*
* Injected (not ambient) so the kernel stays pure: it owns no queue and
* names no feature — it just calls the callback and appends what it gets.
* Only invoked when a step PRODUCED tool calls (the tool-result boundary);
* a step that ends without tool calls does not drain (the caller decides
* what to do with any pending messages after the turn ends).
*/
readonly drainSteering?: () => readonly ChatMessage[];
/**
* Optional. Called by the runtime after each step's messages are finalized
* (the assistant message + tool-result messages are built). The caller can
* use this to persist step messages incrementally — assigning seq numbers
* during generation so consumers can `GET /conversations/:id?sinceSeq=N`
* mid-turn. When omitted, the caller must persist all messages at turn end
* (via `RunTurnResult.messages`). The messages passed to this callback are
* the SAME objects in `RunTurnResult.messages` — the caller must NOT
* double-persist them.
*/
readonly onStepComplete?: (messages: readonly ChatMessage[]) => Promise<void> | void;
}
/**
* The result of a completed turn. The session-orchestrator uses this to
* persist the new messages and report usage.
*/
export interface RunTurnResult {
/** The assistant messages produced by this turn (appended to history). */
readonly messages: readonly ChatMessage[];
/** Aggregated token usage across all steps in the turn. */
readonly usage: Usage;
/** Why the turn ended. */
readonly finishReason: FinishReason;
}
|