@ekairos/events 1.22.35-beta.development.0 → 1.22.35

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 (73) hide show
  1. package/README.md +5 -3
  2. package/dist/codex.d.ts +11 -2
  3. package/dist/codex.js +16 -8
  4. package/dist/context.action-calls.d.ts +48 -0
  5. package/dist/context.action-calls.js +123 -0
  6. package/dist/context.action.d.ts +55 -0
  7. package/dist/context.action.js +25 -0
  8. package/dist/context.builder.d.ts +71 -43
  9. package/dist/context.builder.js +123 -28
  10. package/dist/context.config.d.ts +2 -1
  11. package/dist/context.config.js +8 -3
  12. package/dist/context.contract.d.ts +2 -4
  13. package/dist/context.contract.js +3 -9
  14. package/dist/context.d.ts +3 -2
  15. package/dist/context.engine.d.ts +60 -52
  16. package/dist/context.engine.js +506 -297
  17. package/dist/context.events.js +28 -87
  18. package/dist/context.js +1 -0
  19. package/dist/context.part-identity.d.ts +40 -0
  20. package/dist/context.part-identity.js +270 -0
  21. package/dist/context.parts.d.ts +389 -164
  22. package/dist/context.parts.js +343 -218
  23. package/dist/context.registry.d.ts +1 -1
  24. package/dist/context.runtime.d.ts +14 -4
  25. package/dist/context.runtime.js +21 -3
  26. package/dist/context.step-stream.d.ts +16 -2
  27. package/dist/context.step-stream.js +58 -16
  28. package/dist/context.store.d.ts +55 -10
  29. package/dist/context.stream.d.ts +14 -4
  30. package/dist/context.stream.js +31 -3
  31. package/dist/domain.d.ts +1 -0
  32. package/dist/domain.js +1 -0
  33. package/dist/index.d.ts +13 -10
  34. package/dist/index.js +7 -6
  35. package/dist/react.context-event-parts.d.ts +18 -0
  36. package/dist/react.context-event-parts.js +509 -0
  37. package/dist/react.d.ts +7 -42
  38. package/dist/react.js +4 -87
  39. package/dist/react.step-stream.d.ts +39 -0
  40. package/dist/react.step-stream.js +625 -0
  41. package/dist/react.types.d.ts +121 -0
  42. package/dist/react.types.js +2 -0
  43. package/dist/react.use-context.d.ts +7 -0
  44. package/dist/react.use-context.js +867 -0
  45. package/dist/reactors/ai-sdk.chunk-map.d.ts +1 -0
  46. package/dist/reactors/ai-sdk.chunk-map.js +56 -5
  47. package/dist/reactors/ai-sdk.reactor.d.ts +8 -9
  48. package/dist/reactors/ai-sdk.reactor.js +6 -9
  49. package/dist/reactors/ai-sdk.step.d.ts +4 -5
  50. package/dist/reactors/ai-sdk.step.js +24 -17
  51. package/dist/reactors/scripted.reactor.d.ts +7 -4
  52. package/dist/reactors/types.d.ts +19 -10
  53. package/dist/runtime.d.ts +6 -0
  54. package/dist/runtime.js +9 -0
  55. package/dist/runtime.step.js +1 -1
  56. package/dist/schema.d.ts +268 -2
  57. package/dist/schema.js +4 -9
  58. package/dist/steps/do-context-stream-step.js +4 -4
  59. package/dist/steps/durable.steps.d.ts +28 -0
  60. package/dist/steps/durable.steps.js +34 -0
  61. package/dist/steps/store.steps.d.ts +64 -22
  62. package/dist/steps/store.steps.js +192 -35
  63. package/dist/steps/stream.steps.d.ts +32 -0
  64. package/dist/steps/stream.steps.js +124 -6
  65. package/dist/steps/trace.steps.d.ts +4 -4
  66. package/dist/steps/trace.steps.js +21 -6
  67. package/dist/stores/instant.store.d.ts +11 -11
  68. package/dist/stores/instant.store.js +136 -6
  69. package/dist/tools-to-model-tools.d.ts +4 -2
  70. package/dist/tools-to-model-tools.js +30 -11
  71. package/package.json +18 -7
  72. package/dist/context.toolcalls.d.ts +0 -60
  73. package/dist/context.toolcalls.js +0 -117
