@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,70 @@
1
+ import type { AnnotatedDirectorFactory, DirectorConfigSchema, DirectorFactory, DirectorRef } from "./director-types.js";
2
+ import type { BaseEnv } from "./env.js";
3
+ /**
4
+ * Result of `defineDirector`. The `factory` half is what the registry
5
+ * stores; the `build` half is what the agent-definition author calls
6
+ * to construct a `DirectorRef` referencing this director.
7
+ *
8
+ * The `factory` field is **type-erased** in its `Config` parameter. The
9
+ * registry stores factories of heterogeneous config types alongside
10
+ * each other; if `factory` carried the narrow `Config`, contravariant
11
+ * function-parameter variance would prevent the assignment. The agent
12
+ * only invokes the factory with `ref.config: unknown` sourced from a
13
+ * `DirectorRef`, and the schema has already validated that config at
14
+ * `build` time, so the erasure is safe at the call site.
15
+ */
16
+ export interface DefinedDirector<Config, EnvReq extends BaseEnv = BaseEnv> {
17
+ readonly factory: AnnotatedDirectorFactory<unknown, EnvReq>;
18
+ build(config: Config): DirectorRef<Config>;
19
+ }
20
+ /**
21
+ * Define a director factory.
22
+ *
23
+ * - `id` must be package-namespaced. Bare ids throw at definition
24
+ * time.
25
+ * - `configSchema` is an arktype validator. The schema validates the
26
+ * config at `build(config)` time. Consumers that compute a deploy
27
+ * hash over the ref call `canonicalizeForHash(ref.config)`
28
+ * themselves; `build` does not run that check.
29
+ * - `requires` enumerates the env keys the factory touches beyond
30
+ * `BaseEnv`. `validateEnv` checks presence at instantiation.
31
+ * - `factory(config, env, agentContext)` returns a `ReactorDirector`.
32
+ * `agentContext` carries the agent definition's resolved system
33
+ * prompt and tool definitions; the factory uses them when its
34
+ * director needs to see the model's tools or seed prompt.
35
+ *
36
+ * Two-stage construction (factory + build) lets the registry index the
37
+ * factory by id while callers stamp configs into refs as data. Same
38
+ * bundle = same factory; refs hash by id and config.
39
+ */
40
+ export declare function defineDirector<Config, EnvReq extends BaseEnv = BaseEnv>(opts: {
41
+ readonly id: string;
42
+ readonly configSchema: DirectorConfigSchema;
43
+ readonly requires?: readonly string[];
44
+ readonly factory: DirectorFactory<Config, EnvReq>;
45
+ }): DefinedDirector<Config, EnvReq>;
46
+ /**
47
+ * Run the registered config schema against a `DirectorRef.config`.
48
+ * Throws on schema rejection or on a non-callable schema.
49
+ *
50
+ * `defineDirector.build(config)` runs this at definition-construction
51
+ * time. `createAgent` runs it again at resolve time so a caller that
52
+ * hand-constructs a `DirectorRef` (the type is public; nothing forces
53
+ * refs through `build`) cannot bypass the schema check and hand a
54
+ * malformed config to the factory.
55
+ */
56
+ export declare function validateDirectorConfig(config: unknown, schema: DirectorConfigSchema): void;
57
+ /**
58
+ * Structural check for an `AnnotatedDirectorFactory` export. The shape is
59
+ * callable + `{ id: string, requires: string[], configSchema: function }`.
60
+ * The `configSchema` field is the discriminator against tool factories
61
+ * (which carry only `id` and `requires`); without it, any tool-factory
62
+ * export from a directors-entry module would be accepted as a director.
63
+ *
64
+ * Shared by the tool-package loader (`@intx/tool-packaging`) and the
65
+ * workflow-closure director loader (`@intx/workflow-host`) so both accept
66
+ * and reject exactly the same shapes -- one accept/reject rule the
67
+ * approval-time probe and the runtime cannot drift apart on. Two copies of
68
+ * "is this a valid director" would be a silent congruence hole.
69
+ */
70
+ export declare function isAnnotatedDirectorFactory(value: unknown): value is AnnotatedDirectorFactory<unknown, BaseEnv>;
@@ -0,0 +1,131 @@
1
+ // `defineDirector` -- the env-DI factory shape for author-defined
2
+ // directors.
3
+ //
4
+ // `defineDirector({ id, configSchema, requires?, factory })` returns
5
+ // `{ build, factory }`. The `factory` is an `AnnotatedDirectorFactory`
6
+ // the registry stores by id; the bundle that ships the director
7
+ // re-exports it so a caller can pass it into `createDirectorRegistry`.
8
+ // The `build(config)` constructor produces a `DirectorRef` from a
9
+ // config that the schema validates. Both halves are needed: the
10
+ // registry stores the factory, the agent definition stores the ref.
11
+ //
12
+ // The `defineDirector` runtime does not register the factory as a
13
+ // module-load side effect. Each runtime instance constructs its
14
+ // registry explicitly via `createDirectorRegistry`, listing the
15
+ // factories it wants rather than relying on import-order.
16
+ import { type } from "arktype";
17
+ import { validateNamespacedId } from "./namespace.js";
18
+ import { isAnnotatedPluginFactory } from "./tool.js";
19
+ /**
20
+ * Define a director factory.
21
+ *
22
+ * - `id` must be package-namespaced. Bare ids throw at definition
23
+ * time.
24
+ * - `configSchema` is an arktype validator. The schema validates the
25
+ * config at `build(config)` time. Consumers that compute a deploy
26
+ * hash over the ref call `canonicalizeForHash(ref.config)`
27
+ * themselves; `build` does not run that check.
28
+ * - `requires` enumerates the env keys the factory touches beyond
29
+ * `BaseEnv`. `validateEnv` checks presence at instantiation.
30
+ * - `factory(config, env, agentContext)` returns a `ReactorDirector`.
31
+ * `agentContext` carries the agent definition's resolved system
32
+ * prompt and tool definitions; the factory uses them when its
33
+ * director needs to see the model's tools or seed prompt.
34
+ *
35
+ * Two-stage construction (factory + build) lets the registry index the
36
+ * factory by id while callers stamp configs into refs as data. Same
37
+ * bundle = same factory; refs hash by id and config.
38
+ */
39
+ export function defineDirector(opts) {
40
+ validateNamespacedId(opts.id);
41
+ const requires = Object.freeze([
42
+ ...(opts.requires ?? []),
43
+ ]);
44
+ // Wrap the caller's factory rather than mutating it. A caller that
45
+ // shares a factory function across multiple `defineDirector` calls
46
+ // (e.g. registering the same factory under two ids) needs each
47
+ // annotated factory to be a distinct identity with its own metadata;
48
+ // a direct `Object.assign` on `opts.factory` would let the second
49
+ // call silently overwrite the first's annotations.
50
+ const wrapped = (config, env, agent) => opts.factory(config, env, agent);
51
+ const annotatedTyped = Object.assign(wrapped, {
52
+ id: opts.id,
53
+ requires,
54
+ configSchema: opts.configSchema,
55
+ });
56
+ // Erase the Config parameter for registry storage. The factory body
57
+ // continues to expect the narrow Config (via the closure on
58
+ // `opts.factory`); the registry just sees `(config: unknown, ...)`.
59
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- intentional contravariant erasure for heterogeneous registry storage
60
+ const annotated = annotatedTyped;
61
+ function build(config) {
62
+ validateConfig(config, opts.configSchema);
63
+ return { id: opts.id, config };
64
+ }
65
+ return { factory: annotated, build };
66
+ }
67
+ function validateConfig(config, schema) {
68
+ // The schema is typed as `unknown` at the type level so this module
69
+ // does not have to import arktype. At runtime it must be an arktype
70
+ // validator -- a callable that returns either the validated value or
71
+ // a `type.errors` instance. If the schema is not callable, treat
72
+ // that as a definition-time author error.
73
+ if (typeof schema !== "function") {
74
+ throw new Error("director configSchema must be an arktype validator (callable)");
75
+ }
76
+ const result = schema(config);
77
+ if (result instanceof type.errors) {
78
+ throw new Error(`director config validation failed: ${result.summary}`);
79
+ }
80
+ }
81
+ /**
82
+ * Run the registered config schema against a `DirectorRef.config`.
83
+ * Throws on schema rejection or on a non-callable schema.
84
+ *
85
+ * `defineDirector.build(config)` runs this at definition-construction
86
+ * time. `createAgent` runs it again at resolve time so a caller that
87
+ * hand-constructs a `DirectorRef` (the type is public; nothing forces
88
+ * refs through `build`) cannot bypass the schema check and hand a
89
+ * malformed config to the factory.
90
+ */
91
+ export function validateDirectorConfig(config, schema) {
92
+ validateConfig(config, schema);
93
+ }
94
+ /**
95
+ * Structural check for an `AnnotatedDirectorFactory` export. The shape is
96
+ * callable + `{ id: string, requires: string[], configSchema: function }`.
97
+ * The `configSchema` field is the discriminator against tool factories
98
+ * (which carry only `id` and `requires`); without it, any tool-factory
99
+ * export from a directors-entry module would be accepted as a director.
100
+ *
101
+ * Shared by the tool-package loader (`@intx/tool-packaging`) and the
102
+ * workflow-closure director loader (`@intx/workflow-host`) so both accept
103
+ * and reject exactly the same shapes -- one accept/reject rule the
104
+ * approval-time probe and the runtime cannot drift apart on. Two copies of
105
+ * "is this a valid director" would be a silent congruence hole.
106
+ */
107
+ export function isAnnotatedDirectorFactory(value) {
108
+ if (typeof value !== "function")
109
+ return false;
110
+ if (isAnnotatedPluginFactory(value))
111
+ return false;
112
+ if (!("id" in value) || !("requires" in value))
113
+ return false;
114
+ if (!("configSchema" in value))
115
+ return false;
116
+ const id = value.id;
117
+ const requires = value.requires;
118
+ const configSchema = value.configSchema;
119
+ if (typeof id !== "string")
120
+ return false;
121
+ if (!Array.isArray(requires))
122
+ return false;
123
+ if (!requires.every((r) => typeof r === "string"))
124
+ return false;
125
+ // `defineDirector` requires a callable arktype validator. A non-callable
126
+ // schema would crash later inside config validation; reject here so the
127
+ // failure surfaces at load time rather than at first config-validation.
128
+ if (typeof configSchema !== "function")
129
+ return false;
130
+ return true;
131
+ }
@@ -0,0 +1,59 @@
1
+ import type { AgentDefinition } from "./definition.js";
2
+ import type { DirectorRef, DirectorRegistry } from "./director-types.js";
3
+ import { type BaseEnv } from "./env.js";
4
+ /**
5
+ * The director ref the agent will resolve against the registry. Falls
6
+ * back to the registry's canonical default when the definition omits a
7
+ * director. Used identically by `validateEnv` and
8
+ * `getRequiredEnvKeys` so the absent-director normalization is
9
+ * consistent.
10
+ *
11
+ * The parameter is typed as `Pick<AgentDefinition<BaseEnv>, "director">`
12
+ * rather than the full `AgentDefinition<EnvReq>` because the function
13
+ * only reads `def.director`, which is invariant in `EnvReq`. The
14
+ * `Pick` is a structural supertype of every `AgentDefinition<EnvReq>`
15
+ * so every caller passes its own narrower generic without an unsafe
16
+ * cast at the call site.
17
+ */
18
+ export declare function effectiveDirectorRef(def: Pick<AgentDefinition<BaseEnv>, "director">, registry: DirectorRegistry): DirectorRef;
19
+ /**
20
+ * The result of `getRequiredEnvKeys`. `keys` is the env-key set the
21
+ * supplied definition's tools and director declare (plus the
22
+ * `BaseEnv` core keys). `unresolvedDirectorId` is `null` when the
23
+ * director resolved cleanly and the keys list is complete; non-null
24
+ * when the registry could not resolve the definition's director, in
25
+ * which case `keys` is the best partial answer (BaseEnv + tool keys
26
+ * only -- the director's `requires` could not be enumerated).
27
+ *
28
+ * `unresolvedDirectorId` is `string | null` rather than an optional
29
+ * field so the caller has to acknowledge it exists; an optional that
30
+ * resolves to `undefined` is too easy to ignore.
31
+ */
32
+ export interface RequiredEnvKeys {
33
+ readonly keys: readonly string[];
34
+ readonly unresolvedDirectorId: string | null;
35
+ }
36
+ /**
37
+ * Returns the env-key surface the supplied definition declares via
38
+ * `BaseEnv`, tool factory `requires`, and the resolved director's
39
+ * `requires`. When the registry does not contain the definition's
40
+ * director, the returned `keys` is the best partial answer (BaseEnv
41
+ * + tool keys only) and the unresolved id surfaces on
42
+ * `unresolvedDirectorId` so a single call answers both "what env
43
+ * keys must I populate?" and "did the director resolve?".
44
+ */
45
+ export declare function getRequiredEnvKeys(def: AgentDefinition<BaseEnv>, registry: DirectorRegistry): RequiredEnvKeys;
46
+ /**
47
+ * Presence-only env validation. Throws `AgentEnvError` listing every
48
+ * missing key, the contributors that declared each one, and any
49
+ * director ids the registry could not resolve.
50
+ *
51
+ * `BaseEnv` contributes its core keys; each tool factory
52
+ * contributes under the label `tool:<id>`; the director contributes
53
+ * under `director:<id>`. Multiple contributors blaming the same
54
+ * missing key collapse into a single error. Unknown director ids land
55
+ * on the error's separate `unresolvedDirectors` field rather than
56
+ * being mixed into `missing` (env keys) so consumers can distinguish
57
+ * the two failure modes.
58
+ */
59
+ export declare function validateEnv<EnvReq extends BaseEnv>(def: AgentDefinition<EnvReq>, env: EnvReq): void;
@@ -0,0 +1,180 @@
1
+ // Presence-only env validation.
2
+ //
3
+ // `validateEnv(def, env)` walks the env keys every contributor in the
4
+ // definition declared and asserts each is present and non-nullish on
5
+ // the supplied env. The check is structural-shallow: a key is "present"
6
+ // when `env[key] !== undefined && env[key] !== null`. Value shape is
7
+ // not validated -- tool factories whose env contents are structurally
8
+ // wrong are expected to fail loud at construction.
9
+ //
10
+ // `getRequiredEnvKeys(def, registry)` returns the env-key surface in
11
+ // a `RequiredEnvKeys` struct alongside an `unresolvedDirectorId` field
12
+ // that surfaces the registry's inability to resolve the definition's
13
+ // director (so a UI consumer learns both pieces of information in a
14
+ // single call). `validateEnv` walks the key set inline rather than
15
+ // delegating to this helper -- it needs per-key blame metadata that
16
+ // the flat key list does not carry -- so the two functions stay in
17
+ // sync through the shared `BASE_ENV_KEYS` constant and the
18
+ // `effectiveDirectorRef` helper below.
19
+ //
20
+ // `effectiveDirectorRef(def, registry)` is the shared helper both
21
+ // `validateEnv` and `getRequiredEnvKeys` use to normalize the
22
+ // absent-director case. Defined once so the absent-director shape
23
+ // stays consistent across callers.
24
+ import { UnknownDirectorIdError } from "./director-registry.js";
25
+ import { AgentEnvError } from "./env.js";
26
+ const BASE_ENV_KEYS = [
27
+ "sources",
28
+ "defaultSource",
29
+ "storage",
30
+ "workdir",
31
+ "audit",
32
+ "authorize",
33
+ "directors",
34
+ ];
35
+ /**
36
+ * The director ref the agent will resolve against the registry. Falls
37
+ * back to the registry's canonical default when the definition omits a
38
+ * director. Used identically by `validateEnv` and
39
+ * `getRequiredEnvKeys` so the absent-director normalization is
40
+ * consistent.
41
+ *
42
+ * The parameter is typed as `Pick<AgentDefinition<BaseEnv>, "director">`
43
+ * rather than the full `AgentDefinition<EnvReq>` because the function
44
+ * only reads `def.director`, which is invariant in `EnvReq`. The
45
+ * `Pick` is a structural supertype of every `AgentDefinition<EnvReq>`
46
+ * so every caller passes its own narrower generic without an unsafe
47
+ * cast at the call site.
48
+ */
49
+ export function effectiveDirectorRef(def, registry) {
50
+ return def.director ?? registry.buildDefaultRef();
51
+ }
52
+ /**
53
+ * Returns the env-key surface the supplied definition declares via
54
+ * `BaseEnv`, tool factory `requires`, and the resolved director's
55
+ * `requires`. When the registry does not contain the definition's
56
+ * director, the returned `keys` is the best partial answer (BaseEnv
57
+ * + tool keys only) and the unresolved id surfaces on
58
+ * `unresolvedDirectorId` so a single call answers both "what env
59
+ * keys must I populate?" and "did the director resolve?".
60
+ */
61
+ export function getRequiredEnvKeys(def, registry) {
62
+ const keys = new Set(BASE_ENV_KEYS);
63
+ for (const factory of def.toolFactories) {
64
+ for (const key of factory.requires) {
65
+ keys.add(key);
66
+ }
67
+ }
68
+ const ref = effectiveDirectorRef(def, registry);
69
+ let unresolvedDirectorId = null;
70
+ try {
71
+ const directorFactory = registry.resolve(ref);
72
+ for (const key of directorFactory.requires) {
73
+ keys.add(key);
74
+ }
75
+ }
76
+ catch (cause) {
77
+ // Same policy as `validateEnv`: only swallow the documented
78
+ // unknown-id case. Other faults from a custom registry propagate
79
+ // so the caller sees the real exception.
80
+ if (!(cause instanceof UnknownDirectorIdError))
81
+ throw cause;
82
+ unresolvedDirectorId = ref.id;
83
+ }
84
+ return Object.freeze({
85
+ keys: Object.freeze([...keys]),
86
+ unresolvedDirectorId,
87
+ });
88
+ }
89
+ /**
90
+ * Presence-only env validation. Throws `AgentEnvError` listing every
91
+ * missing key, the contributors that declared each one, and any
92
+ * director ids the registry could not resolve.
93
+ *
94
+ * `BaseEnv` contributes its core keys; each tool factory
95
+ * contributes under the label `tool:<id>`; the director contributes
96
+ * under `director:<id>`. Multiple contributors blaming the same
97
+ * missing key collapse into a single error. Unknown director ids land
98
+ * on the error's separate `unresolvedDirectors` field rather than
99
+ * being mixed into `missing` (env keys) so consumers can distinguish
100
+ * the two failure modes.
101
+ */
102
+ export function validateEnv(def, env) {
103
+ const missing = new Set();
104
+ const blame = new Set();
105
+ // Per-contributor map of the keys that contributor declared as
106
+ // missing. Built in parallel with the flat `missing` / `blame`
107
+ // sets so we can surface the contributor → key association without
108
+ // changing the flat-array shape callers already consume.
109
+ const byContributor = new Map();
110
+ const noteMissing = (key, contributor) => {
111
+ missing.add(key);
112
+ blame.add(contributor);
113
+ let bucket = byContributor.get(contributor);
114
+ if (bucket === undefined) {
115
+ bucket = new Set();
116
+ byContributor.set(contributor, bucket);
117
+ }
118
+ bucket.add(key);
119
+ };
120
+ const unresolvedDirectors = new Set();
121
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- shape-erase env to index by key without enumerating the union of generic env keys
122
+ const envRecord = env;
123
+ for (const key of BASE_ENV_KEYS) {
124
+ const value = envRecord[key];
125
+ if (value === undefined || value === null) {
126
+ noteMissing(key, "BaseEnv");
127
+ }
128
+ }
129
+ for (const factory of def.toolFactories) {
130
+ for (const key of factory.requires) {
131
+ const value = envRecord[key];
132
+ if (value === undefined || value === null) {
133
+ noteMissing(key, `tool:${factory.id}`);
134
+ }
135
+ }
136
+ }
137
+ // Director resolution can throw on unknown ids. Presence of the
138
+ // director registry itself is already covered by the BaseEnv loop
139
+ // above. If the registry is missing here, we have already recorded
140
+ // it and cannot dereference -- short-circuit on that case. If
141
+ // resolve throws (unknown director id), surface the id through
142
+ // AgentEnvError's `unresolvedDirectors` field so the caller gets a
143
+ // single uniform exception path while still being able to
144
+ // distinguish "missing env key" from "unknown director id"
145
+ // programmatically.
146
+ if (env.directors !== undefined && env.directors !== null) {
147
+ // `effectiveDirectorRef` is `Pick<AgentDefinition, "director">`-shaped
148
+ // -- every `AgentDefinition<EnvReq>` is a structural supertype of
149
+ // that pick, so no cast is needed here.
150
+ const ref = effectiveDirectorRef(def, env.directors);
151
+ try {
152
+ const directorFactory = env.directors.resolve(ref);
153
+ for (const key of directorFactory.requires) {
154
+ const value = envRecord[key];
155
+ if (value === undefined || value === null) {
156
+ noteMissing(key, `director:${directorFactory.id}`);
157
+ }
158
+ }
159
+ }
160
+ catch (cause) {
161
+ // Only catch the documented unknown-id case. Other faults from a
162
+ // custom registry (TypeError on a malformed ref, an internal
163
+ // Map failure, etc.) propagate so the caller sees the real
164
+ // exception rather than a silently-relabelled "unresolved
165
+ // director id."
166
+ if (!(cause instanceof UnknownDirectorIdError))
167
+ throw cause;
168
+ unresolvedDirectors.add(ref.id);
169
+ }
170
+ }
171
+ if (missing.size > 0 || unresolvedDirectors.size > 0) {
172
+ const frozenByContributor = new Map();
173
+ for (const [contributor, keys] of byContributor) {
174
+ frozenByContributor.set(contributor, Object.freeze([...keys]));
175
+ }
176
+ throw new AgentEnvError([...missing], [...blame], frozenByContributor, [
177
+ ...unresolvedDirectors,
178
+ ]);
179
+ }
180
+ }
package/dist/env.d.ts ADDED
@@ -0,0 +1,160 @@
1
+ import type { AuthzCallResult, Dependencies } from "@intx/inference";
2
+ import type { AuditStore, Compactor, ContextStore, InferenceSource } from "@intx/types/runtime";
3
+ import type { DirectorRegistry } from "./director-types.js";
4
+ export type { Dependencies };
5
+ /**
6
+ * Authorization callback shape. Tools call `authorize` before invoking;
7
+ * the reactor assembly's authz extension threads the call through. The
8
+ * shape matches `@intx/inference`'s `AuthzExtensionOptions.authorize`.
9
+ *
10
+ * `Ctx` parameterizes the per-call context the closure receives.
11
+ * `unknown` is the default: bare callers do not interpret it. Higher-
12
+ * layer runtimes that have a richer notion of context (the workflow
13
+ * runtime supplies `{ stepId, attempt, runId }` via `@intx/workflow`'s
14
+ * `AuthorizeContext`) construct a closure whose third arg is ignored
15
+ * and which delegates to a runtime-typed authorize with the context
16
+ * captured at closure-build time. The third arg in the public signature
17
+ * is plumbing so the inference layer can pass through whatever shape
18
+ * the caller's runtime chooses without learning that runtime's
19
+ * vocabulary.
20
+ */
21
+ export type AuthorizeFn<Ctx = unknown> = (resource: string, action: string, context: Ctx) => Promise<AuthzCallResult>;
22
+ /**
23
+ * Required base env for every agent. Tools declare additional keys via
24
+ * `defineTool({ requires })`; directors via `defineDirector({ requires })`.
25
+ *
26
+ * `audit` and `authorize` are required. `directors` is required so
27
+ * `createAgent` can resolve the agent definition's `DirectorRef` (or
28
+ * fall back to the registry's canonical default) without invoking any
29
+ * read-site fallback.
30
+ */
31
+ export interface BaseEnv {
32
+ /**
33
+ * Ordered inference sources supplied at instantiation. The agent copies
34
+ * these into its own internal source registry; the head of the priority
35
+ * order (the source whose id is `defaultSource`) starts active, and the
36
+ * tail is the failover chain. Later `setSource`/`setSources` calls mutate
37
+ * the registry's copy, not these objects.
38
+ */
39
+ sources: InferenceSource[];
40
+ /** Id of the source that starts active (the head of the priority order). */
41
+ defaultSource: string;
42
+ /** Backing context store. The caller owns its lifetime. */
43
+ storage: ContextStore;
44
+ /**
45
+ * The directory the agent treats as its singleton lock boundary.
46
+ *
47
+ * For isogit-backed storage this MUST equal the directory passed to
48
+ * `createIsogitStore`. Two agents constructed against the same
49
+ * `workdir` fail the lock; two agents constructed against differing
50
+ * `workdir` values pointing at the same on-disk storage directory
51
+ * will silently corrupt each other -- the invariant is the caller's
52
+ * to maintain.
53
+ */
54
+ workdir: string;
55
+ /** Audit sink. Required; no read-site fallback. */
56
+ audit: AuditStore;
57
+ /** Authorization callback. Required; no read-site fallback. */
58
+ authorize: AuthorizeFn;
59
+ /** Director registry. Required; no read-site fallback. */
60
+ directors: DirectorRegistry;
61
+ /**
62
+ * Compactors registered for this deployment, keyed by name. The
63
+ * director picks a registered name and emits
64
+ * `caps.compact(name, reason)`; the reactor resolves the name against
65
+ * this map and runs the compactor's `apply()` on the conversation
66
+ * turns. Registered names are surfaced to the director factory at
67
+ * construction via `agentContext.compactorNames` so the director
68
+ * picks against a known set rather than guessing by convention.
69
+ *
70
+ * Optional: omitting the field is the same shape as registering an
71
+ * empty map. A `caps.compact(name, …)` call against an absent or
72
+ * empty registry produces the reactor's existing
73
+ * "no compactor registered" fatal error.
74
+ *
75
+ * Field placement mirrors `directors`: "what's registered at this
76
+ * deployment" is an env question, not an agent-definition question.
77
+ */
78
+ compactors?: Record<string, Compactor>;
79
+ /**
80
+ * Inference dependencies (notably `fetch` and the adapter registry) for
81
+ * the reactor's underlying `runInference` call.
82
+ *
83
+ * Production callers omit this field -- `createAgent` fills it from
84
+ * `@intx/inference/providers`' `createDefaultDependencies()`, which binds
85
+ * `globalThis.fetch` and the built-in adapter registry. Pass an explicit
86
+ * `Dependencies` to override: tests supply `setupHarness().deps` from
87
+ * `@intx/inference-testing` for a deterministic stub fetch, and hosts with
88
+ * custom adapters pass a registry built via `loadAdapterRegistry`.
89
+ *
90
+ * Optional; do not require this field on the production path.
91
+ */
92
+ deps?: Dependencies;
93
+ /**
94
+ * Optional deterministic session id. Production callers omit and let
95
+ * the agent generate a fresh UUID; tests that assert on audit-record
96
+ * sessionIds supply a stable value.
97
+ */
98
+ sessionId?: string;
99
+ /**
100
+ * Override for the default 10 000-character tool-result size cap.
101
+ * Forwarded to the reactor assembly's size-cap transform.
102
+ */
103
+ sizeCapMaxChars?: number;
104
+ /**
105
+ * Maximum number of pending sends (active + queued). Beyond this,
106
+ * `send()` rejects with `SendQueueFullError`. Defaults to 16.
107
+ */
108
+ sendQueueMax?: number;
109
+ /**
110
+ * Maximum events any single `stream()` consumer may buffer. Beyond
111
+ * this, the next read on that consumer's iterator throws
112
+ * `StreamBackpressureError`; other consumers are unaffected. Defaults
113
+ * to 1024.
114
+ */
115
+ streamBufferMax?: number;
116
+ /**
117
+ * Maximum milliseconds `close()` waits for the reactor's shutdown
118
+ * sequence (audit flush, in-flight commits) before releasing the
119
+ * lock and returning. Defaults to 5000. Zero disables the wait
120
+ * (useful for tests whose reactor shutdown is intentionally blocked).
121
+ */
122
+ closeTimeoutMs?: number;
123
+ /**
124
+ * Plugin instances produced by plugin factories the host loaded
125
+ * before instantiating tool factories. Each entry is the value the
126
+ * plugin factory returned (`AnnotatedPluginFactory`'s `Result`).
127
+ *
128
+ * Tool packages that accept plugins read this field and filter for
129
+ * plugins they recognise (by structural shape or a kind marker the
130
+ * host-side packages agree on). The agent runtime delivers plugins
131
+ * without interpreting them — composition is the receiving tool
132
+ * package's responsibility.
133
+ */
134
+ plugins?: readonly unknown[];
135
+ }
136
+ /**
137
+ * Thrown by `validateEnv` when the env-shape check fails. Two failure
138
+ * modes are reported separately so consumers can react to each:
139
+ *
140
+ * - `missing` lists the env key names that were absent. `contributors`
141
+ * lists every tool / director / `BaseEnv` label that declared at
142
+ * least one missing key (`BaseEnv` is the contributor for the six
143
+ * core fields). `missingByContributor` pairs each contributor with
144
+ * the specific keys it declared as missing so consumers can render
145
+ * an error UI that tells the author which factory blamed which key
146
+ * (the flat `missing` and `contributors` arrays carry the same data
147
+ * without the join).
148
+ * - `unresolvedDirectors` lists the `DirectorRef.id`s the registry
149
+ * could not resolve (the agent definition referenced a director the
150
+ * registry does not contain). These land on a separate field rather
151
+ * than being mixed into `missing` (env-key names) so consumers can
152
+ * distinguish the two failure modes programmatically.
153
+ */
154
+ export declare class AgentEnvError extends Error {
155
+ readonly missing: readonly string[];
156
+ readonly contributors: readonly string[];
157
+ readonly missingByContributor: ReadonlyMap<string, readonly string[]>;
158
+ readonly unresolvedDirectors: readonly string[];
159
+ constructor(missing: readonly string[], contributors: readonly string[], missingByContributor?: ReadonlyMap<string, readonly string[]>, unresolvedDirectors?: readonly string[]);
160
+ }
package/dist/env.js ADDED
@@ -0,0 +1,53 @@
1
+ // The agent's runtime environment contract.
2
+ //
3
+ // `BaseEnv` is what every `createAgent(def, env)` call requires. Tools
4
+ // and directors may extend it with additional keys declared via their
5
+ // `defineTool` / `defineDirector` `requires` metadata; the runtime
6
+ // `validateEnv` (see `env-validation.ts`)
7
+ // asserts presence of every declared key before the agent constructs a
8
+ // reactor.
9
+ //
10
+ // `audit`, `authorize`, and `directors` are required fields. There are
11
+ // no read-site defaults: a caller that omits them is making an
12
+ // affirmative choice the env contract rejects. No-op implementations for
13
+ // tests and examples ship from `@intx/agent/testing`.
14
+ /**
15
+ * Thrown by `validateEnv` when the env-shape check fails. Two failure
16
+ * modes are reported separately so consumers can react to each:
17
+ *
18
+ * - `missing` lists the env key names that were absent. `contributors`
19
+ * lists every tool / director / `BaseEnv` label that declared at
20
+ * least one missing key (`BaseEnv` is the contributor for the six
21
+ * core fields). `missingByContributor` pairs each contributor with
22
+ * the specific keys it declared as missing so consumers can render
23
+ * an error UI that tells the author which factory blamed which key
24
+ * (the flat `missing` and `contributors` arrays carry the same data
25
+ * without the join).
26
+ * - `unresolvedDirectors` lists the `DirectorRef.id`s the registry
27
+ * could not resolve (the agent definition referenced a director the
28
+ * registry does not contain). These land on a separate field rather
29
+ * than being mixed into `missing` (env-key names) so consumers can
30
+ * distinguish the two failure modes programmatically.
31
+ */
32
+ export class AgentEnvError extends Error {
33
+ missing;
34
+ contributors;
35
+ missingByContributor;
36
+ unresolvedDirectors;
37
+ constructor(missing, contributors, missingByContributor = new Map(), unresolvedDirectors = []) {
38
+ const parts = [];
39
+ if (missing.length > 0) {
40
+ parts.push(`missing required keys: ${missing.join(", ")} ` +
41
+ `(required by: ${contributors.join(", ")})`);
42
+ }
43
+ if (unresolvedDirectors.length > 0) {
44
+ parts.push(`unresolved director ids: ${unresolvedDirectors.join(", ")}`);
45
+ }
46
+ super(`agent env validation failed: ${parts.join("; ")}`);
47
+ this.name = "AgentEnvError";
48
+ this.missing = missing;
49
+ this.contributors = contributors;
50
+ this.missingByContributor = missingByContributor;
51
+ this.unresolvedDirectors = unresolvedDirectors;
52
+ }
53
+ }