@ekairos/thread 1.21.88-beta.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.
Files changed (86) hide show
  1. package/README.md +363 -0
  2. package/dist/codex.d.ts +95 -0
  3. package/dist/codex.js +91 -0
  4. package/dist/env.d.ts +12 -0
  5. package/dist/env.js +62 -0
  6. package/dist/events.d.ts +35 -0
  7. package/dist/events.js +102 -0
  8. package/dist/index.d.ts +9 -0
  9. package/dist/index.js +9 -0
  10. package/dist/mcp.d.ts +1 -0
  11. package/dist/mcp.js +1 -0
  12. package/dist/mirror.d.ts +41 -0
  13. package/dist/mirror.js +1 -0
  14. package/dist/oidc.d.ts +7 -0
  15. package/dist/oidc.js +25 -0
  16. package/dist/polyfills/dom-events.d.ts +1 -0
  17. package/dist/polyfills/dom-events.js +89 -0
  18. package/dist/react.d.ts +62 -0
  19. package/dist/react.js +101 -0
  20. package/dist/runtime.d.ts +17 -0
  21. package/dist/runtime.js +23 -0
  22. package/dist/runtime.step.d.ts +9 -0
  23. package/dist/runtime.step.js +7 -0
  24. package/dist/schema.d.ts +2 -0
  25. package/dist/schema.js +200 -0
  26. package/dist/steps/do-story-stream-step.d.ts +29 -0
  27. package/dist/steps/do-story-stream-step.js +89 -0
  28. package/dist/steps/do-thread-stream-step.d.ts +29 -0
  29. package/dist/steps/do-thread-stream-step.js +90 -0
  30. package/dist/steps/mirror.steps.d.ts +6 -0
  31. package/dist/steps/mirror.steps.js +48 -0
  32. package/dist/steps/reaction.steps.d.ts +43 -0
  33. package/dist/steps/reaction.steps.js +354 -0
  34. package/dist/steps/store.steps.d.ts +98 -0
  35. package/dist/steps/store.steps.js +512 -0
  36. package/dist/steps/stream.steps.d.ts +41 -0
  37. package/dist/steps/stream.steps.js +99 -0
  38. package/dist/steps/trace.steps.d.ts +37 -0
  39. package/dist/steps/trace.steps.js +265 -0
  40. package/dist/stores/instant.document-parser.d.ts +6 -0
  41. package/dist/stores/instant.document-parser.js +210 -0
  42. package/dist/stores/instant.documents.d.ts +16 -0
  43. package/dist/stores/instant.documents.js +152 -0
  44. package/dist/stores/instant.store.d.ts +78 -0
  45. package/dist/stores/instant.store.js +530 -0
  46. package/dist/story.actions.d.ts +60 -0
  47. package/dist/story.actions.js +120 -0
  48. package/dist/story.builder.d.ts +115 -0
  49. package/dist/story.builder.js +130 -0
  50. package/dist/story.config.d.ts +54 -0
  51. package/dist/story.config.js +125 -0
  52. package/dist/story.d.ts +2 -0
  53. package/dist/story.engine.d.ts +224 -0
  54. package/dist/story.engine.js +464 -0
  55. package/dist/story.hooks.d.ts +21 -0
  56. package/dist/story.hooks.js +31 -0
  57. package/dist/story.js +6 -0
  58. package/dist/story.registry.d.ts +21 -0
  59. package/dist/story.registry.js +30 -0
  60. package/dist/story.store.d.ts +107 -0
  61. package/dist/story.store.js +1 -0
  62. package/dist/story.toolcalls.d.ts +60 -0
  63. package/dist/story.toolcalls.js +73 -0
  64. package/dist/thread.builder.d.ts +118 -0
  65. package/dist/thread.builder.js +134 -0
  66. package/dist/thread.config.d.ts +15 -0
  67. package/dist/thread.config.js +30 -0
  68. package/dist/thread.d.ts +3 -0
  69. package/dist/thread.engine.d.ts +229 -0
  70. package/dist/thread.engine.js +471 -0
  71. package/dist/thread.events.d.ts +35 -0
  72. package/dist/thread.events.js +105 -0
  73. package/dist/thread.hooks.d.ts +21 -0
  74. package/dist/thread.hooks.js +31 -0
  75. package/dist/thread.js +7 -0
  76. package/dist/thread.reactor.d.ts +82 -0
  77. package/dist/thread.reactor.js +65 -0
  78. package/dist/thread.registry.d.ts +21 -0
  79. package/dist/thread.registry.js +30 -0
  80. package/dist/thread.store.d.ts +121 -0
  81. package/dist/thread.store.js +1 -0
  82. package/dist/thread.toolcalls.d.ts +60 -0
  83. package/dist/thread.toolcalls.js +73 -0
  84. package/dist/tools-to-model-tools.d.ts +19 -0
  85. package/dist/tools-to-model-tools.js +21 -0
  86. package/package.json +133 -0
