@noetic-tools/core 0.0.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.
Files changed (207) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +38 -0
  3. package/dist/adapters/in-memory-fs-adapter.d.ts +3 -0
  4. package/dist/adapters/in-memory-fs-adapter.js +245 -0
  5. package/dist/adapters/in-memory-shell-adapter.d.ts +9 -0
  6. package/dist/adapters/in-memory-shell-adapter.js +20 -0
  7. package/dist/adapters/in-memory-subprocess-adapter.d.ts +5 -0
  8. package/dist/adapters/in-memory-subprocess-adapter.js +126 -0
  9. package/dist/adapters/local-fs-adapter.d.ts +3 -0
  10. package/dist/adapters/local-fs-adapter.js +43 -0
  11. package/dist/adapters/local-shell-adapter.d.ts +51 -0
  12. package/dist/adapters/local-shell-adapter.js +167 -0
  13. package/dist/adapters/local-subprocess-adapter.d.ts +16 -0
  14. package/dist/adapters/local-subprocess-adapter.js +187 -0
  15. package/dist/adapters/openrouter.d.ts +57 -0
  16. package/dist/adapters/openrouter.js +248 -0
  17. package/dist/builders/channel-builder.d.ts +36 -0
  18. package/dist/builders/channel-builder.js +15 -0
  19. package/dist/builders/control-flow-builders.d.ts +79 -0
  20. package/dist/builders/control-flow-builders.js +53 -0
  21. package/dist/builders/every.d.ts +45 -0
  22. package/dist/builders/every.js +60 -0
  23. package/dist/builders/layer-provides-builders.d.ts +32 -0
  24. package/dist/builders/layer-provides-builders.js +33 -0
  25. package/dist/builders/loop-builder.d.ts +23 -0
  26. package/dist/builders/loop-builder.js +46 -0
  27. package/dist/builders/memory-builder.d.ts +11 -0
  28. package/dist/builders/memory-builder.js +15 -0
  29. package/dist/builders/provide-builder.d.ts +20 -0
  30. package/dist/builders/provide-builder.js +34 -0
  31. package/dist/builders/spawn-builder.d.ts +20 -0
  32. package/dist/builders/spawn-builder.js +33 -0
  33. package/dist/builders/step-builders.d.ts +62 -0
  34. package/dist/builders/step-builders.js +99 -0
  35. package/dist/builders/tool-builder.d.ts +52 -0
  36. package/dist/builders/tool-builder.js +59 -0
  37. package/dist/conditions/conditions.d.ts +114 -0
  38. package/dist/conditions/conditions.js +271 -0
  39. package/dist/conditions/cosine-similarity.d.ts +11 -0
  40. package/dist/conditions/cosine-similarity.js +28 -0
  41. package/dist/conditions/index.d.ts +3 -0
  42. package/dist/conditions/index.js +2 -0
  43. package/dist/errors/noetic-config-error.d.ts +41 -0
  44. package/dist/errors/noetic-config-error.js +44 -0
  45. package/dist/errors/noetic-error.d.ts +18 -0
  46. package/dist/errors/noetic-error.js +52 -0
  47. package/dist/index.d.ts +152 -0
  48. package/dist/index.js +112 -0
  49. package/dist/internal-test.d.ts +11 -0
  50. package/dist/internal-test.js +11 -0
  51. package/dist/interpreter/clone-guard.d.ts +4 -0
  52. package/dist/interpreter/clone-guard.js +18 -0
  53. package/dist/interpreter/collect-tools.d.ts +12 -0
  54. package/dist/interpreter/collect-tools.js +70 -0
  55. package/dist/interpreter/execute-branch.d.ts +3 -0
  56. package/dist/interpreter/execute-branch.js +10 -0
  57. package/dist/interpreter/execute-every.d.ts +15 -0
  58. package/dist/interpreter/execute-every.js +143 -0
  59. package/dist/interpreter/execute-fork.d.ts +3 -0
  60. package/dist/interpreter/execute-fork.js +215 -0
  61. package/dist/interpreter/execute-llm.d.ts +4 -0
  62. package/dist/interpreter/execute-llm.js +217 -0
  63. package/dist/interpreter/execute-loop.d.ts +3 -0
  64. package/dist/interpreter/execute-loop.js +171 -0
  65. package/dist/interpreter/execute-provide.d.ts +10 -0
  66. package/dist/interpreter/execute-provide.js +64 -0
  67. package/dist/interpreter/execute-run.d.ts +3 -0
  68. package/dist/interpreter/execute-run.js +39 -0
  69. package/dist/interpreter/execute-spawn.d.ts +11 -0
  70. package/dist/interpreter/execute-spawn.js +113 -0
  71. package/dist/interpreter/execute-tool.d.ts +5 -0
  72. package/dist/interpreter/execute-tool.js +81 -0
  73. package/dist/interpreter/execute.d.ts +14 -0
  74. package/dist/interpreter/execute.js +119 -0
  75. package/dist/interpreter/framework-cast.d.ts +10 -0
  76. package/dist/interpreter/framework-cast.js +16 -0
  77. package/dist/interpreter/message-helpers.d.ts +9 -0
  78. package/dist/interpreter/message-helpers.js +42 -0
  79. package/dist/interpreter/typeguards.d.ts +25 -0
  80. package/dist/interpreter/typeguards.js +50 -0
  81. package/dist/memory/budget.d.ts +23 -0
  82. package/dist/memory/budget.js +117 -0
  83. package/dist/memory/function-call-utils.d.ts +6 -0
  84. package/dist/memory/function-call-utils.js +25 -0
  85. package/dist/memory/layer-api.d.ts +14 -0
  86. package/dist/memory/layer-api.js +91 -0
  87. package/dist/memory/layer-lifecycle.d.ts +160 -0
  88. package/dist/memory/layer-lifecycle.js +552 -0
  89. package/dist/memory/layer-usage.d.ts +15 -0
  90. package/dist/memory/layer-usage.js +48 -0
  91. package/dist/memory/layers/durable-task-state.d.ts +79 -0
  92. package/dist/memory/layers/durable-task-state.js +118 -0
  93. package/dist/memory/layers/file-reference.d.ts +55 -0
  94. package/dist/memory/layers/file-reference.js +538 -0
  95. package/dist/memory/layers/history-window.d.ts +29 -0
  96. package/dist/memory/layers/history-window.js +114 -0
  97. package/dist/memory/layers/observational-memory.d.ts +53 -0
  98. package/dist/memory/layers/observational-memory.js +110 -0
  99. package/dist/memory/layers/plan.d.ts +55 -0
  100. package/dist/memory/layers/plan.js +369 -0
  101. package/dist/memory/layers/static-content.d.ts +27 -0
  102. package/dist/memory/layers/static-content.js +35 -0
  103. package/dist/memory/layers/steering.d.ts +51 -0
  104. package/dist/memory/layers/steering.js +258 -0
  105. package/dist/memory/layers/tool-memory-layer.d.ts +16 -0
  106. package/dist/memory/layers/tool-memory-layer.js +43 -0
  107. package/dist/memory/layers/working-memory.d.ts +44 -0
  108. package/dist/memory/layers/working-memory.js +92 -0
  109. package/dist/memory/projector.d.ts +10 -0
  110. package/dist/memory/projector.js +19 -0
  111. package/dist/memory/scope.d.ts +3 -0
  112. package/dist/memory/scope.js +31 -0
  113. package/dist/memory/strip-unresolved.d.ts +13 -0
  114. package/dist/memory/strip-unresolved.js +35 -0
  115. package/dist/observability/genai-attributes.d.ts +11 -0
  116. package/dist/observability/genai-attributes.js +12 -0
  117. package/dist/observability/span-impl.d.ts +20 -0
  118. package/dist/observability/span-impl.js +33 -0
  119. package/dist/observability/trace-exporter.d.ts +15 -0
  120. package/dist/observability/trace-exporter.js +26 -0
  121. package/dist/patterns/flow.d.ts +52 -0
  122. package/dist/patterns/flow.js +103 -0
  123. package/dist/patterns/interview.d.ts +53 -0
  124. package/dist/patterns/interview.js +113 -0
  125. package/dist/patterns/plans.d.ts +45 -0
  126. package/dist/patterns/plans.js +125 -0
  127. package/dist/patterns/ralph-wiggum.d.ts +20 -0
  128. package/dist/patterns/ralph-wiggum.js +37 -0
  129. package/dist/patterns/react.d.ts +18 -0
  130. package/dist/patterns/react.js +39 -0
  131. package/dist/portable.d.ts +26 -0
  132. package/dist/portable.js +34 -0
  133. package/dist/runtime/agent-harness.d.ts +206 -0
  134. package/dist/runtime/agent-harness.js +1170 -0
  135. package/dist/runtime/broadcaster-utils.d.ts +30 -0
  136. package/dist/runtime/broadcaster-utils.js +53 -0
  137. package/dist/runtime/channel-store.d.ts +18 -0
  138. package/dist/runtime/channel-store.js +187 -0
  139. package/dist/runtime/context-impl.d.ts +80 -0
  140. package/dist/runtime/context-impl.js +140 -0
  141. package/dist/runtime/cwd-helpers.d.ts +42 -0
  142. package/dist/runtime/cwd-helpers.js +68 -0
  143. package/dist/runtime/detached-handle.d.ts +14 -0
  144. package/dist/runtime/detached-handle.js +56 -0
  145. package/dist/runtime/event-broadcaster.d.ts +61 -0
  146. package/dist/runtime/event-broadcaster.js +187 -0
  147. package/dist/runtime/exec-context-factory.d.ts +25 -0
  148. package/dist/runtime/exec-context-factory.js +30 -0
  149. package/dist/runtime/in-memory-storage.d.ts +9 -0
  150. package/dist/runtime/in-memory-storage.js +33 -0
  151. package/dist/runtime/item-log-impl.d.ts +11 -0
  152. package/dist/runtime/item-log-impl.js +25 -0
  153. package/dist/runtime/message-queue.d.ts +30 -0
  154. package/dist/runtime/message-queue.js +46 -0
  155. package/dist/runtime/session-runner.d.ts +77 -0
  156. package/dist/runtime/session-runner.js +253 -0
  157. package/dist/runtime/session-streams.d.ts +11 -0
  158. package/dist/runtime/session-streams.js +204 -0
  159. package/dist/runtime/tool-memory.d.ts +6 -0
  160. package/dist/runtime/tool-memory.js +22 -0
  161. package/dist/schemas/item.d.ts +35 -0
  162. package/dist/schemas/item.js +155 -0
  163. package/dist/types/channel.d.ts +29 -0
  164. package/dist/types/channel.js +1 -0
  165. package/dist/types/common.d.ts +111 -0
  166. package/dist/types/common.js +1 -0
  167. package/dist/types/context.d.ts +93 -0
  168. package/dist/types/context.js +1 -0
  169. package/dist/types/detached.d.ts +17 -0
  170. package/dist/types/detached.js +7 -0
  171. package/dist/types/embed.d.ts +5 -0
  172. package/dist/types/embed.js +1 -0
  173. package/dist/types/error.d.ts +51 -0
  174. package/dist/types/error.js +1 -0
  175. package/dist/types/fs-adapter.d.ts +51 -0
  176. package/dist/types/fs-adapter.js +3 -0
  177. package/dist/types/harness-result.d.ts +43 -0
  178. package/dist/types/harness-result.js +2 -0
  179. package/dist/types/items.d.ts +115 -0
  180. package/dist/types/items.js +2 -0
  181. package/dist/types/memory.d.ts +336 -0
  182. package/dist/types/memory.js +13 -0
  183. package/dist/types/mutable-context.d.ts +13 -0
  184. package/dist/types/mutable-context.js +1 -0
  185. package/dist/types/observability.d.ts +30 -0
  186. package/dist/types/observability.js +1 -0
  187. package/dist/types/runtime.d.ts +255 -0
  188. package/dist/types/runtime.js +1 -0
  189. package/dist/types/shell-adapter.d.ts +34 -0
  190. package/dist/types/shell-adapter.js +10 -0
  191. package/dist/types/steering.d.ts +89 -0
  192. package/dist/types/steering.js +14 -0
  193. package/dist/types/step.d.ts +206 -0
  194. package/dist/types/step.js +1 -0
  195. package/dist/types/subprocess-adapter.d.ts +41 -0
  196. package/dist/types/subprocess-adapter.js +1 -0
  197. package/dist/types/tool-context.d.ts +34 -0
  198. package/dist/types/tool-context.js +1 -0
  199. package/dist/unstable.d.ts +28 -0
  200. package/dist/unstable.js +24 -0
  201. package/dist/until/combinators.d.ts +17 -0
  202. package/dist/until/combinators.js +49 -0
  203. package/dist/until/never.d.ts +9 -0
  204. package/dist/until/never.js +12 -0
  205. package/dist/until/predicates.d.ts +43 -0
  206. package/dist/until/predicates.js +126 -0
  207. package/package.json +81 -0
