@ekairos/structure 1.21.62-beta.0 → 1.21.69-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -9,8 +9,6 @@ export function createClearDatasetTool({ datasetId, sandboxId, env }) {
9
9
  reason: z.string().describe("Reason for clearing"),
10
10
  }),
11
11
  execute: async ({ reason }) => {
12
- console.log("[ekairos/structure] clearDataset.tool execute begin");
13
- console.log("[ekairos/structure] clearDataset.tool execute datasetId", datasetId);
14
12
  const outputPath = getDatasetOutputPath(datasetId);
15
13
  try {
16
14
  await runDatasetSandboxCommandStep({ env, sandboxId, cmd: "rm", args: ["-f", outputPath] });
@@ -18,7 +16,6 @@ export function createClearDatasetTool({ datasetId, sandboxId, env }) {
18
16
  catch {
19
17
  // best-effort
20
18
  }
21
- console.log("[ekairos/structure] clearDataset.tool execute ok");
22
19
  return { success: true, message: "Cleared", reason };
23
20
  },
24
21
  });
@@ -24,8 +24,6 @@ export function createCompleteObjectTool({ datasetId, sandboxId, env }) {
24
24
  message: "Provide result, resultJson or resultPath",
25
25
  }),
26
26
  execute: async (input) => {
27
- console.log("[ekairos/structure] completeObject.tool execute begin");
28
- console.log("[ekairos/structure] completeObject.tool execute datasetId", datasetId);
29
27
  const contextKey = `structure:${datasetId}`;
30
28
  const ctxResult = await structureGetContextStep({ env, contextKey });
31
29
  if (!ctxResult.ok)
@@ -37,7 +35,6 @@ export function createCompleteObjectTool({ datasetId, sandboxId, env }) {
37
35
  else {
38
36
  let jsonText = input.resultJson ?? "";
39
37
  if (!jsonText && input.resultPath) {
40
- console.log("[ekairos/structure] completeObject.tool execute readingResultPath");
41
38
  const fileRead = await readDatasetSandboxTextFileStep({ env, sandboxId, path: input.resultPath });
42
39
  jsonText = fileRead.text ?? "";
43
40
  }
@@ -46,7 +43,6 @@ export function createCompleteObjectTool({ datasetId, sandboxId, env }) {
46
43
  }
47
44
  catch (error) {
48
45
  const message = error instanceof Error ? error.message : String(error);
49
- console.log("[ekairos/structure] completeObject.tool execute invalidJson");
50
46
  return { success: false, error: `Invalid JSON: ${message}` };
51
47
  }
52
48
  }
@@ -62,17 +58,14 @@ export function createCompleteObjectTool({ datasetId, sandboxId, env }) {
62
58
  const errors = Array.isArray(validator.errors)
63
59
  ? validator.errors.map((err) => err.message || "Unknown validation error")
64
60
  : ["Unknown validation error"];
65
- console.log("[ekairos/structure] completeObject.tool execute schemaValidationFailed");
66
61
  return { success: false, error: errors.slice(0, 5).join("; ") };
67
62
  }
68
63
  }
69
64
  catch (error) {
70
65
  const message = error instanceof Error ? error.message : String(error);
71
- console.log("[ekairos/structure] completeObject.tool execute schemaValidateThrew");
72
66
  return { success: false, error: `Failed to validate schema: ${message}` };
73
67
  }
74
68
  }
75
- console.log("[ekairos/structure] completeObject.tool execute ok");
76
69
  return { success: true, summary: input.summary, result: obj };
77
70
  },
78
71
  });
@@ -18,8 +18,6 @@ export function createCompleteRowsTool({ datasetId, sandboxId, env }) {
18
18
  summary: z.string().describe("Summary of the completed dataset"),
19
19
  }),