package/README.md ADDED
@@ -0,0 +1,363 @@
1
+ # @ekairos/thread
2
+
3
+ Durable AI threads for production apps.
4
+
5
+ `@ekairos/thread` gives you an execution model that is:
6
+
7
+ - workflow-compatible,
8
+ - persistence-first,
9
+ - traceable by design,
10
+ - simple to embed in domain applications.
11
+
12
+ It is the runtime used by Ekairos coding agents and domain agents.
13
+
14
+ ## Why Thread
15
+
16
+ Most chat abstractions stop at "messages in, text out".
17
+ Thread models the full lifecycle:
18
+
19
+ 1. Persist trigger event.
20
+ 2. Create execution.
21
+ 3. Run model reaction.
22
+ 4. Persist normalized parts.
23
+ 5. Execute actions (tools).
24
+ 6. Persist tool outcomes.
25
+ 7. Decide continue or end.
26
+ 8. Emit traces for every durable step.
27
+
28
+ This design supports long-running, resumable agent runs without losing state.
29
+
30
+ ## Core Concepts
31
+
32
+ - `Thread`: durable loop orchestrator.
33
+ - `Reactor`: pluggable reaction implementation (`AI SDK`, `Codex`, `Claude`, `Cursor`, ...).
34
+ - `Thread Key`: stable public identifier (`thread.key`) for continuity.
35
+ - `Context`: typed persistent state attached to a thread.
36
+ - `Item`: normalized event (`input_text`, `output_text`, etc).
37
+ - `Execution`: one run for a trigger/reaction pair.
38
+ - `Step`: one loop iteration inside an execution.
39
+ - `Part`: normalized content fragment persisted by step.
40
+ - `Trace`: machine timeline (`thread.*`, `workflow.*`) for observability.
41
+
42
+ ## Installation
43
+
44
+ ```bash
45
+ pnpm add @ekairos/thread
46
+ ```
47
+
48
+ Optional subpaths:
49
+
50
+ - `@ekairos/thread/runtime`
51
+ - `@ekairos/thread/schema`
52
+ - `@ekairos/thread/instant`
53
+ - `@ekairos/thread/codex`
54
+ - `@ekairos/thread/mcp`
55
+ - `@ekairos/thread/oidc`
56
+
57
+ ## Quick Start
58
+
59
+ ### 1) Configure app runtime once
60
+
61
+ Thread resolves persistence through runtime.
62
+ Do this once in app bootstrap (`src/ekairos.ts`):
63
+
64
+ ```ts
65
+ import "server-only";
66
+ import { configureRuntime } from "@ekairos/domain/runtime";
67
+ import { getOrgAdminDb } from "@/lib/admin-org-db";
68
+ import appDomain from "@/lib/domain";
69
+
70
+ export const runtimeConfig = configureRuntime({
71
+ runtime: async (env: { orgId: string }) => {
72
+ const db = await getOrgAdminDb(env.orgId, appDomain);
73
+ return { db };
74
+ },
75
+ domain: { domain: appDomain },
76
+ });
77
+ ```
78
+
79
+ ### 2) Define a thread
80
+
81
+ ```ts
82
+ import { createThread } from "@ekairos/thread";
83
+ import { tool } from "ai";
84
+ import { z } from "zod";
85
+
86
+ type Env = { orgId: string; sessionId: string };
87
+ type Ctx = { orgId: string; sessionId: string };
88
+
89
+ export const helloThread = createThread<Env>("hello.thread")
90
+ .context(async (stored, env) => ({
91
+ orgId: env.orgId,
92
+ sessionId: env.sessionId,
93
+ ...(stored.content ?? {}),
94
+ }))
95
+ .narrative((ctx) => `You are a precise assistant. Session=${ctx.content?.sessionId}`)
96
+ .actions(() => ({
97
+ ping: tool({
98
+ description: "Return pong",
99
+ inputSchema: z.object({ text: z.string().optional() }),
100
+ execute: async ({ text }) => ({ pong: text ?? "ok" }),
101
+ }),
102
+ }))
103
+ .model("openai/gpt-5.2")
104
+ .build();
105
+ ```
106
+
107
+ ### 2.1) Reactor model (new)
108
+
109
+ Thread runs through a `reactor`:
110
+
111
+ - default: `createAiSdkReactor()` (included in `@ekairos/thread`)
112
+ - optional: custom/provider reactor via `.reactor(...)`
113
+
114
+ ```ts
115
+ import { createThread, createAiSdkReactor } from "@ekairos/thread";
116
+
117
+ const thread = createThread<{ orgId: string }>("my.thread")
118
+ .context((stored, env) => ({ ...(stored.content ?? {}), orgId: env.orgId }))
119
+ .narrative(() => "System prompt")
120
+ .actions(() => ({}))
121
+ .reactor(createAiSdkReactor())
122
+ .build();
123
+ ```
124
+
125
+ `createAiSdkReactor` also accepts optional per-turn config hooks:
126
+
127
+ ```ts
128
+ import { createAiSdkReactor } from "@ekairos/thread";
129
+
130
+ const reactor = createAiSdkReactor({
131
+ resolveConfig: async ({ env }) => {
132
+ "use step";
133
+ return { model: env.model ?? "openai/gpt-5.2", maxModelSteps: 2 };
134
+ },
135
+ selectModel: ({ config, baseModel }) => config.model ?? baseModel,
136
+ selectMaxModelSteps: ({ config, baseMaxModelSteps }) =>
137
+ typeof config.maxModelSteps === "number"
138
+ ? config.maxModelSteps
139
+ : baseMaxModelSteps,
140
+ });
141
+ ```
142
+
143
+ Provider reactors live in `packages/reactors/*`:
144
+
145
+ - `@ekairos/openai-reactor` (`createOpenAIReactor`, `createCodexReactor`)
146
+ - `@ekairos/claude-reactor` (scaffold)
147
+ - `@ekairos/cursor-reactor` (scaffold)
148
+
149
+ ### 3) Run from a workflow
150
+
151
+ ```ts
152
+ import { getWritable } from "workflow";
153
+ import type { UIMessageChunk } from "ai";
154
+ import type { ThreadItem } from "@ekairos/thread";
155
+ import { helloThread } from "./hello.thread";
156
+
157
+ export async function helloWorkflow(params: {
158
+ env: { orgId: string; sessionId: string };
159
+ triggerEvent: ThreadItem;
160
+ threadKey?: string;
161
+ }) {
162
+ "use workflow";
163
+
164
+ const writable = getWritable<UIMessageChunk>();
165
+ return await helloThread.react(params.triggerEvent, {
166
+ env: params.env,
167
+ context: params.threadKey ? { key: params.threadKey } : null,
168
+ options: { writable, maxIterations: 2, maxModelSteps: 1 },
169
+ });
170
+ }
171
+ ```
172
+
173
+ ## Thread Lifecycle (Detailed)
174
+
175
+ For each `react(...)` call:
176
+
177
+ 1. `initializeContext` creates or loads context.
178
+ 2. `saveTriggerAndCreateExecution` persists trigger and execution.
179
+ 3. `createThreadStep` starts iteration record.
180
+ 4. `buildSystemPrompt` and `buildTools` are evaluated.
181
+ 5. `executeReaction` runs model + tool call planning.
182
+ 6. `saveThreadPartsStep` persists normalized parts.
183
+ 7. `saveReactionItem` or `updateItem` updates stable reaction item.
184
+ 8. Tool executions run and are merged into persisted parts.
185
+ 9. `shouldContinue(...)` decides next iteration or completion.
186
+ 10. `completeExecution` closes run status.
187
+
188
+ All side effects are executed through workflow-safe steps.
189
+
190
+ ## Event and Item Model
191
+
192
+ Key utilities:
193
+
194
+ - `createUserItemFromUIMessages(...)`
195
+ - `createAssistantItemFromUIMessages(...)`
196
+ - `convertItemsToModelMessages(...)`
197
+ - `convertModelMessageToItem(...)`
198
+ - `didToolExecute(...)`
199
+ - `extractToolCallsFromParts(...)`
200
+
201
+ This keeps a stable internal representation while remaining compatible with UI/model formats.
202
+
203
+ ## Runtime and Persistence
204
+
205
+ Thread runtime resolves from `@ekairos/domain/runtime` bootstrap.
206
+
207
+ Default persistence adapter:
208
+
209
+ - `InstantStore` (`@ekairos/thread/instant`)
210
+
211
+ Schema:
212
+
213
+ - `thread_threads`
214
+ - `thread_contexts`
215
+ - `thread_items`
216
+ - `thread_executions`
217
+ - `thread_steps`
218
+ - `thread_parts`
219
+ - `thread_trace_events`
220
+ - `thread_trace_runs`
221
+ - `thread_trace_spans`
222
+
223
+ Import domain schema:
224
+
225
+ ```ts
226
+ import { threadDomain } from "@ekairos/thread/schema";
227
+ ```
228
+
229
+ ## Streaming
230
+
231
+ Thread writes `UIMessageChunk` to workflow writable streams.
232
+
233
+ Options:
234
+
235
+ - `writable`: custom stream.
236
+ - `silent`: disable stream writes, keep persistence.
237
+ - `preventClose`: do not close writer.
238
+ - `sendFinish`: control final `finish` chunk.
239
+
240
+ Namespaced streams are supported using `context:<contextId>`.
241
+
242
+ ## Identity Model
243
+
244
+ - `thread.key` is the functional continuity id.
245
+ - `context.id` is internal state id for typed context persistence.
246
+ - A thread can own one or more contexts; default runtime behavior is one active context per thread.
247
+
248
+ ### Open Responses alignment
249
+
250
+ Thread is protocol-aligned with Open Responses item/event semantics and keeps durable execution
251
+ through Workflow.
252
+
253
+ - Public continuity id should be `thread.key`.
254
+ - Context remains internal typed state, but can be exposed as an extension field in thread query APIs.
255
+ - Safe extension pattern: include `context` object in thread payload while preserving standard fields.
256
+
257
+ Example shape for a thread query response:
258
+
259
+ ```json
260
+ {
261
+ "object": "conversation",
262
+ "id": "thread-key-or-id",
263
+ "status": "completed",
264
+ "context": {
265
+ "id": "ctx_123",
266
+ "key": "code.agent.session.abc",
267
+ "status": "completed",
268
+ "content": {}
269
+ }
270
+ }
271
+ ```
272
+
273
+ This extension is additive and does not break Open Responses compatibility.
274
+
275
+ ## Tracing and Observability
276
+
277
+ Thread emits lifecycle traces by default through step operations.
278
+
279
+ Typical namespaces:
280
+
281
+ - `thread.run`
282
+ - `thread.context`
283
+ - `thread.execution`
284
+ - `thread.step`
285
+ - `thread.item`
286
+ - `thread.part`
287
+ - `thread.review`
288
+ - `thread.llm`
289
+ - `workflow.run`
290
+
291
+ These traces are intended for local persistence plus optional mirror ingestion to central collectors.
292
+
293
+ ## Registry API
294
+
295
+ Register and resolve threads by key:
296
+
297
+ ```ts
298
+ import { registerThread, getThread } from "@ekairos/thread";
299
+ ```
300
+
301
+ Builder convenience:
302
+
303
+ ```ts
304
+ const builder = createThread<Env>("my.key").context(...).narrative(...).actions(...);
305
+ builder.register();
306
+ ```
307
+
308
+ ## Preconfigured Codex Thread
309
+
310
+ Use `@ekairos/thread/codex` to create coding threads with minimal wiring.
311
+
312
+ ```ts
313
+ import { createCodexThreadBuilder } from "@ekairos/thread/codex";
314
+
315
+ const builder = createCodexThreadBuilder({
316
+ key: "code.agent",
317
+ context: async (stored, env) => ({ ...(stored.content ?? {}), ...env }),
318
+ executeCodex: async ({ input, env }) => {
319
+ // Call Codex app server here (usually in a use-step function)
320
+ return {
321
+ threadId: "t_123",
322
+ turnId: "turn_123",
323
+ assistantText: "done",
324
+ reasoningText: "",
325
+ diff: "",
326
+ toolParts: [],
327
+ };
328
+ },
329
+ });
330
+ ```
331
+
332
+ What it configures for you:
333
+
334
+ - `codex` action schema,
335
+ - default model selection (`openai/gpt-5.2`),
336
+ - default continue rule (`stop after codex action executes`),
337
+ - default narrative fallback.
338
+
339
+ For direct Codex runtime (without "tool indirection"), use
340
+ `@ekairos/openai-reactor` + `createCodexReactor(...)`.
341
+
342
+ ## MCP and OIDC
343
+
344
+ Utilities are exposed for protocol integration:
345
+
346
+ - `@ekairos/thread/mcp`
347
+ - `@ekairos/thread/oidc`
348
+
349
+ Use these from server-side API routes when exposing thread-driven tools via MCP.
350
+
351
+ ## DX Guidelines
352
+
353
+ - Keep `env` serializable.
354
+ - Keep thread definition declarative.
355
+ - Put DB/network side effects inside step functions.
356
+ - Prefer `context.id` for deterministic resume.
357
+ - Use explicit thread keys (`domain.agent.name` format).
358
+
359
+ ## Breaking-Change Policy
360
+
361
+ Thread prioritizes runtime correctness over implicit compatibility shims.
362
+
363
+ When behavior conflicts with durability or protocol clarity, explicit configuration is preferred over hidden fallbacks.
@@ -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/thread: env is not configured for this workflow run. " +
61
+ "Call registerThreadEnv(env) at workflow start or ensure the thread runtime registers env.");
62
+ }
@@ -0,0 +1,35 @@
1
+ import { type ModelMessage, type UIMessage } from "ai";
2
+ import type { ContextEvent } from "./story.store.js";
3
+ export declare const USER_MESSAGE_TYPE = "user.message";
4
+ export declare const ASSISTANT_MESSAGE_TYPE = "assistant.message";
5
+ export declare const SYSTEM_MESSAGE_TYPE = "system.message";
6
+ export declare const WEB_CHANNEL = "web";
7
+ export declare const AGENT_CHANNEL = "whatsapp";
8
+ export declare const EMAIL_CHANNEL = "email";
9
+ export declare function createUserEventFromUIMessages(messages: UIMessage[]): ContextEvent;
10
+ export declare function createAssistantEventFromUIMessages(eventId: string, messages: UIMessage[]): ContextEvent;
11
+ export declare function convertToUIMessage(event: ContextEvent): UIMessage;
12
+ /**
13
+ * Converts stored ContextEvents to AI SDK ModelMessages.
14
+ *
15
+ * IMPORTANT:
16
+ * - Store-agnostic and workflow-safe.
17
+ * - Attachment/document handling MUST happen in the store boundary:
18
+ * `StoryStore.eventsToModelMessages(events)`.
19
+ */
20
+ export declare function convertEventsToModelMessages(events: ContextEvent[]): Promise<ModelMessage[]>;
21
+ export declare function convertEventToModelMessages(event: ContextEvent): Promise<ModelMessage[]>;
22
+ export type AIMessage = {
23
+ id: string;
24
+ role: "user" | "assistant" | "system";
25
+ content: string;
26
+ createdAt: Date;
27
+ };
28
+ export type ResponseMessage = {
29
+ id: string;
30
+ timestamp: Date;
31
+ modelId: string;
32
+ headers?: Record<string, string>;
33
+ message: ModelMessage;
34
+ };
35
+ export declare function convertModelMessageToEvent(eventId: string, message: ResponseMessage): ContextEvent;