@@ -0,0 +1,253 @@
1
+ import { emitFrameworkEvent } from './broadcaster-utils';
2
+ import { EventBroadcaster } from './event-broadcaster';
3
+ import { MessageQueue } from './message-queue';
4
+ //#endregion
5
+ //#region Helpers
6
+ function inputToItems(input) {
7
+ if (typeof input === 'string') {
8
+ if (input.length === 0) {
9
+ return [];
10
+ }
11
+ const item = {
12
+ id: `user-${crypto.randomUUID()}`,
13
+ type: 'message',
14
+ role: 'user',
15
+ status: 'completed',
16
+ content: [
17
+ {
18
+ type: 'input_text',
19
+ text: input,
20
+ },
21
+ ],
22
+ };
23
+ return [
24
+ item,
25
+ ];
26
+ }
27
+ if (Array.isArray(input)) {
28
+ return input;
29
+ }
30
+ return [
31
+ input,
32
+ ];
33
+ }
34
+ function mergeInputsToItems(messages) {
35
+ const items = [];
36
+ for (const msg of messages) {
37
+ items.push(...inputToItems(msg.input));
38
+ }
39
+ return items;
40
+ }
41
+ //#endregion
42
+ //#region SessionRunner
43
+ /**
44
+ * @internal
45
+ *
46
+ * Per-thread runner. Drains the MessageQueue and drives turns on the shared
47
+ * session broadcaster. Emits `turn_started` / `turn_completed` /
48
+ * `inbox_injected` framework events so consumers can correlate queued
49
+ * messages with the turn that delivered them.
50
+ *
51
+ * Abort semantics: when `abort()` is called, the current AbortController is
52
+ * fired (cancelling the in-flight LLM call) and a flag is set so the runner
53
+ * does NOT auto-restart from the queue until the caller re-kicks. For the
54
+ * ESC-restart UX, callers typically enqueue the new message first, then
55
+ * abort — the new message is the head of the queue when the runner wakes.
56
+ */
57
+ export class SessionRunner {
58
+ threadId;
59
+ broadcaster = new EventBroadcaster();
60
+ queue = new MessageQueue();
61
+ agentName;
62
+ runTurn;
63
+ createContext;
64
+ status = {
65
+ kind: 'idle',
66
+ };
67
+ currentController;
68
+ currentCtx;
69
+ loopPromise;
70
+ lastResponse;
71
+ lastError;
72
+ responseWaiters = [];
73
+ constructor(opts) {
74
+ this.threadId = opts.threadId;
75
+ this.agentName = opts.agentName;
76
+ this.runTurn = opts.runTurn;
77
+ this.createContext = opts.createContext;
78
+ this.queue.subscribe(() => {
79
+ this.kick();
80
+ });
81
+ }
82
+ getStatus() {
83
+ return this.status;
84
+ }
85
+ /** Resolves once the queue has been drained and the runner is idle with a
86
+ * response available. If nothing has run yet, waits for the first turn.
87
+ *
88
+ * The cached `lastError` is only surfaced when the session is genuinely
89
+ * idle with no pending work. If a new turn is running or a message is
90
+ * queued, the caller is asking about the NEXT response — we must wait
91
+ * for that turn rather than reject with the stale error. */
92
+ getAgentResponse() {
93
+ if (this.status.kind === 'idle' && this.queue.size === 0 && this.lastResponse) {
94
+ return Promise.resolve(this.lastResponse);
95
+ }
96
+ if (this.status.kind === 'idle' && this.queue.size === 0 && this.lastError) {
97
+ return Promise.reject(this.lastError);
98
+ }
99
+ return new Promise((resolve, reject) => {
100
+ this.responseWaiters.push({
101
+ resolve,
102
+ reject,
103
+ });
104
+ });
105
+ }
106
+ /** Cancel the in-flight turn. Does NOT re-kick — callers that want restart
107
+ * behaviour should enqueue a message before/after calling abort. */
108
+ async abort(reason) {
109
+ if (this.status.kind !== 'generating') {
110
+ return;
111
+ }
112
+ const turnId = this.status.turnId;
113
+ this.status = {
114
+ kind: 'aborting',
115
+ turnId,
116
+ };
117
+ const ctx = this.currentCtx;
118
+ if (ctx) {
119
+ ctx.abort(reason);
120
+ }
121
+ this.currentController?.abort(reason);
122
+ await this.loopPromise?.catch(() => {
123
+ // The abort surfaces as a rejection; we swallow it here because callers
124
+ // observe it via the broadcaster / lastError, not the abort() return.
125
+ });
126
+ }
127
+ /** Start the drain loop if not already running. Safe to call many times. */
128
+ kick() {
129
+ if (this.loopPromise) {
130
+ return;
131
+ }
132
+ if (this.queue.size === 0) {
133
+ return;
134
+ }
135
+ this.loopPromise = this.drain().finally(() => {
136
+ this.loopPromise = undefined;
137
+ // If messages arrived during finally, run again.
138
+ if (this.queue.size > 0) {
139
+ this.kick();
140
+ }
141
+ });
142
+ }
143
+ async drain() {
144
+ while (this.queue.size > 0) {
145
+ const messages = this.queue.drainAll();
146
+ if (messages.length === 0) {
147
+ break;
148
+ }
149
+ await this.runOneTurn(messages);
150
+ }
151
+ }
152
+ async runOneTurn(messages) {
153
+ const turnId = crypto.randomUUID();
154
+ const startedAt = Date.now();
155
+ this.status = {
156
+ kind: 'generating',
157
+ startedAt,
158
+ turnId,
159
+ };
160
+ const controller = new AbortController();
161
+ this.currentController = controller;
162
+ // The runner is the single point of ExecuteInput → Item[] conversion —
163
+ // the harness's createContext callback receives Item[] directly and
164
+ // seeds the context without re-converting.
165
+ const items = mergeInputsToItems(messages);
166
+ const ctx = this.createContext(items, turnId, messages);
167
+ this.currentCtx = ctx;
168
+ emitFrameworkEvent({
169
+ broadcaster: this.broadcaster,
170
+ agentName: this.agentName,
171
+ eventType: 'turn_started',
172
+ data: {
173
+ turnId,
174
+ messageIds: messages.map((m) => m.id),
175
+ },
176
+ });
177
+ const turn = {
178
+ turnId,
179
+ session: this,
180
+ };
181
+ try {
182
+ const text = await this.runTurn(ctx, turn, controller.signal);
183
+ const response = buildResponse(text, ctx);
184
+ this.lastResponse = response;
185
+ this.lastError = undefined;
186
+ emitFrameworkEvent({
187
+ broadcaster: this.broadcaster,
188
+ agentName: this.agentName,
189
+ eventType: 'turn_completed',
190
+ data: {
191
+ turnId,
192
+ durationMs: Date.now() - startedAt,
193
+ },
194
+ });
195
+ // Only resolve waiters when the queue is fully drained; otherwise let
196
+ // the next turn's completion surface the final response. This matches
197
+ // `getAgentResponse`'s contract: "once the session has finished
198
+ // processing its queue".
199
+ if (this.queue.size === 0) {
200
+ this.resolveWaiters(response);
201
+ }
202
+ }
203
+ catch (err) {
204
+ const error = err instanceof Error ? err : new Error(String(err));
205
+ this.lastError = error;
206
+ emitFrameworkEvent({
207
+ broadcaster: this.broadcaster,
208
+ agentName: this.agentName,
209
+ eventType: 'turn_aborted',
210
+ data: {
211
+ turnId,
212
+ reason: error.message,
213
+ },
214
+ });
215
+ this.rejectWaiters(error);
216
+ }
217
+ finally {
218
+ this.currentCtx = undefined;
219
+ this.currentController = undefined;
220
+ this.status = {
221
+ kind: 'idle',
222
+ };
223
+ }
224
+ }
225
+ resolveWaiters(response) {
226
+ const pending = this.responseWaiters.splice(0, this.responseWaiters.length);
227
+ for (const w of pending) {
228
+ w.resolve(response);
229
+ }
230
+ }
231
+ rejectWaiters(err) {
232
+ const pending = this.responseWaiters.splice(0, this.responseWaiters.length);
233
+ for (const w of pending) {
234
+ w.reject(err);
235
+ }
236
+ }
237
+ }
238
+ //#endregion
239
+ //#region buildResponse
240
+ function buildResponse(text, ctx) {
241
+ return {
242
+ items: ctx.itemLog.items,
243
+ usage: {
244
+ inputTokens: ctx.tokens.input,
245
+ outputTokens: ctx.tokens.output,
246
+ cachedTokens: ctx.tokens.cached && ctx.tokens.cached > 0 ? ctx.tokens.cached : undefined,
247
+ },
248
+ cost: ctx.cost > 0 ? ctx.cost : undefined,
249
+ text,
250
+ lastLayerUsage: ctx.lastLayerUsage,
251
+ };
252
+ }
253
+ //#endregion
@@ -0,0 +1,11 @@
1
+ import type { ItemSchemaRegistry } from '../schemas/item';
2
+ import type { StreamingItem } from '../types/harness-result';
3
+ import type { EventBroadcaster } from './event-broadcaster';
4
+ /** @internal Yield text deltas from the session broadcaster. */
5
+ export declare function filterTextStream(broadcaster: EventBroadcaster): AsyncIterable<string>;
6
+ /** @internal Yield reasoning-token deltas from the session broadcaster. */
7
+ export declare function filterReasoningStream(broadcaster: EventBroadcaster): AsyncIterable<string>;
8
+ /** @internal Yield cumulative StreamingItem snapshots. Uses roundOffset keyed by
9
+ * response.created so accumulators across multiple tool rounds and across
10
+ * multiple turns within a session don't collide. */
11
+ export declare function buildItemStream(broadcaster: EventBroadcaster, itemSchemas?: ItemSchemaRegistry): AsyncIterable<StreamingItem>;
@@ -0,0 +1,204 @@
1
+ import { defaultItemSchemaRegistry } from '../schemas/item';
2
+ //#endregion
3
+ //#region Helpers
4
+ function isItemRecord(value) {
5
+ return typeof value === 'object' && value !== null && 'type' in value;
6
+ }
7
+ function appendText(part, delta) {
8
+ Object.assign(part, {
9
+ text: part.text + delta,
10
+ });
11
+ }
12
+ function createAccumulatorFromItemAdded(event, itemSchemas) {
13
+ if (event.source !== 'sdk' || event.type !== 'response.output_item.added') {
14
+ return null;
15
+ }
16
+ if (!isItemRecord(event.data.item)) {
17
+ return null;
18
+ }
19
+ const itemData = event.data.item;
20
+ const id = typeof itemData.id === 'string' ? itemData.id : crypto.randomUUID();
21
+ if (itemData.type === 'message') {
22
+ return {
23
+ id,
24
+ item: {
25
+ id,
26
+ type: 'message',
27
+ role: 'assistant',
28
+ status: 'in_progress',
29
+ content: [
30
+ {
31
+ type: 'output_text',
32
+ text: '',
33
+ },
34
+ ],
35
+ },
36
+ isComplete: false,
37
+ };
38
+ }
39
+ if (itemData.type === 'function_call') {
40
+ const parsed = itemSchemas.parse(itemData);
41
+ return {
42
+ id,
43
+ item: {
44
+ ...parsed,
45
+ id,
46
+ type: 'function_call',
47
+ status: 'in_progress',
48
+ callId: typeof itemData.callId === 'string' ? itemData.callId : '',
49
+ name: typeof itemData.name === 'string' ? itemData.name : '',
50
+ arguments: '',
51
+ },
52
+ isComplete: false,
53
+ };
54
+ }
55
+ const parsed = itemSchemas.parse(itemData);
56
+ return {
57
+ id,
58
+ item: parsed,
59
+ isComplete: false,
60
+ };
61
+ }
62
+ function updateMessageAccumulator(acc, event) {
63
+ if (event.source !== 'sdk') {
64
+ return;
65
+ }
66
+ if (event.type === 'response.output_text.delta' && typeof event.data.delta === 'string') {
67
+ const msg = acc.item;
68
+ if (msg.type !== 'message' || !('content' in msg)) {
69
+ return;
70
+ }
71
+ const lastPart = msg.content[msg.content.length - 1];
72
+ if (lastPart && 'text' in lastPart && typeof lastPart.text === 'string') {
73
+ appendText(lastPart, event.data.delta);
74
+ }
75
+ }
76
+ if (event.type === 'response.output_text.done') {
77
+ acc.isComplete = true;
78
+ }
79
+ }
80
+ function updateFunctionCallAccumulator(acc, event) {
81
+ if (event.source !== 'sdk') {
82
+ return;
83
+ }
84
+ if (event.type === 'response.function_call_arguments.delta' &&
85
+ typeof event.data.delta === 'string') {
86
+ const fc = acc.item;
87
+ if (fc.type !== 'function_call' || !('arguments' in fc) || typeof fc.arguments !== 'string') {
88
+ return;
89
+ }
90
+ fc.arguments += event.data.delta;
91
+ }
92
+ if (event.type === 'response.function_call_arguments.done') {
93
+ acc.isComplete = true;
94
+ }
95
+ }
96
+ /** Compute a composite key from round offset and output index to avoid collisions across tool rounds. */
97
+ function accumulatorKey(roundOffset, outputIndex) {
98
+ return `${roundOffset}:${outputIndex}`;
99
+ }
100
+ function completeStreamingItem(item) {
101
+ if ('status' in item) {
102
+ return {
103
+ ...item,
104
+ status: 'completed',
105
+ isComplete: true,
106
+ };
107
+ }
108
+ return {
109
+ ...item,
110
+ isComplete: true,
111
+ };
112
+ }
113
+ //#endregion
114
+ //#region Public stream filters
115
+ /** @internal Yield text deltas from the session broadcaster. */
116
+ export async function* filterTextStream(broadcaster) {
117
+ for await (const event of broadcaster) {
118
+ if (event.source !== 'sdk' || event.type !== 'response.output_text.delta') {
119
+ continue;
120
+ }
121
+ const delta = event.data.delta;
122
+ if (typeof delta === 'string') {
123
+ yield delta;
124
+ }
125
+ }
126
+ }
127
+ /** @internal Yield reasoning-token deltas from the session broadcaster. */
128
+ export async function* filterReasoningStream(broadcaster) {
129
+ for await (const event of broadcaster) {
130
+ if (event.source !== 'sdk' || event.type !== 'response.reasoning.delta') {
131
+ continue;
132
+ }
133
+ const delta = event.data.delta;
134
+ if (typeof delta === 'string') {
135
+ yield delta;
136
+ }
137
+ }
138
+ }
139
+ /** @internal Yield cumulative StreamingItem snapshots. Uses roundOffset keyed by
140
+ * response.created so accumulators across multiple tool rounds and across
141
+ * multiple turns within a session don't collide. */
142
+ export async function* buildItemStream(broadcaster, itemSchemas = defaultItemSchemaRegistry) {
143
+ const accumulators = new Map();
144
+ let roundOffset = 0;
145
+ for await (const event of broadcaster) {
146
+ if (event.source !== 'sdk') {
147
+ continue;
148
+ }
149
+ if (event.type === 'response.created') {
150
+ roundOffset++;
151
+ continue;
152
+ }
153
+ if (event.type === 'response.output_item.added') {
154
+ const outputIndex = typeof event.outputIndex === 'number' ? event.outputIndex : accumulators.size;
155
+ const key = accumulatorKey(roundOffset, outputIndex);
156
+ const acc = createAccumulatorFromItemAdded(event, itemSchemas);
157
+ if (acc) {
158
+ accumulators.set(key, acc);
159
+ yield {
160
+ ...acc.item,
161
+ isComplete: false,
162
+ };
163
+ }
164
+ continue;
165
+ }
166
+ if (event.type === 'response.output_item.done') {
167
+ const outputIndex = typeof event.outputIndex === 'number' ? event.outputIndex : -1;
168
+ const key = accumulatorKey(roundOffset, outputIndex);
169
+ const acc = accumulators.get(key);
170
+ if (acc) {
171
+ acc.isComplete = true;
172
+ yield completeStreamingItem(acc.item);
173
+ }
174
+ continue;
175
+ }
176
+ if (event.type === 'response.output_text.delta' || event.type === 'response.output_text.done') {
177
+ const outputIndex = typeof event.outputIndex === 'number' ? event.outputIndex : 0;
178
+ const key = accumulatorKey(roundOffset, outputIndex);
179
+ const acc = accumulators.get(key);
180
+ if (acc) {
181
+ updateMessageAccumulator(acc, event);
182
+ yield {
183
+ ...acc.item,
184
+ isComplete: acc.isComplete,
185
+ };
186
+ }
187
+ continue;
188
+ }
189
+ if (event.type === 'response.function_call_arguments.delta' ||
190
+ event.type === 'response.function_call_arguments.done') {
191
+ const outputIndex = typeof event.outputIndex === 'number' ? event.outputIndex : 0;
192
+ const key = accumulatorKey(roundOffset, outputIndex);
193
+ const acc = accumulators.get(key);
194
+ if (acc) {
195
+ updateFunctionCallAccumulator(acc, event);
196
+ yield {
197
+ ...acc.item,
198
+ isComplete: acc.isComplete,
199
+ };
200
+ }
201
+ }
202
+ }
203
+ }
204
+ //#endregion
@@ -0,0 +1,6 @@
1
+ import type { TurnContext } from '@openrouter/agent';
2
+ import type { Context } from '../types/context';
3
+ import type { AgentHarnessContract } from '../types/runtime';
4
+ import type { ToolExecutionContext, ToolMemory } from '../types/tool-context';
5
+ export declare function buildToolMemory(harness: AgentHarnessContract, ctx: Context): ToolMemory;
6
+ export declare function buildToolExecutionContext(ctx: Context, harness: AgentHarnessContract, turnContext?: TurnContext): ToolExecutionContext;
@@ -0,0 +1,22 @@
1
+ export function buildToolMemory(harness, ctx) {
2
+ return {
3
+ get(layerId) {
4
+ return harness.getLayerState(ctx.id, layerId);
5
+ },
6
+ set(layerId, state) {
7
+ harness.setLayerState(ctx.id, layerId, state);
8
+ },
9
+ };
10
+ }
11
+ export function buildToolExecutionContext(ctx, harness, turnContext) {
12
+ return {
13
+ ctx,
14
+ harness,
15
+ fs: harness.fs,
16
+ shell: harness.shell,
17
+ memory: buildToolMemory(harness, ctx),
18
+ assembledView: ctx.itemLog.items,
19
+ lastStepMeta: ctx.lastStepMeta,
20
+ turnContext,
21
+ };
22
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Runtime schema for the `Item` union.
3
+ *
4
+ * The `Item` type is a discriminated union of framework-created items
5
+ * (`InputMessageItem`, `FunctionCallOutputItem`) and provider-shaped items
6
+ * from `@openrouter/agent` whose shapes are defined by the SDK's generated
7
+ * types. Mirroring those provider shapes as Zod schemas would drift as the
8
+ * SDK evolves, so the runtime schema validates only the structural invariant
9
+ * every `Item` shares: an object with a non-empty string `type` discriminant.
10
+ *
11
+ * Use this at trust boundaries (loading persisted session files, accepting
12
+ * externally-supplied transcripts) where we need to reject garbage JSON but
13
+ * don't need to re-validate every provider field TypeScript already types.
14
+ */
15
+ import { z } from 'zod';
16
+ import type { Item, ItemSchemaExtensions } from '../types/items';
17
+ /** @public Structural validator for `Item`. Asserts non-empty string `type` discriminant. */
18
+ export declare const ItemSchema: z.ZodCustom<Item, Item>;
19
+ type ItemSchemaCategory = keyof Required<ItemSchemaExtensions>;
20
+ export declare function mergeExtensions(a: ItemSchemaExtensions | undefined, b: ItemSchemaExtensions | undefined): ItemSchemaExtensions;
21
+ /** @public Runtime item validator with optional tool, memory-layer, and harness extension schemas. */
22
+ export declare class ItemSchemaRegistry {
23
+ readonly extensions: ItemSchemaExtensions;
24
+ readonly strictUnknownExtensions: boolean;
25
+ constructor(extensions?: ItemSchemaExtensions, opts?: {
26
+ strictUnknownExtensions?: boolean;
27
+ });
28
+ extend(extensions?: ItemSchemaExtensions): ItemSchemaRegistry;
29
+ parse(value: unknown): Item;
30
+ parseMany(values: Iterable<unknown>): Item[];
31
+ parseWithCategory(value: unknown, category: ItemSchemaCategory): Item;
32
+ }
33
+ /** @public Default item schema registry. */
34
+ export declare const defaultItemSchemaRegistry: ItemSchemaRegistry;
35
+ export {};
@@ -0,0 +1,155 @@
1
+ /**
2
+ * Runtime schema for the `Item` union.
3
+ *
4
+ * The `Item` type is a discriminated union of framework-created items
5
+ * (`InputMessageItem`, `FunctionCallOutputItem`) and provider-shaped items
6
+ * from `@openrouter/agent` whose shapes are defined by the SDK's generated
7
+ * types. Mirroring those provider shapes as Zod schemas would drift as the
8
+ * SDK evolves, so the runtime schema validates only the structural invariant
9
+ * every `Item` shares: an object with a non-empty string `type` discriminant.
10
+ *
11
+ * Use this at trust boundaries (loading persisted session files, accepting
12
+ * externally-supplied transcripts) where we need to reject garbage JSON but
13
+ * don't need to re-validate every provider field TypeScript already types.
14
+ */
15
+ import { z } from 'zod';
16
+ function isItemLike(value) {
17
+ if (typeof value !== 'object' || value === null) {
18
+ return false;
19
+ }
20
+ if (!('type' in value)) {
21
+ return false;
22
+ }
23
+ const { type } = value;
24
+ return typeof type === 'string' && type.length > 0;
25
+ }
26
+ /** @public Structural validator for `Item`. Asserts non-empty string `type` discriminant. */
27
+ export const ItemSchema = z.custom(isItemLike, {
28
+ message: 'Expected an Item object with a non-empty string "type" discriminant.',
29
+ });
30
+ function hasSchemas(extensions) {
31
+ return Boolean(extensions.items?.length ||
32
+ extensions.developerMessages?.length ||
33
+ extensions.toolCalls?.length ||
34
+ extensions.toolResults?.length);
35
+ }
36
+ export function mergeExtensions(a, b) {
37
+ return {
38
+ items: [
39
+ ...(a?.items ?? []),
40
+ ...(b?.items ?? []),
41
+ ],
42
+ developerMessages: [
43
+ ...(a?.developerMessages ?? []),
44
+ ...(b?.developerMessages ?? []),
45
+ ],
46
+ toolCalls: [
47
+ ...(a?.toolCalls ?? []),
48
+ ...(b?.toolCalls ?? []),
49
+ ],
50
+ toolResults: [
51
+ ...(a?.toolResults ?? []),
52
+ ...(b?.toolResults ?? []),
53
+ ],
54
+ };
55
+ }
56
+ function schemasForCategory(extensions, category) {
57
+ return extensions[category] ?? [];
58
+ }
59
+ function isRecord(value) {
60
+ return typeof value === 'object' && value !== null;
61
+ }
62
+ function relevantCategories(value) {
63
+ const categories = [
64
+ 'items',
65
+ ];
66
+ if (isRecord(value) && value.type === 'message' && value.role === 'developer') {
67
+ categories.push('developerMessages');
68
+ }
69
+ if (isRecord(value) && value.type === 'function_call') {
70
+ categories.push('toolCalls');
71
+ }
72
+ if (isRecord(value) && value.type === 'function_call_output') {
73
+ categories.push('toolResults');
74
+ }
75
+ return categories;
76
+ }
77
+ function isKnownBaseType(type) {
78
+ return (type === 'message' ||
79
+ type === 'function_call' ||
80
+ type === 'function_call_output' ||
81
+ type === 'reasoning' ||
82
+ type === 'web_search_call' ||
83
+ type === 'file_search_call' ||
84
+ type === 'image_generation_call' ||
85
+ type.startsWith('openrouter:'));
86
+ }
87
+ function schemaMismatch(category) {
88
+ return new z.ZodError([
89
+ {
90
+ code: 'custom',
91
+ message: `Item did not match any registered ${category} extension schema.`,
92
+ path: [],
93
+ },
94
+ ]);
95
+ }
96
+ /** @public Runtime item validator with optional tool, memory-layer, and harness extension schemas. */
97
+ export class ItemSchemaRegistry {
98
+ extensions;
99
+ strictUnknownExtensions;
100
+ constructor(extensions, opts) {
101
+ this.extensions = mergeExtensions(undefined, extensions);
102
+ this.strictUnknownExtensions = opts?.strictUnknownExtensions ?? true;
103
+ }
104
+ extend(extensions) {
105
+ if (!extensions || !hasSchemas(extensions)) {
106
+ return this;
107
+ }
108
+ return new ItemSchemaRegistry(mergeExtensions(this.extensions, extensions), {
109
+ strictUnknownExtensions: this.strictUnknownExtensions,
110
+ });
111
+ }
112
+ parse(value) {
113
+ const base = ItemSchema.parse(value);
114
+ const categories = relevantCategories(base);
115
+ const schemas = categories.flatMap((category) => schemasForCategory(this.extensions, category));
116
+ for (const schema of schemas) {
117
+ const parsed = schema.safeParse(value);
118
+ if (parsed.success) {
119
+ return ItemSchema.parse(parsed.data);
120
+ }
121
+ }
122
+ if (this.strictUnknownExtensions && !isKnownBaseType(base.type)) {
123
+ throw new z.ZodError([
124
+ {
125
+ code: 'custom',
126
+ message: `Item type '${base.type}' did not match any registered item extension schema.`,
127
+ path: [
128
+ 'type',
129
+ ],
130
+ },
131
+ ]);
132
+ }
133
+ return base;
134
+ }
135
+ parseMany(values) {
136
+ return Array.from(values, (value) => this.parse(value));
137
+ }
138
+ parseWithCategory(value, category) {
139
+ const schemas = schemasForCategory(this.extensions, category);
140
+ if (schemas.length === 0) {
141
+ return this.parse(value);
142
+ }
143
+ for (const schema of schemas) {
144
+ const parsed = schema.safeParse(value);
145
+ if (parsed.success) {
146
+ return ItemSchema.parse(parsed.data);
147
+ }
148
+ }
149
+ throw schemaMismatch(category);
150
+ }
151
+ }
152
+ /** @public Default item schema registry. */
153
+ export const defaultItemSchemaRegistry = new ItemSchemaRegistry(undefined, {
154
+ strictUnknownExtensions: true,
155
+ });