@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.
- package/LICENSE +176 -0
- package/README.md +80 -5
- package/dist/agent.d.ts +116 -0
- package/dist/agent.js +682 -0
- package/dist/canonicalize.d.ts +15 -0
- package/dist/canonicalize.js +160 -0
- package/dist/default-director.d.ts +24 -0
- package/dist/default-director.js +45 -0
- package/dist/definition.d.ts +139 -0
- package/dist/definition.js +40 -0
- package/dist/director-registry.d.ts +47 -0
- package/dist/director-registry.js +87 -0
- package/dist/director-types.d.ts +80 -0
- package/dist/director-types.js +13 -0
- package/dist/director.d.ts +70 -0
- package/dist/director.js +131 -0
- package/dist/env-validation.d.ts +59 -0
- package/dist/env-validation.js +180 -0
- package/dist/env.d.ts +160 -0
- package/dist/env.js +53 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +23 -0
- package/dist/internal-fixtures/mail.d.ts +39 -0
- package/dist/internal-fixtures/mail.js +86 -0
- package/dist/internal-fixtures/planner.d.ts +19 -0
- package/dist/internal-fixtures/planner.js +49 -0
- package/dist/lock.d.ts +16 -0
- package/dist/lock.js +47 -0
- package/dist/namespace.d.ts +12 -0
- package/dist/namespace.js +39 -0
- package/dist/send-queue.d.ts +25 -0
- package/dist/send-queue.js +147 -0
- package/dist/source.d.ts +43 -0
- package/dist/source.js +118 -0
- package/dist/stream.d.ts +16 -0
- package/dist/stream.js +115 -0
- package/dist/testing/audit-noop.d.ts +7 -0
- package/dist/testing/audit-noop.js +25 -0
- package/dist/testing/authorize-allow.d.ts +8 -0
- package/dist/testing/authorize-allow.js +19 -0
- package/dist/testing/index.d.ts +2 -0
- package/dist/testing/index.js +17 -0
- package/dist/tool.d.ts +238 -0
- package/dist/tool.js +244 -0
- package/package.json +26 -7
- package/src/agent.test.ts +0 -46
- package/src/agent.ts +0 -494
- package/src/index.ts +0 -38
- package/src/lock.test.ts +0 -93
- package/src/lock.ts +0 -57
- package/src/send-queue.test.ts +0 -207
- package/src/send-queue.ts +0 -200
- package/src/source.test.ts +0 -171
- package/src/source.ts +0 -93
- package/src/stream.test.ts +0 -167
- package/src/stream.ts +0 -142
- package/src/tool.test.ts +0 -217
- package/src/tool.ts +0 -148
- package/tsconfig.json +0 -4
- 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>;
|
package/dist/director.js
ADDED
|
@@ -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
|
+
}
|