20
20
  execute: async ({ summary }) => {
21
- console.log("[ekairos/structure] completeRows.tool execute begin");
22
- console.log("[ekairos/structure] completeRows.tool execute datasetId", datasetId);
23
21
  const contextKey = `structure:${datasetId}`;
24
22
  const outputPath = getDatasetOutputPath(datasetId);
25
23
  try {
@@ -27,7 +25,6 @@ export function createCompleteRowsTool({ datasetId, sandboxId, env }) {
27
25
  }
28
26
  catch (error) {
29
27
  const message = error instanceof Error ? error.message : String(error);
30
- console.log("[ekairos/structure] completeRows.tool execute missingOutputFile");
31
28
  return { success: false, error: message };
32
29
  }
33
30
  const ctxResult = await structureGetContextStep({ env, contextKey });
@@ -36,7 +33,6 @@ export function createCompleteRowsTool({ datasetId, sandboxId, env }) {
36
33
  const content = (ctxResult.data?.content ?? {});
37
34
  const outputSchema = content?.structure?.outputSchema;
38
35
  if (!outputSchema?.schema) {
39
- console.log("[ekairos/structure] completeRows.tool execute schemaMissing");
40
36
  return { success: false, error: "Schema not found in database. Please generate schema first." };
41
37
  }
42
38
  const schemaJson = outputSchema.schema;
@@ -48,11 +44,9 @@ export function createCompleteRowsTool({ datasetId, sandboxId, env }) {
48
44
  catch (error) {
49
45
  const message = error instanceof Error ? error.message : String(error);
50
46
  if (mode === "auto") {
51
- console.log("[ekairos/structure] completeRows.tool execute schemaCompileSkippedAuto");
52
47
  validator = null;
53
48
  }
54
49
  else {
55
- console.log("[ekairos/structure] completeRows.tool execute schemaCompileFailed");
56
50
  return { success: false, error: `Failed to compile schema: ${message}` };
57
51
  }
58
52
  }
@@ -63,7 +57,6 @@ export function createCompleteRowsTool({ datasetId, sandboxId, env }) {
63
57
  return validationResult;
64
58
  totalValidRows = validationResult.validRowCount ?? 0;
65
59
  }
66
- console.log("[ekairos/structure] completeRows.tool execute validRowCount", totalValidRows);
67
60
  const fileRead = await readDatasetSandboxFileStep({ env, sandboxId, path: outputPath });
68
61
  if (!fileRead.contentBase64)
69
62
  return { success: false, error: "Empty file content" };
@@ -74,8 +67,6 @@ export function createCompleteRowsTool({ datasetId, sandboxId, env }) {
74
67
  });
75
68
  if (!uploadResult.ok)
76
69
  return { success: false, error: uploadResult.error };
77
- console.log("[ekairos/structure] completeRows.tool execute uploadedFileId", uploadResult.data.fileId);
78
- console.log("[ekairos/structure] completeRows.tool execute ok");
79
70
  return {
80
71
  success: true,
81
72
  summary,
@@ -30,20 +30,15 @@ export function createExecuteCommandTool({ datasetId, sandboxId, env }) {
30
30
  .describe("Name for the script file in snake_case (e.g., 'inspect_file', 'parse_csv', 'generate_output'). The file will be saved as <scriptName>.py in the workstation."),
31
31
  }),
32
32
  execute: async ({ pythonCode, scriptName }) => {
33
- console.log("[ekairos/structure] executeCommand.tool execute begin");
34
- console.log("[ekairos/structure] executeCommand.tool execute datasetId", datasetId);
35
- console.log("[ekairos/structure] executeCommand.tool execute scriptName", scriptName);
36
33
  const workstation = getDatasetWorkstation(datasetId);
37
34
  const scriptNameWithExt = `${normalizeScriptName(scriptName)}.py`;
38
35
  const scriptFile = `${workstation}/${scriptNameWithExt}`;
39
- console.log("[ekairos/structure] executeCommand.tool execute writingScript");
40
36
  await writeDatasetSandboxTextFileStep({
41
37
  env,
42
38
  sandboxId,
43
39
  path: scriptFile,
44
40
  text: pythonCode,
45
41
  });
46
- console.log("[ekairos/structure] executeCommand.tool execute runningPython");
47
42
  const result = await runDatasetSandboxCommandStep({
48
43
  env,
49
44
  sandboxId,
@@ -58,7 +53,6 @@ export function createExecuteCommandTool({ datasetId, sandboxId, env }) {
58
53
  const stdoutCapped = isStdoutTruncated ? stdout.slice(0, MAX_STDOUT_CHARS) : stdout;
59
54
  const stderrCapped = isStderrTruncated ? stderr.slice(0, MAX_STDERR_CHARS) : stderr;
60
55
  if (exitCode !== 0) {
61
- console.log("[ekairos/structure] executeCommand.tool execute failedExitCode");
62
56
  return {
63
57
  success: false,
64
58
  exitCode,
@@ -73,7 +67,6 @@ export function createExecuteCommandTool({ datasetId, sandboxId, env }) {
73
67
  };
74
68
  }
75
69
  if (stderr && (stderr.includes("Traceback") || stderr.toLowerCase().includes("error"))) {
76
- console.log("[ekairos/structure] executeCommand.tool execute pythonErrorDetected");
77
70
  return {
78
71
  success: false,
79
72
  exitCode,
@@ -87,7 +80,6 @@ export function createExecuteCommandTool({ datasetId, sandboxId, env }) {
87
80
  stderrOriginalLength: stderr.length,
88
81
  };
89
82
  }
90
- console.log("[ekairos/structure] executeCommand.tool execute ok");
91
83
  return {
92
84
  success: true,
93
85
  exitCode,
@@ -11,11 +11,6 @@ export function createGenerateSchemaTool({ datasetId, sandboxId, env }) {
11
11
  schemaJson: z.string().describe("A JSON Schema string"),
12
12
  }),
13
13
  execute: async ({ schemaTitle, schemaDescription, schemaJson }) => {
14
- console.log("[ekairos/structure] generateSchema.tool execute begin");
15
- console.log("[ekairos/structure] generateSchema.tool execute datasetId", datasetId);
16
- console.log("[ekairos/structure] generateSchema.tool execute sandboxId", sandboxId);
17
- console.log("[ekairos/structure] generateSchema.tool execute schemaTitle", schemaTitle);
18
- console.log("[ekairos/structure] generateSchema.tool execute schemaJsonLength", String(schemaJson ?? "").length);
19
14
  const schemaData = {
20
15
  title: schemaTitle,
21
16
  description: schemaDescription,
@@ -24,17 +19,12 @@ export function createGenerateSchemaTool({ datasetId, sandboxId, env }) {
24
19
  return JSON.parse(schemaJson);
25
20
  }
26
21
  catch (e) {
27
- const message = e instanceof Error ? e.message : String(e);
28
- console.log("[ekairos/structure] generateSchema.tool execute invalidSchemaJson");
29
- console.log("[ekairos/structure] generateSchema.tool execute invalidSchemaJsonMessage", message);
30
- console.log("[ekairos/structure] generateSchema.tool execute invalidSchemaJsonPreview", String(schemaJson ?? "").slice(0, 300));
31
22
  throw e;
32
23
  }
33
24
  })(),
34
25
  };
35
26
  const schemaPath = getDatasetOutputSchemaPath(datasetId);
36
27
  try {
37
- console.log("[ekairos/structure] generateSchema.tool execute writingSchemaPath", schemaPath);
38
28
  await writeDatasetSandboxTextFileStep({
39
29
  env,
40
30
  sandboxId,
@@ -43,17 +33,9 @@ export function createGenerateSchemaTool({ datasetId, sandboxId, env }) {
43
33
  });
44
34
  }
45
35
  catch (e) {
46
- console.log("[ekairos/structure] generateSchema.tool execute failed");
47
36
  const message = e instanceof Error ? e.message : String(e);
48
- console.log("[ekairos/structure] generateSchema.tool execute failedMessage", message);
49
- if (e instanceof Error && e.stack) {
50
- console.log("[ekairos/structure] generateSchema.tool execute failedStack", e.stack);
51
- }
52
- console.log("[ekairos/structure] generateSchema.tool execute failedSchemaPath", schemaPath);
53
- console.log("[ekairos/structure] generateSchema.tool execute failedHasEnv", Boolean(env));
54
37
  return { success: false, error: message };
55
38
  }
56
- console.log("[ekairos/structure] generateSchema.tool execute ok");
57
39
  return { success: true, message: "Schema written", schemaPath };
58
40
  },
59
41
  });
@@ -0,0 +1,25 @@
1
+ import type { StructureRowsOutputPagingCursor } from "./rowsOutputPaging";
2
+ export type StructureSplitRowsOutputToDatasetResult = {
3
+ datasetId?: string;
4
+ rowsWritten: number;
5
+ nextCursor: StructureRowsOutputPagingCursor;
6
+ done: boolean;
7
+ };
8
+ /**
9
+ * Step:
10
+ * Split a sandbox-local `output.jsonl` into a child dataset (also `output.jsonl`) of up to `limit` ROW entries.
11
+ *
12
+ * Key property:
13
+ * - Does NOT return rows; it persists a child dataset and returns only `{ datasetId, nextCursor, done }`.
14
+ *
15
+ * This is useful for workflows where you want to batch work (e.g. 300 rows) without moving large payloads
16
+ * through workflow/step params.
17
+ */
18
+ export declare function structureSplitRowsOutputToDatasetStep(params: {
19
+ env: any;
20
+ sandboxId: string;
21
+ localPath: string;
22
+ cursor?: Partial<StructureRowsOutputPagingCursor>;
23
+ limit: number;
24
+ childDatasetId: string;
25
+ }): Promise<StructureSplitRowsOutputToDatasetResult>;
@@ -0,0 +1,131 @@
1
+ import { getDatasetOutputPath, getDatasetWorkstation } from "./datasetFiles";
2
+ import { readDatasetSandboxFileStep, runDatasetSandboxCommandStep } from "./sandbox/steps";
3
+ import { getStoryRuntime } from "./runtime";
4
+ /**
5
+ * Step:
6
+ * Split a sandbox-local `output.jsonl` into a child dataset (also `output.jsonl`) of up to `limit` ROW entries.
7
+ *
8
+ * Key property:
9
+ * - Does NOT return rows; it persists a child dataset and returns only `{ datasetId, nextCursor, done }`.
10
+ *
11
+ * This is useful for workflows where you want to batch work (e.g. 300 rows) without moving large payloads
12
+ * through workflow/step params.
13
+ */
14
+ export async function structureSplitRowsOutputToDatasetStep(params) {
15
+ "use step";
16
+ const byteOffset = params.cursor?.byteOffset ?? 0;
17
+ const rowOffset = params.cursor?.rowOffset ?? 0;
18
+ const workstation = getDatasetWorkstation(params.childDatasetId);
19
+ const outPath = getDatasetOutputPath(params.childDatasetId);
20
+ await runDatasetSandboxCommandStep({
21
+ env: params.env,
22
+ sandboxId: params.sandboxId,
23
+ cmd: "mkdir",
24
+ args: ["-p", workstation],
25
+ });
26
+ // Read from parent jsonl and write a child jsonl containing only ROW records, preserving `{ type, data }` lines.
27
+ const py = [
28
+ "import sys, json",
29
+ "in_path = sys.argv[1]",
30
+ "out_path = sys.argv[2]",
31
+ "byte_offset = int(sys.argv[3])",
32
+ "row_offset = int(sys.argv[4])",
33
+ "limit = int(sys.argv[5])",
34
+ "rows_written = 0",
35
+ "next_byte = byte_offset",
36
+ "next_row = row_offset",
37
+ "with open(in_path, 'rb') as f_in:",
38
+ " f_in.seek(byte_offset)",
39
+ " with open(out_path, 'wb') as f_out:",
40
+ " while rows_written < limit:",
41
+ " line = f_in.readline()",
42
+ " if not line:",
43
+ " break",
44
+ " next_byte = f_in.tell()",
45
+ " try:",
46
+ " obj = json.loads(line.decode('utf-8'))",
47
+ " except Exception:",
48
+ " continue",
49
+ " if obj.get('type') != 'row':",
50
+ " continue",
51
+ " f_out.write(line if line.endswith(b'\\n') else (line + b'\\n'))",
52
+ " rows_written += 1",
53
+ " next_row += 1",
54
+ "done = rows_written < limit",
55
+ "print(json.dumps({",
56
+ " 'rowsWritten': rows_written,",
57
+ " 'nextByteOffset': next_byte,",
58
+ " 'nextRowOffset': next_row,",
59
+ " 'done': done,",
60
+ "}))",
61
+ ].join("\n");
62
+ const res = await runDatasetSandboxCommandStep({
63
+ env: params.env,
64
+ sandboxId: params.sandboxId,
65
+ cmd: "python",
66
+ args: ["-c", py, params.localPath, outPath, String(byteOffset), String(rowOffset), String(params.limit)],
67
+ });
68
+ if (res.exitCode !== 0) {
69
+ throw new Error(res.stderr || "Failed to split rows output to dataset");
70
+ }
71
+ const parsed = JSON.parse(String(res.stdout ?? "").trim());
72
+ const rowsWritten = Number(parsed?.rowsWritten ?? 0);
73
+ const nextCursor = {
74
+ byteOffset: Number(parsed?.nextByteOffset ?? byteOffset),
75
+ rowOffset: Number(parsed?.nextRowOffset ?? rowOffset),
76
+ };
77
+ const done = Boolean(parsed?.done);
78
+ // No work to persist: return only paging state.
79
+ if (rowsWritten <= 0) {
80
+ return { datasetId: undefined, rowsWritten: 0, nextCursor, done: true };
81
+ }
82
+ const fileRes = await readDatasetSandboxFileStep({
83
+ env: params.env,
84
+ sandboxId: params.sandboxId,
85
+ path: outPath,
86
+ });
87
+ const storyRuntime = await getStoryRuntime(params.env);
88
+ const db = storyRuntime.db;
89
+ const store = storyRuntime.store;
90
+ const storagePath = `/structure/${params.childDatasetId}/output.jsonl`;
91
+ const fileBuffer = Buffer.from(fileRes.contentBase64 ?? "", "base64");
92
+ const uploadResult = await db.storage.uploadFile(storagePath, fileBuffer, {
93
+ contentType: "application/x-ndjson",
94
+ contentDisposition: "output.jsonl",
95
+ });
96
+ const fileId = uploadResult?.data?.id;
97
+ if (!fileId)
98
+ throw new Error("Failed to upload child dataset output file to storage");
99
+ const contextKey = `structure:${params.childDatasetId}`;
100
+ const ctx = await store.getOrCreateContext({ key: contextKey });
101
+ const ctxId = ctx?.id;
102
+ if (!ctxId)
103
+ throw new Error("Failed to create child dataset context");
104
+ // Link the output file to the context (used by DatasetService.readRecordsFromFile).
105
+ await db.transact([db.tx.context_contexts[ctxId].link({ structure_output_file: fileId })]);
106
+ // Patch metadata under `structure` namespace (never clobber Story runtime keys).
107
+ const existingContent = (ctx?.content ?? {});
108
+ const existingStructure = (existingContent?.structure ?? {});
109
+ const updatedAt = Date.now();
110
+ await store.updateContextContent({ key: contextKey }, {
111
+ ...existingContent,
112
+ structure: {
113
+ ...existingStructure,
114
+ kind: "ekairos.structure",
115
+ version: Number(existingStructure?.version ?? 1),
116
+ structureId: params.childDatasetId,
117
+ output: "rows",
118
+ updatedAt,
119
+ outputs: {
120
+ ...(existingStructure?.outputs ?? {}),
121
+ rows: {
122
+ format: "jsonl",
123
+ fileId,
124
+ storagePath,
125
+ rowCount: rowsWritten,
126
+ },
127
+ },
128
+ },
129
+ });
130
+ return { datasetId: params.childDatasetId, rowsWritten, nextCursor, done };
131
+ }
@@ -1,7 +1,5 @@
1
1
  export async function createDatasetSandboxStep(params) {
2
2
  "use step";
3
- console.log("[ekairos/structure] sandbox.step createSandbox begin");
4
- console.log("[ekairos/structure] sandbox.step createSandbox runtime", params.runtime);
5
3
  const { resolveStoryRuntime } = await import("@ekairos/story/runtime");
6
4
  const db = (await resolveStoryRuntime(params.env)).db;
7
5
  const { SandboxService } = (await import("@ekairos/sandbox"));
@@ -9,15 +7,10 @@ export async function createDatasetSandboxStep(params) {
9
7
  const created = await service.createSandbox(params);
10
8
  if (!created.ok)
11
9
  throw new Error(created.error);
12
- console.log("[ekairos/structure] sandbox.step createSandbox ok");
13
- console.log("[ekairos/structure] sandbox.step createSandbox sandboxId", created.data.sandboxId);
14
10
  return { sandboxId: created.data.sandboxId };
15
11
  }
16
12
  export async function runDatasetSandboxCommandStep(params) {
17
13
  "use step";
18
- console.log("[ekairos/structure] sandbox.step runCommand begin");
19
- console.log("[ekairos/structure] sandbox.step runCommand sandboxId", params.sandboxId);
20
- console.log("[ekairos/structure] sandbox.step runCommand cmd", params.cmd);
21
14
  const { resolveStoryRuntime } = await import("@ekairos/story/runtime");
22
15
  const db = (await resolveStoryRuntime(params.env)).db;
23
16
  const { SandboxService } = (await import("@ekairos/sandbox"));
@@ -30,15 +23,10 @@ export async function runDatasetSandboxCommandStep(params) {
30
23
  stdout: result.data.output ?? "",
31
24
  stderr: result.data.error ?? "",
32
25
  };
33
- console.log("[ekairos/structure] sandbox.step runCommand ok");
34
- console.log("[ekairos/structure] sandbox.step runCommand exitCode", normalized.exitCode);
35
26
  return normalized;
36
27
  }
37
28
  export async function writeDatasetSandboxFilesStep(params) {
38
29
  "use step";
39
- console.log("[ekairos/structure] sandbox.step writeFiles begin");
40
- console.log("[ekairos/structure] sandbox.step writeFiles sandboxId", params.sandboxId);
41
- console.log("[ekairos/structure] sandbox.step writeFiles paths", params.files.map((f) => f.path));
42
30
  const { resolveStoryRuntime } = await import("@ekairos/story/runtime");
43
31
  const db = (await resolveStoryRuntime(params.env)).db;
44
32
  const { SandboxService } = (await import("@ekairos/sandbox"));
@@ -46,7 +34,6 @@ export async function writeDatasetSandboxFilesStep(params) {
46
34
  const result = await service.writeFiles(params.sandboxId, params.files);
47
35
  if (!result.ok)
48
36
  throw new Error(result.error);
49
- console.log("[ekairos/structure] sandbox.step writeFiles ok");
50
37
  }
51
38
  /**
52
39
  * Workflow-safe helper:
@@ -66,9 +53,6 @@ export async function writeDatasetSandboxTextFileStep(params) {
66
53
  }
67
54
  export async function readDatasetSandboxFileStep(params) {
68
55
  "use step";
69
- console.log("[ekairos/structure] sandbox.step readFile begin");
70
- console.log("[ekairos/structure] sandbox.step readFile sandboxId", params.sandboxId);
71
- console.log("[ekairos/structure] sandbox.step readFile path", params.path);
72
56
  const { resolveStoryRuntime } = await import("@ekairos/story/runtime");
73
57
  const db = (await resolveStoryRuntime(params.env)).db;
74
58
  const { SandboxService } = (await import("@ekairos/sandbox"));
@@ -76,7 +60,6 @@ export async function readDatasetSandboxFileStep(params) {
76
60
  const result = await service.readFile(params.sandboxId, params.path);
77
61
  if (!result.ok)
78
62
  throw new Error(result.error);
79
- console.log("[ekairos/structure] sandbox.step readFile ok");
80
63
  return result.data;
81
64
  }
82
65
  /**
@@ -94,8 +77,6 @@ export async function readDatasetSandboxTextFileStep(params) {
94
77
  }
95
78
  export async function stopDatasetSandboxStep(params) {
96
79
  "use step";
97
- console.log("[ekairos/structure] sandbox.step stopSandbox begin");
98
- console.log("[ekairos/structure] sandbox.step stopSandbox sandboxId", params.sandboxId);
99
80
  const { resolveStoryRuntime } = await import("@ekairos/story/runtime");
100
81
  const db = (await resolveStoryRuntime(params.env)).db;
101
82
  const { SandboxService } = (await import("@ekairos/sandbox"));
@@ -103,5 +84,4 @@ export async function stopDatasetSandboxStep(params) {
103
84
  const result = await service.stopSandbox(params.sandboxId);
104
85
  if (!result.ok)
105
86
  throw new Error(result.error);
106
- console.log("[ekairos/structure] sandbox.step stopSandbox ok");
107
87
  }
@@ -39,11 +39,8 @@ export async function persistObjectResultFromStoryStep(params) {
39
39
  const { resolveStoryRuntime } = await import("@ekairos/story/runtime");
40
40
  const runtime = (await resolveStoryRuntime(params.env));
41
41
  const store = runtime.store;
42
- console.log("[ekairos/structure] persistObjectFromStory.step execute begin");
43
- console.log("[ekairos/structure] persistObjectFromStory.step execute datasetId", params.datasetId);
44
42
  const contextKey = `structure:${params.datasetId}`;
45
43
  const events = await store.getEvents({ key: contextKey });
46
- console.log("[ekairos/structure] persistObjectFromStory.step execute eventsCount", Array.isArray(events) ? events.length : -1);
47
44
  const { structurePatchContextContentStep, structureGetContextStep } = await import("../dataset/steps");
48
45
  const ctxResult = await structureGetContextStep({ env: params.env, contextKey });
49
46
  const existingContent = ctxResult.ok ? (ctxResult.data?.content ?? {}) : {};
@@ -58,7 +55,6 @@ export async function persistObjectResultFromStoryStep(params) {
58
55
  .trim();
59
56
  const obj = extractJsonObject(text);
60
57
  if (obj) {
61
- console.log("[ekairos/structure] persistObjectFromStory.step execute parsedJsonOk");
62
58
  const patchResult = await structurePatchContextContentStep({
63
59
  env: params.env,
64
60
  contextKey,
@@ -80,10 +76,8 @@ export async function persistObjectResultFromStoryStep(params) {
80
76
  const err = patchResult?.error ?? "Failed to persist object result";
81
77
  throw new Error(err);
82
78
  }
83
- console.log("[ekairos/structure] persistObjectFromStory.step execute savedOk");
84
79
  return { ok: true };
85
80
  }
86
81
  }
87
- console.log("[ekairos/structure] persistObjectFromStory.step execute noJsonFound");
88
82
  return { ok: false };
89
83
  }
@@ -18,6 +18,16 @@ export type StructureRowsReadResult = {
18
18
  cursor: StructureRowsOutputPagingCursor;
19
19
  done: boolean;
20
20
  };
21
+ export type StructureRowsSplitResult = {
22
+ /**
23
+ * Child datasetId containing a JSONL `output.jsonl` with up to `limit` ROW entries.
24
+ * Omitted when there are no more rows to split.
25
+ */
26
+ datasetId?: string;
27
+ rowsWritten: number;
28
+ cursor: StructureRowsOutputPagingCursor;
29
+ done: boolean;
30
+ };
21
31
  export type StructureRowsReader = {
22
32
  /**
23
33
  * Workflow-friendly rows reader.
@@ -33,6 +43,18 @@ export type StructureRowsReader = {
33
43
  cursor?: Partial<StructureRowsOutputPagingCursor>;
34
44
  limit?: number;
35
45
  }): Promise<StructureRowsReadResult>;
46
+ /**
47
+ * Split the rows output into a child dataset (jsonl) and return paging state.
48
+ *
49
+ * Unlike `read()`, this does not return `rows[]` (avoids moving payloads through params/results).
50
+ */
51
+ split(): Promise<StructureRowsSplitResult>;
52
+ split(cursor?: Partial<StructureRowsOutputPagingCursor>, limit?: number): Promise<StructureRowsSplitResult>;
53
+ split(params?: {
54
+ cursor?: Partial<StructureRowsOutputPagingCursor>;
55
+ limit?: number;
56
+ datasetId?: string;
57
+ }): Promise<StructureRowsSplitResult>;
36
58
  };
37
59
  export type StructureBuildResult = {
38
60
  datasetId: string;
package/dist/structure.js CHANGED
@@ -1,9 +1,11 @@
1
1
  import { createStory, didToolExecute, USER_MESSAGE_TYPE, WEB_CHANNEL } from "@ekairos/story";
2
2
  import { getDatasetOutputPath, getDatasetOutputSchemaPath, getDatasetWorkstation } from "./datasetFiles";
3
3
  import { structureDownloadRowsOutputToSandboxStep, structureReadRowsOutputPageFromSandboxStep, } from "./rowsOutputPaging";
4
+ import { structureSplitRowsOutputToDatasetStep } from "./rowsOutputSplit";
4
5
  import { createDatasetSandboxStep, readDatasetSandboxFileStep, readDatasetSandboxTextFileStep, runDatasetSandboxCommandStep, writeDatasetSandboxFilesStep, writeDatasetSandboxTextFileStep, } from "./sandbox/steps";
5
6
  import { readInstantFileStep } from "./file/steps";
6
7
  import { structureGetContextStep, structureGetContextWithRowsOutputFileStep, structureReadRowsOutputJsonlStep, } from "./dataset/steps";
8
+ import { getWorkflowMetadata } from "workflow";
7
9
  import { buildStructurePrompt } from "./prompts";
8
10
  import { createExecuteCommandTool } from "./executeCommand.tool";
9
11
  import { createGenerateSchemaTool } from "./generateSchema.tool";
@@ -20,6 +22,21 @@ function createUuidV4() {
20
22
  return v.toString(16);
21
23
  });
22
24
  }
25
+ function assertRunningInsideWorkflow(params) {
26
+ try {
27
+ const meta = getWorkflowMetadata();
28
+ const runId = meta?.workflowRunId;
29
+ if (!runId) {
30
+ throw new Error("Missing workflowRunId");
31
+ }
32
+ return meta;
33
+ }
34
+ catch (e) {
35
+ const msg = e instanceof Error ? e.message : String(e);
36
+ throw new Error(`@ekairos/structure: structure().build() must be called from a "use workflow" function. ` +
37
+ `datasetId=${params.datasetId}. getWorkflowMetadata failed: ${msg}`);
38
+ }
39
+ }
23
40
  function guessTextFileExtension(mimeType, name) {
24
41
  const n = String(name ?? "").toLowerCase();
25
42
  if (n.includes("."))
@@ -38,18 +55,11 @@ async function ensureSandboxPrepared(params) {
38
55
  const workstation = getDatasetWorkstation(datasetId);
39
56
  const outputPath = getDatasetOutputPath(datasetId);
40
57
  if (state.initialized) {
41
- console.log("[ekairos/structure] structure.ts ensureSandboxPrepared alreadyInitialized");
42
58
  return { preparedSources: state.sources, workstation, outputPath };
43
59
  }
44
- console.log("[ekairos/structure] structure.ts ensureSandboxPrepared begin");
45
- console.log("[ekairos/structure] structure.ts ensureSandboxPrepared datasetId", datasetId);
46
- console.log("[ekairos/structure] structure.ts ensureSandboxPrepared sourcesCount", sources.length);
47
- console.log("[ekairos/structure] structure.ts ensureSandboxPrepared sandbox.mkdir", workstation);
48
60
  const mkdirRes = await runDatasetSandboxCommandStep({ env, sandboxId, cmd: "mkdir", args: ["-p", workstation] });
49
- console.log("[ekairos/structure] structure.ts ensureSandboxPrepared sandbox.mkdir exitCode", mkdirRes.exitCode);
50
61
  // Align with dataset sandbox behavior: install python deps up-front (once per dataset sandbox).
51
62
  // This avoids tool-level "install if used" heuristics and ensures scripts can import pandas.
52
- console.log("[ekairos/structure] structure.ts ensureSandboxPrepared installingPythonDeps pandas+openpyxl");
53
63
  const pipInstall = await runDatasetSandboxCommandStep({
54
64
  env,
55
65
  sandboxId,
@@ -65,8 +75,6 @@ async function ensureSandboxPrepared(params) {
65
75
  for (let i = 0; i < sources.length; i++) {
66
76
  const src = sources[i];
67
77
  if (src.kind === "file") {
68
- console.log("[ekairos/structure] structure.ts ensureSandboxPrepared preparingFileSource");
69
- console.log("[ekairos/structure] structure.ts ensureSandboxPrepared fileId", src.fileId);
70
78
  const file = await readInstantFileStep({ env, fileId: src.fileId });
71
79
  const fileName = String(file.contentDisposition ?? "");
72
80
  const ext = fileName.includes(".") ? fileName.substring(fileName.lastIndexOf(".")) : "";
@@ -76,13 +84,10 @@ async function ensureSandboxPrepared(params) {
76
84
  sandboxId,
77
85
  files: [{ path, contentBase64: file.contentBase64 }],
78
86
  });
79
- console.log("[ekairos/structure] structure.ts ensureSandboxPrepared wroteFile path", path);
80
87
  prepared.push({ kind: "file", id: src.fileId, path });
81
88
  continue;
82
89
  }
83
90
  if (src.kind === "dataset") {
84
- console.log("[ekairos/structure] structure.ts ensureSandboxPrepared preparingDatasetSource");
85
- console.log("[ekairos/structure] structure.ts ensureSandboxPrepared sourceDatasetId", src.datasetId);
86
91
  const content = await structureReadRowsOutputJsonlStep({ env, structureId: src.datasetId });
87
92
  if (!content.ok) {
88
93
  throw new Error(content.error);
@@ -93,38 +98,30 @@ async function ensureSandboxPrepared(params) {
93
98
  sandboxId,
94
99
  files: [{ path, contentBase64: content.data.contentBase64 }],
95
100
  });
96
- console.log("[ekairos/structure] structure.ts ensureSandboxPrepared wroteDatasetJsonl path", path);
97
101
  prepared.push({ kind: "dataset", id: src.datasetId, path });
98
102
  continue;
99
103
  }
100
104
  if (src.kind === "text") {
101
- console.log("[ekairos/structure] structure.ts ensureSandboxPrepared preparingTextSource");
102
105
  const ext = guessTextFileExtension(src.mimeType, src.name);
103
106
  const textId = `text_${i}`;
104
107
  const path = `${workstation}/${textId}${ext}`;
105
108
  await writeDatasetSandboxTextFileStep({ env, sandboxId, path, text: String(src.text ?? "") });
106
- console.log("[ekairos/structure] structure.ts ensureSandboxPrepared wroteText path", path);
107
109
  prepared.push({ kind: "text", id: textId, path, name: src.name, mimeType: src.mimeType });
108
110
  continue;
109
111
  }
110
112
  }
111
113
  state.initialized = true;
112
114
  state.sources = prepared;
113
- console.log("[ekairos/structure] structure.ts ensureSandboxPrepared ok");
114
115
  return { preparedSources: prepared, workstation, outputPath };
115
116
  }
116
117
  async function readSchemaFromSandboxIfPresent(params) {
117
118
  const schemaPath = getDatasetOutputSchemaPath(params.datasetId);
118
- console.log("[ekairos/structure] structure.ts readSchemaFromSandboxIfPresent begin");
119
- console.log("[ekairos/structure] structure.ts readSchemaFromSandboxIfPresent datasetId", params.datasetId);
120
- console.log("[ekairos/structure] structure.ts readSchemaFromSandboxIfPresent schemaPath", schemaPath);
121
119
  const exists = await runDatasetSandboxCommandStep({
122
120
  env: params.env,
123
121
  sandboxId: params.sandboxId,
124
122
  cmd: "test",
125
123
  args: ["-f", schemaPath],
126
124
  });
127
- console.log("[ekairos/structure] structure.ts readSchemaFromSandboxIfPresent existsExitCode", exists.exitCode);
128
125
  if (exists.exitCode !== 0)
129
126
  return null;
130
127
  const fileRead = await readDatasetSandboxFileStep({
@@ -143,11 +140,9 @@ async function readSchemaFromSandboxIfPresent(params) {
143
140
  return null;
144
141
  try {
145
142
  const parsed = JSON.parse(jsonText);
146
- console.log("[ekairos/structure] structure.ts readSchemaFromSandboxIfPresent parsedOk");
147
143
  return parsed;
148
144
  }
149
145
  catch {
150
- console.log("[ekairos/structure] structure.ts readSchemaFromSandboxIfPresent parsedFailed");
151
146
  return null;
152
147
  }
153
148
  }
@@ -156,13 +151,11 @@ function createStructureStoryDefinition(config) {
156
151
  const model = config.model ?? "openai/gpt-5.2";
157
152
  const story = createStory("ekairos.structure")
158
153
  .context(async (stored, env) => {
159
- console.log("[ekairos/structure] structure.ts story.context begin");
160
154
  const prev = stored?.content ?? {};
161
155
  const sandboxState = prev.sandboxState ?? { initialized: false, sources: [] };
162
156
  const existingSandboxId = prev.sandboxId ?? config.sandboxId ?? "";
163
157
  let sandboxId = existingSandboxId;
164
158
  if (!sandboxId) {
165
- console.log("[ekairos/structure] structure.ts story.context creatingSandbox");
166
159
  const created = await createDatasetSandboxStep({ env, runtime: "python3.13", timeoutMs: 10 * 60 * 1000 });
167
160
  sandboxId = created.sandboxId;
168
161
  }
@@ -174,7 +167,6 @@ function createStructureStoryDefinition(config) {
174
167
  state: sandboxState,
175
168
  });
176
169
  if (config.mode === "schema" && config.outputSchema) {
177
- console.log("[ekairos/structure] structure.ts story.context schemaMode outputSchemaProvided");
178
170
  }
179
171
  const promptContext = {
180
172
  datasetId,
@@ -231,15 +223,12 @@ function createStructureStoryDefinition(config) {
231
223
  };
232
224
  })
233
225
  .narrative(async (stored) => {
234
- console.log("[ekairos/structure] structure.ts story.narrative begin");
235
226
  const promptContext = stored?.content?.promptContext;
236
227
  const base = buildStructurePrompt(promptContext);
237
228
  const userInstructions = String(config.instructions ?? "").trim();
238
229
  if (!userInstructions) {
239
- console.log("[ekairos/structure] structure.ts story.narrative okNoUserInstructions");
240
230
  return base;
241
231
  }
242
- console.log("[ekairos/structure] structure.ts story.narrative okWithUserInstructions");
243
232
  return [
244
233
  "## USER INSTRUCTIONS",
245
234
  "The following instructions were provided by the user. Apply them in addition to (and with higher priority than) the default instructions.",
@@ -250,14 +239,10 @@ function createStructureStoryDefinition(config) {
250
239
  ].join("\n");
251
240
  })
252
241
  .actions(async (stored, env) => {
253
- console.log("[ekairos/structure] structure.ts story.actions begin");
254
242
  const sandboxId = stored?.content?.sandboxId;
255
243
  const output = config.output;
256
244
  const content = (stored?.content ?? {});
257
245
  const hasOutputSchema = Boolean(content?.structure?.outputSchema?.schema);
258
- console.log("[ekairos/structure] structure.ts story.actions datasetId", datasetId);
259
- console.log("[ekairos/structure] structure.ts story.actions mode", config.mode);
260
- console.log("[ekairos/structure] structure.ts story.actions hasOutputSchema", hasOutputSchema);
261
246
  const actions = {
262
247
  executeCommand: createExecuteCommandTool({ datasetId, sandboxId, env }),
263
248
  clear: createClearDatasetTool({ datasetId, sandboxId, env }),
@@ -274,7 +259,6 @@ function createStructureStoryDefinition(config) {
274
259
  ? createCompleteRowsTool({ datasetId, sandboxId, env })
275
260
  : createCompleteObjectTool({ datasetId, sandboxId, env });
276
261
  }
277
- console.log("[ekairos/structure] structure.ts story.actions ok");
278
262
  return actions;
279
263
  })
280
264
  .shouldContinue(({ reactionEvent }) => {
@@ -319,11 +303,9 @@ export function structure(env, opts) {
319
303
  return api;
320
304
  },
321
305
  async build(userPrompt) {
322
- console.log("[ekairos/structure] structure.ts build begin");
323
- console.log("[ekairos/structure] structure.ts build datasetId", datasetId);
324
- console.log("[ekairos/structure] structure.ts build mode", mode);
325
- console.log("[ekairos/structure] structure.ts build output", output);
326
- console.log("[ekairos/structure] structure.ts build sourcesCount", sources.length);
306
+ // Guardrail: structure build MUST run inside workflow runtime ("use workflow").
307
+ const workflowMeta = assertRunningInsideWorkflow({ datasetId });
308
+ void workflowMeta?.workflowRunId;
327
309
  const contextKey = `structure:${datasetId}`;
328
310
  const storyConfig = {
329
311
  datasetId,
@@ -344,22 +326,14 @@ export function structure(env, opts) {
344
326
  };
345
327
  }
346
328
  async function runStory(evt) {
347
- console.log("[ekairos/structure] structure.ts build storyReact begin");
348
- console.log("[ekairos/structure] structure.ts build storyReact contextKey", contextKey);
349
- console.log("[ekairos/structure] structure.ts build storyReact silent", true);
350
- console.log("[ekairos/structure] structure.ts build storyReact eventId", evt?.id);
351
- console.log("[ekairos/structure] structure.ts build storyReact eventType", evt?.type);
352
329
  await story.react(evt, {
353
330
  env,
354
331
  context: { key: contextKey },
355
332
  options: { silent: true, preventClose: true, sendFinish: false, maxIterations: 40, maxModelSteps: 10 },
356
333
  });
357
- console.log("[ekairos/structure] structure.ts build storyReact end");
358
334
  // Tools are intentionally pure: persist completion outputs post-react by reading tool results from events.
359
335
  const commit = await structureCommitFromEventsStep({ env, structureId: datasetId });
360
336
  if (!commit.ok) {
361
- console.log("[ekairos/structure] structure.ts build commitFromEvents failed");
362
- console.log("[ekairos/structure] structure.ts build commitFromEvents error", commit.error);
363
337
  }
364
338
  }
365
339
  async function getContextOrThrow() {
@@ -388,7 +362,6 @@ export function structure(env, opts) {
388
362
  const content = (ctx?.content ?? {});
389
363
  const hasSchema = Boolean(content?.structure?.outputSchema?.schema);
390
364
  if (!hasSchema) {
391
- console.log("[ekairos/structure] structure.ts build autoSchemaMissing followUp");
392
365
  await runStory(makeUserMessageEvent([
393
366
  "CRITICAL: You did not generate a schema yet.",
394
367
  "1) Investigate Sources using executeCommand (inspect paths, read files, infer structure).",
@@ -399,38 +372,30 @@ export function structure(env, opts) {
399
372
  }
400
373
  }
401
374
  const needsSecondPass = output === "rows" ? !(await isRowsCompleted()) : !isObjectCompleted(ctx);
402
- console.log("[ekairos/structure] structure.ts build needsSecondPass", needsSecondPass);
403
375
  if (needsSecondPass) {
404
- console.log("[ekairos/structure] structure.ts build secondPass begin");
405
376
  const followUpText = output === "rows"
406
377
  ? "Finalize now: write output.jsonl to OutputPath and call complete."
407
378
  : "Finalize now: call complete with summary and resultJson (inline JSON).";
408
379
  await runStory(makeUserMessageEvent(followUpText));
409
380
  ctx = await getContextOrThrow();
410
- console.log("[ekairos/structure] structure.ts build secondPass end");
411
381
  }
412
382
  const stillIncompleteAfterSecondPass = output === "rows" ? !(await isRowsCompleted()) : !isObjectCompleted(ctx);
413
383
  if (stillIncompleteAfterSecondPass) {
414
- console.log("[ekairos/structure] structure.ts build thirdPass begin");
415
384
  const finalText = output === "rows"
416
385
  ? "CRITICAL: Do not do anything else. Ensure output.jsonl exists at OutputPath and immediately call complete."
417
386
  : "CRITICAL: Do not do anything else. Immediately call complete with summary and resultJson (inline JSON).";
418
387
  await runStory(makeUserMessageEvent(finalText));
419
388
  ctx = await getContextOrThrow();
420
- console.log("[ekairos/structure] structure.ts build thirdPass end");
421
389
  }
422
390
  if (output === "rows" && !(await isRowsCompleted())) {
423
391
  throw new Error("Rows output not completed");
424
392
  }
425
393
  if (output === "object" && !isObjectCompleted(ctx)) {
426
- console.log("[ekairos/structure] structure.ts build objectNotCompleted tryingPersistFallback");
427
394
  const persisted = await persistObjectResultFromStoryStep({ env, datasetId });
428
395
  if (persisted.ok) {
429
- console.log("[ekairos/structure] structure.ts build persistFallback ok");
430
396
  ctx = await getContextOrThrow();
431
397
  }
432
398
  else {
433
- console.log("[ekairos/structure] structure.ts build persistFallback failed");
434
399
  }
435
400
  }
436
401
  if (output === "object" && !isObjectCompleted(ctx)) {
@@ -464,8 +429,40 @@ export function structure(env, opts) {
464
429
  done: page.done,
465
430
  };
466
431
  },
432
+ split: async (cursorOrParams, limit) => {
433
+ if (output !== "rows") {
434
+ throw new Error("reader.split() is only supported for output=rows");
435
+ }
436
+ if (!rowsSandboxRef) {
437
+ rowsSandboxRef = await structureDownloadRowsOutputToSandboxStep({
438
+ env,
439
+ structureId: datasetId,
440
+ });
441
+ }
442
+ const params = cursorOrParams && typeof cursorOrParams === "object" && ("cursor" in cursorOrParams || "limit" in cursorOrParams)
443
+ ? cursorOrParams
444
+ : {
445
+ cursor: cursorOrParams,
446
+ limit,
447
+ datasetId: undefined,
448
+ };
449
+ const childDatasetId = params?.datasetId ?? createUuidV4();
450
+ const res = await structureSplitRowsOutputToDatasetStep({
451
+ env,
452
+ sandboxId: rowsSandboxRef.sandboxId,
453
+ localPath: rowsSandboxRef.localPath,
454
+ cursor: params?.cursor,
455
+ limit: params?.limit ?? 300,
456
+ childDatasetId,
457
+ });
458
+ return {
459
+ datasetId: res.datasetId,
460
+ rowsWritten: res.rowsWritten,
461
+ cursor: res.nextCursor,
462
+ done: res.done,
463
+ };
464
+ },
467
465
  };
468
- console.log("[ekairos/structure] structure.ts build ok");
469
466
  return output === "object" ? { datasetId, reader, dataset: ctx } : { datasetId, reader };
470
467
  },
471
468
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ekairos/structure",
3
- "version": "1.21.62-beta.0",
3
+ "version": "1.21.69-beta.0",
4
4
  "description": "Ekairos Structure - Unified structured extraction (rows or object) from file/text/dataset inputs",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -36,12 +36,13 @@
36
36
  "typecheck": "tsc --noEmit"
37
37
  },
38
38
  "dependencies": {
39
- "@ekairos/domain": "^1.21.62-beta.0",
39
+ "@ekairos/domain": "^1.21.69-beta.0",
40
40
  "@ekairos/sandbox": "^1.21.60-beta.0",
41
41
  "@instantdb/admin": "^0.22.13",
42
42
  "@instantdb/core": "^0.22.13",
43
43
  "ai": "^5.0.95",
44
44
  "ajv": "^8.17.1",
45
+ "workflow": "4.0.1-beta.41",
45
46
  "zod": "^4.1.8"
46
47
  },
47
48
  "peerDependencies": {