@intx/agent 0.1.2 → 0.3.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.
Files changed (60) hide show
  1. package/LICENSE +176 -0
  2. package/README.md +80 -5
  3. package/dist/agent.d.ts +116 -0
  4. package/dist/agent.js +682 -0
  5. package/dist/canonicalize.d.ts +15 -0
  6. package/dist/canonicalize.js +160 -0
  7. package/dist/default-director.d.ts +24 -0
  8. package/dist/default-director.js +45 -0
  9. package/dist/definition.d.ts +139 -0
  10. package/dist/definition.js +40 -0
  11. package/dist/director-registry.d.ts +47 -0
  12. package/dist/director-registry.js +87 -0
  13. package/dist/director-types.d.ts +80 -0
  14. package/dist/director-types.js +13 -0
  15. package/dist/director.d.ts +70 -0
  16. package/dist/director.js +131 -0
  17. package/dist/env-validation.d.ts +59 -0
  18. package/dist/env-validation.js +180 -0
  19. package/dist/env.d.ts +160 -0
  20. package/dist/env.js +53 -0
  21. package/dist/index.d.ts +16 -0
  22. package/dist/index.js +23 -0
  23. package/dist/internal-fixtures/mail.d.ts +39 -0
  24. package/dist/internal-fixtures/mail.js +86 -0
  25. package/dist/internal-fixtures/planner.d.ts +19 -0
  26. package/dist/internal-fixtures/planner.js +49 -0
  27. package/dist/lock.d.ts +16 -0
  28. package/dist/lock.js +47 -0
  29. package/dist/namespace.d.ts +12 -0
  30. package/dist/namespace.js +39 -0
  31. package/dist/send-queue.d.ts +25 -0
  32. package/dist/send-queue.js +147 -0
  33. package/dist/source.d.ts +43 -0
  34. package/dist/source.js +118 -0
  35. package/dist/stream.d.ts +16 -0
  36. package/dist/stream.js +115 -0
  37. package/dist/testing/audit-noop.d.ts +7 -0
  38. package/dist/testing/audit-noop.js +25 -0
  39. package/dist/testing/authorize-allow.d.ts +8 -0
  40. package/dist/testing/authorize-allow.js +19 -0
  41. package/dist/testing/index.d.ts +2 -0
  42. package/dist/testing/index.js +17 -0
  43. package/dist/tool.d.ts +238 -0
  44. package/dist/tool.js +244 -0
  45. package/package.json +26 -7
  46. package/src/agent.test.ts +0 -46
  47. package/src/agent.ts +0 -494
  48. package/src/index.ts +0 -38
  49. package/src/lock.test.ts +0 -93
  50. package/src/lock.ts +0 -57
  51. package/src/send-queue.test.ts +0 -207
  52. package/src/send-queue.ts +0 -200
  53. package/src/source.test.ts +0 -171
  54. package/src/source.ts +0 -93
  55. package/src/stream.test.ts +0 -167
  56. package/src/stream.ts +0 -142
  57. package/src/tool.test.ts +0 -217
  58. package/src/tool.ts +0 -148
  59. package/tsconfig.json +0 -4
  60. package/tsconfig.tsbuildinfo +0 -1
