zeitlich 0.2.16 → 0.2.18

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 (50) hide show
  1. package/README.md +75 -64
  2. package/dist/adapters/sandbox/virtual/index.cjs +45 -0
  3. package/dist/adapters/sandbox/virtual/index.cjs.map +1 -1
  4. package/dist/adapters/sandbox/virtual/index.d.cts +3 -3
  5. package/dist/adapters/sandbox/virtual/index.d.ts +3 -3
  6. package/dist/adapters/sandbox/virtual/index.js +43 -1
  7. package/dist/adapters/sandbox/virtual/index.js.map +1 -1
  8. package/dist/adapters/thread/google-genai/index.d.cts +2 -2
  9. package/dist/adapters/thread/google-genai/index.d.ts +2 -2
  10. package/dist/adapters/thread/langchain/index.cjs +2 -2
  11. package/dist/adapters/thread/langchain/index.cjs.map +1 -1
  12. package/dist/adapters/thread/langchain/index.d.cts +2 -2
  13. package/dist/adapters/thread/langchain/index.d.ts +2 -2
  14. package/dist/adapters/thread/langchain/index.js +2 -2
  15. package/dist/adapters/thread/langchain/index.js.map +1 -1
  16. package/dist/index.cjs +104 -9
  17. package/dist/index.cjs.map +1 -1
  18. package/dist/index.d.cts +6 -6
  19. package/dist/index.d.ts +6 -6
  20. package/dist/index.js +100 -10
  21. package/dist/index.js.map +1 -1
  22. package/dist/{types-DCi2qXjN.d.cts → queries-DnX72m_Y.d.cts} +43 -2
  23. package/dist/{types-BSOte_8s.d.ts → queries-TwukRZ8b.d.ts} +43 -2
  24. package/dist/{types-XPtivmSJ.d.ts → types-CdB2D5Sq.d.ts} +32 -13
  25. package/dist/{types-BMXzv7TN.d.cts → types-CmOSypVk.d.cts} +2 -2
  26. package/dist/{types-BMXzv7TN.d.ts → types-CmOSypVk.d.ts} +2 -2
  27. package/dist/{types-Drli9aCK.d.cts → types-DRvq2miV.d.cts} +32 -13
  28. package/dist/workflow.cjs +104 -9
  29. package/dist/workflow.cjs.map +1 -1
  30. package/dist/workflow.d.cts +114 -40
  31. package/dist/workflow.d.ts +114 -40
  32. package/dist/workflow.js +100 -10
  33. package/dist/workflow.js.map +1 -1
  34. package/package.json +1 -1
  35. package/src/adapters/sandbox/virtual/index.ts +2 -0
  36. package/src/adapters/sandbox/virtual/queries.ts +97 -0
  37. package/src/adapters/thread/langchain/activities.ts +7 -5
  38. package/src/lib/session/session.integration.test.ts +1 -0
  39. package/src/lib/subagent/define.ts +34 -47
  40. package/src/lib/subagent/handler.ts +9 -6
  41. package/src/lib/subagent/index.ts +4 -1
  42. package/src/lib/subagent/register.ts +7 -4
  43. package/src/lib/subagent/subagent.integration.test.ts +206 -51
  44. package/src/lib/subagent/types.ts +41 -11
  45. package/src/lib/subagent/workflow.ts +114 -0
  46. package/src/lib/tool-router/router.ts +2 -1
  47. package/src/lib/tool-router/types.ts +2 -2
  48. package/src/lib/workflow.test.ts +131 -0
  49. package/src/lib/workflow.ts +45 -0
  50. package/src/workflow.ts +12 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zeitlich",
3
- "version": "0.2.16",
3
+ "version": "0.2.18",
4
4
  "description": "[EXPERIMENTAL] An opinionated AI agent implementation for Temporal",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",
@@ -77,6 +77,8 @@ export function createVirtualSandbox<
77
77
  export { VirtualSandboxFileSystem } from "./filesystem";
78
78
  export { VirtualSandboxProvider } from "./provider";
79
79
  export { withVirtualSandbox } from "./with-virtual-sandbox";
