@ekairos/thread 1.22.3 → 1.22.4-beta.feature-thread-unify.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/dist/index.d.ts CHANGED
@@ -1,9 +1,13 @@
1
- export { thread, createThread, createAiSdkReactor, type CreateAiSdkReactorOptions, type ThreadConfig, type ThreadInstance, type ThreadOptions, type ThreadStreamOptions, type ThreadReactor, type ThreadReactorParams, type ThreadReactionResult, type ThreadReactionToolCall, type ThreadReactionLLM, Thread, type RegistrableThreadBuilder, } from "./thread.js";
1
+ export { thread, createThread, createAiSdkReactor, createScriptedReactor, type CreateAiSdkReactorOptions, type CreateScriptedReactorOptions, type ScriptedReactorStep, type ThreadConfig, type ThreadInstance, type ThreadOptions, type ThreadStreamOptions, type ThreadReactor, type ThreadReactorParams, type ThreadReactionResult, type ThreadReactionToolCall, type ThreadReactionLLM, Thread, type RegistrableThreadBuilder, } from "./thread.js";
2
2
  export type { ThreadStore, ThreadIdentifier, ContextIdentifier, StoredThread, StoredContext, ThreadItem, } from "./thread.store.js";
3
3
  export type { WireDate, ThreadMirrorContext, ThreadMirrorExecution, ThreadMirrorWrite, ThreadMirrorRequest, } from "./mirror.js";
4
4
  export { registerThread, getThread, getThreadFactory, hasThread, listThreads, type ThreadKey, } from "./thread.registry.js";
5
5
  export { threadDomain } from "./schema.js";
6
6
  export { didToolExecute, extractToolCallsFromParts } from "./thread.toolcalls.js";
7
7
  export { INPUT_TEXT_ITEM_TYPE, OUTPUT_TEXT_ITEM_TYPE, SYSTEM_TEXT_ITEM_TYPE, WEB_CHANNEL, AGENT_CHANNEL, EMAIL_CHANNEL, createUserItemFromUIMessages, createAssistantItemFromUIMessages, convertToUIMessage, convertItemToModelMessages, convertItemsToModelMessages, convertModelMessageToItem, type ResponseMessage, } from "./thread.events.js";
8
+ export { THREAD_STATUSES, THREAD_CONTEXT_STATUSES, THREAD_EXECUTION_STATUSES, THREAD_STEP_STATUSES, THREAD_ITEM_STATUSES, THREAD_ITEM_TYPES, THREAD_CHANNELS, THREAD_TRACE_EVENT_KINDS, THREAD_STREAM_CHUNK_TYPES, THREAD_CONTEXT_SUBSTATE_KEYS, THREAD_THREAD_TRANSITIONS, THREAD_CONTEXT_TRANSITIONS, THREAD_EXECUTION_TRANSITIONS, THREAD_STEP_TRANSITIONS, THREAD_ITEM_TRANSITIONS, canThreadTransition, canContextTransition, canExecutionTransition, canStepTransition, canItemTransition, assertThreadTransition, assertContextTransition, assertExecutionTransition, assertStepTransition, assertItemTransition, assertThreadPartKey, } from "./thread.contract.js";
9
+ export type { Transition, ThreadThreadStatus, ThreadContextStatus, ThreadExecutionStatus, ThreadStepStatus, ThreadItemStatus, ThreadItemType, ThreadChannel, ThreadTraceEventKind, ThreadStreamChunkType, ThreadContextSubstateKey, ThreadTransition, ContextTransition, ExecutionTransition, StepTransition, ItemTransition, } from "./thread.contract.js";
8
10
  export { DEFAULT_CODEX_TOOL_NAME, DEFAULT_CODEX_MODEL, codexToolInputSchema, buildDefaultCodexNarrative, didCodexToolExecute, createCodexThreadBuilder, type CodexThreadRuntimeMode, type CodexThreadReasoningLevel, type CodexThreadRuntime, type CodexThreadEnv, type CodexToolInput, type CodexToolOutput, type CodexExecuteArgs, type CodexThreadBuilderConfig, type CodexThreadBuilder, } from "./codex.js";
9
11
  export { useThread, type ThreadSnapshot, type ThreadStreamChunk, type UseThreadOptions, } from "./react.js";