@@ -1,7 +1,7 @@
1
1
  import "../polyfills/dom-events.js";
2
- import type { DomainSchemaResult } from "@ekairos/domain";
2
+ import type { DomainLike } from "@ekairos/domain";
3
3
  import type { ModelMessage } from "ai";
4
- import type { ContextItem, ContextIdentifier, ContextStatus, StoredContext, ContextStore } from "../context.store.js";
4
+ import type { ContextItem, ContextIdentifier, ContextResource, ContextStatus, StoredContextResource, StoredContext, ContextStore } from "../context.store.js";
5
5
  export { parseAndStoreDocument } from "./instant.document-parser.js";
6
6
  export { coerceDocumentTextPages, expandEventsWithInstantDocuments, } from "./instant.documents.js";
7
7
  export type InstantStoreDb = any;
@@ -14,12 +14,20 @@ export declare class InstantStore implements ContextStore {
14
14
  getOrCreateContext<C>(contextIdentifier: ContextIdentifier | null): Promise<StoredContext<C>>;
15
15
  getContext<C>(contextIdentifier: ContextIdentifier): Promise<StoredContext<C> | null>;
16
16
  updateContextContent<C>(contextIdentifier: ContextIdentifier, content: C): Promise<StoredContext<C>>;
17
+ updateContextDefinition<C>(contextIdentifier: ContextIdentifier, definition: {
18
+ description?: string | null;
19
+ goal?: string | null;
20
+ }): Promise<StoredContext<C>>;
17
21
  updateContextReactor<C>(contextIdentifier: ContextIdentifier, reactor: {
18
22
  kind: string;
19
23
  state?: Record<string, unknown> | null;
20
24
  }): Promise<StoredContext<C>>;
21
25
  updateContextStatus(contextIdentifier: ContextIdentifier, status: ContextStatus): Promise<void>;
22
26
  private resolveContext;
27
+ private normalizeContextResource;
28
+ private normalizeContextResources;
29
+ getContextResources(contextIdentifier: ContextIdentifier): Promise<StoredContextResource[]>;
30
+ upsertContextResources(contextIdentifier: ContextIdentifier, resources: ContextResource[]): Promise<StoredContextResource[]>;
23
31
  saveItem(contextIdentifier: ContextIdentifier, event: ContextItem): Promise<ContextItem>;
24
32
  updateItem(eventId: string, event: ContextItem): Promise<ContextItem>;
25
33
  getItem(eventId: string): Promise<ContextItem | null>;
@@ -38,14 +46,6 @@ export declare class InstantStore implements ContextStore {
38
46
  }>;
39
47
  updateStep(stepId: string, patch: Partial<{
40
48
  status: "running" | "completed" | "failed";
41
- kind: "message" | "action_execute" | "action_result";
42
- actionName: string;
43
- actionInput: unknown;
44
- actionOutput: unknown;
45
- actionError: string;
46
- actionRequests: any;
47
- actionResults: any;
48
- continueLoop: boolean;
49
49
  errorText: string;
50
50
  updatedAt: Date;
51
51
  }>): Promise<void>;
@@ -62,7 +62,7 @@ export declare class InstantStore implements ContextStore {
62
62
  export declare function createInstantStoreRuntime(params: {
63
63
  getDb: (orgId: string) => Promise<InstantStoreDb> | InstantStoreDb;
64
64
  getOrgId?: (env: Record<string, unknown>) => string;
65
- domain?: DomainSchemaResult;
65
+ domain?: DomainLike;
66
66
  }): (env: Record<string, unknown>) => Promise<{
67
67
  store: InstantStore;
68
68
  db: InstantStoreDb;
@@ -71,6 +71,43 @@ function ensureValidEntityId(value, label) {
71
71
  }
72
72
  return normalized;
73
73
  }
74
+ function sanitizeInstantString(value) {
75
+ return value.includes("\u0000") ? value.replace(/\u0000/g, "") : value;
76
+ }
77
+ function isOpaqueJsonValue(value) {
78
+ return (value instanceof Date ||
79
+ value instanceof ArrayBuffer ||
80
+ ArrayBuffer.isView(value));
81
+ }
82
+ function sanitizeInstantValue(value, seen = new WeakMap()) {
83
+ if (typeof value === "string") {
84
+ return sanitizeInstantString(value);
85
+ }
86
+ if (value === null || value === undefined || typeof value !== "object") {
87
+ return value;
88
+ }
89
+ if (isOpaqueJsonValue(value)) {
90
+ return value;
91
+ }
92
+ const cached = seen.get(value);
93
+ if (cached) {
94
+ return cached;
95
+ }
96
+ if (Array.isArray(value)) {
97
+ const out = [];
98
+ seen.set(value, out);
99
+ for (const item of value) {
100
+ out.push(sanitizeInstantValue(item, seen));
101
+ }
102
+ return out;
103
+ }
104
+ const out = {};
105
+ seen.set(value, out);
106
+ for (const [key, entryValue] of Object.entries(value)) {
107
+ out[sanitizeInstantString(key)] = sanitizeInstantValue(entryValue, seen);
108
+ }
109
+ return out;
110
+ }
74
111
  function logInstantTransactFailure(params) {
75
112
  if (!shouldDebugInstantStore())
76
113
  return;
@@ -118,6 +155,9 @@ export class InstantStore {
118
155
  ? new Date(row.updatedAt)
119
156
  : undefined,
120
157
  content: row?.content ?? null,
158
+ description: typeof row?.description === "string" ? row.description : null,
159
+ goal: typeof row?.goal === "string" ? row.goal : null,
160
+ resources: this.normalizeContextResources(row?.resources),
121
161
  reactor: row?.reactor && typeof row.reactor === "object"
122
162
  ? row.reactor
123
163
  : null,
@@ -147,6 +187,9 @@ export class InstantStore {
147
187
  key,
148
188
  status: "open_idle",
149
189
  content: {},
190
+ description: undefined,
191
+ goal: undefined,
192
+ resources: [],
150
193
  reactor: undefined,
151
194
  }),
152
195
  ]);
@@ -207,6 +250,28 @@ export class InstantStore {
207
250
  throw new Error("InstantStore: context not found after update");
208
251
  return updated;
209
252
  }
253
+ async updateContextDefinition(contextIdentifier, definition) {
254
+ const context = await this.getContext(contextIdentifier);
255
+ if (!context?.id)
256
+ throw new Error("InstantStore: context not found");
257
+ const update = {
258
+ updatedAt: new Date(),
259
+ };
260
+ if (definition.description !== undefined) {
261
+ update.description =
262
+ typeof definition.description === "string" ? definition.description : undefined;
263
+ }
264
+ if (definition.goal !== undefined) {
265
+ update.goal = typeof definition.goal === "string" ? definition.goal : undefined;
266
+ }
267
+ await this.db.transact([
268
+ this.db.tx.event_contexts[context.id].update(update),
269
+ ]);
270
+ const updated = await this.getContext({ id: context.id });
271
+ if (!updated)
272
+ throw new Error("InstantStore: context not found after definition update");
273
+ return updated;
274
+ }
210
275
  async updateContextReactor(contextIdentifier, reactor) {
211
276
  const context = await this.getContext(contextIdentifier);
212
277
  if (!context?.id)
@@ -242,8 +307,69 @@ export class InstantStore {
242
307
  throw new Error("InstantStore: context not found");
243
308
  return context;
244
309
  }
310
+ normalizeContextResource(resource) {
311
+ if (!resource || typeof resource !== "object")
312
+ return null;
313
+ const record = resource;
314
+ const key = typeof record.key === "string" ? record.key.trim() : "";
315
+ const type = typeof record.type === "string" ? record.type.trim() : "";
316
+ const name = typeof record.name === "string" ? record.name.trim() : "";
317
+ const description = typeof record.description === "string" ? record.description.trim() : "";
318
+ if (!key || !type || !name || !description)
319
+ return null;
320
+ return {
321
+ ...record,
322
+ key,
323
+ type,
324
+ name,
325
+ description,
326
+ };
327
+ }
328
+ normalizeContextResources(value) {
329
+ if (!Array.isArray(value))
330
+ return [];
331
+ return value.flatMap((resource) => {
332
+ const normalized = this.normalizeContextResource(resource);
333
+ return normalized ? [normalized] : [];
334
+ });
335
+ }
336
+ async getContextResources(contextIdentifier) {
337
+ const context = await this.resolveContext(contextIdentifier);
338
+ return context.resources ?? [];
339
+ }
340
+ async upsertContextResources(contextIdentifier, resources) {
341
+ const context = await this.resolveContext(contextIdentifier);
342
+ const now = new Date();
343
+ const storedResources = [];
344
+ for (const resource of resources) {
345
+ const resourceKey = typeof resource.key === "string" ? resource.key.trim() : "";
346
+ const type = typeof resource.type === "string" ? resource.type.trim() : "";
347
+ const name = typeof resource.name === "string" ? resource.name.trim() : "";
348
+ const description = typeof resource.description === "string" ? resource.description.trim() : "";
349
+ if (!resourceKey || !type || !name || !description) {
350
+ throw new Error("InstantStore: context resources require key, type, name, and description.");
351
+ }
352
+ const sanitizedResource = sanitizeInstantValue(resource);
353
+ const storedResource = {
354
+ ...sanitizedResource,
355
+ key: resourceKey,
356
+ type,
357
+ name,
358
+ description,
359
+ };
360
+ storedResources.push(storedResource);
361
+ }
362
+ await this.db.transact([
363
+ this.db.tx.event_contexts[context.id].update({
364
+ resources: storedResources,
365
+ updatedAt: now,
366
+ }),
367
+ ]);
368
+ return await this.getContextResources({ id: context.id });
369
+ }
245
370
  async saveItem(contextIdentifier, event) {
246
371
  const eventId = ensureValidEntityId(event?.id, "event.id");
372
+ const sanitizedEvent = sanitizeInstantValue(event);
247
373
  const context = await this.resolveContext(contextIdentifier);
248
374
  const existing = await this.getItem(eventId);
249
375
  if (existing?.status && existing.status !== "stored") {
@@ -251,7 +377,7 @@ export class InstantStore {
251
377
  }
252
378
  const txs = [
253
379
  this.db.tx.event_items[eventId].update({
254
- ...event,
380
+ ...sanitizedEvent,
255
381
  id: eventId,
256
382
  status: "stored",
257
383
  }),
@@ -277,7 +403,7 @@ export class InstantStore {
277
403
  throw error;
278
404
  }
279
405
  return {
280
- ...event,
406
+ ...sanitizedEvent,
281
407
  id: eventId,
282
408
  status: "stored",
283
409
  };
@@ -287,10 +413,11 @@ export class InstantStore {
287
413
  if (current?.status && event.status && current.status !== event.status) {
288
414
  assertItemTransition(current.status, event.status);
289
415
  }
290
- await this.db.transact([this.db.tx.event_items[eventId].update(event)]);
416
+ const sanitizedEvent = sanitizeInstantValue(event);
417
+ await this.db.transact([this.db.tx.event_items[eventId].update(sanitizedEvent)]);
291
418
  return {
292
419
  ...current,
293
- ...event,
420
+ ...sanitizedEvent,
294
421
  id: eventId,
295
422
  };
296
423
  }
@@ -517,9 +644,12 @@ export class InstantStore {
517
644
  }
518
645
  }
519
646
  const update = {
520
- ...patch,
521
647
  updatedAt: patch.updatedAt ?? new Date(),
522
648
  };
649
+ if (patch.status !== undefined)
650
+ update.status = patch.status;
651
+ if (patch.errorText !== undefined)
652
+ update.errorText = patch.errorText;
523
653
  await this.db.transact([this.db.tx.event_steps[stepId].update(update)]);
524
654
  }
525
655
  async linkItemToExecution(params) {
@@ -528,7 +658,7 @@ export class InstantStore {
528
658
  ]);
529
659
  }
530
660
  async saveStepParts(params) {
531
- const parts = normalizePartsForPersistence(Array.isArray(params.parts) ? params.parts : []);
661
+ const parts = sanitizeInstantValue(normalizePartsForPersistence(Array.isArray(params.parts) ? params.parts : []));
532
662
  if (parts.length === 0)
533
663
  return;
534
664
  const txs = parts.map((part, idx) => {
@@ -1,4 +1,3 @@
1
- import { type Tool } from "ai";
2
1
  /**
3
2
  * Serializable "tool" shape to pass across the Workflow step boundary.
4
3
  *
@@ -10,6 +9,7 @@ export type SerializableFunctionActionSpec = {
10
9
  type?: "function";
11
10
  description?: string;
12
11
  inputSchema: unknown;
12
+ outputSchema?: unknown;
13
13
  providerOptions?: unknown;
14
14
  };
15
15
  export type SerializableProviderDefinedActionSpec = {
@@ -29,7 +29,7 @@ export type SerializableToolForModel = SerializableActionSpec;
29
29
  * This matches DurableAgent's internal `toolsToModelTools` behavior:
30
30
  * `inputSchema: asSchema(tool.inputSchema).jsonSchema`
31
31
  */
32
- export declare function actionsToActionSpecs(tools: Record<string, Tool>): Record<string, SerializableActionSpec>;
32
+ export declare function actionsToActionSpecs(tools: Record<string, unknown>): Record<string, SerializableActionSpec>;
33
33
  export declare function actionSpecToAiSdkTool(name: string, spec: SerializableActionSpec, wrapJsonSchema: (schema: unknown) => unknown): {
34
34
  type: "provider-defined";
35
35
  id: string;
@@ -37,11 +37,13 @@ export declare function actionSpecToAiSdkTool(name: string, spec: SerializableAc
37
37
  args: Record<string, unknown>;
38
38
  description?: undefined;
39
39
  inputSchema?: undefined;
40
+ outputSchema?: undefined;
40
41
  providerOptions?: undefined;
41
42
  } | {
42
43
  type: "function";
43
44
  description: string | undefined;
44
45
  inputSchema: unknown;
46
+ outputSchema: unknown;
45
47
  providerOptions: unknown;
46
48
  id?: undefined;
47
49
  name?: undefined;
@@ -1,10 +1,25 @@
1
- import { asSchema } from "ai";
1
+ import { z } from "zod";
2
+ function toJsonSchema(schema) {
3
+ if (!schema)
4
+ return schema;
5
+ const jsonSchema = schema?.jsonSchema;
6
+ if (jsonSchema)
7
+ return jsonSchema;
8
+ try {
9
+ return z.toJSONSchema(schema);
10
+ }
11
+ catch {
12
+ return schema;
13
+ }
14
+ }
15
+ function asRecord(value) {
16
+ return value && typeof value === "object" ? value : {};
17
+ }
2
18
  function isProviderDefinedTool(tool) {
3
- return (Boolean(tool) &&
4
- typeof tool === "object" &&
5
- tool.type === "provider-defined" &&
6
- typeof tool.id === "string" &&
7
- tool.id.trim().length > 0);
19
+ const record = asRecord(tool);
20
+ return (record.type === "provider-defined" &&
21
+ typeof record.id === "string" &&
22
+ record.id.trim().length > 0);
8
23
  }
9
24
  /**
10
25
  * Convert AI SDK tools to a serializable representation that can be passed to `"use-step"` functions.
@@ -24,15 +39,18 @@ export function actionsToActionSpecs(tools) {
24
39
  };
25
40
  continue;
26
41
  }
27
- const inputSchema = tool?.inputSchema;
42
+ const record = asRecord(tool);
43
+ const inputSchema = record.inputSchema ?? record.input;
28
44
  if (!inputSchema) {
29
- throw new Error(`Context: tool "${name}" is missing inputSchema (required for model tool calls)`);
45
+ throw new Error(`Context: action "${name}" is missing input/inputSchema (required for model action calls)`);
30
46
  }
47
+ const outputSchema = record.outputSchema ?? record.output;
31
48
  out[name] = {
32
49
  type: "function",
33
- description: tool?.description,
34
- inputSchema: asSchema(inputSchema).jsonSchema,
35
- providerOptions: tool?.providerOptions,
50
+ description: typeof record.description === "string" ? record.description : undefined,
51
+ inputSchema: toJsonSchema(inputSchema),
52
+ outputSchema: outputSchema ? toJsonSchema(outputSchema) : undefined,
53
+ providerOptions: record.providerOptions,
36
54
  };
37
55
  }
38
56
  return out;
@@ -50,6 +68,7 @@ export function actionSpecToAiSdkTool(name, spec, wrapJsonSchema) {
50
68
  type: "function",
51
69
  description: spec.description,
52
70
  inputSchema: wrapJsonSchema(spec.inputSchema),
71
+ outputSchema: spec.outputSchema ? wrapJsonSchema(spec.outputSchema) : undefined,
53
72
  providerOptions: spec.providerOptions,
54
73
  };
55
74
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ekairos/events",
3
- "version": "1.22.35-beta.development.0",
3
+ "version": "1.22.35",
4
4
  "description": "Ekairos Events - Context-first workflow runtime",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -30,6 +30,12 @@
30
30
  "require": "./dist/schema.js",
31
31
  "default": "./dist/schema.js"
32
32
  },
33
+ "./domain": {
34
+ "types": "./dist/domain.d.ts",
35
+ "import": "./dist/domain.js",
36
+ "require": "./dist/domain.js",
37
+ "default": "./dist/domain.js"
38
+ },
33
39
  "./runtime": {
34
40
  "types": "./dist/runtime.d.ts",
35
41
  "import": "./dist/runtime.js",
@@ -78,6 +84,9 @@
78
84
  "schema": [
79
85
  "dist/schema.d.ts"
80
86
  ],
87
+ "domain": [
88
+ "dist/domain.d.ts"
89
+ ],
81
90
  "runtime": [
82
91
  "dist/runtime.d.ts"
83
92
  ],
@@ -107,6 +116,7 @@
107
116
  "watch": "tsc -p tsconfig.json --watch",
108
117
  "clean": "node -e \"require('fs').rmSync('dist', {recursive:true, force:true})\"",
109
118
  "typecheck": "tsc --noEmit",
119
+ "typecheck:tests": "tsc --noEmit -p tsconfig.typecheck.json",
110
120
  "test": "vitest run -c vitest.config.mts",
111
121
  "test:workflow": "vitest run -c vitest.workflow.config.mts",
112
122
  "pretest:ai-sdk-reactor": "pnpm --filter @ekairos/domain build",
@@ -117,7 +127,7 @@
117
127
  },
118
128
  "dependencies": {
119
129
  "@ai-sdk/openai": "^2.0.52",
120
- "@ekairos/domain": "^1.22.35-beta.development.0",
130
+ "@ekairos/domain": "^1.22.35",
121
131
  "@instantdb/admin": "0.22.158",
122
132
  "@instantdb/core": "0.22.142",
123
133
  "@vercel/mcp-adapter": "^1.0.0",
@@ -126,20 +136,21 @@
126
136
  "ajv": "^8.17.1",
127
137
  "jose": "^6.1.3",
128
138
  "llamaindex": "^0.12.0",
139
+ "partial-json": "^0.1.7",
129
140
  "react": "^19.2.0",
130
- "workflow": "5.0.0-beta.1",
141
+ "workflow": "5.0.0-beta.5",
131
142
  "xmlbuilder2": "^3.1.1",
132
143
  "zod": "^4.3.6"
133
144
  },
134
145
  "devDependencies": {
135
146
  "@ekairos/testing": "workspace:*",
136
147
  "@ekairos/tsconfig": "workspace:*",
137
- "@workflow/serde": "5.0.0-beta.0",
138
- "@workflow/vitest": "5.0.0-beta.1",
139
148
  "@types/node": "^24.5.0",
140
149
  "@types/react": "^19.2.2",
150
+ "@workflow/serde": "5.0.0-beta.1",
151
+ "@workflow/vitest": "5.0.0-beta.5",
141
152
  "dotenv": "^17.2.3",
142
- "vitest": "^3.2.4",
143
- "typescript": "^5.9.2"
153
+ "typescript": "^5.9.2",
154
+ "vitest": "^4.0.8"
144
155
  }
145
156
  }
@@ -1,60 +0,0 @@
1
- /**
2
- * ## context.toolcalls.ts
3
- *
4
- * This module isolates the **tool-call plumbing** used by `context.engine.ts`.
5
- *
6
- * In our runtime, tool calls are represented as **event parts** produced by the AI SDK.
7
- * The engine needs to:
8
- * - extract a normalized list of tool calls from `event.content.parts`, and
9
- * - merge tool execution outcomes back into those parts (so the persisted event reflects
10
- * `output-available` / `output-error`, etc.).
11
- *
12
- * Keeping this logic here helps `context.engine.ts` read like orchestration, and keeps
13
- * these transformations testable and reusable.
14
- */
15
- import type { ContextItem } from "./context.store.js";
16
- export type ToolCall = {
17
- toolCallId: string;
18
- toolName: string;
19
- args: any;
20
- };
21
- /**
22
- * Extracts tool calls from an event's `parts` array.
23
- *
24
- * Expected part shape (loosely):
25
- * - `type`: string like `"tool-<toolName>"`
26
- * - `toolCallId`: string
27
- * - `input`: any (tool args)
28
- *
29
- * We intentionally treat the input as `any` because the part schema is produced by the AI SDK.
30
- */
31
- export declare function extractToolCallsFromParts(parts: any[] | undefined | null): ToolCall[];
32
- /**
33
- * Applies a tool execution outcome to the matching tool part.
34
- *
35
- * This does not mutate `parts` — it returns a new array.
36
- *
37
- * We match the tool part by:
38
- * - `type === "tool-<toolName>"` and
39
- * - `toolCallId` equality
40
- *
41
- * Then we set:
42
- * - on success: `{ state: "output-available", output: <result> }`
43
- * - on failure: `{ state: "output-error", errorText: <message> }`
44
- */
45
- export declare function applyToolExecutionResultToParts(parts: any[], toolCall: Pick<ToolCall, "toolCallId" | "toolName">, execution: {
46
- success: boolean;
47
- result: any;
48
- message?: string;
49
- }): any[];
50
- /**
51
- * Returns `true` when a given tool has a **settled** execution result in an event's parts.
52
- *
53
- * We treat a tool part as "executed" once it has either:
54
- * - `state: "output-available"` (success), or
55
- * - `state: "output-error"` (failure).
56
- *
57
- * This is useful for stop/continue logic in `context.shouldContinue(...)` where you want to
58
- * decide based on the persisted `reactionEvent` (not ephemeral in-memory arrays).
59
- */
60
- export declare function didToolExecute(event: Pick<ContextItem, "content">, toolName: string): boolean;
@@ -1,117 +0,0 @@
1
- /**
2
- * ## context.toolcalls.ts
3
- *
4
- * This module isolates the **tool-call plumbing** used by `context.engine.ts`.
5
- *
6
- * In our runtime, tool calls are represented as **event parts** produced by the AI SDK.
7
- * The engine needs to:
8
- * - extract a normalized list of tool calls from `event.content.parts`, and
9
- * - merge tool execution outcomes back into those parts (so the persisted event reflects
10
- * `output-available` / `output-error`, etc.).
11
- *
12
- * Keeping this logic here helps `context.engine.ts` read like orchestration, and keeps
13
- * these transformations testable and reusable.
14
- */
15
- import { isContextPartEnvelope, normalizePartsForPersistence, normalizeToolResultContentToBlocks, } from "./context.parts.js";
16
- /**
17
- * Extracts tool calls from an event's `parts` array.
18
- *
19
- * Expected part shape (loosely):
20
- * - `type`: string like `"tool-<toolName>"`
21
- * - `toolCallId`: string
22
- * - `input`: any (tool args)
23
- *
24
- * We intentionally treat the input as `any` because the part schema is produced by the AI SDK.
25
- */
26
- export function extractToolCallsFromParts(parts) {
27
- const safeParts = parts ?? [];
28
- return safeParts.reduce((acc, p) => {
29
- if (isContextPartEnvelope(p) && p.type === "tool-call") {
30
- const firstContent = Array.isArray(p.content) ? p.content[0] : undefined;
31
- const args = firstContent && firstContent.type === "json" ? firstContent.value : p.content;
32
- acc.push({
33
- toolCallId: p.toolCallId,
34
- toolName: p.toolName,
35
- args,
36
- });
37
- return acc;
38
- }
39
- if (typeof p?.type === "string" && p.type.startsWith("tool-")) {
40
- const toolName = p.type.split("-").slice(1).join("-");
41
- acc.push({ toolCallId: p.toolCallId, toolName, args: p.input });
42
- }
43
- return acc;
44
- }, []);
45
- }
46
- /**
47
- * Applies a tool execution outcome to the matching tool part.
48
- *
49
- * This does not mutate `parts` — it returns a new array.
50
- *
51
- * We match the tool part by:
52
- * - `type === "tool-<toolName>"` and
53
- * - `toolCallId` equality
54
- *
55
- * Then we set:
56
- * - on success: `{ state: "output-available", output: <result> }`
57
- * - on failure: `{ state: "output-error", errorText: <message> }`
58
- */
59
- export function applyToolExecutionResultToParts(parts, toolCall, execution) {
60
- const normalized = normalizePartsForPersistence(parts);
61
- const next = [];
62
- let insertedResult = false;
63
- const resultContent = execution.success
64
- ? normalizeToolResultContentToBlocks(execution.result)
65
- : [
66
- {
67
- type: "text",
68
- text: String(execution.message || "Error"),
69
- },
70
- ];
71
- for (const part of normalized) {
72
- next.push(part);
73
- if (part.type !== "tool-call") {
74
- continue;
75
- }
76
- if (part.toolCallId !== toolCall.toolCallId ||
77
- part.toolName !== toolCall.toolName) {
78
- continue;
79
- }
80
- next.push({
81
- type: "tool-result",
82
- toolCallId: toolCall.toolCallId,
83
- toolName: toolCall.toolName,
84
- state: execution.success ? "output-available" : "output-error",
85
- content: resultContent,
86
- });
87
- insertedResult = true;
88
- }
89
- if (!insertedResult) {
90
- next.push({
91
- type: "tool-result",
92
- toolCallId: toolCall.toolCallId,
93
- toolName: toolCall.toolName,
94
- state: execution.success ? "output-available" : "output-error",
95
- content: resultContent,
96
- });
97
- }
98
- return next;
99
- }
100
- /**
101
- * Returns `true` when a given tool has a **settled** execution result in an event's parts.
102
- *
103
- * We treat a tool part as "executed" once it has either:
104
- * - `state: "output-available"` (success), or
105
- * - `state: "output-error"` (failure).
106
- *
107
- * This is useful for stop/continue logic in `context.shouldContinue(...)` where you want to
108
- * decide based on the persisted `reactionEvent` (not ephemeral in-memory arrays).
109
- */
110
- export function didToolExecute(event, toolName) {
111
- const parts = (event.content.parts ?? []).flatMap((part) => isContextPartEnvelope(part) ? [part] : normalizePartsForPersistence([part]));
112
- return parts.some((p) => (p.type === "tool-result" &&
113
- p.toolName === toolName &&
114
- (p.state === "output-available" || p.state === "output-error")) ||
115
- (p.type === `tool-${toolName}` &&
116
- (p.state === "output-available" || p.state === "output-error")));
117
- }