@ekairos/events 1.22.30-beta.development.0 → 1.22.31-beta.development.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/context.builder.d.ts +4 -0
- package/dist/context.builder.js +9 -0
- package/dist/context.d.ts +1 -0
- package/dist/context.engine.d.ts +2 -0
- package/dist/context.engine.js +35 -1
- package/dist/context.skill.d.ts +9 -0
- package/dist/context.skill.js +1 -0
- package/dist/context.step-stream.d.ts +26 -0
- package/dist/context.step-stream.js +59 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +1 -0
- package/dist/reactors/ai-sdk.reactor.js +3 -2
- package/dist/{steps/reaction.steps.d.ts → reactors/ai-sdk.step.d.ts} +10 -6
- package/dist/{steps/reaction.steps.js → reactors/ai-sdk.step.js} +98 -81
- package/dist/reactors/types.d.ts +3 -0
- package/dist/runtime.d.ts +1 -0
- package/dist/runtime.js +1 -0
- package/dist/runtime.step.js +1 -1
- package/dist/schema.js +21 -0
- package/dist/steps/stream.steps.d.ts +75 -0
- package/dist/steps/stream.steps.js +242 -0
- package/package.json +6 -4
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ContextEnvironment } from "./context.config.js";
|
|
2
|
+
import type { ContextSkillPackage } from "./context.skill.js";
|
|
2
3
|
import { ContextEngine, type ContextModelInit, type ContextOptions, type ContextTool, type ShouldContinue, type ContextShouldContinueArgs, type ContextReactParams } from "./context.engine.js";
|
|
3
4
|
import type { ContextReactor } from "./context.reactor.js";
|
|
4
5
|
import type { ContextItem, StoredContext } from "./context.store.js";
|
|
@@ -7,6 +8,7 @@ export interface ContextConfig<Context, Env extends ContextEnvironment = Context
|
|
|
7
8
|
context: (context: StoredContext<Context>, env: Env) => Promise<Context> | Context;
|
|
8
9
|
expandEvents?: (events: ContextItem[], context: StoredContext<Context>, env: Env) => Promise<ContextItem[]> | ContextItem[];
|
|
9
10
|
narrative: (context: StoredContext<Context>, env: Env) => Promise<string> | string;
|
|
11
|
+
skills?: (context: StoredContext<Context>, env: Env) => Promise<ContextSkillPackage[]> | ContextSkillPackage[];
|
|
10
12
|
actions: (context: StoredContext<Context>, env: Env) => Promise<Record<string, ContextTool>> | Record<string, ContextTool>;
|
|
11
13
|
/**
|
|
12
14
|
* @deprecated Use `actions()` instead.
|
|
@@ -25,6 +27,7 @@ export declare function context<Context, Env extends ContextEnvironment = Contex
|
|
|
25
27
|
type AnyContextInitializer<Env extends ContextEnvironment> = (context: StoredContext<any>, env: Env) => Promise<any> | any;
|
|
26
28
|
type InferContextFromInitializer<I extends AnyContextInitializer<any>> = Awaited<ReturnType<I>>;
|
|
27
29
|
type BuilderSystemPrompt<Context, Env extends ContextEnvironment> = (context: StoredContext<Context>, env: Env) => Promise<string> | string;
|
|
30
|
+
type BuilderSkills<Context, Env extends ContextEnvironment> = (context: StoredContext<Context>, env: Env) => Promise<ContextSkillPackage[]> | ContextSkillPackage[];
|
|
28
31
|
type BuilderTools<Context, Env extends ContextEnvironment> = (context: StoredContext<Context>, env: Env) => Promise<Record<string, ContextTool>> | Record<string, ContextTool>;
|
|
29
32
|
type BuilderExpandEvents<Context, Env extends ContextEnvironment> = (events: ContextItem[], context: StoredContext<Context>, env: Env) => Promise<ContextItem[]> | ContextItem[];
|
|
30
33
|
type BuilderShouldContinue<Context, Env extends ContextEnvironment> = (args: ContextShouldContinueArgs<Context, Env>) => Promise<ShouldContinue> | ShouldContinue;
|
|
@@ -38,6 +41,7 @@ type FluentContextBuilder<Context, Env extends ContextEnvironment> = {
|
|
|
38
41
|
expandEvents(fn: BuilderExpandEvents<Context, Env>): FluentContextBuilder<Context, Env>;
|
|
39
42
|
narrative(fn: BuilderSystemPrompt<Context, Env>): FluentContextBuilder<Context, Env>;
|
|
40
43
|
system(fn: BuilderSystemPrompt<Context, Env>): FluentContextBuilder<Context, Env>;
|
|
44
|
+
skills(fn: BuilderSkills<Context, Env>): FluentContextBuilder<Context, Env>;
|
|
41
45
|
actions(fn: BuilderTools<Context, Env>): FluentContextBuilder<Context, Env>;
|
|
42
46
|
tools(fn: BuilderTools<Context, Env>): FluentContextBuilder<Context, Env>;
|
|
43
47
|
model(model: BuilderModel<Context, Env>): FluentContextBuilder<Context, Env>;
|
package/dist/context.builder.js
CHANGED
|
@@ -22,6 +22,11 @@ export function context(config) {
|
|
|
22
22
|
return config.narrative(contextValue, env);
|
|
23
23
|
throw new Error("Context config is missing narrative()");
|
|
24
24
|
}
|
|
25
|
+
async buildSkills(contextValue, env) {
|
|
26
|
+
if (config.skills)
|
|
27
|
+
return config.skills(contextValue, env);
|
|
28
|
+
return [];
|
|
29
|
+
}
|
|
25
30
|
async buildTools(contextValue, env) {
|
|
26
31
|
if (config.actions)
|
|
27
32
|
return config.actions(contextValue, env);
|
|
@@ -84,6 +89,10 @@ export function createContext(key) {
|
|
|
84
89
|
fluentState.narrative = system;
|
|
85
90
|
return builder;
|
|
86
91
|
},
|
|
92
|
+
skills(skillsFactory) {
|
|
93
|
+
fluentState.skills = skillsFactory;
|
|
94
|
+
return builder;
|
|
95
|
+
},
|
|
87
96
|
actions(actionsFactory) {
|
|
88
97
|
fluentState.actions = actionsFactory;
|
|
89
98
|
return builder;
|
package/dist/context.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
export { ContextEngine, type ContextOptions, type ContextStreamOptions, type ShouldContinue, type ContextShouldContinueArgs, type ContextReactParams, type ContextReactResult, type ContextDurableWorkflowPayload, type ContextDurableWorkflowFunction, type ContextModelInit, type ContextTool, runContextReactionDirect, } from "./context.engine.js";
|
|
2
2
|
export { context, createContext, type ContextConfig, type ContextInstance, type RegistrableContextBuilder, } from "./context.builder.js";
|
|
3
3
|
export { createAiSdkReactor, createScriptedReactor, type CreateAiSdkReactorOptions, type CreateScriptedReactorOptions, type ScriptedReactorStep, type ContextReactor, type ContextReactorParams, type ContextReactionResult, type ContextActionRequest, type ContextReactionLLM, } from "./context.reactor.js";
|
|
4
|
+
export type { ContextSkillPackage, ContextSkillPackageFile, } from "./context.skill.js";
|
package/dist/context.engine.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Tool, UIMessageChunk } from "ai";
|
|
2
2
|
import type { ContextEnvironment } from "./context.config.js";
|
|
3
3
|
import type { ContextExecution, ContextItem, ContextIdentifier, StoredContext } from "./context.store.js";
|
|
4
|
+
import type { ContextSkillPackage } from "./context.skill.js";
|
|
4
5
|
import { type ContextReactor } from "./context.reactor.js";
|
|
5
6
|
import { getClientResumeHookUrl, toolApprovalHookToken, toolApprovalWebhookToken } from "./context.hooks.js";
|
|
6
7
|
export interface ContextOptions<Context = any, Env extends ContextEnvironment = ContextEnvironment> {
|
|
@@ -168,6 +169,7 @@ export declare abstract class ContextEngine<Context, Env extends ContextEnvironm
|
|
|
168
169
|
protected abstract initialize(context: StoredContext<Context>, env: Env): Promise<Context> | Context;
|
|
169
170
|
protected abstract buildSystemPrompt(context: StoredContext<Context>, env: Env): Promise<string> | string;
|
|
170
171
|
protected abstract buildTools(context: StoredContext<Context>, env: Env): Promise<Record<string, ContextTool>> | Record<string, ContextTool>;
|
|
172
|
+
protected buildSkills(_context: StoredContext<Context>, _env: Env): Promise<ContextSkillPackage[]>;
|
|
171
173
|
/**
|
|
172
174
|
* First-class event expansion stage (runs on every iteration of the durable loop).
|
|
173
175
|
*
|
package/dist/context.engine.js
CHANGED
|
@@ -3,7 +3,7 @@ import { OUTPUT_ITEM_TYPE, WEB_CHANNEL } from "./context.events.js";
|
|
|
3
3
|
import { applyToolExecutionResultToParts } from "./context.toolcalls.js";
|
|
4
4
|
import { toolsToModelTools } from "./tools-to-model-tools.js";
|
|
5
5
|
import { createAiSdkReactor, } from "./context.reactor.js";
|
|
6
|
-
import { closeContextStream, } from "./steps/stream.steps.js";
|
|
6
|
+
import { abortPersistedContextStepStream, closePersistedContextStepStream, createPersistedContextStepStream, closeContextStream, } from "./steps/stream.steps.js";
|
|
7
7
|
import { completeExecution, createContextStep, initializeContext, saveTriggerAndCreateExecution, saveContextPartsStep, updateContextContent, updateContextStatus, updateItem, updateContextStep, } from "./steps/store.steps.js";
|
|
8
8
|
import { getClientResumeHookUrl, toolApprovalHookToken, toolApprovalWebhookToken, } from "./context.hooks.js";
|
|
9
9
|
import { getContextDurableWorkflow } from "./context.durable.js";
|
|
@@ -267,6 +267,9 @@ export class ContextEngine {
|
|
|
267
267
|
this.opts = opts;
|
|
268
268
|
this.reactor = reactor ?? createAiSdkReactor();
|
|
269
269
|
}
|
|
270
|
+
async buildSkills(_context, _env) {
|
|
271
|
+
return [];
|
|
272
|
+
}
|
|
270
273
|
/**
|
|
271
274
|
* First-class event expansion stage (runs on every iteration of the durable loop).
|
|
272
275
|
*
|
|
@@ -422,6 +425,7 @@ export class ContextEngine {
|
|
|
422
425
|
const executionId = execution.id;
|
|
423
426
|
let updatedContext = { ...currentContext, status: "open_streaming" };
|
|
424
427
|
let currentStepId = null;
|
|
428
|
+
let currentStepStream = null;
|
|
425
429
|
const failExecution = async () => {
|
|
426
430
|
try {
|
|
427
431
|
await ops.completeExecution(activeContextSelector, executionId, "failed");
|
|
@@ -468,6 +472,11 @@ export class ContextEngine {
|
|
|
468
472
|
iteration: iter,
|
|
469
473
|
}));
|
|
470
474
|
currentStepId = stepCreate.stepId;
|
|
475
|
+
currentStepStream = await createPersistedContextStepStream({
|
|
476
|
+
env: params.env,
|
|
477
|
+
executionId,
|
|
478
|
+
stepId: stepCreate.stepId,
|
|
479
|
+
});
|
|
471
480
|
await emitContextEvents({
|
|
472
481
|
silent,
|
|
473
482
|
writable,
|
|
@@ -501,6 +510,7 @@ export class ContextEngine {
|
|
|
501
510
|
const systemPrompt = await measureBenchmark(params.__benchmark, `${stagePrefix}.narrativeMs`, async () => await story.buildSystemPrompt(updatedContext, params.env));
|
|
502
511
|
// Hook: Context DSL `actions()` (implemented by subclasses via `buildTools()`)
|
|
503
512
|
const toolsAll = await measureBenchmark(params.__benchmark, `${stagePrefix}.actionsMs`, async () => await story.buildTools(updatedContext, params.env));
|
|
513
|
+
const skillsAll = await measureBenchmark(params.__benchmark, `${stagePrefix}.skillsMs`, async () => await story.buildSkills(updatedContext, params.env));
|
|
504
514
|
// IMPORTANT: step args must be serializable.
|
|
505
515
|
// Match DurableAgent behavior: convert tool input schemas to plain JSON Schema in workflow context.
|
|
506
516
|
const toolsForModel = toolsToModelTools(toolsAll);
|
|
@@ -547,6 +557,7 @@ export class ContextEngine {
|
|
|
547
557
|
systemPrompt,
|
|
548
558
|
actions: toolsAll,
|
|
549
559
|
toolsForModel,
|
|
560
|
+
skills: skillsAll,
|
|
550
561
|
eventId: reactionEventId,
|
|
551
562
|
executionId,
|
|
552
563
|
contextId: String(currentContext.id),
|
|
@@ -556,6 +567,7 @@ export class ContextEngine {
|
|
|
556
567
|
// Only emit a `start` chunk once per story turn.
|
|
557
568
|
sendStart: !silent && iter === 0,
|
|
558
569
|
silent,
|
|
570
|
+
contextStepStream: currentStepStream?.stream,
|
|
559
571
|
writable,
|
|
560
572
|
persistReactionParts,
|
|
561
573
|
}));
|
|
@@ -622,6 +634,13 @@ export class ContextEngine {
|
|
|
622
634
|
status: "pending",
|
|
623
635
|
};
|
|
624
636
|
reactionEvent = await measureBenchmark(params.__benchmark, `${stagePrefix}.persistAssistantReactionMs`, async () => await ops.updateItem(reactionEvent.id, nextReactionEvent, { executionId, contextId: String(currentContext.id) }));
|
|
637
|
+
if (currentStepStream) {
|
|
638
|
+
await closePersistedContextStepStream({
|
|
639
|
+
env: params.env,
|
|
640
|
+
session: currentStepStream,
|
|
641
|
+
});
|
|
642
|
+
currentStepStream = null;
|
|
643
|
+
}
|
|
625
644
|
story.opts.onEventCreated?.(assistantEventEffective);
|
|
626
645
|
const firstActionRequest = actionRequests?.[0];
|
|
627
646
|
await measureBenchmark(params.__benchmark, `${stagePrefix}.markStepRunningMs`, async () => await ops.updateContextStep({
|
|
@@ -989,6 +1008,21 @@ export class ContextEngine {
|
|
|
989
1008
|
throw new Error(`ContextEngine: maxIterations reached (${maxIterations}) without completion`);
|
|
990
1009
|
}
|
|
991
1010
|
catch (error) {
|
|
1011
|
+
if (currentStepStream) {
|
|
1012
|
+
try {
|
|
1013
|
+
await abortPersistedContextStepStream({
|
|
1014
|
+
env: params.env,
|
|
1015
|
+
session: currentStepStream,
|
|
1016
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
1017
|
+
});
|
|
1018
|
+
}
|
|
1019
|
+
catch {
|
|
1020
|
+
// noop
|
|
1021
|
+
}
|
|
1022
|
+
finally {
|
|
1023
|
+
currentStepStream = null;
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
992
1026
|
// Best-effort: persist failure on the current iteration step (if any)
|
|
993
1027
|
if (currentStepId) {
|
|
994
1028
|
const failedStepId = currentStepId;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type ContextStreamChunkType } from "./context.contract.js";
|
|
2
|
+
export declare const CONTEXT_STEP_STREAM_VERSION: 1;
|
|
3
|
+
export type ContextStepStreamChunk = {
|
|
4
|
+
version: typeof CONTEXT_STEP_STREAM_VERSION;
|
|
5
|
+
at: string;
|
|
6
|
+
sequence: number;
|
|
7
|
+
chunkType: ContextStreamChunkType;
|
|
8
|
+
provider?: string;
|
|
9
|
+
providerChunkType?: string;
|
|
10
|
+
actionRef?: string;
|
|
11
|
+
data?: unknown;
|
|
12
|
+
raw?: unknown;
|
|
13
|
+
};
|
|
14
|
+
export declare function createContextStepStreamChunk(params: {
|
|
15
|
+
at?: string;
|
|
16
|
+
sequence: number;
|
|
17
|
+
chunkType: ContextStreamChunkType;
|
|
18
|
+
provider?: string;
|
|
19
|
+
providerChunkType?: string;
|
|
20
|
+
actionRef?: string;
|
|
21
|
+
data?: unknown;
|
|
22
|
+
raw?: unknown;
|
|
23
|
+
}): ContextStepStreamChunk;
|
|
24
|
+
export declare function parseContextStepStreamChunk(value: string | unknown): ContextStepStreamChunk;
|
|
25
|
+
export declare function encodeContextStepStreamChunk(chunk: ContextStepStreamChunk): string;
|
|
26
|
+
export declare function contextStreamByteLength(value: string): number;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { isContextStreamChunkType, } from "./context.contract.js";
|
|
2
|
+
export const CONTEXT_STEP_STREAM_VERSION = 1;
|
|
3
|
+
function assertObject(value, label) {
|
|
4
|
+
if (!value || typeof value !== "object") {
|
|
5
|
+
throw new Error(`Invalid ${label}: expected object.`);
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
function assertString(value, label) {
|
|
9
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
10
|
+
throw new Error(`Invalid ${label}: expected non-empty string.`);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
function assertNumber(value, label) {
|
|
14
|
+
if (typeof value !== "number" || Number.isNaN(value)) {
|
|
15
|
+
throw new Error(`Invalid ${label}: expected number.`);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function assertOptionalString(value, label) {
|
|
19
|
+
if (typeof value === "undefined")
|
|
20
|
+
return;
|
|
21
|
+
assertString(value, label);
|
|
22
|
+
}
|
|
23
|
+
export function createContextStepStreamChunk(params) {
|
|
24
|
+
return {
|
|
25
|
+
version: CONTEXT_STEP_STREAM_VERSION,
|
|
26
|
+
at: params.at ?? new Date().toISOString(),
|
|
27
|
+
sequence: params.sequence,
|
|
28
|
+
chunkType: params.chunkType,
|
|
29
|
+
provider: params.provider,
|
|
30
|
+
providerChunkType: params.providerChunkType,
|
|
31
|
+
actionRef: params.actionRef,
|
|
32
|
+
data: params.data,
|
|
33
|
+
raw: params.raw,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
export function parseContextStepStreamChunk(value) {
|
|
37
|
+
const parsed = typeof value === "string" ? JSON.parse(value) : value;
|
|
38
|
+
assertObject(parsed, "context step stream chunk");
|
|
39
|
+
assertNumber(parsed.version, "context step stream chunk.version");
|
|
40
|
+
if (parsed.version !== CONTEXT_STEP_STREAM_VERSION) {
|
|
41
|
+
throw new Error(`Unsupported context step stream chunk version: ${String(parsed.version)}`);
|
|
42
|
+
}
|
|
43
|
+
assertString(parsed.at, "context step stream chunk.at");
|
|
44
|
+
assertNumber(parsed.sequence, "context step stream chunk.sequence");
|
|
45
|
+
assertString(parsed.chunkType, "context step stream chunk.chunkType");
|
|
46
|
+
if (!isContextStreamChunkType(parsed.chunkType)) {
|
|
47
|
+
throw new Error(`Invalid context step stream chunk.chunkType: ${String(parsed.chunkType)}`);
|
|
48
|
+
}
|
|
49
|
+
assertOptionalString(parsed.provider, "context step stream chunk.provider");
|
|
50
|
+
assertOptionalString(parsed.providerChunkType, "context step stream chunk.providerChunkType");
|
|
51
|
+
assertOptionalString(parsed.actionRef, "context step stream chunk.actionRef");
|
|
52
|
+
return parsed;
|
|
53
|
+
}
|
|
54
|
+
export function encodeContextStepStreamChunk(chunk) {
|
|
55
|
+
return `${JSON.stringify(chunk)}\n`;
|
|
56
|
+
}
|
|
57
|
+
export function contextStreamByteLength(value) {
|
|
58
|
+
return new TextEncoder().encode(value).length;
|
|
59
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -10,4 +10,7 @@ export type { Transition, ContextStatus, ExecutionStatus, StepStatus, StepKind,
|
|
|
10
10
|
export { DEFAULT_CODEX_TOOL_NAME, DEFAULT_CODEX_MODEL, codexToolInputSchema, buildDefaultCodexNarrative, didCodexToolExecute, createCodexContextBuilder, type CodexContextRuntimeMode, type CodexContextReasoningLevel, type CodexContextRuntime, type CodexContextEnv, type CodexToolInput, type CodexToolOutput, type CodexExecuteArgs, type CodexContextBuilderConfig, type CodexContextBuilder, } from "./codex.js";
|
|
11
11
|
export { useContext, type ContextSnapshot, type ContextStreamChunk, type UseContextOptions, } from "./react.js";
|
|
12
12
|
export { parseContextStreamEvent, assertContextStreamTransitions, validateContextStreamTimeline, } from "./context.stream.js";
|
|
13
|
+
export { CONTEXT_STEP_STREAM_VERSION, createContextStepStreamChunk, parseContextStepStreamChunk, encodeContextStepStreamChunk, } from "./context.step-stream.js";
|
|
13
14
|
export type { ContextStreamEvent, ContextCreatedEvent, ContextResolvedEvent, ContextStatusChangedEvent, ContextContentUpdatedEvent, ExecutionCreatedEvent, ExecutionCompletedEvent, ExecutionFailedEvent, ItemCreatedEvent, ItemUpdatedEvent, ItemPendingEvent, ItemCompletedEvent, StepCreatedEvent, StepUpdatedEvent, StepCompletedEvent, StepFailedEvent, PartCreatedEvent, PartUpdatedEvent, ChunkEmittedEvent, } from "./context.stream.js";
|
|
15
|
+
export type { ContextStepStreamChunk } from "./context.step-stream.js";
|
|
16
|
+
export type { ContextSkillPackage, ContextSkillPackageFile } from "./context.skill.js";
|
package/dist/index.js
CHANGED
|
@@ -7,3 +7,4 @@ export { CONTEXT_STATUSES, EXECUTION_STATUSES, STEP_STATUSES, STEP_KINDS, ITEM_S
|
|
|
7
7
|
export { DEFAULT_CODEX_TOOL_NAME, DEFAULT_CODEX_MODEL, codexToolInputSchema, buildDefaultCodexNarrative, didCodexToolExecute, createCodexContextBuilder, } from "./codex.js";
|
|
8
8
|
export { useContext, } from "./react.js";
|
|
9
9
|
export { parseContextStreamEvent, assertContextStreamTransitions, validateContextStreamTimeline, } from "./context.stream.js";
|
|
10
|
+
export { CONTEXT_STEP_STREAM_VERSION, createContextStepStreamChunk, parseContextStepStreamChunk, encodeContextStepStreamChunk, } from "./context.step-stream.js";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { executeAiSdkReaction } from "./ai-sdk.step.js";
|
|
2
2
|
export function createAiSdkReactor(options) {
|
|
3
3
|
return async (params) => {
|
|
4
4
|
let config;
|
|
@@ -34,7 +34,7 @@ export function createAiSdkReactor(options) {
|
|
|
34
34
|
config,
|
|
35
35
|
})
|
|
36
36
|
: params.maxModelSteps;
|
|
37
|
-
const result = await
|
|
37
|
+
const result = await executeAiSdkReaction({
|
|
38
38
|
env: params.env,
|
|
39
39
|
contextIdentifier: params.contextIdentifier,
|
|
40
40
|
model,
|
|
@@ -45,6 +45,7 @@ export function createAiSdkReactor(options) {
|
|
|
45
45
|
maxSteps,
|
|
46
46
|
sendStart: params.sendStart,
|
|
47
47
|
silent: params.silent,
|
|
48
|
+
contextStepStream: params.contextStepStream,
|
|
48
49
|
writable: params.writable,
|
|
49
50
|
executionId: params.executionId,
|
|
50
51
|
contextId: params.contextId,
|
|
@@ -4,13 +4,16 @@ import type { ContextModelInit } from "../context.engine.js";
|
|
|
4
4
|
import type { ContextItem, ContextIdentifier } from "../context.store.js";
|
|
5
5
|
import type { SerializableToolForModel } from "../tools-to-model-tools.js";
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* -
|
|
10
|
-
* -
|
|
11
|
-
* -
|
|
7
|
+
* AI SDK-backed reaction execution inside a single workflow step.
|
|
8
|
+
*
|
|
9
|
+
* Provider-specific responsibilities live here:
|
|
10
|
+
* - load items from store
|
|
11
|
+
* - build model messages
|
|
12
|
+
* - stream through the AI SDK
|
|
13
|
+
* - map provider chunks to the Context stream contract
|
|
14
|
+
* - emit UI chunks and persist step stream chunks
|
|
12
15
|
*/
|
|
13
|
-
export declare function
|
|
16
|
+
export declare function executeAiSdkReaction(params: {
|
|
14
17
|
env: ContextEnvironment;
|
|
15
18
|
contextIdentifier: ContextIdentifier;
|
|
16
19
|
model: ContextModelInit;
|
|
@@ -21,6 +24,7 @@ export declare function executeReaction(params: {
|
|
|
21
24
|
maxSteps: number;
|
|
22
25
|
sendStart?: boolean;
|
|
23
26
|
silent?: boolean;
|
|
27
|
+
contextStepStream?: WritableStream<string>;
|
|
24
28
|
writable?: WritableStream<UIMessageChunk>;
|
|
25
29
|
executionId?: string;
|
|
26
30
|
contextId?: string;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { OUTPUT_ITEM_TYPE } from "../context.events.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { createContextStepStreamChunk, encodeContextStepStreamChunk, } from "../context.step-stream.js";
|
|
3
|
+
import { mapAiSdkChunkToContextEvent } from "./ai-sdk.chunk-map.js";
|
|
4
|
+
import { writeContextTraceEvents } from "../steps/trace.steps.js";
|
|
4
5
|
async function readWorkflowMetadata() {
|
|
5
6
|
try {
|
|
6
7
|
const { getWorkflowMetadata } = await import("workflow");
|
|
@@ -70,13 +71,16 @@ function safeErrorJson(error) {
|
|
|
70
71
|
}
|
|
71
72
|
}
|
|
72
73
|
/**
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
* -
|
|
76
|
-
* -
|
|
77
|
-
* -
|
|
74
|
+
* AI SDK-backed reaction execution inside a single workflow step.
|
|
75
|
+
*
|
|
76
|
+
* Provider-specific responsibilities live here:
|
|
77
|
+
* - load items from store
|
|
78
|
+
* - build model messages
|
|
79
|
+
* - stream through the AI SDK
|
|
80
|
+
* - map provider chunks to the Context stream contract
|
|
81
|
+
* - emit UI chunks and persist step stream chunks
|
|
78
82
|
*/
|
|
79
|
-
export async function
|
|
83
|
+
export async function executeAiSdkReaction(params) {
|
|
80
84
|
"use step";
|
|
81
85
|
const { getContextRuntime } = await import("../runtime.js");
|
|
82
86
|
const { store } = await getContextRuntime(params.env);
|
|
@@ -85,7 +89,7 @@ export async function executeReaction(params) {
|
|
|
85
89
|
events = await store.getItems(params.contextIdentifier);
|
|
86
90
|
}
|
|
87
91
|
catch (error) {
|
|
88
|
-
console.error("[ekairos/story]
|
|
92
|
+
console.error("[ekairos/story] ai-sdk.step store.getItems failed");
|
|
89
93
|
throw error;
|
|
90
94
|
}
|
|
91
95
|
let messagesForModel;
|
|
@@ -93,20 +97,18 @@ export async function executeReaction(params) {
|
|
|
93
97
|
messagesForModel = (await store.itemsToModelMessages(events));
|
|
94
98
|
}
|
|
95
99
|
catch (error) {
|
|
96
|
-
console.error("[ekairos/story]
|
|
100
|
+
console.error("[ekairos/story] ai-sdk.step store.itemsToModelMessages failed", safeErrorJson(error));
|
|
97
101
|
throw error;
|
|
98
102
|
}
|
|
99
103
|
const { jsonSchema, gateway, smoothStream, stepCountIs, streamText } = await import("ai");
|
|
100
104
|
const { extractToolCallsFromParts } = await import("../context.toolcalls.js");
|
|
101
|
-
// Match DurableAgent-style model init behavior:
|
|
102
105
|
const resolvedModel = typeof params.model === "string"
|
|
103
106
|
? gateway(params.model)
|
|
104
107
|
: typeof params.model === "function"
|
|
105
108
|
? await params.model()
|
|
106
109
|
: (() => {
|
|
107
|
-
throw new Error("Invalid model init passed to
|
|
110
|
+
throw new Error("Invalid model init passed to executeAiSdkReaction. Expected a model id string or an async model factory.");
|
|
108
111
|
})();
|
|
109
|
-
// Wrap plain JSON Schema objects so the AI SDK doesn't attempt Zod conversion at runtime.
|
|
110
112
|
const toolsForStreamText = {};
|
|
111
113
|
for (const [name, t] of Object.entries(params.tools)) {
|
|
112
114
|
toolsForStreamText[name] = {
|
|
@@ -124,7 +126,6 @@ export async function executeReaction(params) {
|
|
|
124
126
|
stopWhen: stepCountIs(params.maxSteps),
|
|
125
127
|
experimental_transform: smoothStream({ delayInMs: 30, chunking: "word" }),
|
|
126
128
|
});
|
|
127
|
-
// Ensure the underlying stream is consumed (AI SDK requirement)
|
|
128
129
|
result.consumeStream();
|
|
129
130
|
let resolveFinish;
|
|
130
131
|
let rejectFinish;
|
|
@@ -135,76 +136,96 @@ export async function executeReaction(params) {
|
|
|
135
136
|
});
|
|
136
137
|
const modelId = typeof params.model === "string" ? params.model : "";
|
|
137
138
|
const mappedProvider = modelId.includes("/") ? modelId.split("/")[0] : undefined;
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
139
|
+
const contextStepStreamWriter = params.contextStepStream?.getWriter();
|
|
140
|
+
try {
|
|
141
|
+
const uiStream = result
|
|
142
|
+
.toUIMessageStream({
|
|
143
|
+
sendStart: Boolean(params.sendStart),
|
|
144
|
+
generateMessageId: () => params.eventId,
|
|
145
|
+
messageMetadata() {
|
|
146
|
+
return { eventId: params.eventId };
|
|
147
|
+
},
|
|
148
|
+
onFinish: ({ messages }) => {
|
|
149
|
+
const lastMessage = messages[messages.length - 1];
|
|
150
|
+
const event = {
|
|
151
|
+
id: params.eventId,
|
|
152
|
+
type: OUTPUT_ITEM_TYPE,
|
|
153
|
+
channel: "web",
|
|
154
|
+
createdAt: new Date().toISOString(),
|
|
155
|
+
content: { parts: lastMessage?.parts ?? [] },
|
|
156
|
+
};
|
|
157
|
+
resolveFinish(event);
|
|
158
|
+
},
|
|
159
|
+
onError: (e) => {
|
|
160
|
+
rejectFinish(e);
|
|
161
|
+
return e instanceof Error ? e.message : String(e);
|
|
162
|
+
},
|
|
163
|
+
})
|
|
164
|
+
.pipeThrough(new TransformStream({
|
|
165
|
+
async transform(chunk, controller) {
|
|
166
|
+
const contextId = typeof params.contextId === "string" && params.contextId.length > 0
|
|
167
|
+
? params.contextId
|
|
168
|
+
: undefined;
|
|
169
|
+
if (contextId) {
|
|
170
|
+
const mapped = mapAiSdkChunkToContextEvent({
|
|
171
|
+
chunk,
|
|
172
|
+
contextId,
|
|
173
|
+
executionId: params.executionId,
|
|
174
|
+
stepId: params.stepId,
|
|
175
|
+
itemId: params.eventId,
|
|
176
|
+
provider: mappedProvider,
|
|
177
|
+
sequence: ++chunkSequence,
|
|
178
|
+
});
|
|
179
|
+
const persistedChunk = createContextStepStreamChunk({
|
|
180
|
+
at: mapped.at,
|
|
181
|
+
sequence: mapped.sequence,
|
|
182
|
+
chunkType: mapped.chunkType,
|
|
183
|
+
provider: mapped.provider,
|
|
184
|
+
providerChunkType: mapped.providerChunkType,
|
|
185
|
+
actionRef: mapped.actionRef,
|
|
186
|
+
data: mapped.data && typeof mapped.data === "object"
|
|
187
|
+
? mapped.data
|
|
188
|
+
: undefined,
|
|
189
|
+
raw: mapped.raw && typeof mapped.raw === "object"
|
|
190
|
+
? mapped.raw
|
|
191
|
+
: undefined,
|
|
192
|
+
});
|
|
193
|
+
if (contextStepStreamWriter) {
|
|
194
|
+
await contextStepStreamWriter.write(encodeContextStepStreamChunk(persistedChunk));
|
|
195
|
+
}
|
|
196
|
+
controller.enqueue({
|
|
197
|
+
type: "data-chunk.emitted",
|
|
198
|
+
data: mapped,
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
if (chunk.type === "finish")
|
|
202
|
+
return;
|
|
203
|
+
controller.enqueue(chunk);
|
|
204
|
+
},
|
|
205
|
+
}));
|
|
206
|
+
if (params.writable) {
|
|
207
|
+
await uiStream.pipeTo(params.writable, { preventClose: true });
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
210
|
+
const reader = uiStream.getReader();
|
|
211
|
+
try {
|
|
212
|
+
while (true) {
|
|
213
|
+
const { done } = await reader.read();
|
|
214
|
+
if (done)
|
|
215
|
+
break;
|
|
216
|
+
}
|
|
181
217
|
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
controller.enqueue(chunk);
|
|
185
|
-
},
|
|
186
|
-
}));
|
|
187
|
-
if (params.writable) {
|
|
188
|
-
await uiStream.pipeTo(params.writable, { preventClose: true });
|
|
189
|
-
}
|
|
190
|
-
else {
|
|
191
|
-
const reader = uiStream.getReader();
|
|
192
|
-
try {
|
|
193
|
-
while (true) {
|
|
194
|
-
const { done } = await reader.read();
|
|
195
|
-
if (done)
|
|
196
|
-
break;
|
|
218
|
+
finally {
|
|
219
|
+
reader.releaseLock();
|
|
197
220
|
}
|
|
198
221
|
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
222
|
+
}
|
|
223
|
+
finally {
|
|
224
|
+
contextStepStreamWriter?.releaseLock();
|
|
202
225
|
}
|
|
203
226
|
const assistantEvent = await finishPromise;
|
|
204
227
|
const finishedAtMs = Date.now();
|
|
205
228
|
const toolCalls = extractToolCallsFromParts(assistantEvent?.content?.parts);
|
|
206
|
-
// Best-effort usage extraction (AI SDK provider dependent).
|
|
207
|
-
// We keep this loose because providers differ and SDK evolves quickly.
|
|
208
229
|
const latencyMs = Math.max(0, finishedAtMs - startedAtMs);
|
|
209
230
|
let usage = undefined;
|
|
210
231
|
let providerMetadata = undefined;
|
|
@@ -226,8 +247,6 @@ export async function executeReaction(params) {
|
|
|
226
247
|
catch {
|
|
227
248
|
providerMetadata = undefined;
|
|
228
249
|
}
|
|
229
|
-
// Workflow steps must return serializable values. Provider SDKs may include
|
|
230
|
-
// classes/streams/etc in metadata, so we defensively sanitize.
|
|
231
250
|
function toPlainJson(value) {
|
|
232
251
|
if (typeof value === "undefined")
|
|
233
252
|
return undefined;
|
|
@@ -240,10 +259,8 @@ export async function executeReaction(params) {
|
|
|
240
259
|
}
|
|
241
260
|
const usageJson = toPlainJson(usage);
|
|
242
261
|
const providerMetadataJson = toPlainJson(providerMetadata);
|
|
243
|
-
// Derive provider/model from gateway id when available.
|
|
244
262
|
const provider = modelId.includes("/") ? modelId.split("/")[0] : providerMetadata?.provider;
|
|
245
263
|
const model = modelId.includes("/") ? modelId.split("/").slice(1).join("/") : providerMetadata?.model;
|
|
246
|
-
// Token accounting: attempt to read cached prompt tokens from OpenAI-like usage shapes.
|
|
247
264
|
const promptTokens = Number(usage?.promptTokens ?? usage?.prompt_tokens ?? usage?.inputTokens ?? 0) || 0;
|
|
248
265
|
const completionTokens = Number(usage?.completionTokens ?? usage?.completion_tokens ?? usage?.outputTokens ?? 0) || 0;
|
|
249
266
|
const totalTokens = Number(usage?.totalTokens ?? usage?.total_tokens ?? 0) || (promptTokens + completionTokens);
|
package/dist/reactors/types.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { ModelMessage, UIMessageChunk } from "ai";
|
|
|
2
2
|
import type { ContextEnvironment } from "../context.config.js";
|
|
3
3
|
import type { ContextModelInit } from "../context.engine.js";
|
|
4
4
|
import type { ContextIdentifier, StoredContext, ContextItem } from "../context.store.js";
|
|
5
|
+
import type { ContextSkillPackage } from "../context.skill.js";
|
|
5
6
|
import type { SerializableToolForModel } from "../tools-to-model-tools.js";
|
|
6
7
|
export type ContextActionRequest = {
|
|
7
8
|
actionRef: string;
|
|
@@ -35,6 +36,7 @@ export type ContextReactorParams<Context = unknown, Env extends ContextEnvironme
|
|
|
35
36
|
systemPrompt: string;
|
|
36
37
|
actions: Record<string, unknown>;
|
|
37
38
|
toolsForModel: Record<string, SerializableToolForModel>;
|
|
39
|
+
skills: ContextSkillPackage[];
|
|
38
40
|
eventId: string;
|
|
39
41
|
executionId: string;
|
|
40
42
|
contextId: string;
|
|
@@ -43,6 +45,7 @@ export type ContextReactorParams<Context = unknown, Env extends ContextEnvironme
|
|
|
43
45
|
maxModelSteps: number;
|
|
44
46
|
sendStart: boolean;
|
|
45
47
|
silent: boolean;
|
|
48
|
+
contextStepStream?: WritableStream<string>;
|
|
46
49
|
writable?: WritableStream<UIMessageChunk>;
|
|
47
50
|
persistReactionParts?: (parts: any[]) => Promise<void>;
|
|
48
51
|
};
|
package/dist/runtime.d.ts
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* - Keep `@ekairos/events` main entrypoint safe to import from schema/domain modules.
|
|
10
10
|
*/
|
|
11
11
|
export { getContextRuntime, } from "./runtime.step.js";
|
|
12
|
+
export { createContextStepStreamClientId, createPersistedContextStepStream, readPersistedContextStepStream, resolveContextExecutionStreamPointer, waitForContextExecutionStreamPointer, } from "./steps/stream.steps.js";
|
|
12
13
|
export type { ContextEnvironment, ContextRuntime } from "./context.config.js";
|
|
13
14
|
export { registerContextEnv, getContextEnv } from "./env.js";
|
|
14
15
|
export { configureContextDurableWorkflow } from "./context.durable.js";
|
package/dist/runtime.js
CHANGED
|
@@ -21,5 +21,6 @@ if (typeof globalThis.Event === "undefined") {
|
|
|
21
21
|
globalThis.Event = NodeEvent;
|
|
22
22
|
}
|
|
23
23
|
export { getContextRuntime, } from "./runtime.step.js";
|
|
24
|
+
export { createContextStepStreamClientId, createPersistedContextStepStream, readPersistedContextStepStream, resolveContextExecutionStreamPointer, waitForContextExecutionStreamPointer, } from "./steps/stream.steps.js";
|
|
24
25
|
export { registerContextEnv, getContextEnv } from "./env.js";
|
|
25
26
|
export { configureContextDurableWorkflow } from "./context.durable.js";
|
package/dist/runtime.step.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { coerceContextRuntime } from "./context.config.js";
|
|
2
2
|
import { eventsDomain } from "./schema.js";
|
|
3
3
|
export async function getContextRuntime(env) {
|
|
4
|
-
const { resolveRuntime } = await import("@ekairos/domain/runtime");
|
|
4
|
+
const { resolveRuntime } = await import("@ekairos/domain/runtime-step");
|
|
5
5
|
const resolved = await resolveRuntime(eventsDomain, env);
|
|
6
6
|
return await coerceContextRuntime(resolved);
|
|
7
7
|
}
|
package/dist/schema.js
CHANGED
|
@@ -23,6 +23,10 @@ export const eventsDomain = domain("events")
|
|
|
23
23
|
updatedAt: i.date().optional(),
|
|
24
24
|
status: i.string().optional().indexed(), // executing | completed | failed
|
|
25
25
|
workflowRunId: i.string().optional().indexed(),
|
|
26
|
+
activeStreamId: i.string().optional().indexed(),
|
|
27
|
+
activeStreamClientId: i.string().optional().indexed(),
|
|
28
|
+
lastStreamId: i.string().optional().indexed(),
|
|
29
|
+
lastStreamClientId: i.string().optional().indexed(),
|
|
26
30
|
}),
|
|
27
31
|
event_steps: i.entity({
|
|
28
32
|
createdAt: i.date().indexed(),
|
|
@@ -38,6 +42,11 @@ export const eventsDomain = domain("events")
|
|
|
38
42
|
actionResults: i.any().optional(),
|
|
39
43
|
continueLoop: i.boolean().optional(),
|
|
40
44
|
errorText: i.string().optional(),
|
|
45
|
+
streamId: i.string().optional().indexed(),
|
|
46
|
+
streamClientId: i.string().optional().indexed(),
|
|
47
|
+
streamStartedAt: i.date().optional().indexed(),
|
|
48
|
+
streamFinishedAt: i.date().optional().indexed(),
|
|
49
|
+
streamAbortReason: i.string().optional(),
|
|
41
50
|
}),
|
|
42
51
|
event_parts: i.entity({
|
|
43
52
|
key: i.string().unique().indexed(), // `${stepId}:${idx}`
|
|
@@ -152,6 +161,18 @@ export const eventsDomain = domain("events")
|
|
|
152
161
|
forward: { on: "event_parts", has: "one", label: "step" },
|
|
153
162
|
reverse: { on: "event_steps", has: "many", label: "parts" },
|
|
154
163
|
},
|
|
164
|
+
contextStepStream: {
|
|
165
|
+
forward: { on: "event_steps", has: "one", label: "stream" },
|
|
166
|
+
reverse: { on: "$streams", has: "many", label: "step" },
|
|
167
|
+
},
|
|
168
|
+
contextExecutionActiveStream: {
|
|
169
|
+
forward: { on: "event_executions", has: "one", label: "activeStream" },
|
|
170
|
+
reverse: { on: "$streams", has: "many", label: "activeOf" },
|
|
171
|
+
},
|
|
172
|
+
contextExecutionLastStream: {
|
|
173
|
+
forward: { on: "event_executions", has: "one", label: "lastStream" },
|
|
174
|
+
reverse: { on: "$streams", has: "many", label: "lastOf" },
|
|
175
|
+
},
|
|
155
176
|
documentFile: {
|
|
156
177
|
forward: {
|
|
157
178
|
on: "document_documents",
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { UIMessageChunk } from "ai";
|
|
2
|
+
import type { ContextEnvironment } from "../context.config.js";
|
|
3
|
+
import { type ContextStepStreamChunk } from "../context.step-stream.js";
|
|
2
4
|
import type { ContextStreamEvent } from "../context.stream.js";
|
|
3
5
|
export declare function writeContextEvents(params: {
|
|
4
6
|
events: ContextStreamEvent[];
|
|
@@ -9,3 +11,76 @@ export declare function closeContextStream(params: {
|
|
|
9
11
|
sendFinish?: boolean;
|
|
10
12
|
writable?: WritableStream<UIMessageChunk>;
|
|
11
13
|
}): Promise<void>;
|
|
14
|
+
export declare function createContextStepStreamClientId(stepId: string): string;
|
|
15
|
+
export type PersistedContextStepStreamSession = {
|
|
16
|
+
stream: WritableStream<string>;
|
|
17
|
+
streamId: string;
|
|
18
|
+
clientId: string;
|
|
19
|
+
executionId: string;
|
|
20
|
+
stepId: string;
|
|
21
|
+
};
|
|
22
|
+
export declare function createPersistedContextStepStream(params: {
|
|
23
|
+
env: ContextEnvironment;
|
|
24
|
+
executionId: string;
|
|
25
|
+
stepId: string;
|
|
26
|
+
clientId?: string;
|
|
27
|
+
}): Promise<PersistedContextStepStreamSession>;
|
|
28
|
+
export declare function closePersistedContextStepStream(params: {
|
|
29
|
+
env: ContextEnvironment;
|
|
30
|
+
session: PersistedContextStepStreamSession;
|
|
31
|
+
}): Promise<void>;
|
|
32
|
+
export declare function abortPersistedContextStepStream(params: {
|
|
33
|
+
env: ContextEnvironment;
|
|
34
|
+
session: PersistedContextStepStreamSession;
|
|
35
|
+
reason?: string | null;
|
|
36
|
+
}): Promise<void>;
|
|
37
|
+
export declare function readPersistedContextStepStream(params: {
|
|
38
|
+
db: any;
|
|
39
|
+
clientId?: string;
|
|
40
|
+
streamId?: string;
|
|
41
|
+
byteOffset?: number;
|
|
42
|
+
onChunk?: (chunk: ContextStepStreamChunk) => Promise<void> | void;
|
|
43
|
+
}): Promise<{
|
|
44
|
+
chunks: ContextStepStreamChunk[];
|
|
45
|
+
byteOffset: number;
|
|
46
|
+
}>;
|
|
47
|
+
export declare function resolveContextExecutionStreamPointer(params: {
|
|
48
|
+
db: any;
|
|
49
|
+
contextId: string;
|
|
50
|
+
}): Promise<{
|
|
51
|
+
executionId: string;
|
|
52
|
+
status: string | null;
|
|
53
|
+
source: "active";
|
|
54
|
+
clientId: string | null;
|
|
55
|
+
streamId: string | null;
|
|
56
|
+
} | {
|
|
57
|
+
executionId: string;
|
|
58
|
+
status: string | null;
|
|
59
|
+
source: "last";
|
|
60
|
+
clientId: string | null;
|
|
61
|
+
streamId: string | null;
|
|
62
|
+
} | {
|
|
63
|
+
executionId: string;
|
|
64
|
+
status: string | null;
|
|
65
|
+
source: "none";
|
|
66
|
+
clientId: null;
|
|
67
|
+
streamId: null;
|
|
68
|
+
} | null>;
|
|
69
|
+
export declare function waitForContextExecutionStreamPointer(params: {
|
|
70
|
+
db: any;
|
|
71
|
+
contextId: string;
|
|
72
|
+
timeoutMs?: number;
|
|
73
|
+
pollMs?: number;
|
|
74
|
+
}): Promise<{
|
|
75
|
+
executionId: string;
|
|
76
|
+
status: string | null;
|
|
77
|
+
source: "active";
|
|
78
|
+
clientId: string | null;
|
|
79
|
+
streamId: string | null;
|
|
80
|
+
} | {
|
|
81
|
+
executionId: string;
|
|
82
|
+
status: string | null;
|
|
83
|
+
source: "last";
|
|
84
|
+
clientId: string | null;
|
|
85
|
+
streamId: string | null;
|
|
86
|
+
} | null>;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { contextStreamByteLength, parseContextStepStreamChunk, } from "../context.step-stream.js";
|
|
1
2
|
export async function writeContextEvents(params) {
|
|
2
3
|
"use step";
|
|
3
4
|
const writable = params.writable;
|
|
@@ -36,3 +37,244 @@ export async function closeContextStream(params) {
|
|
|
36
37
|
await writable.close();
|
|
37
38
|
}
|
|
38
39
|
}
|
|
40
|
+
function readWaitUntil(env) {
|
|
41
|
+
const candidate = typeof env?.waitUntil === "function"
|
|
42
|
+
? env.waitUntil
|
|
43
|
+
: typeof env?.after === "function"
|
|
44
|
+
? env.after
|
|
45
|
+
: undefined;
|
|
46
|
+
return typeof candidate === "function" ? candidate : undefined;
|
|
47
|
+
}
|
|
48
|
+
function asRecord(value) {
|
|
49
|
+
return value && typeof value === "object" ? value : {};
|
|
50
|
+
}
|
|
51
|
+
function asString(value) {
|
|
52
|
+
return typeof value === "string" ? value.trim() : "";
|
|
53
|
+
}
|
|
54
|
+
function createUnsetStreamLinkTx(db, executionId, label, streamId) {
|
|
55
|
+
try {
|
|
56
|
+
return db.tx.event_executions[executionId].unlink({ [label]: streamId });
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
export function createContextStepStreamClientId(stepId) {
|
|
63
|
+
const normalized = String(stepId ?? "").trim();
|
|
64
|
+
if (!normalized) {
|
|
65
|
+
throw new Error("createContextStepStreamClientId: stepId is required.");
|
|
66
|
+
}
|
|
67
|
+
return `event-step:${normalized}`;
|
|
68
|
+
}
|
|
69
|
+
export async function createPersistedContextStepStream(params) {
|
|
70
|
+
"use step";
|
|
71
|
+
const { getContextRuntime } = await import("../runtime.js");
|
|
72
|
+
const runtime = await getContextRuntime(params.env);
|
|
73
|
+
const db = runtime?.db;
|
|
74
|
+
if (!db?.streams?.createWriteStream) {
|
|
75
|
+
throw new Error("InstantDB streams are not available on the configured runtime. Upgrade @instantdb/admin to a streams-capable version.");
|
|
76
|
+
}
|
|
77
|
+
const clientId = asString(params.clientId) || createContextStepStreamClientId(params.stepId);
|
|
78
|
+
const startedAt = new Date();
|
|
79
|
+
const waitUntil = readWaitUntil(params.env);
|
|
80
|
+
const writeStream = db.streams.createWriteStream({
|
|
81
|
+
clientId,
|
|
82
|
+
waitUntil,
|
|
83
|
+
});
|
|
84
|
+
const streamId = await writeStream.streamId();
|
|
85
|
+
await db.transact([
|
|
86
|
+
db.tx.event_steps[params.stepId]
|
|
87
|
+
.update({
|
|
88
|
+
streamId,
|
|
89
|
+
streamClientId: clientId,
|
|
90
|
+
streamStartedAt: startedAt,
|
|
91
|
+
streamFinishedAt: null,
|
|
92
|
+
streamAbortReason: null,
|
|
93
|
+
updatedAt: new Date(),
|
|
94
|
+
})
|
|
95
|
+
.link({ stream: streamId }),
|
|
96
|
+
db.tx.event_executions[params.executionId]
|
|
97
|
+
.update({
|
|
98
|
+
activeStreamId: streamId,
|
|
99
|
+
activeStreamClientId: clientId,
|
|
100
|
+
lastStreamId: streamId,
|
|
101
|
+
lastStreamClientId: clientId,
|
|
102
|
+
updatedAt: new Date(),
|
|
103
|
+
})
|
|
104
|
+
.link({ activeStream: streamId, lastStream: streamId }),
|
|
105
|
+
]);
|
|
106
|
+
return {
|
|
107
|
+
stream: writeStream,
|
|
108
|
+
streamId,
|
|
109
|
+
clientId,
|
|
110
|
+
executionId: params.executionId,
|
|
111
|
+
stepId: params.stepId,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
async function finalizePersistedContextStepStream(params) {
|
|
115
|
+
"use step";
|
|
116
|
+
const { getContextRuntime } = await import("../runtime.js");
|
|
117
|
+
const runtime = await getContextRuntime(params.env);
|
|
118
|
+
const db = runtime?.db;
|
|
119
|
+
const writer = params.session.stream.getWriter();
|
|
120
|
+
try {
|
|
121
|
+
if (params.mode === "abort") {
|
|
122
|
+
await writer.abort(params.abortReason ?? "aborted");
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
await writer.close();
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
finally {
|
|
129
|
+
writer.releaseLock();
|
|
130
|
+
}
|
|
131
|
+
const now = new Date();
|
|
132
|
+
const txs = [
|
|
133
|
+
db.tx.event_steps[params.session.stepId].update({
|
|
134
|
+
streamFinishedAt: now,
|
|
135
|
+
streamAbortReason: params.mode === "abort" ? params.abortReason ?? "aborted" : null,
|
|
136
|
+
updatedAt: now,
|
|
137
|
+
}),
|
|
138
|
+
db.tx.event_executions[params.session.executionId].update({
|
|
139
|
+
activeStreamId: null,
|
|
140
|
+
activeStreamClientId: null,
|
|
141
|
+
lastStreamId: params.session.streamId,
|
|
142
|
+
lastStreamClientId: params.session.clientId,
|
|
143
|
+
updatedAt: now,
|
|
144
|
+
}),
|
|
145
|
+
];
|
|
146
|
+
const unsetActive = createUnsetStreamLinkTx(db, params.session.executionId, "activeStream", params.session.streamId);
|
|
147
|
+
if (unsetActive)
|
|
148
|
+
txs.push(unsetActive);
|
|
149
|
+
await db.transact(txs);
|
|
150
|
+
}
|
|
151
|
+
export async function closePersistedContextStepStream(params) {
|
|
152
|
+
return await finalizePersistedContextStepStream({
|
|
153
|
+
env: params.env,
|
|
154
|
+
session: params.session,
|
|
155
|
+
mode: "close",
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
export async function abortPersistedContextStepStream(params) {
|
|
159
|
+
return await finalizePersistedContextStepStream({
|
|
160
|
+
env: params.env,
|
|
161
|
+
session: params.session,
|
|
162
|
+
mode: "abort",
|
|
163
|
+
abortReason: params.reason,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
export async function readPersistedContextStepStream(params) {
|
|
167
|
+
if (!params.db?.streams?.createReadStream) {
|
|
168
|
+
throw new Error("InstantDB streams are not available on the provided runtime.");
|
|
169
|
+
}
|
|
170
|
+
const clientId = asString(params.clientId);
|
|
171
|
+
const streamId = asString(params.streamId);
|
|
172
|
+
if (!clientId && !streamId) {
|
|
173
|
+
throw new Error("readPersistedContextStepStream requires clientId or streamId.");
|
|
174
|
+
}
|
|
175
|
+
const startOffset = typeof params.byteOffset === "number" && Number.isFinite(params.byteOffset)
|
|
176
|
+
? Math.max(0, params.byteOffset)
|
|
177
|
+
: 0;
|
|
178
|
+
const stream = params.db.streams.createReadStream({
|
|
179
|
+
clientId: clientId || undefined,
|
|
180
|
+
streamId: streamId || undefined,
|
|
181
|
+
byteOffset: startOffset,
|
|
182
|
+
});
|
|
183
|
+
const chunks = [];
|
|
184
|
+
let byteOffset = startOffset;
|
|
185
|
+
let buffer = "";
|
|
186
|
+
for await (const rawChunk of stream) {
|
|
187
|
+
const encoded = typeof rawChunk === "string" ? rawChunk : String(rawChunk ?? "");
|
|
188
|
+
if (!encoded)
|
|
189
|
+
continue;
|
|
190
|
+
byteOffset += contextStreamByteLength(encoded);
|
|
191
|
+
buffer += encoded;
|
|
192
|
+
const lines = buffer.split("\n");
|
|
193
|
+
buffer = lines.pop() ?? "";
|
|
194
|
+
for (const line of lines) {
|
|
195
|
+
const trimmed = line.trim();
|
|
196
|
+
if (!trimmed)
|
|
197
|
+
continue;
|
|
198
|
+
const parsed = parseContextStepStreamChunk(trimmed);
|
|
199
|
+
chunks.push(parsed);
|
|
200
|
+
await params.onChunk?.(parsed);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
const trailing = buffer.trim();
|
|
204
|
+
if (trailing) {
|
|
205
|
+
const parsed = parseContextStepStreamChunk(trailing);
|
|
206
|
+
chunks.push(parsed);
|
|
207
|
+
await params.onChunk?.(parsed);
|
|
208
|
+
}
|
|
209
|
+
return {
|
|
210
|
+
chunks,
|
|
211
|
+
byteOffset,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
export async function resolveContextExecutionStreamPointer(params) {
|
|
215
|
+
const snapshot = await params.db.query({
|
|
216
|
+
event_contexts: {
|
|
217
|
+
$: {
|
|
218
|
+
where: { id: params.contextId },
|
|
219
|
+
limit: 1,
|
|
220
|
+
},
|
|
221
|
+
currentExecution: {},
|
|
222
|
+
},
|
|
223
|
+
});
|
|
224
|
+
const contextRow = Array.isArray(snapshot?.event_contexts)
|
|
225
|
+
? snapshot.event_contexts[0]
|
|
226
|
+
: null;
|
|
227
|
+
const executionRow = asRecord(contextRow?.currentExecution);
|
|
228
|
+
const executionId = asString(executionRow.id);
|
|
229
|
+
if (!executionId)
|
|
230
|
+
return null;
|
|
231
|
+
const activeStreamClientId = asString(executionRow.activeStreamClientId);
|
|
232
|
+
const activeStreamId = asString(executionRow.activeStreamId);
|
|
233
|
+
if (activeStreamClientId || activeStreamId) {
|
|
234
|
+
return {
|
|
235
|
+
executionId,
|
|
236
|
+
status: asString(executionRow.status) || null,
|
|
237
|
+
source: "active",
|
|
238
|
+
clientId: activeStreamClientId || null,
|
|
239
|
+
streamId: activeStreamId || null,
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
const lastStreamClientId = asString(executionRow.lastStreamClientId);
|
|
243
|
+
const lastStreamId = asString(executionRow.lastStreamId);
|
|
244
|
+
if (lastStreamClientId || lastStreamId) {
|
|
245
|
+
return {
|
|
246
|
+
executionId,
|
|
247
|
+
status: asString(executionRow.status) || null,
|
|
248
|
+
source: "last",
|
|
249
|
+
clientId: lastStreamClientId || null,
|
|
250
|
+
streamId: lastStreamId || null,
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
return {
|
|
254
|
+
executionId,
|
|
255
|
+
status: asString(executionRow.status) || null,
|
|
256
|
+
source: "none",
|
|
257
|
+
clientId: null,
|
|
258
|
+
streamId: null,
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
export async function waitForContextExecutionStreamPointer(params) {
|
|
262
|
+
const timeoutMs = typeof params.timeoutMs === "number" && Number.isFinite(params.timeoutMs)
|
|
263
|
+
? Math.max(0, params.timeoutMs)
|
|
264
|
+
: 15000;
|
|
265
|
+
const pollMs = typeof params.pollMs === "number" && Number.isFinite(params.pollMs)
|
|
266
|
+
? Math.max(10, params.pollMs)
|
|
267
|
+
: 125;
|
|
268
|
+
const deadline = Date.now() + timeoutMs;
|
|
269
|
+
while (Date.now() <= deadline) {
|
|
270
|
+
const pointer = await resolveContextExecutionStreamPointer({
|
|
271
|
+
db: params.db,
|
|
272
|
+
contextId: params.contextId,
|
|
273
|
+
});
|
|
274
|
+
if (pointer && (pointer.clientId || pointer.streamId)) {
|
|
275
|
+
return pointer;
|
|
276
|
+
}
|
|
277
|
+
await new Promise((resolve) => setTimeout(resolve, pollMs));
|
|
278
|
+
}
|
|
279
|
+
return null;
|
|
280
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ekairos/events",
|
|
3
|
-
"version": "1.22.
|
|
3
|
+
"version": "1.22.31-beta.development.0",
|
|
4
4
|
"description": "Ekairos Events - Context-first workflow runtime",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -108,15 +108,17 @@
|
|
|
108
108
|
"clean": "node -e \"require('fs').rmSync('dist', {recursive:true, force:true})\"",
|
|
109
109
|
"typecheck": "tsc --noEmit",
|
|
110
110
|
"test": "vitest run -c vitest.config.mts",
|
|
111
|
+
"pretest:ai-sdk-reactor": "pnpm --filter @ekairos/domain build",
|
|
111
112
|
"test:ai-sdk-reactor": "vitest run -c vitest.config.mts src/tests/context.ai-sdk-reactor.instant.test.ts",
|
|
112
113
|
"test:ai-sdk-reactor:real": "vitest run -c vitest.config.mts src/tests/context.ai-sdk-reactor.real.instant.test.ts",
|
|
114
|
+
"pretest:scripted-reactor": "pnpm --filter @ekairos/domain build",
|
|
113
115
|
"test:scripted-reactor": "vitest run -c vitest.config.mts src/tests/context.scripted-reactor.instant.test.ts"
|
|
114
116
|
},
|
|
115
117
|
"dependencies": {
|
|
116
118
|
"@ai-sdk/openai": "^2.0.52",
|
|
117
|
-
"@ekairos/domain": "^1.22.
|
|
118
|
-
"@instantdb/admin": "0.22.
|
|
119
|
-
"@instantdb/core": "0.22.
|
|
119
|
+
"@ekairos/domain": "^1.22.31-beta.development.0",
|
|
120
|
+
"@instantdb/admin": "0.22.158",
|
|
121
|
+
"@instantdb/core": "0.22.142",
|
|
120
122
|
"@vercel/mcp-adapter": "^1.0.0",
|
|
121
123
|
"@vercel/sandbox": "^0.0.23",
|
|
122
124
|
"ai": "^5.0.95",
|