12
+ export { parseThreadStreamEvent, assertThreadStreamTransitions, validateThreadStreamTimeline, } from "./thread.stream.js";
13
+ export type { ThreadStreamEvent, ContextCreatedEvent, ContextResolvedEvent, ContextStatusChangedEvent, ThreadCreatedEvent, ThreadResolvedEvent, ThreadStatusChangedEvent, ExecutionCreatedEvent, ExecutionStatusChangedEvent, ItemCreatedEvent, ItemStatusChangedEvent, StepCreatedEvent, StepStatusChangedEvent, PartCreatedEvent, PartUpdatedEvent, ChunkEmittedEvent, ThreadFinishedEvent, } from "./thread.stream.js";
package/dist/index.js CHANGED
@@ -1,9 +1,11 @@
1
1
  export {
2
2
  // Thread API
3
- thread, createThread, createAiSdkReactor, Thread, } from "./thread.js";
3
+ thread, createThread, createAiSdkReactor, createScriptedReactor, Thread, } from "./thread.js";
4
4
  export { registerThread, getThread, getThreadFactory, hasThread, listThreads, } from "./thread.registry.js";
5
5
  export { threadDomain } from "./schema.js";
6
6
  export { didToolExecute, extractToolCallsFromParts } from "./thread.toolcalls.js";
7
7
  export { INPUT_TEXT_ITEM_TYPE, OUTPUT_TEXT_ITEM_TYPE, SYSTEM_TEXT_ITEM_TYPE, WEB_CHANNEL, AGENT_CHANNEL, EMAIL_CHANNEL, createUserItemFromUIMessages, createAssistantItemFromUIMessages, convertToUIMessage, convertItemToModelMessages, convertItemsToModelMessages, convertModelMessageToItem, } from "./thread.events.js";
8
+ export { THREAD_STATUSES, THREAD_CONTEXT_STATUSES, THREAD_EXECUTION_STATUSES, THREAD_STEP_STATUSES, THREAD_ITEM_STATUSES, THREAD_ITEM_TYPES, THREAD_CHANNELS, THREAD_TRACE_EVENT_KINDS, THREAD_STREAM_CHUNK_TYPES, THREAD_CONTEXT_SUBSTATE_KEYS, THREAD_THREAD_TRANSITIONS, THREAD_CONTEXT_TRANSITIONS, THREAD_EXECUTION_TRANSITIONS, THREAD_STEP_TRANSITIONS, THREAD_ITEM_TRANSITIONS, canThreadTransition, canContextTransition, canExecutionTransition, canStepTransition, canItemTransition, assertThreadTransition, assertContextTransition, assertExecutionTransition, assertStepTransition, assertItemTransition, assertThreadPartKey, } from "./thread.contract.js";
8
9
  export { DEFAULT_CODEX_TOOL_NAME, DEFAULT_CODEX_MODEL, codexToolInputSchema, buildDefaultCodexNarrative, didCodexToolExecute, createCodexThreadBuilder, } from "./codex.js";
9
10
  export { useThread, } from "./react.js";
