@ekairos/events 1.22.23-beta.feature-events-package-refactor.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/README.md +451 -0
- package/dist/codex.d.ts +95 -0
- package/dist/codex.js +91 -0
- package/dist/env.d.ts +12 -0
- package/dist/env.js +62 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +11 -0
- package/dist/mcp.d.ts +1 -0
- package/dist/mcp.js +1 -0
- package/dist/mirror.d.ts +41 -0
- package/dist/mirror.js +1 -0
- package/dist/oidc.d.ts +7 -0
- package/dist/oidc.js +25 -0
- package/dist/polyfills/dom-events.d.ts +1 -0
- package/dist/polyfills/dom-events.js +89 -0
- package/dist/react.d.ts +47 -0
- package/dist/react.js +88 -0
- package/dist/reactors/ai-sdk.chunk-map.d.ts +12 -0
- package/dist/reactors/ai-sdk.chunk-map.js +143 -0
- package/dist/reactors/ai-sdk.reactor.d.ts +38 -0
- package/dist/reactors/ai-sdk.reactor.js +69 -0
- package/dist/reactors/scripted.reactor.d.ts +23 -0
- package/dist/reactors/scripted.reactor.js +57 -0
- package/dist/reactors/types.d.ts +48 -0
- package/dist/reactors/types.js +1 -0
- package/dist/runtime.d.ts +17 -0
- package/dist/runtime.js +23 -0
- package/dist/runtime.step.d.ts +9 -0
- package/dist/runtime.step.js +7 -0
- package/dist/schema.d.ts +2 -0
- package/dist/schema.js +203 -0
- package/dist/steps/do-thread-stream-step.d.ts +34 -0
- package/dist/steps/do-thread-stream-step.js +96 -0
- package/dist/steps/mirror.steps.d.ts +6 -0
- package/dist/steps/mirror.steps.js +48 -0
- package/dist/steps/reaction.steps.d.ts +44 -0
- package/dist/steps/reaction.steps.js +329 -0
- package/dist/steps/store.steps.d.ts +97 -0
- package/dist/steps/store.steps.js +542 -0
- package/dist/steps/stream.steps.d.ts +11 -0
- package/dist/steps/stream.steps.js +38 -0
- package/dist/steps/trace.steps.d.ts +38 -0
- package/dist/steps/trace.steps.js +270 -0
- package/dist/stores/instant.document-parser.d.ts +6 -0
- package/dist/stores/instant.document-parser.js +210 -0
- package/dist/stores/instant.documents.d.ts +16 -0
- package/dist/stores/instant.documents.js +152 -0
- package/dist/stores/instant.store.d.ts +84 -0
- package/dist/stores/instant.store.js +743 -0
- package/dist/thread.builder.d.ts +118 -0
- package/dist/thread.builder.js +137 -0
- package/dist/thread.config.d.ts +15 -0
- package/dist/thread.config.js +30 -0
- package/dist/thread.contract.d.ts +53 -0
- package/dist/thread.contract.js +140 -0
- package/dist/thread.d.ts +3 -0
- package/dist/thread.engine.d.ts +233 -0
- package/dist/thread.engine.js +877 -0
- package/dist/thread.events.d.ts +35 -0
- package/dist/thread.events.js +97 -0
- package/dist/thread.hooks.d.ts +21 -0
- package/dist/thread.hooks.js +31 -0
- package/dist/thread.js +7 -0
- package/dist/thread.reactor.d.ts +3 -0
- package/dist/thread.reactor.js +2 -0
- package/dist/thread.registry.d.ts +21 -0
- package/dist/thread.registry.js +30 -0
- package/dist/thread.store.d.ts +125 -0
- package/dist/thread.store.js +1 -0
- package/dist/thread.stream.d.ts +186 -0
- package/dist/thread.stream.js +161 -0
- package/dist/thread.toolcalls.d.ts +60 -0
- package/dist/thread.toolcalls.js +73 -0
- package/dist/tools-to-model-tools.d.ts +19 -0
- package/dist/tools-to-model-tools.js +21 -0
- package/package.json +140 -0
package/README.md
ADDED
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
# @ekairos/thread
|
|
2
|
+
|
|
3
|
+
Durable thread engine for Workflow-compatible AI agents.
|
|
4
|
+
|
|
5
|
+
## Specification
|
|
6
|
+
|
|
7
|
+
Normative contract and compatibility profile:
|
|
8
|
+
|
|
9
|
+
- `SPEC.md`
|
|
10
|
+
|
|
11
|
+
`@ekairos/thread` is the execution layer used by Ekairos agents. It persists context, items, steps, parts, and executions, while streaming UI chunks and enforcing transition contracts.
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pnpm add @ekairos/thread
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Optional subpaths:
|
|
20
|
+
|
|
21
|
+
- `@ekairos/thread/runtime`
|
|
22
|
+
- `@ekairos/thread/schema`
|
|
23
|
+
- `@ekairos/thread/instant`
|
|
24
|
+
- `@ekairos/thread/codex`
|
|
25
|
+
- `@ekairos/thread/mcp`
|
|
26
|
+
- `@ekairos/thread/oidc`
|
|
27
|
+
|
|
28
|
+
## Package Surface (from `src/index.ts`)
|
|
29
|
+
|
|
30
|
+
### Core builders and engine
|
|
31
|
+
|
|
32
|
+
- `createThread`
|
|
33
|
+
- `thread`
|
|
34
|
+
- `Thread`
|
|
35
|
+
- `type ThreadConfig`
|
|
36
|
+
- `type ThreadInstance`
|
|
37
|
+
- `type RegistrableThreadBuilder`
|
|
38
|
+
- `type ThreadOptions`
|
|
39
|
+
- `type ThreadStreamOptions`
|
|
40
|
+
|
|
41
|
+
### Reactors
|
|
42
|
+
|
|
43
|
+
- `createAiSdkReactor`
|
|
44
|
+
- `createScriptedReactor`
|
|
45
|
+
- `type ThreadReactor`
|
|
46
|
+
- `type ThreadReactorParams`
|
|
47
|
+
- `type ThreadReactionResult`
|
|
48
|
+
- `type ThreadReactionToolCall`
|
|
49
|
+
- `type ThreadReactionLLM`
|
|
50
|
+
- `type CreateAiSdkReactorOptions`
|
|
51
|
+
- `type CreateScriptedReactorOptions`
|
|
52
|
+
- `type ScriptedReactorStep`
|
|
53
|
+
|
|
54
|
+
### Contracts and transitions
|
|
55
|
+
|
|
56
|
+
- `THREAD_STATUSES`
|
|
57
|
+
- `THREAD_CONTEXT_STATUSES`
|
|
58
|
+
- `THREAD_EXECUTION_STATUSES`
|
|
59
|
+
- `THREAD_STEP_STATUSES`
|
|
60
|
+
- `THREAD_ITEM_STATUSES`
|
|
61
|
+
- `THREAD_ITEM_TYPES`
|
|
62
|
+
- `THREAD_CHANNELS`
|
|
63
|
+
- `THREAD_TRACE_EVENT_KINDS`
|
|
64
|
+
- `THREAD_STREAM_CHUNK_TYPES`
|
|
65
|
+
- `THREAD_CONTEXT_SUBSTATE_KEYS`
|
|
66
|
+
- `THREAD_THREAD_TRANSITIONS`
|
|
67
|
+
- `THREAD_CONTEXT_TRANSITIONS`
|
|
68
|
+
- `THREAD_EXECUTION_TRANSITIONS`
|
|
69
|
+
- `THREAD_STEP_TRANSITIONS`
|
|
70
|
+
- `THREAD_ITEM_TRANSITIONS`
|
|
71
|
+
- `can*Transition`, `assert*Transition`
|
|
72
|
+
- `assertThreadPartKey`
|
|
73
|
+
|
|
74
|
+
### Stream and parsing
|
|
75
|
+
|
|
76
|
+
- `parseThreadStreamEvent`
|
|
77
|
+
- `assertThreadStreamTransitions`
|
|
78
|
+
- `validateThreadStreamTimeline`
|
|
79
|
+
- `type ThreadStreamEvent`
|
|
80
|
+
- `type ContextCreatedEvent`
|
|
81
|
+
- `type ContextResolvedEvent`
|
|
82
|
+
- `type ContextStatusChangedEvent`
|
|
83
|
+
- `type ThreadCreatedEvent`
|
|
84
|
+
- `type ThreadResolvedEvent`
|
|
85
|
+
- `type ThreadStatusChangedEvent`
|
|
86
|
+
- `type ExecutionCreatedEvent`
|
|
87
|
+
- `type ExecutionStatusChangedEvent`
|
|
88
|
+
- `type ItemCreatedEvent`
|
|
89
|
+
- `type ItemStatusChangedEvent`
|
|
90
|
+
- `type StepCreatedEvent`
|
|
91
|
+
- `type StepStatusChangedEvent`
|
|
92
|
+
- `type PartCreatedEvent`
|
|
93
|
+
- `type PartUpdatedEvent`
|
|
94
|
+
- `type ChunkEmittedEvent`
|
|
95
|
+
- `type ThreadFinishedEvent`
|
|
96
|
+
|
|
97
|
+
### Event conversion helpers
|
|
98
|
+
|
|
99
|
+
- `createUserItemFromUIMessages`
|
|
100
|
+
- `createAssistantItemFromUIMessages`
|
|
101
|
+
- `convertToUIMessage`
|
|
102
|
+
- `convertItemToModelMessages`
|
|
103
|
+
- `convertItemsToModelMessages`
|
|
104
|
+
- `convertModelMessageToItem`
|
|
105
|
+
- `didToolExecute`
|
|
106
|
+
- `extractToolCallsFromParts`
|
|
107
|
+
|
|
108
|
+
### React hook
|
|
109
|
+
|
|
110
|
+
- `useThread`
|
|
111
|
+
- `type UseThreadOptions`
|
|
112
|
+
- `type ThreadSnapshot`
|
|
113
|
+
- `type ThreadStreamChunk`
|
|
114
|
+
|
|
115
|
+
### Registry / codex
|
|
116
|
+
|
|
117
|
+
- `registerThread`
|
|
118
|
+
- `getThread`
|
|
119
|
+
- `getThreadFactory`
|
|
120
|
+
- `hasThread`
|
|
121
|
+
- `listThreads`
|
|
122
|
+
- `createCodexThreadBuilder`
|
|
123
|
+
- codex defaults/types from `codex.ts`
|
|
124
|
+
|
|
125
|
+
## Thread API Specification
|
|
126
|
+
|
|
127
|
+
## `createThread`
|
|
128
|
+
|
|
129
|
+
```ts
|
|
130
|
+
createThread<Env>(key: ThreadKey)
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Builder stages:
|
|
134
|
+
|
|
135
|
+
1. `.context((storedContext, env) => context)` (required)
|
|
136
|
+
2. `.expandEvents((events, context, env) => events)` (optional)
|
|
137
|
+
3. `.narrative((context, env) => string)` (required)
|
|
138
|
+
4. `.actions((context, env) => Record<string, ThreadTool>)` (required)
|
|
139
|
+
5. `.model(modelInit | selector)` (optional)
|
|
140
|
+
6. `.reactor(reactor)` (optional, default is AI SDK reactor)
|
|
141
|
+
7. `.shouldContinue(({ reactionEvent, toolCalls, toolExecutionResults, ... }) => boolean)` (optional)
|
|
142
|
+
8. `.opts(threadOptions)` (optional)
|
|
143
|
+
|
|
144
|
+
Builder terminals:
|
|
145
|
+
|
|
146
|
+
- `.build()` -> `ThreadInstance`
|
|
147
|
+
- `.react(triggerEvent, params)`
|
|
148
|
+
- `.stream(triggerEvent, params)` (deprecated alias)
|
|
149
|
+
- `.register()`
|
|
150
|
+
- `.config()`
|
|
151
|
+
|
|
152
|
+
### `ThreadConfig<Context, Env>`
|
|
153
|
+
|
|
154
|
+
Required keys:
|
|
155
|
+
|
|
156
|
+
- `context`
|
|
157
|
+
- `narrative`
|
|
158
|
+
- `actions` (or legacy `tools`)
|
|
159
|
+
|
|
160
|
+
Optional keys:
|
|
161
|
+
|
|
162
|
+
- `expandEvents`
|
|
163
|
+
- `model`
|
|
164
|
+
- `reactor`
|
|
165
|
+
- `shouldContinue`
|
|
166
|
+
- `opts`
|
|
167
|
+
|
|
168
|
+
### `Thread.react`
|
|
169
|
+
|
|
170
|
+
Primary form:
|
|
171
|
+
|
|
172
|
+
```ts
|
|
173
|
+
thread.react(triggerEvent, {
|
|
174
|
+
env,
|
|
175
|
+
context: { id } | { key } | null,
|
|
176
|
+
options,
|
|
177
|
+
})
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Return shape:
|
|
181
|
+
|
|
182
|
+
```ts
|
|
183
|
+
{
|
|
184
|
+
contextId: string;
|
|
185
|
+
context: StoredContext<Context>;
|
|
186
|
+
triggerEventId: string;
|
|
187
|
+
reactionEventId: string;
|
|
188
|
+
executionId: string;
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### `ThreadStreamOptions`
|
|
193
|
+
|
|
194
|
+
- `maxIterations?: number` (default `20`)
|
|
195
|
+
- `maxModelSteps?: number` (default `1`)
|
|
196
|
+
- `preventClose?: boolean` (default `false`)
|
|
197
|
+
- `sendFinish?: boolean` (default `true`)
|
|
198
|
+
- `silent?: boolean` (default `false`)
|
|
199
|
+
- `writable?: WritableStream<UIMessageChunk>`
|
|
200
|
+
|
|
201
|
+
### `ThreadOptions`
|
|
202
|
+
|
|
203
|
+
Lifecycle callbacks:
|
|
204
|
+
|
|
205
|
+
- `onContextCreated`
|
|
206
|
+
- `onContextUpdated`
|
|
207
|
+
- `onEventCreated`
|
|
208
|
+
- `onToolCallExecuted`
|
|
209
|
+
- `onEnd`
|
|
210
|
+
|
|
211
|
+
## Reactor Specification
|
|
212
|
+
|
|
213
|
+
A reactor receives the full execution context for one iteration and returns normalized assistant output + tool calls.
|
|
214
|
+
|
|
215
|
+
### `ThreadReactorParams`
|
|
216
|
+
|
|
217
|
+
- `env`
|
|
218
|
+
- `context`
|
|
219
|
+
- `contextIdentifier`
|
|
220
|
+
- `triggerEvent`
|
|
221
|
+
- `model`
|
|
222
|
+
- `systemPrompt`
|
|
223
|
+
- `actions`
|
|
224
|
+
- `toolsForModel`
|
|
225
|
+
- `eventId`
|
|
226
|
+
- `executionId`
|
|
227
|
+
- `contextId`
|
|
228
|
+
- `stepId`
|
|
229
|
+
- `iteration`
|
|
230
|
+
- `maxModelSteps`
|
|
231
|
+
- `sendStart`
|
|
232
|
+
- `silent`
|
|
233
|
+
- `writable`
|
|
234
|
+
|
|
235
|
+
### `ThreadReactionResult`
|
|
236
|
+
|
|
237
|
+
- `assistantEvent: ThreadItem`
|
|
238
|
+
- `toolCalls: ThreadReactionToolCall[]`
|
|
239
|
+
- `messagesForModel: ModelMessage[]`
|
|
240
|
+
- `llm?: ThreadReactionLLM`
|
|
241
|
+
|
|
242
|
+
## Built-in Reactors
|
|
243
|
+
|
|
244
|
+
## `createAiSdkReactor` (production default)
|
|
245
|
+
|
|
246
|
+
Uses AI SDK streaming + tool extraction through engine steps.
|
|
247
|
+
|
|
248
|
+
```ts
|
|
249
|
+
import { createAiSdkReactor } from "@ekairos/thread";
|
|
250
|
+
|
|
251
|
+
const reactor = createAiSdkReactor({
|
|
252
|
+
resolveConfig: async ({ env, context, iteration }) => {
|
|
253
|
+
"use step";
|
|
254
|
+
return {
|
|
255
|
+
model: env.model ?? "openai/gpt-5.2",
|
|
256
|
+
maxModelSteps: iteration === 0 ? 2 : 1,
|
|
257
|
+
tenant: context.content?.orgId,
|
|
258
|
+
};
|
|
259
|
+
},
|
|
260
|
+
selectModel: ({ baseModel, config }) => config.model ?? baseModel,
|
|
261
|
+
selectMaxModelSteps: ({ baseMaxModelSteps, config }) =>
|
|
262
|
+
typeof config.maxModelSteps === "number"
|
|
263
|
+
? config.maxModelSteps
|
|
264
|
+
: baseMaxModelSteps,
|
|
265
|
+
});
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
Use in thread:
|
|
269
|
+
|
|
270
|
+
```ts
|
|
271
|
+
createThread<{ orgId: string }>("support.agent")
|
|
272
|
+
.context((stored, env) => ({ ...stored.content, orgId: env.orgId }))
|
|
273
|
+
.narrative(() => "You are a precise assistant")
|
|
274
|
+
.actions(() => ({}))
|
|
275
|
+
.reactor(reactor)
|
|
276
|
+
.build();
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
## `createScriptedReactor` (testing and deterministic local loops)
|
|
280
|
+
|
|
281
|
+
No network/model calls. Returns scripted payloads per iteration.
|
|
282
|
+
|
|
283
|
+
```ts
|
|
284
|
+
import { createScriptedReactor } from "@ekairos/thread";
|
|
285
|
+
|
|
286
|
+
const reactor = createScriptedReactor({
|
|
287
|
+
steps: [
|
|
288
|
+
{
|
|
289
|
+
assistantEvent: {
|
|
290
|
+
content: { parts: [{ type: "text", text: "Deterministic answer" }] },
|
|
291
|
+
},
|
|
292
|
+
toolCalls: [],
|
|
293
|
+
messagesForModel: [],
|
|
294
|
+
},
|
|
295
|
+
],
|
|
296
|
+
repeatLast: true,
|
|
297
|
+
});
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
Rules:
|
|
301
|
+
|
|
302
|
+
- `steps` must contain at least 1 entry.
|
|
303
|
+
- If all steps are consumed and `repeatLast !== true`, reactor throws.
|
|
304
|
+
- `assistantEvent` is normalized with fallback fields:
|
|
305
|
+
- `id = params.eventId`
|
|
306
|
+
- `type = "output_text"`
|
|
307
|
+
- `channel = triggerEvent.channel`
|
|
308
|
+
- `createdAt = now`
|
|
309
|
+
|
|
310
|
+
## Production Pattern
|
|
311
|
+
|
|
312
|
+
```ts
|
|
313
|
+
import { createThread, createAiSdkReactor } from "@ekairos/thread";
|
|
314
|
+
import { tool } from "ai";
|
|
315
|
+
import { z } from "zod";
|
|
316
|
+
|
|
317
|
+
type Env = { orgId: string; sessionId: string };
|
|
318
|
+
|
|
319
|
+
export const supportThread = createThread<Env>("support.agent")
|
|
320
|
+
.context((stored, env) => ({
|
|
321
|
+
orgId: env.orgId,
|
|
322
|
+
sessionId: env.sessionId,
|
|
323
|
+
...stored.content,
|
|
324
|
+
}))
|
|
325
|
+
.narrative((context) => `Assist session ${context.content?.sessionId}`)
|
|
326
|
+
.actions(() => ({
|
|
327
|
+
ping: tool({
|
|
328
|
+
description: "Health check",
|
|
329
|
+
inputSchema: z.object({ text: z.string().optional() }),
|
|
330
|
+
execute: async ({ text }) => ({ pong: text ?? "ok" }),
|
|
331
|
+
}),
|
|
332
|
+
}))
|
|
333
|
+
.reactor(createAiSdkReactor())
|
|
334
|
+
.shouldContinue(({ reactionEvent }) => {
|
|
335
|
+
const parts = reactionEvent.content?.parts ?? [];
|
|
336
|
+
const hasTool = parts.some((part: any) => part?.type === "tool-call");
|
|
337
|
+
return hasTool;
|
|
338
|
+
})
|
|
339
|
+
.build();
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
## Testing Pattern
|
|
343
|
+
|
|
344
|
+
```ts
|
|
345
|
+
import { createThread, createScriptedReactor } from "@ekairos/thread";
|
|
346
|
+
|
|
347
|
+
type Env = { orgId: string };
|
|
348
|
+
|
|
349
|
+
const testThread = createThread<Env>("thread.test")
|
|
350
|
+
.context((stored, env) => ({ orgId: env.orgId, ...stored.content }))
|
|
351
|
+
.narrative(() => "Test narrative")
|
|
352
|
+
.actions(() => ({}))
|
|
353
|
+
.reactor(
|
|
354
|
+
createScriptedReactor({
|
|
355
|
+
steps: [
|
|
356
|
+
{
|
|
357
|
+
assistantEvent: {
|
|
358
|
+
content: { parts: [{ type: "text", text: "ok-1" }] },
|
|
359
|
+
},
|
|
360
|
+
toolCalls: [],
|
|
361
|
+
messagesForModel: [],
|
|
362
|
+
},
|
|
363
|
+
],
|
|
364
|
+
repeatLast: true,
|
|
365
|
+
}),
|
|
366
|
+
)
|
|
367
|
+
.build();
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
## Stream Contract
|
|
371
|
+
|
|
372
|
+
Thread stream events (`thread.stream.ts`) are entity-based.
|
|
373
|
+
|
|
374
|
+
Hierarchy:
|
|
375
|
+
|
|
376
|
+
1. context
|
|
377
|
+
2. thread
|
|
378
|
+
3. item
|
|
379
|
+
4. step
|
|
380
|
+
5. part
|
|
381
|
+
6. chunk
|
|
382
|
+
|
|
383
|
+
Event types:
|
|
384
|
+
|
|
385
|
+
- `context.created`
|
|
386
|
+
- `context.resolved`
|
|
387
|
+
- `context.status.changed`
|
|
388
|
+
- `thread.created`
|
|
389
|
+
- `thread.resolved`
|
|
390
|
+
- `thread.status.changed`
|
|
391
|
+
- `execution.created`
|
|
392
|
+
- `execution.status.changed`
|
|
393
|
+
- `item.created`
|
|
394
|
+
- `item.status.changed`
|
|
395
|
+
- `step.created`
|
|
396
|
+
- `step.status.changed`
|
|
397
|
+
- `part.created`
|
|
398
|
+
- `part.updated`
|
|
399
|
+
- `chunk.emitted`
|
|
400
|
+
- `thread.finished`
|
|
401
|
+
|
|
402
|
+
Chunk types (`THREAD_STREAM_CHUNK_TYPES`):
|
|
403
|
+
|
|
404
|
+
- `data-context-id`
|
|
405
|
+
- `data-context-substate`
|
|
406
|
+
- `data-thread-ping`
|
|
407
|
+
- `tool-output-available`
|
|
408
|
+
- `tool-output-error`
|
|
409
|
+
- `finish`
|
|
410
|
+
|
|
411
|
+
Validation helpers:
|
|
412
|
+
|
|
413
|
+
- `parseThreadStreamEvent(event)`
|
|
414
|
+
- `assertThreadStreamTransitions(event)`
|
|
415
|
+
- `validateThreadStreamTimeline(events)`
|
|
416
|
+
|
|
417
|
+
## Transition Contract
|
|
418
|
+
|
|
419
|
+
Allowed status transitions are exported as constants and enforced by assertion helpers.
|
|
420
|
+
|
|
421
|
+
- Thread: `open -> streaming -> (open | closed | failed)`, `failed -> open`
|
|
422
|
+
- Context: `open <-> streaming`, `(open | streaming) -> closed`
|
|
423
|
+
- Execution: `executing -> (completed | failed)`
|
|
424
|
+
- Step: `running -> (completed | failed)`
|
|
425
|
+
- Item: `stored -> (pending | completed)`, `pending -> completed`
|
|
426
|
+
|
|
427
|
+
## Runtime and Schema
|
|
428
|
+
|
|
429
|
+
- Import schema with `threadDomain` from `@ekairos/thread/schema`
|
|
430
|
+
- Store integration defaults to `InstantStore`
|
|
431
|
+
- Runtime must be configured via `@ekairos/domain/runtime` in host app
|
|
432
|
+
|
|
433
|
+
Persisted entities:
|
|
434
|
+
|
|
435
|
+
- `thread_threads`
|
|
436
|
+
- `thread_contexts`
|
|
437
|
+
- `thread_items`
|
|
438
|
+
- `thread_executions`
|
|
439
|
+
- `thread_steps`
|
|
440
|
+
- `thread_parts`
|
|
441
|
+
- `thread_trace_events`
|
|
442
|
+
- `thread_trace_runs`
|
|
443
|
+
- `thread_trace_spans`
|
|
444
|
+
|
|
445
|
+
## Notes for Productive Usage
|
|
446
|
+
|
|
447
|
+
- Always pass explicit `env`.
|
|
448
|
+
- Prefer `context: { key }` for stable continuation and `context: { id }` for deterministic resume.
|
|
449
|
+
- Keep IO in workflow steps.
|
|
450
|
+
- Use `createScriptedReactor` for deterministic regression tests and component demos.
|
|
451
|
+
- Validate stream timelines with `validateThreadStreamTimeline` when consuming SSE externally.
|
package/dist/codex.d.ts
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { ThreadEnvironment } from "./thread.config.js";
|
|
3
|
+
import type { ThreadModelInit, ThreadOptions, ThreadReactParams, ThreadShouldContinueArgs, ThreadTool } from "./thread.engine.js";
|
|
4
|
+
import type { ThreadKey } from "./thread.registry.js";
|
|
5
|
+
import type { StoredContext, ThreadItem } from "./thread.store.js";
|
|
6
|
+
import type { ThreadInstance } from "./thread.js";
|
|
7
|
+
export declare const DEFAULT_CODEX_TOOL_NAME = "codex";
|
|
8
|
+
export declare const DEFAULT_CODEX_MODEL = "openai/gpt-5.2";
|
|
9
|
+
export type CodexThreadRuntimeMode = "local" | "remote" | "sandbox";
|
|
10
|
+
export type CodexThreadReasoningLevel = "off" | "low" | "medium" | "high";
|
|
11
|
+
export type CodexThreadRuntime = {
|
|
12
|
+
appServerUrl?: string;
|
|
13
|
+
repoPath?: string;
|
|
14
|
+
threadId?: string;
|
|
15
|
+
mode?: CodexThreadRuntimeMode;
|
|
16
|
+
model?: string;
|
|
17
|
+
approvalPolicy?: string;
|
|
18
|
+
sandboxPolicy?: Record<string, unknown>;
|
|
19
|
+
};
|
|
20
|
+
export type CodexThreadEnv = ThreadEnvironment & {
|
|
21
|
+
sessionId: string;
|
|
22
|
+
runtime?: CodexThreadRuntime;
|
|
23
|
+
reasoningLevel?: CodexThreadReasoningLevel;
|
|
24
|
+
model?: string;
|
|
25
|
+
};
|
|
26
|
+
export type CodexToolInput = {
|
|
27
|
+
instruction: string;
|
|
28
|
+
files?: Array<{
|
|
29
|
+
url?: string;
|
|
30
|
+
path?: string;
|
|
31
|
+
mediaType?: string;
|
|
32
|
+
fileId?: string;
|
|
33
|
+
}>;
|
|
34
|
+
};
|
|
35
|
+
export type CodexToolOutput = {
|
|
36
|
+
threadId: string;
|
|
37
|
+
turnId: string;
|
|
38
|
+
assistantText: string;
|
|
39
|
+
reasoningText: string;
|
|
40
|
+
diff: string;
|
|
41
|
+
toolParts: any[];
|
|
42
|
+
};
|
|
43
|
+
export declare const codexToolInputSchema: z.ZodObject<{
|
|
44
|
+
instruction: z.ZodString;
|
|
45
|
+
files: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
46
|
+
url: z.ZodOptional<z.ZodString>;
|
|
47
|
+
path: z.ZodOptional<z.ZodString>;
|
|
48
|
+
mediaType: z.ZodOptional<z.ZodString>;
|
|
49
|
+
fileId: z.ZodOptional<z.ZodString>;
|
|
50
|
+
}, z.core.$strip>>>;
|
|
51
|
+
}, z.core.$strip>;
|
|
52
|
+
export type CodexExecuteArgs<Context, Env extends CodexThreadEnv = CodexThreadEnv> = {
|
|
53
|
+
context: StoredContext<Context>;
|
|
54
|
+
env: Env;
|
|
55
|
+
input: CodexToolInput;
|
|
56
|
+
toolName: string;
|
|
57
|
+
};
|
|
58
|
+
export type CodexThreadBuilderConfig<Context, Env extends CodexThreadEnv = CodexThreadEnv> = {
|
|
59
|
+
key: ThreadKey;
|
|
60
|
+
context: (context: StoredContext<Context>, env: Env) => Promise<Context> | Context;
|
|
61
|
+
executeCodex: (args: CodexExecuteArgs<Context, Env>) => Promise<CodexToolOutput>;
|
|
62
|
+
narrative?: (context: StoredContext<Context>, env: Env) => Promise<string> | string;
|
|
63
|
+
system?: (context: StoredContext<Context>, env: Env) => Promise<string> | string;
|
|
64
|
+
actions?: (context: StoredContext<Context>, env: Env) => Promise<Record<string, ThreadTool>> | Record<string, ThreadTool>;
|
|
65
|
+
model?: ThreadModelInit | ((context: StoredContext<Context>, env: Env) => ThreadModelInit);
|
|
66
|
+
shouldContinue?: (args: ThreadShouldContinueArgs<Context, Env>) => Promise<boolean> | boolean;
|
|
67
|
+
toolName?: string;
|
|
68
|
+
toolDescription?: string;
|
|
69
|
+
opts?: ThreadOptions<Context, Env>;
|
|
70
|
+
};
|
|
71
|
+
export type CodexThreadBuilder<Context, Env extends CodexThreadEnv = CodexThreadEnv> = {
|
|
72
|
+
key: ThreadKey;
|
|
73
|
+
react(triggerEvent: ThreadItem, params: ThreadReactParams<Env>): Promise<{
|
|
74
|
+
contextId: string;
|
|
75
|
+
context: StoredContext<Context>;
|
|
76
|
+
triggerEventId: string;
|
|
77
|
+
reactionEventId: string;
|
|
78
|
+
executionId: string;
|
|
79
|
+
}>;
|
|
80
|
+
stream(triggerEvent: ThreadItem, params: ThreadReactParams<Env>): Promise<{
|
|
81
|
+
contextId: string;
|
|
82
|
+
context: StoredContext<Context>;
|
|
83
|
+
triggerEventId: string;
|
|
84
|
+
reactionEventId: string;
|
|
85
|
+
executionId: string;
|
|
86
|
+
}>;
|
|
87
|
+
register(): void;
|
|
88
|
+
config(): unknown;
|
|
89
|
+
build(): ThreadInstance<Context, Env>;
|
|
90
|
+
};
|
|
91
|
+
export declare function buildDefaultCodexNarrative(content: unknown): string;
|
|
92
|
+
export declare function didCodexToolExecute(event: Pick<{
|
|
93
|
+
content: any;
|
|
94
|
+
}, "content">, toolName?: string): boolean;
|
|
95
|
+
export declare function createCodexThreadBuilder<Context, Env extends CodexThreadEnv = CodexThreadEnv>(config: CodexThreadBuilderConfig<Context, Env>): CodexThreadBuilder<Context, Env>;
|
package/dist/codex.js
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { tool } from "ai";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { createThread } from "./thread.js";
|
|
4
|
+
import { didToolExecute } from "./thread.toolcalls.js";
|
|
5
|
+
export const DEFAULT_CODEX_TOOL_NAME = "codex";
|
|
6
|
+
export const DEFAULT_CODEX_MODEL = "openai/gpt-5.2";
|
|
7
|
+
export const codexToolInputSchema = z.object({
|
|
8
|
+
instruction: z
|
|
9
|
+
.string()
|
|
10
|
+
.describe("The coding instruction to execute for this request."),
|
|
11
|
+
files: z
|
|
12
|
+
.array(z.object({
|
|
13
|
+
url: z.string().optional(),
|
|
14
|
+
path: z.string().optional(),
|
|
15
|
+
mediaType: z.string().optional(),
|
|
16
|
+
fileId: z.string().optional(),
|
|
17
|
+
}))
|
|
18
|
+
.optional(),
|
|
19
|
+
});
|
|
20
|
+
function toRecord(value) {
|
|
21
|
+
if (!value || typeof value !== "object")
|
|
22
|
+
return {};
|
|
23
|
+
return value;
|
|
24
|
+
}
|
|
25
|
+
function toString(value) {
|
|
26
|
+
if (typeof value !== "string")
|
|
27
|
+
return "";
|
|
28
|
+
return value.trim();
|
|
29
|
+
}
|
|
30
|
+
export function buildDefaultCodexNarrative(content) {
|
|
31
|
+
const record = toRecord(content);
|
|
32
|
+
const sessionId = toString(record.sessionId);
|
|
33
|
+
const repoUrl = toString(record.repoUrl);
|
|
34
|
+
const branchName = toString(record.branchName);
|
|
35
|
+
return [
|
|
36
|
+
"You are Codex running as an Ekairos Thread.",
|
|
37
|
+
"",
|
|
38
|
+
"Execution style:",
|
|
39
|
+
"- Execute real coding work via the `codex` action.",
|
|
40
|
+
"- Keep answers concrete and implementation-oriented.",
|
|
41
|
+
"- Preserve repository and branch continuity across turns.",
|
|
42
|
+
"",
|
|
43
|
+
"Context:",
|
|
44
|
+
`- Session: ${sessionId || "unknown"}`,
|
|
45
|
+
`- Repository: ${repoUrl || "unknown"}`,
|
|
46
|
+
`- Branch: ${branchName || "unknown"}`,
|
|
47
|
+
].join("\n");
|
|
48
|
+
}
|
|
49
|
+
export function didCodexToolExecute(event, toolName = DEFAULT_CODEX_TOOL_NAME) {
|
|
50
|
+
return didToolExecute(event, toolName);
|
|
51
|
+
}
|
|
52
|
+
export function createCodexThreadBuilder(config) {
|
|
53
|
+
const toolName = config.toolName ?? DEFAULT_CODEX_TOOL_NAME;
|
|
54
|
+
const toolDescription = config.toolDescription ??
|
|
55
|
+
"Run the coding request using a Codex app server and stream output.";
|
|
56
|
+
const narrative = config.narrative ??
|
|
57
|
+
config.system ??
|
|
58
|
+
((ctx) => buildDefaultCodexNarrative(ctx?.content));
|
|
59
|
+
const model = config.model ??
|
|
60
|
+
((_ctx, env) => (typeof env.model === "string" && env.model.trim()) || DEFAULT_CODEX_MODEL);
|
|
61
|
+
const shouldContinue = config.shouldContinue ??
|
|
62
|
+
((args) => !didToolExecute(args.reactionEvent, toolName));
|
|
63
|
+
let builder = createThread(config.key)
|
|
64
|
+
.context(config.context)
|
|
65
|
+
.narrative(narrative)
|
|
66
|
+
.actions(async (ctx, env) => {
|
|
67
|
+
const additional = config.actions ? await config.actions(ctx, env) : {};
|
|
68
|
+
if (Object.prototype.hasOwnProperty.call(additional, toolName)) {
|
|
69
|
+
throw new Error(`createCodexThreadBuilder: action "${toolName}" is reserved for Codex.`);
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
...additional,
|
|
73
|
+
[toolName]: tool({
|
|
74
|
+
description: toolDescription,
|
|
75
|
+
inputSchema: codexToolInputSchema,
|
|
76
|
+
execute: async (input) => await config.executeCodex({
|
|
77
|
+
context: ctx,
|
|
78
|
+
env,
|
|
79
|
+
input,
|
|
80
|
+
toolName,
|
|
81
|
+
}),
|
|
82
|
+
}),
|
|
83
|
+
};
|
|
84
|
+
})
|
|
85
|
+
.model(model)
|
|
86
|
+
.shouldContinue(shouldContinue);
|
|
87
|
+
if (config.opts) {
|
|
88
|
+
builder = builder.opts(config.opts);
|
|
89
|
+
}
|
|
90
|
+
return builder;
|
|
91
|
+
}
|
package/dist/env.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ThreadEnvironment } from "./thread.config.js";
|
|
2
|
+
/**
|
|
3
|
+
* Register the current workflow env for later use inside "use step" functions.
|
|
4
|
+
*
|
|
5
|
+
* If runId is provided, it will be stored under that run. If not, a default env is set.
|
|
6
|
+
*/
|
|
7
|
+
export declare function registerThreadEnv(env: ThreadEnvironment, runId?: string | null): void;
|
|
8
|
+
/**
|
|
9
|
+
* Resolve the env for the current workflow/step.
|
|
10
|
+
* Falls back to the default env if no run-specific env exists.
|
|
11
|
+
*/
|
|
12
|
+
export declare function getThreadEnv(): Promise<ThreadEnvironment>;
|
package/dist/env.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
const envByRunIdSymbol = Symbol.for("ekairos.thread.envByRunId");
|
|
2
|
+
const defaultEnvSymbol = Symbol.for("ekairos.thread.defaultEnv");
|
|
3
|
+
function getEnvMap() {
|
|
4
|
+
if (typeof globalThis === "undefined")
|
|
5
|
+
return new Map();
|
|
6
|
+
const existing = globalThis[envByRunIdSymbol];
|
|
7
|
+
if (existing)
|
|
8
|
+
return existing;
|
|
9
|
+
const created = new Map();
|
|
10
|
+
globalThis[envByRunIdSymbol] = created;
|
|
11
|
+
return created;
|
|
12
|
+
}
|
|
13
|
+
function setDefaultEnv(env) {
|
|
14
|
+
if (typeof globalThis === "undefined")
|
|
15
|
+
return;
|
|
16
|
+
globalThis[defaultEnvSymbol] = env ?? null;
|
|
17
|
+
}
|
|
18
|
+
function getDefaultEnv() {
|
|
19
|
+
if (typeof globalThis === "undefined")
|
|
20
|
+
return null;
|
|
21
|
+
return globalThis[defaultEnvSymbol] ?? null;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Register the current workflow env for later use inside "use step" functions.
|
|
25
|
+
*
|
|
26
|
+
* If runId is provided, it will be stored under that run. If not, a default env is set.
|
|
27
|
+
*/
|
|
28
|
+
export function registerThreadEnv(env, runId) {
|
|
29
|
+
if (runId) {
|
|
30
|
+
getEnvMap().set(String(runId), env);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
setDefaultEnv(env);
|
|
34
|
+
}
|
|
35
|
+
async function tryGetWorkflowRunId() {
|
|
36
|
+
try {
|
|
37
|
+
const mod = await import("workflow");
|
|
38
|
+
const meta = mod?.getWorkflowMetadata?.();
|
|
39
|
+
const runId = meta?.workflowRunId;
|
|
40
|
+
return runId ? String(runId) : null;
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Resolve the env for the current workflow/step.
|
|
48
|
+
* Falls back to the default env if no run-specific env exists.
|
|
49
|
+
*/
|
|
50
|
+
export async function getThreadEnv() {
|
|
51
|
+
const runId = await tryGetWorkflowRunId();
|
|
52
|
+
if (runId) {
|
|
53
|
+
const stored = getEnvMap().get(runId);
|
|
54
|
+
if (stored)
|
|
55
|
+
return stored;
|
|
56
|
+
}
|
|
57
|
+
const fallback = getDefaultEnv();
|
|
58
|
+
if (fallback)
|
|
59
|
+
return fallback;
|
|
60
|
+
throw new Error("@ekairos/events: env is not configured for this workflow run. " +
|
|
61
|
+
"Call registerThreadEnv(env) at workflow start or ensure the thread runtime registers env.");
|
|
62
|
+
}
|