chore: enforce folder module discipline in @uncaged/workflow

Each folder now has:
- types.ts for all type definitions
- index.ts with pure re-exports only
- Cross-folder imports go through index.ts

Closes #106
This commit is contained in:
2026-05-08 01:37:23 +00:00
parent a4237c0462
commit 2bbe5a3d0e
38 changed files with 389 additions and 262 deletions
@@ -1,7 +1,7 @@
import * as z from "zod/v4"; import * as z from "zod/v4";
import type { RoleMeta, WorkflowDefinition } from "../types.js"; import type { RoleMeta, WorkflowDefinition } from "../types.js";
import type { WorkflowDescriptor, WorkflowRoleSchema } from "./workflow-descriptor.js"; import type { WorkflowDescriptor, WorkflowRoleSchema } from "./types.js";
function stripJsonSchemaMeta(json: Record<string, unknown>): WorkflowRoleSchema { function stripJsonSchemaMeta(json: Record<string, unknown>): WorkflowRoleSchema {
const { $schema: _drop, ...rest } = json; const { $schema: _drop, ...rest } = json;
@@ -10,6 +10,11 @@ import type {
Program, Program,
VariableDeclaration, VariableDeclaration,
} from "acorn"; } from "acorn";
import * as acorn from "acorn";
import { err, ok, type Result } from "../util/index.js";
import type { WorkflowBundleValidationInput } from "./types.js";
/** Acorn Node with index-access for property traversal. */ /** Acorn Node with index-access for property traversal. */
type AcornNode = Node & { [key: string]: unknown }; type AcornNode = Node & { [key: string]: unknown };
@@ -22,17 +27,6 @@ function narrowNode<T extends Node>(node: Node): T {
return node as unknown as T; return node as unknown as T;
} }
import * as acorn from "acorn";
import { err, ok, type Result } from "../util/result.js";
export type WorkflowBundleValidationInput = {
/** Absolute or relative path (used for `.esm.js` suffix checks). */
filePath: string;
/** UTF-8 source of the bundle. */
source: string;
};
function endsWithEsmJs(path: string): boolean { function endsWithEsmJs(path: string): boolean {
return path.endsWith(".esm.js"); return path.endsWith(".esm.js");
} }
@@ -1,20 +1,10 @@
import type { WorkflowFn } from "../types.js"; import type { WorkflowFn } from "../types.js";
import { err, ok, type Result } from "../util/result.js"; import { err, ok, type Result } from "../util/index.js";
import { importWorkflowBundleModule } from "./bundle-import-env.js"; import { importWorkflowBundleModule } from "./bundle-import-env.js";
import { ensureUncagedWorkflowSymlink } from "./ensure-uncaged-workflow-symlink.js"; import { ensureUncagedWorkflowSymlink } from "./ensure-uncaged-workflow-symlink.js";
import type { WorkflowDescriptor } from "./workflow-descriptor.js"; import type { ExtractBundleExportsOptions, ExtractedBundleExports } from "./types.js";
import { validateWorkflowDescriptor } from "./workflow-descriptor.js"; import { validateWorkflowDescriptor } from "./workflow-descriptor.js";
export type ExtractedBundleExports = {
run: WorkflowFn;
descriptor: WorkflowDescriptor;
};
export type ExtractBundleExportsOptions = {
/** When set, ensures `node_modules/@uncaged/workflow` exists under this root before import. */
storageRoot: string | null;
};
/** Load a workflow `.esm.js` bundle and read its named exports (`run`, `descriptor`). */ /** Load a workflow `.esm.js` bundle and read its named exports (`run`, `descriptor`). */
export async function extractBundleExports( export async function extractBundleExports(
bundlePath: string, bundlePath: string,
@@ -1,6 +1,6 @@
import { stringify } from "yaml"; import { stringify } from "yaml";
import type { WorkflowDescriptor } from "./workflow-descriptor.js"; import type { WorkflowDescriptor } from "./types.js";
/** Serialize a validated workflow descriptor to YAML for storage next to the bundle. */ /** Serialize a validated workflow descriptor to YAML for storage next to the bundle. */
export function stringifyWorkflowDescriptor(descriptor: WorkflowDescriptor): string { export function stringifyWorkflowDescriptor(descriptor: WorkflowDescriptor): string {
+15
View File
@@ -0,0 +1,15 @@
export { buildDescriptor } from "./build-descriptor.js";
export { importWorkflowBundleModule } from "./bundle-import-env.js";
export { validateWorkflowBundle } from "./bundle-validator.js";
export { ensureUncagedWorkflowSymlink } from "./ensure-uncaged-workflow-symlink.js";
export { extractBundleExports } from "./extract-bundle-exports.js";
export { stringifyWorkflowDescriptor } from "./generate-descriptor.js";
export type {
ExtractBundleExportsOptions,
ExtractedBundleExports,
WorkflowBundleValidationInput,
WorkflowDescriptor,
WorkflowRoleDescriptor,
WorkflowRoleSchema,
} from "./types.js";
export { validateWorkflowDescriptor } from "./workflow-descriptor.js";
+32
View File
@@ -0,0 +1,32 @@
import type { WorkflowFn } from "../types.js";
/** JSON Schema fragment describing one role's `meta` shape (subset supported by code generation). */
export type WorkflowRoleSchema = Record<string, unknown>;
export type WorkflowRoleDescriptor = {
description: string;
schema: WorkflowRoleSchema;
};
/** Workflow metadata exported as `export const descriptor` from `.esm.js` bundles. */
export type WorkflowDescriptor = {
description: string;
roles: Record<string, WorkflowRoleDescriptor>;
};
export type WorkflowBundleValidationInput = {
/** Absolute or relative path (used for `.esm.js` suffix checks). */
filePath: string;
/** UTF-8 source of the bundle. */
source: string;
};
export type ExtractedBundleExports = {
run: WorkflowFn;
descriptor: WorkflowDescriptor;
};
export type ExtractBundleExportsOptions = {
/** When set, ensures `node_modules/@uncaged/workflow` exists under this root before import. */
storageRoot: string | null;
};
@@ -1,18 +1,6 @@
import { err, ok, type Result } from "../util/result.js"; import { err, ok, type Result } from "../util/index.js";
/** JSON Schema fragment describing one role's `meta` shape (subset supported by code generation). */ import type { WorkflowDescriptor, WorkflowRoleDescriptor, WorkflowRoleSchema } from "./types.js";
export type WorkflowRoleSchema = Record<string, unknown>;
export type WorkflowRoleDescriptor = {
description: string;
schema: WorkflowRoleSchema;
};
/** Workflow metadata exported as `export const descriptor` from `.esm.js` bundles. */
export type WorkflowDescriptor = {
description: string;
roles: Record<string, WorkflowRoleDescriptor>;
};
export function validateWorkflowDescriptor(value: unknown): Result<WorkflowDescriptor, string> { export function validateWorkflowDescriptor(value: unknown): Result<WorkflowDescriptor, string> {
if (value === null || typeof value !== "object" || Array.isArray(value)) { if (value === null || typeof value !== "object" || Array.isArray(value)) {
+1 -7
View File
@@ -2,13 +2,7 @@ import { mkdir, readdir, readFile, rename, unlink, writeFile } from "node:fs/pro
import { join } from "node:path"; import { join } from "node:path";
import { hashString } from "./hash.js"; import { hashString } from "./hash.js";
import type { CasStore } from "./types.js";
export type CasStore = {
put(content: string): Promise<string>;
get(hash: string): Promise<string | null>;
delete(hash: string): Promise<void>;
list(): Promise<string[]>;
};
export function createCasStore(casDir: string): CasStore { export function createCasStore(casDir: string): CasStore {
async function ensureDir(): Promise<void> { async function ensureDir(): Promise<void> {
+1 -1
View File
@@ -2,7 +2,7 @@ import { Buffer } from "node:buffer";
import XXH from "xxhashjs"; import XXH from "xxhashjs";
import { encodeUint64AsCrockford } from "../util/base32.js"; import { encodeUint64AsCrockford } from "../util/index.js";
function digestToUint64(digest: { toString(radix?: number): string }): bigint { function digestToUint64(digest: { toString(radix?: number): string }): bigint {
const hex = digest.toString(16).padStart(16, "0"); const hex = digest.toString(16).padStart(16, "0");
+18
View File
@@ -0,0 +1,18 @@
export { createCasStore, createThreadCas } from "./cas.js";
export { hashString, hashWorkflowBundleBytes } from "./hash.js";
export {
createContentMerkleNode,
getContentMerklePayload,
parseMerkleNode,
putContentMerkleNode,
putStepMerkleNode,
putThreadMerkleNode,
serializeMerkleNode,
} from "./merkle.js";
export type {
CasStore,
MerkleNode,
MerkleNodeType,
StepMerklePayload,
ThreadMerklePayload,
} from "./types.js";
+1 -23
View File
@@ -1,14 +1,6 @@
import { parse, stringify } from "yaml"; import { parse, stringify } from "yaml";
import type { CasStore } from "./cas.js"; import type { CasStore, MerkleNode, StepMerklePayload, ThreadMerklePayload } from "./types.js";
export type MerkleNodeType = "content" | "step" | "thread";
export type MerkleNode = {
type: MerkleNodeType;
payload: string | Record<string, unknown>;
children: string[];
};
export function serializeMerkleNode(node: MerkleNode): string { export function serializeMerkleNode(node: MerkleNode): string {
return stringify( return stringify(
@@ -53,20 +45,6 @@ export function createContentMerkleNode(payload: string): MerkleNode {
return { type: "content", payload, children: [] }; return { type: "content", payload, children: [] };
} }
export type StepMerklePayload = {
role: string;
meta: Record<string, unknown>;
};
export type ThreadMerklePayload = {
workflow: string;
threadId: string;
result: {
returnCode: number;
summary: string;
};
};
/** Serializes a step Merkle node (role + meta + content child) and stores it in CAS. */ /** Serializes a step Merkle node (role + meta + content child) and stores it in CAS. */
export async function putStepMerkleNode( export async function putStepMerkleNode(
store: CasStore, store: CasStore,
+28
View File
@@ -0,0 +1,28 @@
export type CasStore = {
put(content: string): Promise<string>;
get(hash: string): Promise<string | null>;
delete(hash: string): Promise<void>;
list(): Promise<string[]>;
};
export type MerkleNodeType = "content" | "step" | "thread";
export type MerkleNode = {
type: MerkleNodeType;
payload: string | Record<string, unknown>;
children: string[];
};
export type StepMerklePayload = {
role: string;
meta: Record<string, unknown>;
};
export type ThreadMerklePayload = {
workflow: string;
threadId: string;
result: {
returnCode: number;
summary: string;
};
};
@@ -1,7 +1,6 @@
import type { CasStore } from "../cas/cas.js"; import type { CasStore } from "../cas/index.js";
import { putContentMerkleNode } from "../cas/merkle.js"; import { putContentMerkleNode } from "../cas/index.js";
import { buildExtractUserContent, type ExtractFn } from "../extract/extract-fn.js"; import { buildExtractUserContent, type ExtractFn, reactExtract } from "../extract/index.js";
import { reactExtract } from "../extract/react-extract.js";
import { import {
type AgentBinding, type AgentBinding,
type AgentContext, type AgentContext,
@@ -20,7 +19,7 @@ import {
type WorkflowFn, type WorkflowFn,
type WorkflowFnOptions, type WorkflowFnOptions,
} from "../types.js"; } from "../types.js";
import { mergeRefsWithContentHash } from "../util/refs-field.js"; import { mergeRefsWithContentHash } from "../util/index.js";
function isRoleNext<M extends RoleMeta>( function isRoleNext<M extends RoleMeta>(
next: (keyof M & string) | typeof END, next: (keyof M & string) | typeof END,
+8 -36
View File
@@ -1,8 +1,12 @@
import { appendFile, mkdir } from "node:fs/promises"; import { appendFile, mkdir } from "node:fs/promises";
import { dirname } from "node:path"; import { dirname } from "node:path";
import type { CasStore } from "../cas/cas.js"; import {
import { getContentMerklePayload, putStepMerkleNode, putThreadMerkleNode } from "../cas/merkle.js"; type CasStore,
getContentMerklePayload,
putStepMerkleNode,
putThreadMerkleNode,
} from "../cas/index.js";
import type { import type {
ThreadInput, ThreadInput,
WorkflowCompletion, WorkflowCompletion,
@@ -10,41 +14,9 @@ import type {
WorkflowFnOptions, WorkflowFnOptions,
WorkflowResult, WorkflowResult,
} from "../types.js"; } from "../types.js";
import type { LogFn } from "../util/logger.js"; import { type LogFn, normalizeRefsField } from "../util/index.js";
import { normalizeRefsField } from "../util/refs-field.js";
export type ExecuteThreadIo = { import type { ExecuteThreadIo, ExecuteThreadOptions } from "./types.js";
threadId: string;
hash: string;
dataJsonlPath: string;
infoJsonlPath: string;
cas: CasStore;
};
/** One persisted role line in `.data.jsonl` (engine adds these for fork replay before running the generator). */
export type PrefilledDiskStep = {
role: string;
contentHash: string;
meta: Record<string, unknown>;
refs: string[];
timestamp: number;
};
export type ExecuteThreadOptions = {
maxRounds: number;
/** Passed to the bundle as `WorkflowFnOptions.depth`. */
depth: number;
signal: AbortSignal;
/** Invoked after each successful yield (and outer-loop checks); used for pause/resume. */
awaitAfterEachYield: () => Promise<void>;
/** When non-null, written into the start record so tooling can trace lineage. */
forkSourceThreadId: string | null;
/**
* Written to `.data.jsonl` immediately after the start record, before the generator runs.
* Must match `input.steps` length and order when present.
*/
prefilledDiskSteps: PrefilledDiskStep[] | null;
};
async function appendDataLine(path: string, record: unknown): Promise<void> { async function appendDataLine(path: string, record: unknown): Promise<void> {
const line = `${JSON.stringify(record)}\n`; const line = `${JSON.stringify(record)}\n`;
+3 -23
View File
@@ -1,18 +1,7 @@
import type { RoleOutput, WorkflowCompletion } from "../types.js"; import type { WorkflowCompletion } from "../types.js";
import { normalizeRefsField } from "../util/refs-field.js"; import { err, normalizeRefsField, ok, type Result } from "../util/index.js";
import { err, ok, type Result } from "../util/result.js";
/** Role steps replayed from `.data.jsonl`, including persisted timestamps. */ import type { ForkHistoricalStep, ForkPlan, ParsedThreadStartRecord } from "./types.js";
export type ForkHistoricalStep = RoleOutput & { timestamp: number };
export type ParsedThreadStartRecord = {
workflowName: string;
hash: string;
threadId: string;
prompt: string;
maxRounds: number;
depth: number;
};
/** Recognizes a persisted workflow completion line (no `role`; has numeric `returnCode` and string `summary`). Omits `rootHash` when absent. */ /** Recognizes a persisted workflow completion line (no `role`; has numeric `returnCode` and string `summary`). Omits `rootHash` when absent. */
export function tryParseWorkflowResultRecord( export function tryParseWorkflowResultRecord(
@@ -228,15 +217,6 @@ export function selectForkHistoricalSteps(
return ok(roleSteps.slice(0, idx + 1)); return ok(roleSteps.slice(0, idx + 1));
} }
export type ForkPlan = {
workflowName: string;
hash: string;
sourceThreadId: string;
prompt: string;
runOptions: { maxRounds: number; depth: number };
historicalSteps: ForkHistoricalStep[];
};
/** /**
* Read `.data.jsonl` text and compute fork payload for the worker `run` command. * Read `.data.jsonl` text and compute fork payload for the worker `run` command.
*/ */
+3 -10
View File
@@ -1,16 +1,9 @@
import { readdir, readFile } from "node:fs/promises"; import { readdir, readFile } from "node:fs/promises";
import { join } from "node:path"; import { join } from "node:path";
import { type CasStore, createCasStore } from "../cas/cas.js"; import { type CasStore, createCasStore } from "../cas/index.js";
import { err, ok, type Result } from "../util/result.js"; import { err, getGlobalCasDir, ok, type Result } from "../util/index.js";
import { getGlobalCasDir } from "../util/storage-root.js";
import { parseThreadDataJsonl } from "./fork-thread.js"; import { parseThreadDataJsonl } from "./fork-thread.js";
import type { GcResult } from "./types.js";
export type GcResult = {
scannedThreads: number;
activeRefs: number;
deletedEntries: number;
deletedHashes: string[];
};
async function listThreadDataJsonlPaths(storageRoot: string): Promise<Result<string[], string>> { async function listThreadDataJsonlPaths(storageRoot: string): Promise<Result<string[], string>> {
const logsRoot = join(storageRoot, "logs"); const logsRoot = join(storageRoot, "logs");
+22
View File
@@ -0,0 +1,22 @@
export { createWorkflow } from "./create-workflow.js";
export { executeThread } from "./engine.js";
export {
buildForkPlan,
parseThreadDataJsonl,
selectForkHistoricalSteps,
tryParseRoleStepRecord,
tryParseWorkflowResultRecord,
} from "./fork-thread.js";
export { garbageCollectCas } from "./gc.js";
export { createThreadPauseGate } from "./thread-pause-gate.js";
export type {
ExecuteThreadIo,
ExecuteThreadOptions,
ForkHistoricalStep,
ForkPlan,
GcResult,
ParsedThreadStartRecord,
PrefilledDiskStep,
ThreadPauseGate,
} from "./types.js";
export { getWorkerHostScriptPath } from "./worker-entry-path.js";
@@ -1,11 +1,6 @@
import { err, ok, type Result } from "../util/result.js"; import { err, ok, type Result } from "../util/index.js";
export type ThreadPauseGate = { import type { ThreadPauseGate } from "./types.js";
awaitAfterYield: () => Promise<void>;
pause: () => Result<void, string>;
resume: () => Result<void, string>;
isPaused: () => boolean;
};
/** /**
* Pause/resume gate for workflow threads: after each generator yield the engine awaits * Pause/resume gate for workflow threads: after each generator yield the engine awaits
+71
View File
@@ -0,0 +1,71 @@
import type { CasStore } from "../cas/index.js";
import type { RoleOutput } from "../types.js";
import type { Result } from "../util/index.js";
export type ExecuteThreadIo = {
threadId: string;
hash: string;
dataJsonlPath: string;
infoJsonlPath: string;
cas: CasStore;
};
/** One persisted role line in `.data.jsonl` (engine adds these for fork replay before running the generator). */
export type PrefilledDiskStep = {
role: string;
contentHash: string;
meta: Record<string, unknown>;
refs: string[];
timestamp: number;
};
export type ExecuteThreadOptions = {
maxRounds: number;
/** Passed to the bundle as `WorkflowFnOptions.depth`. */
depth: number;
signal: AbortSignal;
/** Invoked after each successful yield (and outer-loop checks); used for pause/resume. */
awaitAfterEachYield: () => Promise<void>;
/** When non-null, written into the start record so tooling can trace lineage. */
forkSourceThreadId: string | null;
/**
* Written to `.data.jsonl` immediately after the start record, before the generator runs.
* Must match `input.steps` length and order when present.
*/
prefilledDiskSteps: PrefilledDiskStep[] | null;
};
/** Role steps replayed from `.data.jsonl`, including persisted timestamps. */
export type ForkHistoricalStep = RoleOutput & { timestamp: number };
export type ParsedThreadStartRecord = {
workflowName: string;
hash: string;
threadId: string;
prompt: string;
maxRounds: number;
depth: number;
};
export type ForkPlan = {
workflowName: string;
hash: string;
sourceThreadId: string;
prompt: string;
runOptions: { maxRounds: number; depth: number };
historicalSteps: ForkHistoricalStep[];
};
export type GcResult = {
scannedThreads: number;
activeRefs: number;
deletedEntries: number;
deletedHashes: string[];
};
export type ThreadPauseGate = {
awaitAfterYield: () => Promise<void>;
pause: () => Result<void, string>;
resume: () => Result<void, string>;
isPaused: () => boolean;
};
+13 -10
View File
@@ -1,17 +1,20 @@
import { appendFile, mkdir, unlink, writeFile } from "node:fs/promises"; import { appendFile, mkdir, unlink, writeFile } from "node:fs/promises";
import { createServer, type Socket } from "node:net"; import { createServer, type Socket } from "node:net";
import { dirname, join } from "node:path"; import { dirname, join } from "node:path";
import { importWorkflowBundleModule } from "../bundle/bundle-import-env.js"; import { ensureUncagedWorkflowSymlink, importWorkflowBundleModule } from "../bundle/index.js";
import { ensureUncagedWorkflowSymlink } from "../bundle/ensure-uncaged-workflow-symlink.js"; import { createCasStore } from "../cas/index.js";
import { createCasStore } from "../cas/cas.js";
import type { RoleOutput, WorkflowFn, WorkflowResult } from "../types.js"; import type { RoleOutput, WorkflowFn, WorkflowResult } from "../types.js";
import { createLogger } from "../util/logger.js"; import {
import { normalizeRefsField } from "../util/refs-field.js"; createLogger,
import { err, ok, type Result } from "../util/result.js"; err,
import { getGlobalCasDir } from "../util/storage-root.js"; getGlobalCasDir,
import type { PrefilledDiskStep } from "./engine.js"; normalizeRefsField,
import { type ExecuteThreadIo, executeThread } from "./engine.js"; ok,
import { createThreadPauseGate, type ThreadPauseGate } from "./thread-pause-gate.js"; type Result,
} from "../util/index.js";
import { executeThread } from "./engine.js";
import { createThreadPauseGate } from "./thread-pause-gate.js";
import type { ExecuteThreadIo, PrefilledDiskStep, ThreadPauseGate } from "./types.js";
const bootLog = createLogger({ sink: { kind: "stderr" } }); const bootLog = createLogger({ sink: { kind: "stderr" } });
+3 -4
View File
@@ -1,8 +1,7 @@
import { readWorkflowRegistry } from "./registry/registry.js"; import type { WorkflowConfig } from "./registry/index.js";
import type { WorkflowConfig } from "./registry/registry-types.js"; import { readWorkflowRegistry } from "./registry/index.js";
import type { LlmProvider } from "./types.js"; import type { LlmProvider } from "./types.js";
import { err, ok, type Result } from "./util/result.js"; import { err, getDefaultWorkflowStorageRoot, ok, type Result } from "./util/index.js";
import { getDefaultWorkflowStorageRoot } from "./util/storage-root.js";
const DEFAULT_WORKFLOW_AS_AGENT_MAX_DEPTH = 3; const DEFAULT_WORKFLOW_AS_AGENT_MAX_DEPTH = 3;
+3 -7
View File
@@ -1,13 +1,9 @@
import type * as z from "zod/v4"; import type * as z from "zod/v4";
import { getContentMerklePayload } from "../cas/merkle.js";
import { getContentMerklePayload } from "../cas/index.js";
import type { ExtractContext, LlmProvider } from "../types.js"; import type { ExtractContext, LlmProvider } from "../types.js";
import { llmExtractWithRetry } from "./llm-extract.js"; import { llmExtractWithRetry } from "./llm-extract.js";
import type { ExtractFn } from "./types.js";
export type ExtractFn = <T extends Record<string, unknown>>(
schema: z.ZodType<T>,
prompt: string,
ctx: ExtractContext,
) => Promise<T>;
/** Builds the user-side extraction prompt (thread + agent output + instruction). */ /** Builds the user-side extraction prompt (thread + agent output + instruction). */
export async function buildExtractUserContent( export async function buildExtractUserContent(
+17
View File
@@ -0,0 +1,17 @@
export {
buildExtractUserContent,
createExtract,
} from "./extract-fn.js";
export {
extractFunctionToolFromZodSchema,
llmErrorToCause,
llmExtract,
llmExtractWithRetry,
} from "./llm-extract.js";
export { reactExtract } from "./react-extract.js";
export type {
ExtractFn,
LlmError,
LlmExtractArgs,
ReactExtractArgs,
} from "./types.js";
+2 -14
View File
@@ -1,20 +1,8 @@
import * as z from "zod/v4"; import * as z from "zod/v4";
import type { LlmProvider } from "../types.js";
import { err, ok, type Result } from "../util/result.js";
export type LlmExtractArgs<T> = { import { err, ok, type Result } from "../util/index.js";
text: string;
schema: z.ZodType<T>;
provider: LlmProvider;
};
export type LlmError = import type { LlmError, LlmExtractArgs } from "./types.js";
| { kind: "http_error"; status: number; body: string }
| { kind: "invalid_response_json"; message: string }
| { kind: "no_tool_call"; preview: string }
| { kind: "tool_arguments_invalid_json"; message: string }
| { kind: "schema_validation_failed"; message: string }
| { kind: "network_error"; message: string };
function chatCompletionsUrl(baseUrl: string): string { function chatCompletionsUrl(baseUrl: string): string {
const trimmed = baseUrl.replace(/\/+$/, ""); const trimmed = baseUrl.replace(/\/+$/, "");
@@ -1,16 +1,11 @@
import type * as z from "zod/v4"; import type * as z from "zod/v4";
import type { CasStore } from "../cas/cas.js"; import type { CasStore } from "../cas/index.js";
import type { LlmProvider } from "../types.js"; import type { LlmProvider } from "../types.js";
import { err, ok, type Result } from "../util/result.js"; import { err, ok, type Result } from "../util/index.js";
import { extractFunctionToolFromZodSchema } from "./llm-extract.js";
export type ReactExtractArgs<T extends Record<string, unknown>> = { import { extractFunctionToolFromZodSchema } from "./llm-extract.js";
text: string; import type { ReactExtractArgs } from "./types.js";
schema: z.ZodType<T>;
provider: LlmProvider;
cas: CasStore;
};
const MAX_REACT_ROUNDS = 10; const MAX_REACT_ROUNDS = 10;
+31
View File
@@ -0,0 +1,31 @@
import type * as z from "zod/v4";
import type { CasStore } from "../cas/index.js";
import type { ExtractContext, LlmProvider } from "../types.js";
export type ExtractFn = <T extends Record<string, unknown>>(
schema: z.ZodType<T>,
prompt: string,
ctx: ExtractContext,
) => Promise<T>;
export type ReactExtractArgs<T extends Record<string, unknown>> = {
text: string;
schema: z.ZodType<T>;
provider: LlmProvider;
cas: CasStore;
};
export type LlmExtractArgs<T> = {
text: string;
schema: z.ZodType<T>;
provider: LlmProvider;
};
export type LlmError =
| { kind: "http_error"; status: number; body: string }
| { kind: "invalid_response_json"; message: string }
| { kind: "no_tool_call"; preview: string }
| { kind: "tool_arguments_invalid_json"; message: string }
| { kind: "schema_validation_failed"; message: string }
| { kind: "network_error"; message: string };
+35 -33
View File
@@ -1,24 +1,23 @@
export { buildDescriptor } from "./bundle/build-descriptor.js";
export {
validateWorkflowBundle,
type WorkflowBundleValidationInput,
} from "./bundle/bundle-validator.js";
export { export {
buildDescriptor,
type ExtractedBundleExports, type ExtractedBundleExports,
extractBundleExports, extractBundleExports,
} from "./bundle/extract-bundle-exports.js"; stringifyWorkflowDescriptor,
export { stringifyWorkflowDescriptor } from "./bundle/generate-descriptor.js"; validateWorkflowBundle,
export {
validateWorkflowDescriptor, validateWorkflowDescriptor,
type WorkflowBundleValidationInput,
type WorkflowDescriptor, type WorkflowDescriptor,
type WorkflowRoleDescriptor, type WorkflowRoleDescriptor,
type WorkflowRoleSchema, type WorkflowRoleSchema,
} from "./bundle/workflow-descriptor.js"; } from "./bundle/index.js";
export { type CasStore, createCasStore, createThreadCas } from "./cas/cas.js";
export { hashString, hashWorkflowBundleBytes } from "./cas/hash.js";
export { export {
type CasStore,
createCasStore,
createContentMerkleNode, createContentMerkleNode,
createThreadCas,
getContentMerklePayload, getContentMerklePayload,
hashString,
hashWorkflowBundleBytes,
type MerkleNode, type MerkleNode,
type MerkleNodeType, type MerkleNodeType,
parseMerkleNode, parseMerkleNode,
@@ -28,35 +27,37 @@ export {
type StepMerklePayload, type StepMerklePayload,
serializeMerkleNode, serializeMerkleNode,
type ThreadMerklePayload, type ThreadMerklePayload,
} from "./cas/merkle.js"; } from "./cas/index.js";
export { createWorkflow } from "./engine/create-workflow.js";
export { export {
buildForkPlan,
createThreadPauseGate,
createWorkflow,
type ExecuteThreadIo, type ExecuteThreadIo,
type ExecuteThreadOptions, type ExecuteThreadOptions,
executeThread, executeThread,
type PrefilledDiskStep,
} from "./engine/engine.js";
export {
buildForkPlan,
type ForkHistoricalStep, type ForkHistoricalStep,
type ForkPlan, type ForkPlan,
type GcResult,
garbageCollectCas,
getWorkerHostScriptPath,
type ParsedThreadStartRecord, type ParsedThreadStartRecord,
type PrefilledDiskStep,
parseThreadDataJsonl, parseThreadDataJsonl,
selectForkHistoricalSteps, selectForkHistoricalSteps,
type ThreadPauseGate,
tryParseRoleStepRecord, tryParseRoleStepRecord,
tryParseWorkflowResultRecord, tryParseWorkflowResultRecord,
} from "./engine/fork-thread.js"; } from "./engine/index.js";
export { type GcResult, garbageCollectCas } from "./engine/gc.js";
export { createThreadPauseGate, type ThreadPauseGate } from "./engine/thread-pause-gate.js";
export { getWorkerHostScriptPath } from "./engine/worker-entry-path.js";
export { createExtract, type ExtractFn } from "./extract/extract-fn.js";
export { export {
createExtract,
type ExtractFn,
type LlmError, type LlmError,
llmErrorToCause, llmErrorToCause,
llmExtract, llmExtract,
llmExtractWithRetry, llmExtractWithRetry,
} from "./extract/llm-extract.js"; type ReactExtractArgs,
export { type ReactExtractArgs, reactExtract } from "./extract/react-extract.js"; reactExtract,
} from "./extract/index.js";
export { getExtractProvider } from "./extract-provider.js"; export { getExtractProvider } from "./extract-provider.js";
export { export {
type ExtractProviderConfig, type ExtractProviderConfig,
@@ -74,7 +75,7 @@ export {
type WorkflowRegistryFile, type WorkflowRegistryFile,
workflowRegistryPath, workflowRegistryPath,
writeWorkflowRegistry, writeWorkflowRegistry,
} from "./registry/registry.js"; } from "./registry/index.js";
export { export {
type AgentBinding, type AgentBinding,
type AgentContext, type AgentContext,
@@ -101,18 +102,19 @@ export {
} from "./types.js"; } from "./types.js";
export { export {
CROCKFORD_BASE32_ALPHABET, CROCKFORD_BASE32_ALPHABET,
type CreateLoggerOptions,
createLogger,
decodeCrockfordBase32Bits, decodeCrockfordBase32Bits,
decodeCrockfordToUint64, decodeCrockfordToUint64,
encodeCrockfordBase32Bits, encodeCrockfordBase32Bits,
encodeUint64AsCrockford, encodeUint64AsCrockford,
} from "./util/base32.js"; err,
export { generateUlid,
type CreateLoggerOptions, getDefaultWorkflowStorageRoot,
createLogger, getGlobalCasDir,
type LogFn, type LogFn,
type LoggerSink, type LoggerSink,
} from "./util/logger.js"; ok,
export { err, ok, type Result } from "./util/result.js"; type Result,
export { getDefaultWorkflowStorageRoot, getGlobalCasDir } from "./util/storage-root.js"; } from "./util/index.js";
export { generateUlid } from "./util/ulid.js";
export { type WorkflowAsAgentOptions, workflowAsAgent } from "./workflow-as-agent.js"; export { type WorkflowAsAgentOptions, workflowAsAgent } from "./workflow-as-agent.js";
+19
View File
@@ -0,0 +1,19 @@
export {
getRegisteredWorkflow,
listRegisteredWorkflowNames,
parseWorkflowRegistryYaml,
readWorkflowRegistry,
registerWorkflowVersion,
rollbackWorkflowToHistoryHash,
stringifyWorkflowRegistryYaml,
unregisterWorkflow,
workflowRegistryPath,
writeWorkflowRegistry,
} from "./registry.js";
export type {
ExtractProviderConfig,
WorkflowConfig,
WorkflowHistoryEntry,
WorkflowRegistryEntry,
WorkflowRegistryFile,
} from "./types.js";
@@ -1,11 +1,11 @@
import { err, ok, type Result } from "../util/result.js"; import { err, ok, type Result } from "../util/index.js";
import type { import type {
ExtractProviderConfig, ExtractProviderConfig,
WorkflowConfig, WorkflowConfig,
WorkflowHistoryEntry, WorkflowHistoryEntry,
WorkflowRegistryEntry, WorkflowRegistryEntry,
WorkflowRegistryFile, WorkflowRegistryFile,
} from "./registry-types.js"; } from "./types.js";
function resolveRegistryApiKey(raw: string): Result<string, Error> { function resolveRegistryApiKey(raw: string): Result<string, Error> {
if (raw.startsWith("env:")) { if (raw.startsWith("env:")) {
+2 -14
View File
@@ -2,21 +2,9 @@ import { mkdir, readFile, writeFile } from "node:fs/promises";
import { dirname, join } from "node:path"; import { dirname, join } from "node:path";
import { parseDocument, stringify } from "yaml"; import { parseDocument, stringify } from "yaml";
import { err, ok, type Result } from "../util/result.js"; import { err, ok, type Result } from "../util/index.js";
import { normalizeWorkflowRegistryRoot } from "./registry-normalize.js"; import { normalizeWorkflowRegistryRoot } from "./registry-normalize.js";
import type { import type { WorkflowHistoryEntry, WorkflowRegistryEntry, WorkflowRegistryFile } from "./types.js";
WorkflowHistoryEntry,
WorkflowRegistryEntry,
WorkflowRegistryFile,
} from "./registry-types.js";
export type {
ExtractProviderConfig,
WorkflowConfig,
WorkflowHistoryEntry,
WorkflowRegistryEntry,
WorkflowRegistryFile,
} from "./registry-types.js";
export function workflowRegistryPath(storageRoot: string): string { export function workflowRegistryPath(storageRoot: string): string {
return join(storageRoot, "workflow.yaml"); return join(storageRoot, "workflow.yaml");
+1 -1
View File
@@ -1,6 +1,6 @@
import type * as z from "zod/v4"; import type * as z from "zod/v4";
import type { CasStore } from "./cas/cas.js"; import type { CasStore } from "./cas/index.js";
/** Sentinel values for automaton control flow. */ /** Sentinel values for automaton control flow. */
export const START = "__start__" as const; export const START = "__start__" as const;
+2 -1
View File
@@ -1,4 +1,5 @@
import { err, ok, type Result } from "./result.js"; import { err, ok } from "./result.js";
import type { Result } from "./types.js";
/** Crockford Base32 alphabet (no I, L, O, U) — exactly 32 symbols. */ /** Crockford Base32 alphabet (no I, L, O, U) — exactly 32 symbols. */
export const CROCKFORD_BASE32_ALPHABET = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"; export const CROCKFORD_BASE32_ALPHABET = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
+13
View File
@@ -0,0 +1,13 @@
export {
CROCKFORD_BASE32_ALPHABET,
decodeCrockfordBase32Bits,
decodeCrockfordToUint64,
encodeCrockfordBase32Bits,
encodeUint64AsCrockford,
} from "./base32.js";
export { createLogger } from "./logger.js";
export { mergeRefsWithContentHash, normalizeRefsField } from "./refs-field.js";
export { err, ok } from "./result.js";
export { getDefaultWorkflowStorageRoot, getGlobalCasDir } from "./storage-root.js";
export type { CreateLoggerOptions, LogFn, LoggerSink, Result } from "./types.js";
export { generateUlid } from "./ulid.js";
+1 -8
View File
@@ -1,6 +1,7 @@
import { appendFileSync } from "node:fs"; import { appendFileSync } from "node:fs";
import { CROCKFORD_BASE32_ALPHABET } from "./base32.js"; import { CROCKFORD_BASE32_ALPHABET } from "./base32.js";
import type { CreateLoggerOptions, LogFn } from "./types.js";
const TAG_LENGTH = 8; const TAG_LENGTH = 8;
@@ -22,14 +23,6 @@ function assertValidLogTag(tag: string): void {
} }
} }
export type LoggerSink = { kind: "stderr" } | { kind: "file"; path: string };
export type CreateLoggerOptions = {
sink: LoggerSink;
};
export type LogFn = (tag: string, content: string) => void;
/** Append one JSONL log record: `{ tag, content, timestamp }` per RFC-001. */ /** Append one JSONL log record: `{ tag, content, timestamp }` per RFC-001. */
export function createLogger(options: CreateLoggerOptions): LogFn { export function createLogger(options: CreateLoggerOptions): LogFn {
if (options.sink.kind === "stderr") { if (options.sink.kind === "stderr") {
+1 -1
View File
@@ -1,4 +1,4 @@
export type Result<T, E = Error> = { ok: true; value: T } | { ok: false; error: E }; import type { Result } from "./types.js";
export function ok<T>(value: T): Result<T, never> { export function ok<T>(value: T): Result<T, never> {
return { ok: true, value }; return { ok: true, value };
+9
View File
@@ -0,0 +1,9 @@
export type Result<T, E = Error> = { ok: true; value: T } | { ok: false; error: E };
export type LoggerSink = { kind: "stderr" } | { kind: "file"; path: string };
export type CreateLoggerOptions = {
sink: LoggerSink;
};
export type LogFn = (tag: string, content: string) => void;
+11 -7
View File
@@ -1,14 +1,18 @@
import { join } from "node:path"; import { join } from "node:path";
import { extractBundleExports } from "./bundle/extract-bundle-exports.js"; import { extractBundleExports } from "./bundle/index.js";
import { createCasStore } from "./cas/cas.js"; import { createCasStore } from "./cas/index.js";
import { type ExecuteThreadIo, executeThread } from "./engine/engine.js"; import type { ExecuteThreadIo } from "./engine/index.js";
import { executeThread } from "./engine/index.js";
import { getWorkflowAsAgentMaxDepth } from "./extract-provider.js"; import { getWorkflowAsAgentMaxDepth } from "./extract-provider.js";
import { getRegisteredWorkflow, readWorkflowRegistry } from "./registry/registry.js"; import { getRegisteredWorkflow, readWorkflowRegistry } from "./registry/index.js";
import type { AgentContext, AgentFn, ThreadInput } from "./types.js"; import type { AgentContext, AgentFn, ThreadInput } from "./types.js";
import { createLogger } from "./util/logger.js"; import {
import { getDefaultWorkflowStorageRoot, getGlobalCasDir } from "./util/storage-root.js"; createLogger,
import { generateUlid } from "./util/ulid.js"; generateUlid,
getDefaultWorkflowStorageRoot,
getGlobalCasDir,
} from "./util/index.js";
export type WorkflowAsAgentOptions = { export type WorkflowAsAgentOptions = {
/** When `null`, uses `getDefaultWorkflowStorageRoot()`. */ /** When `null`, uses `getDefaultWorkflowStorageRoot()`. */