11
+ export { parseThreadStreamEvent, assertThreadStreamTransitions, validateThreadStreamTimeline, } from "./thread.stream.js";
package/dist/react.d.ts CHANGED
@@ -1,14 +1,15 @@
1
+ import type { ThreadContextStatus, ThreadContextSubstateKey, ThreadThreadStatus } from "./thread.contract.js";
1
2
  export type ThreadSnapshot<Context = unknown, Item = Record<string, unknown>> = {
2
3
  thread: {
3
4
  id: string;
4
5
  key: string;
5
- status: string;
6
+ status: ThreadThreadStatus;
6
7
  createdAt: string | null;
7
8
  updatedAt: string | null;
8
9
  };
9
10
  context: {
10
11
  id: string;
11
- status: string;
12
+ status: ThreadContextStatus;
12
13
  content: Context;
13
14
  createdAt: string | null;
14
15
  updatedAt: string | null;
@@ -24,7 +25,7 @@ export type ThreadStreamChunk = {
24
25
  } | {
25
26
  type: "data-context-substate";
26
27
  data?: {
27
- key?: string | null;
28
+ key?: ThreadContextSubstateKey | null;
28
29
  };
29
30
  transient?: boolean;
30
31
  } | {
@@ -0,0 +1,38 @@
1
+ import type { ThreadEnvironment } from "../thread.config.js";
2
+ import type { ThreadModelInit } from "../thread.engine.js";
3
+ import type { ContextIdentifier, StoredContext, ThreadItem } from "../thread.store.js";
4
+ import type { ThreadReactor } from "./types.js";
5
+ export type CreateAiSdkReactorOptions<Context = unknown, Env extends ThreadEnvironment = ThreadEnvironment, Config = unknown> = {
6
+ resolveConfig?: (params: {
7
+ env: Env;
8
+ context: StoredContext<Context>;
9
+ contextIdentifier: ContextIdentifier;
10
+ triggerEvent: ThreadItem;
11
+ model: ThreadModelInit;
12
+ eventId: string;
13
+ executionId: string;
14
+ contextId: string;
15
+ stepId: string;
16
+ iteration: number;
17
+ }) => Promise<Config> | Config;
18
+ selectModel?: (params: {
19
+ env: Env;
20
+ context: StoredContext<Context>;
21
+ triggerEvent: ThreadItem;
22
+ baseModel: ThreadModelInit;
23
+ config: Config;
24
+ }) => Promise<ThreadModelInit> | ThreadModelInit;
25
+ selectMaxModelSteps?: (params: {
26
+ env: Env;
27
+ context: StoredContext<Context>;
28
+ triggerEvent: ThreadItem;
29
+ baseMaxModelSteps: number;
30
+ config: Config;
31
+ }) => Promise<number> | number;
32
+ };
33
+ /**
34
+ * Default reactor for Thread: Vercel AI SDK (`streamText`) with tool-call extraction.
35
+ *
36
+ * This keeps current behavior and can be replaced per-thread using `.reactor(...)`.
37
+ */
38
+ export declare function createAiSdkReactor<Context = unknown, Env extends ThreadEnvironment = ThreadEnvironment, Config = unknown>(options?: CreateAiSdkReactorOptions<Context, Env, Config>): ThreadReactor<Context, Env>;
@@ -0,0 +1,65 @@
1
+ import { executeReaction } from "../steps/reaction.steps.js";
2
+ /**
3
+ * Default reactor for Thread: Vercel AI SDK (`streamText`) with tool-call extraction.
4
+ *
5
+ * This keeps current behavior and can be replaced per-thread using `.reactor(...)`.
6
+ */
7
+ export function createAiSdkReactor(options) {
8
+ return async (params) => {
9
+ let config;
10
+ if (options?.resolveConfig) {
11
+ config = await options.resolveConfig({
12
+ env: params.env,
13
+ context: params.context,
14
+ contextIdentifier: params.contextIdentifier,
15
+ triggerEvent: params.triggerEvent,
16
+ model: params.model,
17
+ eventId: params.eventId,
18
+ executionId: params.executionId,
19
+ contextId: params.contextId,
20
+ stepId: params.stepId,
21
+ iteration: params.iteration,
22
+ });
23
+ }
24
+ const model = options?.selectModel && config !== undefined
25
+ ? await options.selectModel({
26
+ env: params.env,
27
+ context: params.context,
28
+ triggerEvent: params.triggerEvent,
29
+ baseModel: params.model,
30
+ config,
31
+ })
32
+ : params.model;
33
+ const maxSteps = options?.selectMaxModelSteps && config !== undefined
34
+ ? await options.selectMaxModelSteps({
35
+ env: params.env,
36
+ context: params.context,
37
+ triggerEvent: params.triggerEvent,
38
+ baseMaxModelSteps: params.maxModelSteps,
39
+ config,
40
+ })
41
+ : params.maxModelSteps;
42
+ const result = await executeReaction({
43
+ env: params.env,
44
+ contextIdentifier: params.contextIdentifier,
45
+ model,
46
+ system: params.systemPrompt,
47
+ tools: params.toolsForModel,
48
+ eventId: params.eventId,
49
+ iteration: params.iteration,
50
+ maxSteps,
51
+ sendStart: params.sendStart,
52
+ silent: params.silent,
53
+ writable: params.writable,
54
+ executionId: params.executionId,
55
+ contextId: params.contextId,
56
+ stepId: params.stepId,
57
+ });
58
+ return {
59
+ assistantEvent: result.assistantEvent,
60
+ toolCalls: result.toolCalls,
61
+ messagesForModel: result.messagesForModel,
62
+ llm: result.llm,
63
+ };
64
+ };
65
+ }
@@ -0,0 +1,23 @@
1
+ import type { ModelMessage } from "ai";
2
+ import type { ThreadEnvironment } from "../thread.config.js";
3
+ import type { ThreadItem } from "../thread.store.js";
4
+ import type { ThreadReactionLLM, ThreadReactionToolCall, ThreadReactor, ThreadReactorParams } from "./types.js";
5
+ type ScriptedReactionPayload = {
6
+ assistantEvent?: Partial<ThreadItem>;
7
+ toolCalls?: ThreadReactionToolCall[];
8
+ messagesForModel?: ModelMessage[];
9
+ llm?: ThreadReactionLLM;
10
+ };
11
+ export type ScriptedReactorStep<Context = unknown, Env extends ThreadEnvironment = ThreadEnvironment> = ScriptedReactionPayload | ((params: ThreadReactorParams<Context, Env>) => Promise<ScriptedReactionPayload> | ScriptedReactionPayload);
12
+ export type CreateScriptedReactorOptions<Context = unknown, Env extends ThreadEnvironment = ThreadEnvironment> = {
13
+ steps: ScriptedReactorStep<Context, Env>[];
14
+ repeatLast?: boolean;
15
+ };
16
+ /**
17
+ * Deterministic reactor used for tests and local loop iteration.
18
+ *
19
+ * - No model/network calls
20
+ * - Predictable scripted outputs per iteration
21
+ */
22
+ export declare function createScriptedReactor<Context = unknown, Env extends ThreadEnvironment = ThreadEnvironment>(options: CreateScriptedReactorOptions<Context, Env>): ThreadReactor<Context, Env>;
23
+ export {};
@@ -0,0 +1,56 @@
1
+ function normalizeScriptedAssistantEvent(params, assistantEvent) {
2
+ const fallback = {
3
+ id: params.eventId,
4
+ type: "output_text",
5
+ channel: params.triggerEvent.channel,
6
+ createdAt: new Date().toISOString(),
7
+ content: { parts: [] },
8
+ };
9
+ const mergedContent = {
10
+ ...fallback.content,
11
+ ...(assistantEvent?.content ?? {}),
12
+ };
13
+ return {
14
+ ...fallback,
15
+ ...assistantEvent,
16
+ id: assistantEvent?.id ?? fallback.id,
17
+ createdAt: assistantEvent?.createdAt ?? fallback.createdAt,
18
+ content: mergedContent,
19
+ };
20
+ }
21
+ /**
22
+ * Deterministic reactor used for tests and local loop iteration.
23
+ *
24
+ * - No model/network calls
25
+ * - Predictable scripted outputs per iteration
26
+ */
27
+ export function createScriptedReactor(options) {
28
+ const steps = Array.isArray(options.steps) ? options.steps : [];
29
+ if (steps.length === 0) {
30
+ throw new Error("createScriptedReactor: options.steps must contain at least one step.");
31
+ }
32
+ let index = 0;
33
+ return async (params) => {
34
+ const hasCurrentStep = index < steps.length;
35
+ if (!hasCurrentStep && !options.repeatLast) {
36
+ throw new Error(`createScriptedReactor: no scripted step available at index ${index}. ` +
37
+ `Provided steps=${steps.length}. Enable repeatLast to reuse the final step.`);
38
+ }
39
+ const stepIndex = hasCurrentStep ? index : steps.length - 1;
40
+ const scriptedStep = steps[stepIndex];
41
+ if (hasCurrentStep) {
42
+ index += 1;
43
+ }
44
+ const payload = typeof scriptedStep === "function"
45
+ ? await scriptedStep(params)
46
+ : scriptedStep;
47
+ return {
48
+ assistantEvent: normalizeScriptedAssistantEvent(params, payload.assistantEvent),
49
+ toolCalls: Array.isArray(payload.toolCalls) ? payload.toolCalls : [],
50
+ messagesForModel: Array.isArray(payload.messagesForModel)
51
+ ? payload.messagesForModel
52
+ : [],
53
+ llm: payload.llm,
54
+ };
55
+ };
56
+ }
@@ -0,0 +1,48 @@
1
+ import type { ModelMessage, UIMessageChunk } from "ai";
2
+ import type { ThreadEnvironment } from "../thread.config.js";
3
+ import type { ThreadModelInit } from "../thread.engine.js";
4
+ import type { ContextIdentifier, StoredContext, ThreadItem } from "../thread.store.js";
5
+ import type { SerializableToolForModel } from "../tools-to-model-tools.js";
6
+ export type ThreadReactionToolCall = {
7
+ toolCallId: string;
8
+ toolName: string;
9
+ args: unknown;
10
+ };
11
+ export type ThreadReactionLLM = {
12
+ provider?: string;
13
+ model?: string;
14
+ promptTokens?: number;
15
+ promptTokensCached?: number;
16
+ promptTokensUncached?: number;
17
+ completionTokens?: number;
18
+ totalTokens?: number;
19
+ latencyMs?: number;
20
+ rawUsage?: unknown;
21
+ rawProviderMetadata?: unknown;
22
+ };
23
+ export type ThreadReactionResult = {
24
+ assistantEvent: ThreadItem;
25
+ toolCalls: ThreadReactionToolCall[];
26
+ messagesForModel: ModelMessage[];
27
+ llm?: ThreadReactionLLM;
28
+ };
29
+ export type ThreadReactorParams<Context = unknown, Env extends ThreadEnvironment = ThreadEnvironment> = {
30
+ env: Env;
31
+ context: StoredContext<Context>;
32
+ contextIdentifier: ContextIdentifier;
33
+ triggerEvent: ThreadItem;
34
+ model: ThreadModelInit;
35
+ systemPrompt: string;
36
+ actions: Record<string, unknown>;
37
+ toolsForModel: Record<string, SerializableToolForModel>;
38
+ eventId: string;
39
+ executionId: string;
40
+ contextId: string;
41
+ stepId: string;
42
+ iteration: number;
43
+ maxModelSteps: number;
44
+ sendStart: boolean;
45
+ silent: boolean;
46
+ writable: WritableStream<UIMessageChunk>;
47
+ };
48
+ export type ThreadReactor<Context = unknown, Env extends ThreadEnvironment = ThreadEnvironment> = (params: ThreadReactorParams<Context, Env>) => Promise<ThreadReactionResult>;
@@ -0,0 +1 @@
1
+ export {};
@@ -1,6 +1,7 @@
1
1
  import type { ModelMessage } from "ai";
2
2
  import type { ThreadItem } from "../thread.store.js";
3
3
  import type { SerializableToolForModel } from "../tools-to-model-tools.js";
4
+ import type { ThreadModelInit } from "../thread.engine.js";
4
5
  /**
5
6
  * Runs a single LLM streaming step as a Workflow step.
6
7
  *
@@ -9,7 +10,7 @@ import type { SerializableToolForModel } from "../tools-to-model-tools.js";
9
10
  * - Returns the assistant event + extracted tool calls for the workflow loop.
10
11
  */
11
12
  export declare function doThreadStreamStep(params: {
12
- model: any;
13
+ model: ThreadModelInit;
13
14
  system: string;
14
15
  messages: ModelMessage[];
15
16
  tools: Record<string, SerializableToolForModel>;
@@ -11,7 +11,7 @@ export async function doThreadStreamStep(params) {
11
11
  const { getWritable } = await import("workflow");
12
12
  const writable = getWritable();
13
13
  const { jsonSchema, gateway, smoothStream, stepCountIs, streamText } = await import("ai");
14
- const { extractToolCallsFromParts } = await import("@ekairos/thread");
14
+ const { extractToolCallsFromParts } = await import("../thread.toolcalls.js");
15
15
  // Match DurableAgent's model init behavior:
16
16
  // - string => AI Gateway model id, resolved via `gateway(...)` in the step runtime
17
17
  // - function => model factory (should be a `"use step"` function for workflow serialization)
@@ -19,7 +19,9 @@ export async function doThreadStreamStep(params) {
19
19
  ? gateway(params.model)
20
20
  : typeof params.model === "function"
21
21
  ? await params.model()
22
- : params.model;
22
+ : (() => {
23
+ throw new Error("Invalid model init passed to doThreadStreamStep. Expected a model id string or an async model factory.");
24
+ })();
23
25
  // IMPORTANT:
24
26
  // `streamText` expects tools in the AI SDK ToolSet shape, where `inputSchema` is a Schema-like value.
25
27
  // We pass plain JSON schema objects across the step boundary (serializable), then wrap them here with
@@ -1,5 +1,6 @@
1
1
  import type { ModelMessage, UIMessageChunk } from "ai";
2
2
  import type { ThreadEnvironment } from "../thread.config.js";
3
+ import type { ThreadModelInit } from "../thread.engine.js";
3
4
  import type { ThreadItem, ContextIdentifier } from "../thread.store.js";
4
5
  import type { SerializableToolForModel } from "../tools-to-model-tools.js";
5
6
  /**
@@ -12,7 +13,7 @@ import type { SerializableToolForModel } from "../tools-to-model-tools.js";
12
13
  export declare function executeReaction(params: {
13
14
  env: ThreadEnvironment;
14
15
  contextIdentifier: ContextIdentifier;
15
- model: any;
16
+ model: ThreadModelInit;
16
17
  system: string;
17
18
  tools: Record<string, SerializableToolForModel>;
18
19
  eventId: string;
@@ -20,7 +21,7 @@ export declare function executeReaction(params: {
20
21
  maxSteps: number;
21
22
  sendStart?: boolean;
22
23
  silent?: boolean;
23
- writable?: WritableStream<UIMessageChunk>;
24
+ writable: WritableStream<UIMessageChunk>;
24
25
  executionId?: string;
25
26
  contextId?: string;
26
27
  stepId?: string;
@@ -100,75 +100,16 @@ export async function executeReaction(params) {
100
100
  console.error("[ekairos/story] reaction.step store.itemsToModelMessages failed", safeErrorJson(error));
101
101
  throw error;
102
102
  }
103
- const writable = params.silent || !params.writable
104
- ? new WritableStream({ write() { } })
105
- : params.writable;
106
103
  const { jsonSchema, gateway, smoothStream, stepCountIs, streamText } = await import("ai");
107
- const { extractToolCallsFromParts } = await import("@ekairos/thread");
108
- const isMockModelConfig = (value) => {
109
- if (!value || typeof value !== "object")
110
- return false;
111
- if ("specificationVersion" in value)
112
- return false;
113
- if (value.source === "mock")
114
- return true;
115
- return typeof value.provider === "string" && typeof value.modelId === "string";
116
- };
117
- const buildMockModel = async (config) => {
118
- const toolName = typeof config.toolName === "string" && config.toolName.trim()
119
- ? config.toolName.trim()
120
- : Object.keys(params.tools || {})[0] || "tool";
121
- const provider = config.provider ?? "mock-provider";
122
- const modelId = config.modelId ?? "mock-model-id";
123
- return {
124
- specificationVersion: "v2",
125
- provider,
126
- modelId,
127
- supportedUrls: {},
128
- doGenerate: async () => ({
129
- content: [
130
- {
131
- type: "tool-call",
132
- toolCallId: "mock-tool-call",
133
- toolName,
134
- input: JSON.stringify({ instruction: "" }),
135
- },
136
- ],
137
- finishReason: "tool-calls",
138
- usage: { inputTokens: 1, outputTokens: 1, totalTokens: 2 },
139
- warnings: [],
140
- }),
141
- doStream: async () => {
142
- const toolCallId = `mock-tool-${Date.now()}`;
143
- const stream = new ReadableStream({
144
- start(controller) {
145
- controller.enqueue({ type: "stream-start", warnings: [] });
146
- controller.enqueue({
147
- type: "tool-call",
148
- toolCallId,
149
- toolName,
150
- input: JSON.stringify({ instruction: "" }),
151
- });
152
- controller.enqueue({
153
- type: "finish",
154
- finishReason: "tool-calls",
155
- usage: { inputTokens: 1, outputTokens: 1, totalTokens: 2 },
156
- });
157
- controller.close();
158
- },
159
- });
160
- return { stream };
161
- },
162
- };
163
- };
104
+ const { extractToolCallsFromParts } = await import("../thread.toolcalls.js");
164
105
  // Match DurableAgent-style model init behavior:
165
106
  const resolvedModel = typeof params.model === "string"
166
107
  ? gateway(params.model)
167
- : isMockModelConfig(params.model)
168
- ? await buildMockModel(params.model)
169
- : typeof params.model === "function"
170
- ? await params.model()
171
- : params.model;
108
+ : typeof params.model === "function"
109
+ ? await params.model()
110
+ : (() => {
111
+ throw new Error("Invalid model init passed to executeReaction. Expected a model id string or an async model factory.");
112
+ })();
172
113
  // Wrap plain JSON Schema objects so the AI SDK doesn't attempt Zod conversion at runtime.
173
114
  const toolsForStreamText = {};
174
115
  for (const [name, t] of Object.entries(params.tools)) {
@@ -226,7 +167,7 @@ export async function executeReaction(params) {
226
167
  controller.enqueue(chunk);
227
168
  },
228
169
  }));
229
- await uiStream.pipeTo(writable, { preventClose: true });
170
+ await uiStream.pipeTo(params.writable, { preventClose: true });
230
171
  const assistantEvent = await finishPromise;
231
172
  const finishedAtMs = Date.now();
232
173
  const toolCalls = extractToolCallsFromParts(assistantEvent?.content?.parts);
@@ -75,8 +75,7 @@ function inferDirection(item) {
75
75
  return undefined;
76
76
  }
77
77
  function shouldDebugThreadStoreSteps() {
78
- return (process.env.EKAIROS_THREAD_DEBUG === "1" ||
79
- process.env.PLAYWRIGHT_TEST === "1");
78
+ return process.env.EKAIROS_THREAD_DEBUG === "1";
80
79
  }
81
80
  function summarizeStepError(error) {
82
81
  const err = error;
@@ -1,9 +1,10 @@
1
1
  import "../polyfills/dom-events.js";
2
2
  import type { ThreadEnvironment } from "../thread.config.js";
3
+ import type { ThreadTraceEventKind } from "../thread.contract.js";
3
4
  export type ThreadTraceEventWrite = {
4
5
  workflowRunId: string;
5
6
  eventId: string;
6
- eventKind: string;
7
+ eventKind: ThreadTraceEventKind;
7
8
  seq?: number;
8
9
  eventAt?: string;
9
10
  contextKey?: string;
@@ -61,10 +61,15 @@ async function getTraceAuthHeader(baseUrl, projectId) {
61
61
  return `Bearer ${apiKey}`;
62
62
  }
63
63
  async function readProjectId() {
64
- const { getRuntimeProjectId } = await import("@ekairos/domain/runtime");
65
- const fromConfig = String(getRuntimeProjectId() || "").trim();
66
- if (fromConfig)
67
- return fromConfig;
64
+ try {
65
+ const { getRuntimeProjectId } = await import("@ekairos/domain/runtime");
66
+ const fromConfig = String(getRuntimeProjectId?.() || "").trim();
67
+ if (fromConfig)
68
+ return fromConfig;
69
+ }
70
+ catch {
71
+ // ignore and fall back to env
72
+ }
68
73
  const fallback = typeof process !== "undefined" && process.env
69
74
  ? String(process.env.EKAIROS_PROJECT_ID || "").trim()
70
75
  : "";
@@ -10,7 +10,7 @@ export declare function expandEventsWithInstantDocuments(params: {
10
10
  */
11
11
  maxChars?: number;
12
12
  /**
13
- * Event type used for derived document text. Defaults to "document.parsed".
13
+ * Event type used for derived document text. Defaults to "output_text".
14
14
  */
15
- derivedEventType?: string;
15
+ derivedEventType?: ThreadItem["type"];
16
16
  }): Promise<ThreadItem[]>;
@@ -72,7 +72,7 @@ async function ensureDocumentParsedForFile(db, params) {
72
72
  export async function expandEventsWithInstantDocuments(params) {
73
73
  const db = params.db;
74
74
  const maxChars = typeof params.maxChars === "number" ? params.maxChars : 120000;
75
- const derivedEventType = params.derivedEventType ?? "document.parsed";
75
+ const derivedEventType = params.derivedEventType ?? "output_text";
76
76
  const out = [];
77
77
  for (const event of params.events) {
78
78
  const parts = event?.content?.parts;
@@ -1,6 +1,7 @@
1
1
  import "../polyfills/dom-events.js";
2
2
  import { id, lookup } from "@instantdb/admin";
3
3
  import { convertItemsToModelMessages } from "../thread.events.js";
4
+ import { assertContextTransition, assertExecutionTransition, assertItemTransition, assertStepTransition, assertThreadTransition, } from "../thread.contract.js";
4
5
  export { parseAndStoreDocument } from "./instant.document-parser.js";
5
6
  import { expandEventsWithInstantDocuments } from "./instant.documents.js";
6
7
  export { coerceDocumentTextPages, expandEventsWithInstantDocuments, } from "./instant.documents.js";
@@ -299,6 +300,9 @@ export class InstantStore {
299
300
  const thread = await this.getThread(threadIdentifier);
300
301
  if (!thread)
301
302
  throw new Error("InstantStore: thread not found");
303
+ if (thread.status !== status) {
304
+ assertThreadTransition(thread.status, status);
305
+ }
302
306
  await this.db.transact([
303
307
  this.db.tx.thread_threads[thread.id].update({
304
308
  status,
@@ -409,6 +413,9 @@ export class InstantStore {
409
413
  const context = await this.getContext(contextIdentifier);
410
414
  if (!context?.id)
411
415
  throw new Error("InstantStore: context not found");
416
+ if (context.status !== status) {
417
+ assertContextTransition(context.status, status);
418
+ }
412
419
  const txs = [
413
420
  this.db.tx.thread_contexts[context.id].update({
414
421
  status,
@@ -416,8 +423,13 @@ export class InstantStore {
416
423
  }),
417
424
  ];
418
425
  if (context.threadId) {
426
+ const thread = await this.getThread({ id: context.threadId });
427
+ const nextThreadStatus = (status === "closed" ? "closed" : status);
428
+ if (thread && thread.status !== nextThreadStatus) {
429
+ assertThreadTransition(thread.status, nextThreadStatus);
430
+ }
419
431
  txs.push(this.db.tx.thread_threads[context.threadId].update({
420
- status: (status === "closed" ? "closed" : status),
432
+ status: nextThreadStatus,
421
433
  updatedAt: new Date(),
422
434
  }));
423
435
  }
@@ -440,6 +452,10 @@ export class InstantStore {
440
452
  }
441
453
  async saveItem(contextIdentifier, event) {
442
454
  const { context, thread } = await this.resolveThreadContext(contextIdentifier);
455
+ const existing = await this.getItem(event.id);
456
+ if (existing?.status && existing.status !== "stored") {
457
+ assertItemTransition(existing.status, "stored");
458
+ }
443
459
  const txs = [
444
460
  this.db.tx.thread_items[event.id].update({
445
461
  ...event,
@@ -474,6 +490,10 @@ export class InstantStore {
474
490
  return persisted;
475
491
  }
476
492
  async updateItem(eventId, event) {
493
+ const current = await this.getItem(eventId);
494
+ if (current?.status && event.status && current.status !== event.status) {
495
+ assertItemTransition(current.status, event.status);
496
+ }
477
497
  await this.db.transact([this.db.tx.thread_items[eventId].update(event)]);
478
498
  const persisted = await this.getItem(eventId);
479
499
  if (!persisted)
@@ -571,12 +591,31 @@ export class InstantStore {
571
591
  }
572
592
  async completeExecution(contextIdentifier, executionId, status) {
573
593
  const { context, thread } = await this.resolveThreadContext(contextIdentifier);
594
+ const executionResult = await this.db.query({
595
+ thread_executions: {
596
+ $: { where: { id: executionId }, limit: 1 },
597
+ },
598
+ });
599
+ const executionRow = executionResult?.thread_executions?.[0];
600
+ if (!executionRow)
601
+ throw new Error("InstantStore: execution not found");
602
+ const currentExecutionStatus = String(executionRow.status ?? "executing");
603
+ if (currentExecutionStatus !== status) {
604
+ assertExecutionTransition(currentExecutionStatus, status);
605
+ }
606
+ if (context.status !== "open") {
607
+ assertContextTransition(context.status, "open");
608
+ }
609
+ const nextThreadStatus = status === "failed" ? "failed" : "open";
610
+ if (thread.status !== nextThreadStatus) {
611
+ assertThreadTransition(thread.status, nextThreadStatus);
612
+ }
574
613
  const txs = [];
575
614
  txs.push(this.db.tx.thread_executions[executionId].update({ status, updatedAt: new Date() }));
576
615
  // Update context status back to "open" when execution completes
577
616
  txs.push(this.db.tx.thread_contexts[context.id].update({ status: "open", updatedAt: new Date() }));
578
617
  txs.push(this.db.tx.thread_threads[thread.id].update({
579
- status: status === "failed" ? "failed" : "open",
618
+ status: nextThreadStatus,
580
619
  updatedAt: new Date(),
581
620
  }));
582
621
  await this.db.transact(txs);
@@ -614,6 +653,20 @@ export class InstantStore {
614
653
  return { id: stepId, eventId };
615
654
  }
616
655
  async updateStep(stepId, patch) {
656
+ if (patch.status) {
657
+ const stepResult = await this.db.query({
658
+ thread_steps: {
659
+ $: { where: { id: stepId }, limit: 1 },
660
+ },
661
+ });
662
+ const stepRow = stepResult?.thread_steps?.[0];
663
+ if (!stepRow)
664
+ throw new Error("InstantStore: step not found");
665
+ const currentStepStatus = String(stepRow.status ?? "running");
666
+ if (currentStepStatus !== patch.status) {
667
+ assertStepTransition(currentStepStatus, patch.status);
668
+ }
669
+ }
617
670
  const update = {
618
671
  ...patch,
619
672
  updatedAt: patch.updatedAt ?? new Date(),
@@ -708,12 +761,12 @@ export class InstantStore {
708
761
  }
709
762
  }
710
763
  // Default behavior for Instant-backed stories:
711
- // - Expand file parts into derived `document.parsed` events (persisting parsed content into document_documents)
764
+ // - Expand file parts into derived `output_text` events (persisting parsed content into document_documents)
712
765
  // - Then convert expanded events to model messages
713
766
  const expanded = await expandEventsWithInstantDocuments({
714
767
  db: this.db,
715
768
  events: eventsWithParts,
716
- derivedEventType: "document.parsed",
769
+ derivedEventType: "output_text",
717
770
  });
718
771
  return await convertItemsToModelMessages(expanded);
719
772
  }