@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,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 {};
|