@@ -0,0 +1,15 @@
1
+ export declare class CanonicalizationError extends Error {
2
+ readonly path: readonly string[];
3
+ constructor(message: string, path: readonly string[]);
4
+ }
5
+ /**
6
+ * Produce stable bytes for a value tree. The output is the UTF-8
7
+ * encoded form of a canonical JSON document with sorted object keys,
8
+ * NFC-normalized strings, and no whitespace. Throws
9
+ * `CanonicalizationError` on any non-JSON value or cycle.
10
+ *
11
+ * Equality of two outputs implies equality of the canonical structural
12
+ * form of the inputs; consumers may safely hash the output to compare
13
+ * value identity across local-dev and production bundles.
14
+ */
15
+ export declare function canonicalizeForHash(value: unknown): Uint8Array;
@@ -0,0 +1,160 @@
1
+ // Deterministic JSON serialization for deploy-hash inputs.
2
+ //
3
+ // `canonicalizeForHash` produces stable bytes from a value tree that
4
+ // participates in the deploy hash (notably `DirectorRef.config` and the
5
+ // `AgentDefinition` envelope). The output is the encoded form of a
6
+ // canonical JSON document: object keys NFC-normalized then sorted,
7
+ // strings normalized to NFC, no whitespace. Non-JSON values (Date, Map,
8
+ // Set, function, undefined, symbol, NaN, +/-Infinity) are rejected so
9
+ // the hash cannot silently absorb a value the JSON receiver could not
10
+ // reproduce.
11
+ //
12
+ // The implementation builds a normalized JS value tree first, then
13
+ // `JSON.stringify`s it. The intermediate tree lets the cycle check and
14
+ // type rejections share a single recursive walk.
15
+ //
16
+ // Key-ordering caveat. The walk sorts NFC-normalized keys
17
+ // lexicographically before assigning them into the intermediate plain
18
+ // object. `JSON.stringify` then walks the object's own keys in the
19
+ // engine's iteration order, which per ECMA-262
20
+ // (OrdinaryOwnPropertyKeys) lists integer-indexed string keys first in
21
+ // ascending numeric order, then the remaining keys in insertion order.
22
+ // For purely string-keyed maps the emitted bytes follow the algorithm's
23
+ // lex sort; for integer-keyed maps (string keys like "1", "2", "10")
24
+ // the engine re-orders the integer prefix numerically, so the emitted
25
+ // bytes do not match a strict lex sort of the same keys
26
+ // ("1","10","2"). The behavior is deterministic across every JS engine
27
+ // that implements OrdinaryOwnPropertyKeys (i.e. every engine since
28
+ // ES2020), so deploy-hash equality across producers is preserved. A
29
+ // future engine that changed this rule would change the canonical
30
+ // bytes; if that becomes a concern, replace `JSON.stringify` with a
31
+ // hand-rolled emitter that walks the sorted-key list directly.
32
+ const encoder = new TextEncoder();
33
+ export class CanonicalizationError extends Error {
34
+ path;
35
+ constructor(message, path) {
36
+ super(path.length === 0
37
+ ? message
38
+ : `${message} (at ${path.length === 1 ? path[0] : path.join(".")})`);
39
+ this.name = "CanonicalizationError";
40
+ this.path = path;
41
+ }
42
+ }
43
+ function normalize(value, path, seen) {
44
+ if (value === null)
45
+ return null;
46
+ if (typeof value === "boolean")
47
+ return value;
48
+ if (typeof value === "string") {
49
+ return value.normalize("NFC");
50
+ }
51
+ if (typeof value === "number") {
52
+ if (!Number.isFinite(value)) {
53
+ throw new CanonicalizationError(`non-finite number (${String(value)}) is not valid JSON`, path);
54
+ }
55
+ return value;
56
+ }
57
+ if (typeof value === "undefined") {
58
+ throw new CanonicalizationError("undefined is not valid JSON", path);
59
+ }
60
+ if (typeof value === "symbol") {
61
+ throw new CanonicalizationError("symbol is not valid JSON", path);
62
+ }
63
+ if (typeof value === "function") {
64
+ throw new CanonicalizationError("function is not valid JSON", path);
65
+ }
66
+ if (typeof value === "bigint") {
67
+ throw new CanonicalizationError("bigint is not valid JSON", path);
68
+ }
69
+ // Objects: arrays, plain records, or rejected built-ins. After the
70
+ // primitive checks above, the only remaining narrowed type is
71
+ // `object`.
72
+ const obj = value;
73
+ if (seen.has(obj)) {
74
+ throw new CanonicalizationError("cycle detected", path);
75
+ }
76
+ seen.add(obj);
77
+ try {
78
+ if (Array.isArray(obj)) {
79
+ const out = [];
80
+ for (let i = 0; i < obj.length; i++) {
81
+ out.push(normalize(obj[i], [...path, `[${String(i)}]`], seen));
82
+ }
83
+ return out;
84
+ }
85
+ if (obj instanceof Date ||
86
+ obj instanceof Map ||
87
+ obj instanceof Set ||
88
+ obj instanceof RegExp ||
89
+ obj instanceof Promise ||
90
+ obj instanceof Error ||
91
+ obj instanceof ArrayBuffer ||
92
+ ArrayBuffer.isView(obj)) {
93
+ throw new CanonicalizationError(`${obj.constructor.name} is not valid JSON`, path);
94
+ }
95
+ const proto = Object.getPrototypeOf(obj);
96
+ if (proto !== Object.prototype && proto !== null) {
97
+ let protoName = "unknown";
98
+ if (typeof proto === "object" &&
99
+ proto !== null &&
100
+ "constructor" in proto &&
101
+ typeof proto.constructor === "function") {
102
+ protoName = proto.constructor.name;
103
+ }
104
+ throw new CanonicalizationError(`non-plain object (prototype ${protoName}) is not valid JSON`, path);
105
+ }
106
+ // After the proto check, `obj` is a plain Record<string, unknown>.
107
+ // Index it through a generic record type to drop symbol keys (which
108
+ // Object.keys also drops).
109
+ const record = Object.fromEntries(Object.entries(obj));
110
+ // Normalize keys to NFC before sorting and before indexing the
111
+ // output. Two failure modes ride on this ordering: (a) if two
112
+ // distinct raw keys normalize to the same NFC form, silently
113
+ // overwriting one with the other would drop data and the deploy
114
+ // hash would no longer be a faithful function of the input; (b)
115
+ // sorting raw keys and then normalizing produces an output key
116
+ // order that is not the canonical NFC-sorted order, so two
117
+ // producers (one pre-normalizing, one not) would hash the same
118
+ // logical value to different bytes. Normalize first, raise on any
119
+ // NFC collision, then sort.
120
+ const byNFC = new Map();
121
+ for (const rawKey of Object.keys(record)) {
122
+ const nfcKey = rawKey.normalize("NFC");
123
+ const existing = byNFC.get(nfcKey);
124
+ if (existing !== undefined && existing !== rawKey) {
125
+ throw new CanonicalizationError(`keys ${JSON.stringify(existing)} and ${JSON.stringify(rawKey)} ` +
126
+ `NFC-normalize to the same value (${JSON.stringify(nfcKey)})`, path);
127
+ }
128
+ byNFC.set(nfcKey, rawKey);
129
+ }
130
+ const nfcKeys = [...byNFC.keys()].sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
131
+ const out = {};
132
+ for (const nfcKey of nfcKeys) {
133
+ const rawKey = byNFC.get(nfcKey);
134
+ if (rawKey === undefined)
135
+ continue;
136
+ out[nfcKey] = normalize(record[rawKey], [...path, rawKey], seen);
137
+ }
138
+ return out;
139
+ }
140
+ finally {
141
+ seen.delete(obj);
142
+ }
143
+ }
144
+ /**
145
+ * Produce stable bytes for a value tree. The output is the UTF-8
146
+ * encoded form of a canonical JSON document with sorted object keys,
147
+ * NFC-normalized strings, and no whitespace. Throws
148
+ * `CanonicalizationError` on any non-JSON value or cycle.
149
+ *
150
+ * Equality of two outputs implies equality of the canonical structural
151
+ * form of the inputs; consumers may safely hash the output to compare
152
+ * value identity across local-dev and production bundles.
153
+ */
154
+ export function canonicalizeForHash(value) {
155
+ const normalized = normalize(value, [], new WeakSet());
156
+ // JSON.stringify with no replacer and no space arg produces the
157
+ // canonical form modulo key ordering, which `normalize` has already
158
+ // resolved by constructing plain records with sorted keys.
159
+ return encoder.encode(JSON.stringify(normalized));
160
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Config the default director accepts via `defineDirector.build`. The
3
+ * shape mirrors `DefaultDirectorPolicy` from `@intx/inference` modulo
4
+ * fields that are not yet exposed at the author-facing surface (the
5
+ * `afterInferenceDone` hook is a function and cannot canonicalize, so
6
+ * it stays off the public ref shape).
7
+ */
8
+ export interface DefaultDirectorConfig {
9
+ mode?: "conversational" | "reactive";
10
+ }
11
+ /**
12
+ * The default director factory the agent harness registers. The id is
13
+ * `@intx/agent/default`.
14
+ */
15
+ export declare const defaultDirectorFactory: import("./director-types.js").AnnotatedDirectorFactory<unknown, import("./env.js").BaseEnv>;
16
+ /**
17
+ * Convenience constructor for a `DirectorRef` referencing the default
18
+ * director with the supplied config (or `{}` for "no overrides").
19
+ *
20
+ * The registry's `buildDefaultRef()` constructs the same ref shape; this
21
+ * export exists so author-defined `AgentDefinition` values can name the
22
+ * default director explicitly when they want to pass non-default config.
23
+ */
24
+ export declare const buildDefaultDirectorRef: (config: DefaultDirectorConfig) => import("./director-types.js").DirectorRef<DefaultDirectorConfig>;
@@ -0,0 +1,45 @@
1
+ // Built-in default director, packaged through the new env-DI surface.
2
+ //
3
+ // `defaultDirectorFactory` is the `AnnotatedDirectorFactory` the agent
4
+ // harness registers under id `@intx/agent/default`. It is the canonical
5
+ // entry point for callers that do not author their own directors.
6
+ //
7
+ // The factory delegates to `@intx/inference`'s `createDefaultDirector`,
8
+ // which is already a `ReactorDirector`. The registry's director shape
9
+ // is `ReactorDirector` directly (see director-types.ts); no
10
+ // translation layer is involved.
11
+ //
12
+ // Configuration: `DefaultDirectorConfig` maps the existing
13
+ // `DefaultDirectorPolicy` fields the factory accepts. The arktype
14
+ // schema validates incoming config from `defineDirector.build(config)`.
15
+ import { type } from "arktype";
16
+ import { createDefaultDirector, } from "@intx/inference";
17
+ import { defineDirector } from "./director.js";
18
+ const DefaultDirectorConfigSchema = type({
19
+ "mode?": '"conversational" | "reactive"',
20
+ });
21
+ const defined = defineDirector({
22
+ id: "@intx/agent/default",
23
+ configSchema: DefaultDirectorConfigSchema,
24
+ factory: (config, _env, agent) => {
25
+ const policy = {};
26
+ if (config.mode !== undefined) {
27
+ policy.mode = config.mode;
28
+ }
29
+ return createDefaultDirector(agent.systemPrompt, [...agent.toolDefinitions], policy);
30
+ },
31
+ });
32
+ /**
33
+ * The default director factory the agent harness registers. The id is
34
+ * `@intx/agent/default`.
35
+ */
36
+ export const defaultDirectorFactory = defined.factory;
37
+ /**
38
+ * Convenience constructor for a `DirectorRef` referencing the default
39
+ * director with the supplied config (or `{}` for "no overrides").
40
+ *
41
+ * The registry's `buildDefaultRef()` constructs the same ref shape; this
42
+ * export exists so author-defined `AgentDefinition` values can name the
43
+ * default director explicitly when they want to pass non-default config.
44
+ */
45
+ export const buildDefaultDirectorRef = defined.build;
@@ -0,0 +1,139 @@
1
+ import type { ToolPackagePin } from "@intx/types/tool-packages";
2
+ import type { AnnotatedToolFactory } from "./tool.js";
3
+ import type { BaseEnv } from "./env.js";
4
+ import type { DirectorRef } from "./director-types.js";
5
+ /**
6
+ * Per-source preference describing which providers and models this
7
+ * agent prefers, in order. The field is **hash-only** -- it
8
+ * participates in deploy-time hashing and grant computation but is
9
+ * not consulted for runtime source selection. The agent uses
10
+ * `env.source` for the active inference call. Downstream tooling that
11
+ * resolves preferences against available credentials sets the active
12
+ * `env.source`. Reordering or mutating this field changes the deploy
13
+ * hash; consumers must treat it as immutable across a deployment.
14
+ */
15
+ export interface InferencePreference {
16
+ readonly provider: string;
17
+ readonly model: string;
18
+ readonly parameters?: Readonly<Record<string, unknown>>;
19
+ }
20
+ /**
21
+ * The portable, hashable shape of an agent.
22
+ *
23
+ * `EnvReq` is the intersection of every contributor's env requirements
24
+ * (`BaseEnv` plus whatever each tool factory and the director declare
25
+ * via `requires`). Use `EnvRequiredByAll` (below) to compute it from a
26
+ * factory tuple; `defineAgent` does this for you.
27
+ *
28
+ * Note on the type-level enforcement: a single `ToolFactory<any>` in
29
+ * `toolFactories` collapses `EnvRequiredByAll` to `any`, silently
30
+ * stripping the type-level requirements of every other factory in the
31
+ * same definition. The runtime `validateEnv` (presence-only) is the
32
+ * load-bearing safety guarantee; the type level is best-effort
33
+ * guidance for authors who type their factories tightly.
34
+ */
35
+ export interface AgentDefinition<EnvReq extends BaseEnv = BaseEnv> {
36
+ readonly id: string;
37
+ readonly description?: string;
38
+ readonly systemPrompt: string;
39
+ readonly director?: DirectorRef;
40
+ readonly toolFactories: readonly AnnotatedToolFactory<EnvReq>[];
41
+ /**
42
+ * Tool-package names whose `definePlugin` factories this agent uses
43
+ * (`["@intx/tools-lsp"]`). Unlike a tool factory -- which the agent
44
+ * imports and places in `toolFactories`, so it is agent-visible -- a
45
+ * plugin package contributes NO agent-visible factory: its plugin
46
+ * factory reaches the agent only through `env.plugins`, wired by the
47
+ * host. This explicit per-agent list is therefore the only way per-step
48
+ * plugin scoping and the plugin's contributed tool grants can be known
49
+ * from the definition alone. The field is part of the hashed wire
50
+ * surface (the live->inert projector carries it), so a tampered plugin
51
+ * set fails re-verify. Absent when the agent uses no plugins.
52
+ */
53
+ readonly plugins?: readonly string[];
54
+ readonly capabilities: readonly string[];
55
+ readonly inference: {
56
+ readonly sources: readonly InferencePreference[];
57
+ };
58
+ /**
59
+ * Free-form metadata the agent itself does not consume. The agent's
60
+ * runtime does not read this field on any path; it is a passthrough
61
+ * surface for downstream consumers -- classifiers grouping
62
+ * definitions, audit consumers filtering on deployment cohort,
63
+ * tooling rendering a definition catalog. The shape is
64
+ * `Record<string, string>` deliberately rather than a richer type:
65
+ * tags are human/operator-supplied identifiers, not structured
66
+ * data, and any consumer that wants to interpret a tag's content
67
+ * does so by name agreement with the producer rather than by
68
+ * shape contract. Producers that need structured per-definition
69
+ * data should add their own field on a subtype rather than nesting
70
+ * encoded JSON in a tag value.
71
+ */
72
+ readonly tags?: Readonly<Record<string, string>>;
73
+ /**
74
+ * Tool-package pins the sidecar materializes for this agent, carried on the
75
+ * definition so a folded workflow asset is self-contained rather than
76
+ * depending on pins supplied only at deploy time. Plain-data mirror of the
77
+ * pins the deploy-tree tool channel consumes.
78
+ */
79
+ readonly toolPackagePins?: readonly ToolPackagePin[];
80
+ }
81
+ type UnionToIntersection<U> = (U extends unknown ? (k: U) => void : never) extends (k: infer I) => void ? I : never;
82
+ type EnvRequiredBy<F> = F extends AnnotatedToolFactory<infer E> ? E : never;
83
+ /**
84
+ * Intersection of env requirements across a tuple of annotated tool
85
+ * factories, narrowed to extend `BaseEnv`.
86
+ *
87
+ * Function parameters are contravariant under TypeScript's strict mode,
88
+ * so the tuple's element constraint must be `AnnotatedToolFactory<any>`
89
+ * rather than `AnnotatedToolFactory<BaseEnv>`. A factory typed
90
+ * `AnnotatedToolFactory<MailEnv>` is **not** assignable to
91
+ * `AnnotatedToolFactory<BaseEnv>` (it accepts only `MailEnv`, not every
92
+ * `BaseEnv`), but it is assignable to `AnnotatedToolFactory<any>`. The
93
+ * runtime `validateEnv` is the load-bearing safety guarantee; the
94
+ * type level is best-effort guidance.
95
+ *
96
+ * **Author-facing footgun.** A single `AnnotatedToolFactory<any>` in
97
+ * the tuple collapses the intersection to `any` and silently strips
98
+ * the type-level env requirements of every other factory in the same
99
+ * `defineAgent` call. Third-party factories typed `<any>` -- whether
100
+ * by oversight or by deliberate escape -- erase the compile-time
101
+ * check that the env shape covers their declared `requires`. The
102
+ * runtime `validateEnv` will still blame the missing keys at
103
+ * construction, but the author loses the editor-time feedback that
104
+ * makes env-DI cheap to use. When importing third-party tool
105
+ * factories, prefer ones whose env shape is explicit, and treat an
106
+ * `<any>` factory the same way you would treat an `any`-typed
107
+ * variable elsewhere in the codebase: an opt-out of the type system,
108
+ * not a default.
109
+ *
110
+ * See the note on `AgentDefinition` above.
111
+ */
112
+ export type EnvRequiredByAll<Factories extends readonly AnnotatedToolFactory<any>[]> = UnionToIntersection<EnvRequiredBy<Factories[number]>> & BaseEnv;
113
+ /**
114
+ * Configuration accepted by `defineAgent`. Mirrors `AgentDefinition`
115
+ * but takes `tools` as the input field name (matching the spec's
116
+ * authoring-time shape) and infers `EnvReq` from the supplied
117
+ * factories.
118
+ */
119
+ export interface DefineAgentConfig<Factories extends readonly AnnotatedToolFactory<any>[]> {
120
+ readonly id: string;
121
+ readonly description?: string;
122
+ readonly systemPrompt: string;
123
+ readonly director?: DirectorRef;
124
+ readonly tools: Factories;
125
+ /** Plugin-package names this agent uses; see `AgentDefinition.plugins`. */
126
+ readonly plugins?: readonly string[];
127
+ readonly capabilities: readonly string[];
128
+ readonly inference: {
129
+ readonly sources: readonly InferencePreference[];
130
+ };
131
+ readonly tags?: Readonly<Record<string, string>>;
132
+ }
133
+ /**
134
+ * Construct an `AgentDefinition` from authoring-time config. The
135
+ * returned definition has its env requirement computed as the
136
+ * intersection of every supplied factory's `EnvReq`.
137
+ */
138
+ export declare function defineAgent<const Factories extends readonly AnnotatedToolFactory<any>[]>(config: DefineAgentConfig<Factories>): AgentDefinition<EnvRequiredByAll<Factories>>;
139
+ export {};
@@ -0,0 +1,40 @@
1
+ // `AgentDefinition` -- the portable, hashable data that names what an
2
+ // agent is. Together with `defineAgent`, it produces a deploy unit:
3
+ // hashing the definition yields a deploy hash, walking the definition
4
+ // surfaces the capability and credential grants downstream tooling
5
+ // can require approval for at deploy time, and resolving it against a
6
+ // runtime env (`createAgent(def, env)`) yields a running Agent.
7
+ //
8
+ // `AgentDefinition` deliberately holds no instance state. It is data
9
+ // passed around by callers and consumed by downstream tooling (deploy
10
+ // scaffolding, metadata registries, admin surfaces). Everything that
11
+ // is per-instance (the active inference source, the storage handle,
12
+ // the authorize callback, the audit sink, the directors registry)
13
+ // lives in the env supplied at `createAgent` time.
14
+ /**
15
+ * Construct an `AgentDefinition` from authoring-time config. The
16
+ * returned definition has its env requirement computed as the
17
+ * intersection of every supplied factory's `EnvReq`.
18
+ */
19
+ export function defineAgent(config) {
20
+ // The widened factory tuple is structurally identical; the cast
21
+ // adjusts the type's `EnvReq` parameter to match the inferred
22
+ // intersection.
23
+ const toolFactories =
24
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- adjusting EnvReq parameter; structurally identical
25
+ config.tools;
26
+ const definition = {
27
+ id: config.id,
28
+ systemPrompt: config.systemPrompt,
29
+ toolFactories,
30
+ capabilities: config.capabilities,
31
+ inference: config.inference,
32
+ ...(config.plugins !== undefined ? { plugins: config.plugins } : {}),
33
+ ...(config.description !== undefined
34
+ ? { description: config.description }
35
+ : {}),
36
+ ...(config.director !== undefined ? { director: config.director } : {}),
37
+ ...(config.tags !== undefined ? { tags: config.tags } : {}),
38
+ };
39
+ return definition;
40
+ }
@@ -0,0 +1,47 @@
1
+ import type { AnnotatedDirectorFactory, DirectorRegistry } from "./director-types.js";
2
+ import type { BaseEnv } from "./env.js";
3
+ /**
4
+ * Erased annotated-factory shape the registry stores. `Config` is
5
+ * widened to `unknown` so factories with different configuration types
6
+ * can coexist in the same registry without contravariant assignment
7
+ * failures.
8
+ */
9
+ type RegisteredFactory = AnnotatedDirectorFactory<unknown, BaseEnv>;
10
+ /**
11
+ * Thrown by `DirectorRegistry.resolve` when the supplied ref names an
12
+ * id the registry does not contain. The error is named separately from
13
+ * `Error` so callers (specifically `validateEnv`) can distinguish an
14
+ * unknown-id failure from other runtime faults a custom `directors`
15
+ * implementation might raise. Custom `DirectorRegistry` implementations
16
+ * are expected to throw `UnknownDirectorIdError` on the unknown-id
17
+ * path; anything else propagates as a real failure.
18
+ */
19
+ export declare class UnknownDirectorIdError extends Error {
20
+ readonly directorId: string;
21
+ constructor(directorId: string);
22
+ }
23
+ /**
24
+ * Build a director registry from a flat list of factories. Throws
25
+ * `Error` at construction on duplicate ids or when `defaultId` is not
26
+ * present in `factories`.
27
+ */
28
+ export declare function createDirectorRegistry(opts: {
29
+ readonly factories: readonly RegisteredFactory[];
30
+ readonly defaultId: string;
31
+ }): DirectorRegistry;
32
+ /**
33
+ * The canonical built-ins-only registry. Convenience for callers that
34
+ * do not ship their own director factories. Callers with custom
35
+ * factories pass them into `createDirectorRegistry` directly.
36
+ */
37
+ export declare function createDefaultDirectorRegistry(): DirectorRegistry;
38
+ /**
39
+ * Build the director registry for a workflow closure: the built-in default
40
+ * plus the closure's own `defineDirector` factories. A closure that ships no
41
+ * directors passes `loaded: []` and composes to `[defaultDirectorFactory]` --
42
+ * identical to `createDefaultDirectorRegistry`. A closure director whose id
43
+ * shadows the built-in (or another loaded director) throws at construction,
44
+ * the same fail-loud `createDirectorRegistry` applies to any duplicate.
45
+ */
46
+ export declare function createWorkflowDirectorRegistry(loaded: readonly AnnotatedDirectorFactory<unknown, BaseEnv>[]): DirectorRegistry;
47
+ export {};
@@ -0,0 +1,87 @@
1
+ // Per-runtime director registry implementation.
2
+ //
3
+ // `createDirectorRegistry({ factories, defaultId })` builds a registry
4
+ // from a flat list of `AnnotatedDirectorFactory` values and a designated
5
+ // default id. Id collisions and a missing default fail at construction
6
+ // rather than first lookup. `createDefaultDirectorRegistry()` is the
7
+ // canonical built-ins-only registry the agent harness ships for callers
8
+ // that do not author their own directors.
9
+ import { defaultDirectorFactory } from "./default-director.js";
10
+ /**
11
+ * Thrown by `DirectorRegistry.resolve` when the supplied ref names an
12
+ * id the registry does not contain. The error is named separately from
13
+ * `Error` so callers (specifically `validateEnv`) can distinguish an
14
+ * unknown-id failure from other runtime faults a custom `directors`
15
+ * implementation might raise. Custom `DirectorRegistry` implementations
16
+ * are expected to throw `UnknownDirectorIdError` on the unknown-id
17
+ * path; anything else propagates as a real failure.
18
+ */
19
+ export class UnknownDirectorIdError extends Error {
20
+ directorId;
21
+ constructor(directorId) {
22
+ super(`unknown director in registry: ${directorId}`);
23
+ this.name = "UnknownDirectorIdError";
24
+ this.directorId = directorId;
25
+ }
26
+ }
27
+ /**
28
+ * Build a director registry from a flat list of factories. Throws
29
+ * `Error` at construction on duplicate ids or when `defaultId` is not
30
+ * present in `factories`.
31
+ */
32
+ export function createDirectorRegistry(opts) {
33
+ const byId = new Map();
34
+ for (const factory of opts.factories) {
35
+ if (byId.has(factory.id)) {
36
+ throw new Error(`director id collision in registry: ${factory.id}`);
37
+ }
38
+ byId.set(factory.id, factory);
39
+ }
40
+ const defaultFactory = byId.get(opts.defaultId);
41
+ if (defaultFactory === undefined) {
42
+ throw new Error(`default director ${opts.defaultId} not in registry factories`);
43
+ }
44
+ return {
45
+ resolve(ref) {
46
+ const factory = byId.get(ref.id);
47
+ if (factory === undefined) {
48
+ throw new UnknownDirectorIdError(ref.id);
49
+ }
50
+ return factory;
51
+ },
52
+ defaultFactory() {
53
+ return defaultFactory;
54
+ },
55
+ buildDefaultRef() {
56
+ // Construct fresh each call. There is no module-load constant for
57
+ // the default ref; the spec is explicit about avoiding implicit
58
+ // module-load side effects in the director surface.
59
+ return { id: defaultFactory.id, config: {} };
60
+ },
61
+ };
62
+ }
63
+ /**
64
+ * The canonical built-ins-only registry. Convenience for callers that
65
+ * do not ship their own director factories. Callers with custom
66
+ * factories pass them into `createDirectorRegistry` directly.
67
+ */
68
+ export function createDefaultDirectorRegistry() {
69
+ return createDirectorRegistry({
70
+ factories: [defaultDirectorFactory],
71
+ defaultId: defaultDirectorFactory.id,
72
+ });
73
+ }
74
+ /**
75
+ * Build the director registry for a workflow closure: the built-in default
76
+ * plus the closure's own `defineDirector` factories. A closure that ships no
77
+ * directors passes `loaded: []` and composes to `[defaultDirectorFactory]` --
78
+ * identical to `createDefaultDirectorRegistry`. A closure director whose id
79
+ * shadows the built-in (or another loaded director) throws at construction,
80
+ * the same fail-loud `createDirectorRegistry` applies to any duplicate.
81
+ */
82
+ export function createWorkflowDirectorRegistry(loaded) {
83
+ return createDirectorRegistry({
84
+ factories: [defaultDirectorFactory, ...loaded],
85
+ defaultId: defaultDirectorFactory.id,
86
+ });
87
+ }
@@ -0,0 +1,80 @@
1
+ import type { ReactorDirector, ToolDefinition } from "@intx/types/runtime";
2
+ import type { BaseEnv } from "./env.js";
3
+ /**
4
+ * Agent-instance properties a director factory needs at construction.
5
+ * Sourced from the `AgentDefinition` the agent harness is instantiating:
6
+ * the system prompt and the resolved tool definitions the model will
7
+ * see. Held separately from `BaseEnv` because these values are derived
8
+ * from the agent definition, not supplied by the caller as runtime env.
9
+ *
10
+ * `compactorNames` is the exception to the "derived from definition"
11
+ * shape: it lists the names the deployer registered on `env.compactors`
12
+ * and is surfaced here so the director picks a known name to pass to
13
+ * `caps.compact(name, reason)`. The list is empty when the deployer
14
+ * omits the env field. The shape mirrors `toolDefinitions` for the
15
+ * same reason: a director that emits an action keyed by name benefits
16
+ * from learning the registered names at construction rather than
17
+ * trusting the deployer by convention.
18
+ */
19
+ export interface DirectorAgentContext {
20
+ readonly systemPrompt: string;
21
+ readonly toolDefinitions: readonly ToolDefinition[];
22
+ readonly compactorNames: readonly string[];
23
+ }
24
+ /**
25
+ * Reference to a director shipped with a bundle. The package-namespaced
26
+ * id is the identity. The bundle that ships the director includes the
27
+ * factory that maps the id back to runtime code; same bundle = same
28
+ * factory, so no separate `factoryHash` is needed.
29
+ *
30
+ * `config` is canonical-JSON-serializable so deploy-hash consumers can
31
+ * stably hash the ref via `canonicalizeForHash(ref.config)`.
32
+ */
33
+ export interface DirectorRef<Config = unknown> {
34
+ readonly id: string;
35
+ readonly config: Config;
36
+ }
37
+ /**
38
+ * Factory function shape that produces a `ReactorDirector` from a
39
+ * validated config, the agent's runtime env, and the agent-instance
40
+ * context (`DirectorAgentContext`: system prompt, resolved tool
41
+ * definitions, registered compactor names). The implementation lives
42
+ * in the same bundle as the agent definition; the registry resolves it
43
+ * from `DirectorRef.id`.
44
+ */
45
+ export type DirectorFactory<Config = unknown, EnvReq extends BaseEnv = BaseEnv> = (config: Config, env: EnvReq, agent: DirectorAgentContext) => ReactorDirector;
46
+ /**
47
+ * Arktype validator for a director's config. Stored as `unknown` at the
48
+ * type level so this module does not have to import arktype; the
49
+ * concrete `defineDirector` runtime validates it.
50
+ */
51
+ export type DirectorConfigSchema = unknown;
52
+ /**
53
+ * Runtime metadata attached to a `DirectorFactory` by `defineDirector`.
54
+ * The factory carries its package-namespaced id, its env-key
55
+ * requirements, and the arktype schema that validates its config.
56
+ */
57
+ export interface DirectorFactoryMeta {
58
+ readonly id: string;
59
+ readonly requires: readonly string[];
60
+ readonly configSchema: DirectorConfigSchema;
61
+ }
62
+ /**
63
+ * A director factory with its runtime metadata attached. The registry
64
+ * stores these; `defineDirector` produces them.
65
+ */
66
+ export type AnnotatedDirectorFactory<Config = unknown, EnvReq extends BaseEnv = BaseEnv> = DirectorFactory<Config, EnvReq> & DirectorFactoryMeta;
67
+ /**
68
+ * Per-runtime director registry. Populated explicitly at startup from
69
+ * the bundle's `defineDirector` calls plus built-ins from `@intx/agent`.
70
+ * No module-load side effects.
71
+ *
72
+ * `resolve` returns the factory for a given ref; `defaultFactory` is the
73
+ * canonical built-in; `buildDefaultRef` constructs the default ref on
74
+ * demand (each call constructs a fresh object, no module-load constant).
75
+ */
76
+ export interface DirectorRegistry {
77
+ resolve(ref: DirectorRef): AnnotatedDirectorFactory;
78
+ defaultFactory(): AnnotatedDirectorFactory;
79
+ buildDefaultRef(): DirectorRef;
80
+ }
@@ -0,0 +1,13 @@
1
+ // Type-only surface for the director registry.
2
+ //
3
+ // The runtime implementations -- `createDirectorRegistry`,
4
+ // `defineDirector`, and the built-in default factory -- live in
5
+ // adjacent files. This module holds only the type-level shapes the env
6
+ // contract (`BaseEnv`) depends on, so the env primitives can typecheck
7
+ // independently.
8
+ //
9
+ // `DirectorFactory` returns a `ReactorDirector` directly. The only
10
+ // director that flows through the registry today is the built-in
11
+ // default, which is already `ReactorDirector`-shaped; no translation
12
+ // layer is needed.
13
+ export {};