80
+ export { hasFileWithMimeType, filesWithMimeType, hasDirectory } from "./queries";
81
+ export type { FileTreeAccessor } from "./queries";
80
82
  export type {
81
83
  FileEntry,
82
84
  FileEntryMetadata,
@@ -0,0 +1,97 @@
1
+ import type { FileEntry } from "./types";
2
+
3
+ /**
4
+ * Structural constraint: accepts any `AgentStateManager<T>` whose custom
5
+ * state includes `fileTree: FileEntry<TMeta>[]`.
6
+ */
7
+ export interface FileTreeAccessor<TMeta> {
8
+ get(key: "fileTree"): FileEntry<TMeta>[];
9
+ }
10
+
11
+ /**
12
+ * Check whether any file in the tree has a `metadata.mimeType` that matches
13
+ * the given pattern.
14
+ *
15
+ * Patterns:
16
+ * - Exact: `"application/pdf"`
17
+ * - Wildcard type: `"image/*"`
18
+ *
19
+ * Useful for conditionally enabling tools:
20
+ *
21
+ * ```ts
22
+ * { enabled: hasFileWithMimeType(stateManager, "image/*") }
23
+ * { enabled: hasFileWithMimeType(stateManager, ["image/*", "application/pdf"]) }
24
+ * ```
25
+ */
26
+ export function hasFileWithMimeType<TMeta>(
27
+ stateManager: FileTreeAccessor<TMeta>,
28
+ pattern: string | string[],
29
+ ): boolean {
30
+ const tree = stateManager.get("fileTree");
31
+ const matchers = (Array.isArray(pattern) ? pattern : [pattern]).map(buildMatcher);
32
+ return tree.some((entry) => {
33
+ const meta = entry.metadata as Record<string, unknown> | undefined;
34
+ const mime = meta?.mimeType;
35
+ return typeof mime === "string" && matchers.some((m) => m(mime));
36
+ });
37
+ }
38
+
39
+ /**
40
+ * Return all entries whose `metadata.mimeType` matches the given pattern.
41
+ */
42
+ export function filesWithMimeType<TMeta>(
43
+ stateManager: FileTreeAccessor<TMeta>,
44
+ pattern: string,
45
+ ): FileEntry<TMeta>[] {
46
+ const tree = stateManager.get("fileTree");
47
+ const match = buildMatcher(pattern);
48
+ return tree.filter((entry) => {
49
+ const meta = entry.metadata as Record<string, unknown> | undefined;
50
+ const mime = meta?.mimeType;
51
+ return typeof mime === "string" && match(mime);
52
+ });
53
+ }
54
+
55
+ /**
56
+ * Check whether the tree contains a directory whose name matches the given
57
+ * pattern. Directories are inferred from file paths.
58
+ *
59
+ * Patterns:
60
+ * - Exact: `"src"`
61
+ * - Glob with `*` wildcard: `"test*"`, `"*.generated"`
62
+ *
63
+ * ```ts
64
+ * { enabled: hasDirectory(stateManager, "test*") }
65
+ * ```
66
+ */
67
+ export function hasDirectory<TMeta>(
68
+ stateManager: FileTreeAccessor<TMeta>,
69
+ pattern: string,
70
+ ): boolean {
71
+ const tree = stateManager.get("fileTree");
72
+ const match = buildGlobMatcher(pattern);
73
+ return tree.some((entry) => {
74
+ const segments = entry.path.split("/").filter(Boolean);
75
+ return segments.slice(0, -1).some(match);
76
+ });
77
+ }
78
+
79
+ // ---------------------------------------------------------------------------
80
+ // Internal matchers
81
+ // ---------------------------------------------------------------------------
82
+
83
+ function buildMatcher(pattern: string): (value: string) => boolean {
84
+ if (pattern.endsWith("/*")) {
85
+ const prefix = pattern.slice(0, -1);
86
+ return (v) => v.startsWith(prefix);
87
+ }
88
+ return (v) => v === pattern;
89
+ }
90
+
91
+ function buildGlobMatcher(pattern: string): (value: string) => boolean {
92
+ if (!pattern.includes("*")) return (v) => v === pattern;
93
+ const re = new RegExp(
94
+ "^" + pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*") + "$",
95
+ );
96
+ return (v) => re.test(v);
97
+ }
@@ -96,7 +96,7 @@ export function createLangChainAdapter(
96
96
 
97
97
  async forkThread(
98
98
  sourceThreadId: string,
99
- targetThreadId: string,
99
+ targetThreadId: string
100
100
  ): Promise<void> {
101
101
  const thread = createLangChainThreadManager({
102
102
  redis,
@@ -106,18 +106,20 @@ export function createLangChainAdapter(
106
106
  },
107
107
  };
108
108
 
109
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
110
- const makeInvoker = (model: BaseChatModel<any>): ModelInvoker<StoredMessage> =>
109
+ const makeInvoker = (
110
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
111
+ model: BaseChatModel<any>
112
+ ): ModelInvoker<StoredMessage> =>
111
113
  createLangChainModelInvoker({ redis, model });
112
114
 
113
115
  const invoker: ModelInvoker<StoredMessage> = config.model
114
116
  ? makeInvoker(config.model)
115
- : ((() => {
117
+ : () => {
116
118
  throw new Error(
117
119
  "No default model provided to createLangChainAdapter. " +
118
120
  "Either pass `model` in the config or use `createModelInvoker(model)` instead."
119
121
  );
120
- }) as unknown as ModelInvoker<StoredMessage>);
122
+ };
121
123
 
122
124
  return {
123
125
  threadOps,
@@ -850,3 +850,4 @@ describe("createSession integration", () => {
850
850
  expect(result.usage.totalOutputTokens).toBe(90);
851
851
  });
852
852
  });
853
+
@@ -1,71 +1,58 @@
1
1
  import type { z } from "zod";
2
2
  import type {
3
3
  SubagentConfig,
4
- SubagentHandlerResponse,
4
+ SubagentDefinition,
5
5
  SubagentHooks,
6
+ SubagentWorkflow,
6
7
  } from "./types";
7
8
  import type { SubagentArgs } from "./tool";
8
9
 
9
10
  /**
10
- * Identity function that provides full type inference for subagent configurations.
11
- * Verifies the workflow function's input parameters match the configured context,
12
- * and properly types the lifecycle hooks with Task tool args and inferred result type.
11
+ * Creates a `SubagentConfig` from a `SubagentDefinition` (returned by `defineSubagentWorkflow`).
12
+ * Metadata (name, description, resultSchema) is read from the definition — only configure
13
+ * what's specific to this usage in the parent workflow.
13
14
  *
14
15
  * @example
15
16
  * ```ts
16
- * // With typed context workflow must accept { prompt, context }
17
- * const researcher = defineSubagent({
18
- * name: "researcher",
19
- * description: "Researches topics",
20
- * workflow: researcherWorkflow, // (input: { prompt: string; context: { apiKey: string } }) => Promise<...>
21
- * context: { apiKey: "..." },
22
- * resultSchema: z.object({ findings: z.string() }),
17
+ * // Minimalall metadata comes from the definition
18
+ * export const researcher = defineSubagent(researcherWorkflow);
19
+ *
20
+ * // With parent-specific overrides
21
+ * export const researcher = defineSubagent(researcherWorkflow, {
22
+ * allowThreadContinuation: true,
23
+ * sandbox: "own",
23
24
  * hooks: {
24
- * onPostExecution: ({ result }) => {
25
- * // result is typed as { findings: string }
26
- * },
25
+ * onPostExecution: ({ result }) => console.log(result),
27
26
  * },
28
27
  * });
29
28
  *
30
- * // Without context — workflow only needs { prompt }
31
- * const writer = defineSubagent({
32
- * name: "writer",
33
- * description: "Writes content",
34
- * workflow: writerWorkflow, // (input: { prompt: string }) => Promise<...>
35
- * resultSchema: z.object({ content: z.string() }),
29
+ * // With typed context
30
+ * export const researcher = defineSubagent(researcherWorkflow, {
31
+ * context: { apiKey: "..." },
36
32
  * });
37
33
  * ```
38
34
  */
39
- // With context — verifies workflow accepts { prompt, context: TContext }
40
35
  export function defineSubagent<
41
36
  TResult extends z.ZodType = z.ZodType,
42
37
  TContext extends Record<string, unknown> = Record<string, unknown>,
43
38
  >(
44
- config: Omit<SubagentConfig<TResult>, "hooks" | "workflow" | "context"> & {
45
- workflow:
46
- | string
47
- | ((input: {
48
- prompt: string;
49
- previousThreadId?: string;
50
- context: TContext;
51
- }) => Promise<SubagentHandlerResponse<z.infer<TResult> | null>>);
52
- context: TContext;
53
- hooks?: SubagentHooks<SubagentArgs, z.infer<TResult>>;
54
- }
55
- ): SubagentConfig<TResult>;
56
- // Without context — verifies workflow accepts { prompt }
57
- export function defineSubagent<TResult extends z.ZodType = z.ZodType>(
58
- config: Omit<SubagentConfig<TResult>, "hooks" | "workflow"> & {
59
- workflow:
60
- | string
61
- | ((input: {
62
- prompt: string;
63
- previousThreadId?: string;
64
- }) => Promise<SubagentHandlerResponse<z.infer<TResult> | null>>);
39
+ definition: SubagentDefinition<TResult, TContext>,
40
+ overrides?: {
41
+ context?: TContext;
65
42
  hooks?: SubagentHooks<SubagentArgs, z.infer<TResult>>;
66
- }
67
- ): SubagentConfig<TResult>;
68
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
69
- export function defineSubagent(config: any): SubagentConfig {
70
- return config;
43
+ enabled?: boolean | (() => boolean);
44
+ taskQueue?: string;
45
+ allowThreadContinuation?: boolean;
46
+ sandbox?: "inherit" | "own";
47
+ },
48
+ ): SubagentConfig<TResult> {
49
+ return {
50
+ agentName: definition.agentName,
51
+ description: definition.description,
52
+ workflow: definition as SubagentWorkflow<TResult>,
53
+ ...(definition.resultSchema !== undefined && {
54
+ resultSchema: definition.resultSchema,
55
+ }),
56
+ ...overrides,
57
+ } as SubagentConfig<TResult>;
71
58
  }
@@ -5,7 +5,7 @@ import type { ToolMessageContent } from "../types";
5
5
  import type {
6
6
  InferSubagentResult,
7
7
  SubagentConfig,
8
- SubagentInput,
8
+ SubagentWorkflowInput,
9
9
  } from "./types";
10
10
  import type { SubagentArgs } from "./tool";
11
11
  import type { z } from "zod";
@@ -38,18 +38,21 @@ export function createSubagentHandler<
38
38
  const { sandboxId: parentSandboxId } = context;
39
39
  const inheritSandbox = config.sandbox !== "own" && !!parentSandboxId;
40
40
 
41
- const input: SubagentInput = {
42
- prompt: args.prompt,
43
- ...(config.context && { context: config.context }),
41
+ const workflowInput: SubagentWorkflowInput = {
44
42
  ...(args.threadId &&
45
43
  args.threadId !== null &&
46
- config.allowThreadContinuation && { previousThreadId: args.threadId }),
44
+ config.allowThreadContinuation && {
45
+ previousThreadId: args.threadId,
46
+ }),
47
47
  ...(inheritSandbox && { sandboxId: parentSandboxId }),
48
48
  };
49
49
 
50
50
  const childOpts = {
51
51
  workflowId: childWorkflowId,
52
- args: [input] as const,
52
+ args:
53
+ config.context === undefined
54
+ ? ([args.prompt, workflowInput] as const)
55
+ : ([args.prompt, workflowInput, config.context] as const),
53
56
  taskQueue: config.taskQueue ?? parentTaskQueue,
54
57
  };
55
58
 
@@ -1,13 +1,16 @@
1
1
  export type {
2
2
  SubagentConfig,
3
+ SubagentDefinition,
3
4
  SubagentHooks,
4
- SubagentInput,
5
5
  SubagentHandlerResponse,
6
6
  SubagentWorkflow,
7
+ SubagentWorkflowInput,
8
+ SubagentSessionInput,
7
9
  InferSubagentResult,
8
10
  } from "./types";
9
11
  export { createSubagentTool, SUBAGENT_TOOL_NAME } from "./tool";
10
12
  export type { SubagentArgs } from "./tool";
11
13
  export { createSubagentHandler } from "./handler";
12
14
  export { defineSubagent } from "./define";
15
+ export { defineSubagentWorkflow } from "./workflow";
13
16
  export { buildSubagentRegistration } from "./register";
@@ -13,9 +13,9 @@ import { createSubagentHandler } from "./handler";
13
13
  * Builds a fully wired tool entry for the Subagent tool,
14
14
  * including per-subagent hook delegation.
15
15
  *
16
- * Uses getters for `enabled`, `description`, and `schema` so that
17
- * dynamic changes to SubagentConfig.enabled are re-evaluated each
18
- * time getToolDefinitions() is called.
16
+ * Lazily evaluates `enabled` (supports `boolean | () => boolean`)
17
+ * so that `description` and `schema` reflect the current set of
18
+ * active subagents each time getToolDefinitions() is called.
19
19
  *
20
20
  * Returns null if no subagents are configured.
21
21
  */
@@ -24,7 +24,10 @@ export function buildSubagentRegistration(
24
24
  ): ToolMap[string] | null {
25
25
  if (subagents.length === 0) return null;
26
26
 
27
- const getEnabled = (): SubagentConfig[] => subagents.filter((s) => s.enabled ?? true);
27
+ const getEnabled = (): SubagentConfig[] =>
28
+ subagents.filter((s) =>
29
+ typeof s.enabled === "function" ? s.enabled() : (s.enabled ?? true),
30
+ );
28
31
 
29
32
  const subagentHooksMap = new Map<string, SubagentHooks>();
30
33
  for (const s of subagents) {