@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
package/dist/source.js ADDED
@@ -0,0 +1,118 @@
1
+ // Inference source registry.
2
+ //
3
+ // The agent accepts an array of pre-configured inference sources and a
4
+ // `defaultSource` id at construction. The source whose `id` matches
5
+ // `defaultSource` becomes the active source — the same object reference
6
+ // is what the reactor's assembly holds and reads lazily at each
7
+ // inference call.
8
+ //
9
+ // `setSource` mutates that shared object in place so the next inference
10
+ // call observes the new credentials, model, and bound defaults. In-flight
11
+ // calls keep using the values they read at start-of-call (the reactor
12
+ // does not refetch mid-stream); the swap is therefore safe with respect
13
+ // to torn state.
14
+ import { type } from "arktype";
15
+ import { InferenceSource as InferenceSourceValidator, applyInferenceSourceFields, } from "@intx/types/runtime";
16
+ export class InvalidInferenceSourceError extends Error {
17
+ constructor(message) {
18
+ super(message);
19
+ this.name = "InvalidInferenceSourceError";
20
+ }
21
+ }
22
+ export class SourceNotFoundError extends Error {
23
+ id;
24
+ constructor(id) {
25
+ super(`no source in sources[] has id ${id}`);
26
+ this.name = "SourceNotFoundError";
27
+ this.id = id;
28
+ }
29
+ }
30
+ function validateSources(sources) {
31
+ if (sources.length === 0) {
32
+ throw new InvalidInferenceSourceError("sources[] must be non-empty");
33
+ }
34
+ const validated = [];
35
+ const seenIds = new Set();
36
+ for (const [i, raw] of sources.entries()) {
37
+ const parsed = InferenceSourceValidator(raw);
38
+ if (parsed instanceof type.errors) {
39
+ throw new InvalidInferenceSourceError(`sources[${String(i)}]: ${parsed.summary}`);
40
+ }
41
+ if (seenIds.has(parsed.id)) {
42
+ throw new InvalidInferenceSourceError(`sources[${String(i)}]: duplicate id ${parsed.id}`);
43
+ }
44
+ seenIds.add(parsed.id);
45
+ validated.push(parsed);
46
+ }
47
+ return validated;
48
+ }
49
+ export function createSourceRegistry(opts) {
50
+ // The ordered list, the default index, and the active cursor are private
51
+ // to the registry. Only the registry mutates which source is active.
52
+ let list = validateSources(opts.sources);
53
+ let defaultIndex = indexOfDefault(list, opts.defaultSource);
54
+ let activeIndex = defaultIndex;
55
+ const active = { ...sourceAt(list, activeIndex) };
56
+ function setSource(source) {
57
+ const parsed = InferenceSourceValidator(source);
58
+ if (parsed instanceof type.errors) {
59
+ throw new InvalidInferenceSourceError(parsed.summary);
60
+ }
61
+ applyInferenceSourceFields(active, parsed);
62
+ // A hot-swap is an explicit override of the active source, possibly to a
63
+ // source that is not in the list at all. Park the cursor at the default
64
+ // so the next per-cycle resetToPreferredSource is a no-op and the
65
+ // override survives — even when a failover had moved the cursor off the
66
+ // default before the swap.
67
+ activeIndex = defaultIndex;
68
+ }
69
+ function setSources(sources, defaultSource) {
70
+ const validated = validateSources(sources);
71
+ const index = indexOfDefault(validated, defaultSource);
72
+ list = validated;
73
+ defaultIndex = index;
74
+ activeIndex = index;
75
+ applyInferenceSourceFields(active, sourceAt(list, activeIndex));
76
+ }
77
+ function failOverToNextSource() {
78
+ if (activeIndex >= list.length - 1)
79
+ return false;
80
+ activeIndex += 1;
81
+ applyInferenceSourceFields(active, sourceAt(list, activeIndex));
82
+ return true;
83
+ }
84
+ function resetToPreferredSource() {
85
+ // Only undo a failover that actually moved the cursor. When the active
86
+ // source is already the preferred one, leave `active` untouched — a
87
+ // caller may have hot-swapped it via setSource (e.g. a director rotating
88
+ // the model), and that override must survive the per-cycle reset.
89
+ if (activeIndex === defaultIndex)
90
+ return;
91
+ activeIndex = defaultIndex;
92
+ applyInferenceSourceFields(active, sourceAt(list, activeIndex));
93
+ }
94
+ return {
95
+ active,
96
+ setSource,
97
+ setSources,
98
+ failOverToNextSource,
99
+ resetToPreferredSource,
100
+ };
101
+ }
102
+ function indexOfDefault(list, defaultSource) {
103
+ const match = list.find((s) => s.id === defaultSource);
104
+ if (match === undefined) {
105
+ throw new SourceNotFoundError(defaultSource);
106
+ }
107
+ return list.indexOf(match);
108
+ }
109
+ function sourceAt(list, index) {
110
+ const source = list[index];
111
+ if (source === undefined) {
112
+ // Unreachable: callers only ever pass an in-range index. The guard
113
+ // satisfies noUncheckedIndexedAccess without a non-null assertion and
114
+ // fails loud if that invariant is ever broken.
115
+ throw new InvalidInferenceSourceError(`no source at index ${String(index)}`);
116
+ }
117
+ return source;
118
+ }
@@ -0,0 +1,16 @@
1
+ import type { ReactorEmittedEvent } from "@intx/inference";
2
+ export declare class StreamBackpressureError extends Error {
3
+ readonly maxBuffer: number;
4
+ constructor(maxBuffer: number);
5
+ }
6
+ export type StreamConsumer = {
7
+ /** Deliver an event to this consumer's buffer. */
8
+ push(event: ReactorEmittedEvent): void;
9
+ /** Cleanly terminate the iterator with `done: true`. */
10
+ close(): void;
11
+ /** True once close() or an overflow has poisoned the consumer. */
12
+ readonly closed: boolean;
13
+ /** Iterator handed back to the caller of `stream()`. */
14
+ iterator(): AsyncIterableIterator<ReactorEmittedEvent>;
15
+ };
16
+ export declare function createStreamConsumer(maxBuffer: number): StreamConsumer;
package/dist/stream.js ADDED
@@ -0,0 +1,115 @@
1
+ // Bounded per-consumer fan-out for the agent's reactor event stream.
2
+ //
3
+ // Each call to `agent.stream()` creates a fresh `StreamConsumer`. The
4
+ // agent feeds every reactor event to every consumer; consumers buffer
5
+ // independently. If a consumer falls more than `maxBuffer` events behind
6
+ // it is poisoned with `StreamBackpressureError` and its iterator throws
7
+ // on the next read — the consumer is removed but other consumers keep
8
+ // running.
9
+ //
10
+ // Loud failure matches the defensive-coding rule: silently dropping
11
+ // events would hide consumer bugs, and unbounded buffering would let a
12
+ // stalled consumer balloon the agent's memory. The cap is configurable
13
+ // via `streamBufferMax` on `BaseEnv`.
14
+ export class StreamBackpressureError extends Error {
15
+ maxBuffer;
16
+ constructor(maxBuffer) {
17
+ super(`stream consumer fell more than ${String(maxBuffer)} events behind`);
18
+ this.name = "StreamBackpressureError";
19
+ this.maxBuffer = maxBuffer;
20
+ }
21
+ }
22
+ export function createStreamConsumer(maxBuffer) {
23
+ if (maxBuffer < 1) {
24
+ throw new Error(`streamBufferMax must be >= 1, got ${String(maxBuffer)}`);
25
+ }
26
+ const buffer = [];
27
+ const waiters = [];
28
+ let overflow;
29
+ let done = false;
30
+ function settleOverflowedWaiters(err) {
31
+ while (waiters.length > 0) {
32
+ const w = waiters.shift();
33
+ if (w === undefined)
34
+ return;
35
+ w.reject(err);
36
+ }
37
+ }
38
+ function settleDoneWaiters() {
39
+ while (waiters.length > 0) {
40
+ const w = waiters.shift();
41
+ if (w === undefined)
42
+ return;
43
+ w.resolve({ value: undefined, done: true });
44
+ }
45
+ }
46
+ function push(event) {
47
+ if (done || overflow !== undefined)
48
+ return;
49
+ if (waiters.length > 0) {
50
+ const w = waiters.shift();
51
+ if (w === undefined)
52
+ return;
53
+ w.resolve({ value: event, done: false });
54
+ return;
55
+ }
56
+ if (buffer.length >= maxBuffer) {
57
+ overflow = new StreamBackpressureError(maxBuffer);
58
+ settleOverflowedWaiters(overflow);
59
+ return;
60
+ }
61
+ buffer.push(event);
62
+ }
63
+ function close() {
64
+ if (done)
65
+ return;
66
+ done = true;
67
+ settleDoneWaiters();
68
+ }
69
+ function nextResult() {
70
+ if (overflow !== undefined) {
71
+ // Drain any buffered events before throwing so the caller sees
72
+ // every event up to the overflow point.
73
+ if (buffer.length > 0) {
74
+ const ev = buffer.shift();
75
+ if (ev !== undefined) {
76
+ return Promise.resolve({ value: ev, done: false });
77
+ }
78
+ }
79
+ return Promise.reject(overflow);
80
+ }
81
+ if (buffer.length > 0) {
82
+ const ev = buffer.shift();
83
+ if (ev !== undefined) {
84
+ return Promise.resolve({ value: ev, done: false });
85
+ }
86
+ }
87
+ if (done) {
88
+ return Promise.resolve({ value: undefined, done: true });
89
+ }
90
+ return new Promise((resolve, reject) => {
91
+ waiters.push({ resolve, reject });
92
+ });
93
+ }
94
+ function iterator() {
95
+ const it = {
96
+ next: nextResult,
97
+ async return() {
98
+ close();
99
+ return { value: undefined, done: true };
100
+ },
101
+ [Symbol.asyncIterator]() {
102
+ return it;
103
+ },
104
+ };
105
+ return it;
106
+ }
107
+ return {
108
+ push,
109
+ close,
110
+ get closed() {
111
+ return done || overflow !== undefined;
112
+ },
113
+ iterator,
114
+ };
115
+ }
@@ -0,0 +1,7 @@
1
+ import type { AuditStore } from "@intx/types/runtime";
2
+ /**
3
+ * Construct a no-op AuditStore. Each call returns a fresh object so
4
+ * tests that introspect the store identity (e.g. asserting two agents
5
+ * received different stores) can do so.
6
+ */
7
+ export declare function noopAuditStore(): AuditStore;
@@ -0,0 +1,25 @@
1
+ // No-op AuditStore for tests and examples.
2
+ //
3
+ // Returns immediately for every commit; returns empty arrays for every
4
+ // load. Useful when the agent is exercised in tests that do not assert
5
+ // audit content, or in examples whose purpose is the agent surface
6
+ // rather than the audit ledger. Production callers must supply a real
7
+ // audit store.
8
+ /**
9
+ * Construct a no-op AuditStore. Each call returns a fresh object so
10
+ * tests that introspect the store identity (e.g. asserting two agents
11
+ * received different stores) can do so.
12
+ */
13
+ export function noopAuditStore() {
14
+ return {
15
+ async commitAudit(_records) {
16
+ // No-op.
17
+ },
18
+ async commitErrors(_errors) {
19
+ // No-op.
20
+ },
21
+ async loadAudit(_sessionId) {
22
+ return [];
23
+ },
24
+ };
25
+ }
@@ -0,0 +1,8 @@
1
+ import type { AuthorizeFn } from "../env.js";
2
+ /**
3
+ * Construct a permissive AuthorizeFn that allows every call. The
4
+ * returned function ignores its arguments (including the third
5
+ * per-call context parameter) and returns the same shape the
6
+ * production authz extension expects.
7
+ */
8
+ export declare function permissiveAuthorize(): AuthorizeFn;
@@ -0,0 +1,19 @@
1
+ // Permissive AuthorizeFn for tests and examples.
2
+ //
3
+ // Returns { effect: "allow" } for every call. Useful when the agent is
4
+ // exercised without grants -- the test cares about the agent surface
5
+ // rather than the authz decision. Production callers must supply a real
6
+ // authorize function tied to actual policy.
7
+ /**
8
+ * Construct a permissive AuthorizeFn that allows every call. The
9
+ * returned function ignores its arguments (including the third
10
+ * per-call context parameter) and returns the same shape the
11
+ * production authz extension expects.
12
+ */
13
+ export function permissiveAuthorize() {
14
+ return async (_resource, _action, _context) => ({
15
+ effect: "allow",
16
+ matchingGrants: [],
17
+ resolvedBy: null,
18
+ });
19
+ }
@@ -0,0 +1,2 @@
1
+ export { noopAuditStore } from "./audit-noop.js";
2
+ export { permissiveAuthorize } from "./authorize-allow.js";
@@ -0,0 +1,17 @@
1
+ // @intx/agent/testing -- no-op implementations of the env contract's
2
+ // required fields, for tests and examples.
3
+ //
4
+ // The exports here silently permit every authz decision and discard
5
+ // every audit record. They exist so test fixtures, the in-tree
6
+ // examples, and short-lived demos can satisfy `BaseEnv` without
7
+ // bringing in a real audit store or policy engine. Production
8
+ // deployments replace these with a real `AuditStore` (durably
9
+ // recording audit and error events) and a real `authorize` callback
10
+ // (gating tool calls per the deployment's policy). Importing from
11
+ // this subpath in production silently disables auditing and allows
12
+ // every tool call, which is almost never what a production caller
13
+ // actually wants -- treat the subpath the same way you would treat a
14
+ // hard-coded `() => true` permission check elsewhere in the
15
+ // codebase.
16
+ export { noopAuditStore } from "./audit-noop.js";
17
+ export { permissiveAuthorize } from "./authorize-allow.js";
package/dist/tool.d.ts ADDED
@@ -0,0 +1,238 @@
1
+ import type { GrantEffect } from "@intx/types";
2
+ import type { ToolCall, ToolDefinition, ToolResult, ToolRunner } from "@intx/types/runtime";
3
+ import type { BaseEnv } from "./env.js";
4
+ export type ToolHandler = (call: ToolCall, signal: AbortSignal) => Promise<ToolResult>;
5
+ export type StringToolHandler = (args: Record<string, unknown>, signal: AbortSignal) => Promise<string>;
6
+ export type AgentTool = {
7
+ kind: "full";
8
+ definition: ToolDefinition;
9
+ handler: ToolHandler;
10
+ } | {
11
+ kind: "string";
12
+ definition: ToolDefinition;
13
+ handler: StringToolHandler;
14
+ };
15
+ export declare function tool(args: {
16
+ definition: ToolDefinition;
17
+ handler: ToolHandler;
18
+ }): AgentTool;
19
+ export declare function stringTool(args: {
20
+ definition: ToolDefinition;
21
+ handler: StringToolHandler;
22
+ }): AgentTool;
23
+ /**
24
+ * Adapt a pre-built ToolRunner (e.g. the one returned by
25
+ * `createPosixTools`) into a list of AgentTools that can be passed to
26
+ * `createAgent({ tools })`. Each definition becomes a full-handler
27
+ * AgentTool that delegates to the runner's `run`.
28
+ *
29
+ * Use this when integrating tool packages whose public surface is a
30
+ * single ToolRunner rather than individual handlers.
31
+ */
32
+ export declare function fromToolRunner(runner: {
33
+ readonly definitions: readonly ToolDefinition[];
34
+ run: ToolRunner["run"];
35
+ }): AgentTool[];
36
+ export declare class DuplicateToolError extends Error {
37
+ readonly toolName: string;
38
+ constructor(toolName: string);
39
+ }
40
+ export type AgentToolRunner = ToolRunner & {
41
+ readonly definitions: readonly ToolDefinition[];
42
+ };
43
+ /**
44
+ * A bundle of tools constructed by an `AnnotatedToolFactory`. Exposes
45
+ * the set of tool definitions the model sees, a single dispatcher
46
+ * (`run`), and an optional disposer the caller invokes after the agent
47
+ * closes.
48
+ *
49
+ * Disposer ownership lives with the caller -- the env is the agent's
50
+ * dependency contract; the caller owns the lifetime of what it puts in
51
+ * env. The agent does not invoke `dispose` itself.
52
+ */
53
+ export interface ToolBundle {
54
+ readonly definitions: readonly ToolDefinition[];
55
+ run(call: ToolCall, signal: AbortSignal): Promise<ToolResult>;
56
+ dispose?(): Promise<void>;
57
+ }
58
+ /**
59
+ * Factory function shape -- consumes an env extending `BaseEnv` and
60
+ * produces a `ToolBundle`. The factory is invoked once per agent
61
+ * instantiation.
62
+ */
63
+ export type ToolFactory<EnvReq extends BaseEnv = BaseEnv> = (env: EnvReq) => ToolBundle;
64
+ /**
65
+ * Static, per-definition declaration a tool factory carries so callers
66
+ * (e.g. the deploy-time capability walk) can enumerate the tool names a
67
+ * factory contributes WITHOUT instantiating it. `approval` marks a tool
68
+ * as requiring per-invocation approval; it is a deliberate subset of
69
+ * GrantEffect (only "ask" is expressible here — a declaration can request
70
+ * a gate, never a pre-deny).
71
+ */
72
+ export interface ToolDeclaration {
73
+ readonly name: string;
74
+ readonly approval?: "ask";
75
+ }
76
+ /**
77
+ * Map a tool's static approval mark to the `GrantEffect` floor its
78
+ * `tool:<name>` grant carries: an `ask`-marked tool floors at `ask` (its
79
+ * invocation must clear an approval gate), every other tool floors at
80
+ * `allow`.
81
+ *
82
+ * This is the single canonical derivation of a tool's authorization floor
83
+ * from its declaration. Both the deploy-time capability walk (hub-side)
84
+ * and the per-step tool authorization (sidecar-side) route through here so
85
+ * a pinned tool loaded in the child derives the SAME floor the walk would
86
+ * have derived from an inline declaration. A divergence between the two
87
+ * sites would let a pinned `ask` tool authorize as `allow` (or vice
88
+ * versa), so the mapping lives in exactly one place.
89
+ */
90
+ export declare function toolApprovalEffect(declaration: Pick<ToolDeclaration, "approval">): GrantEffect;
91
+ /**
92
+ * Runtime metadata attached to a `ToolFactory` by `defineTool`. `id` is
93
+ * package-namespaced; `requires` enumerates env keys the factory touches
94
+ * beyond `BaseEnv`'s six core fields; `definitions` statically declares
95
+ * the tool names the factory contributes so callers can enumerate them
96
+ * without instantiating the factory.
97
+ */
98
+ export interface ToolFactoryMeta {
99
+ readonly id: string;
100
+ readonly requires: readonly string[];
101
+ readonly definitions: readonly ToolDeclaration[];
102
+ }
103
+ /**
104
+ * A tool factory carrying its runtime metadata. `defineTool` is the only
105
+ * sanctioned construction path.
106
+ *
107
+ * The intersection `ToolFactory<EnvReq> & ToolFactoryMeta` is the
108
+ * type-level surface; at runtime the meta fields are attached to the
109
+ * factory function via `Object.assign`.
110
+ */
111
+ export type AnnotatedToolFactory<EnvReq extends BaseEnv = BaseEnv> = ToolFactory<EnvReq> & ToolFactoryMeta;
112
+ /**
113
+ * Define a tool bundle factory.
114
+ *
115
+ * - `id` must be package-namespaced ("@vendor/pkg/name" or
116
+ * "pkg/name"). Bare ids throw `Error` at definition time.
117
+ * - `requires` enumerates the env keys this factory touches beyond
118
+ * `BaseEnv`'s six core fields. The runtime `validateEnv` checks
119
+ * presence; the factory itself may also fail loud at construction
120
+ * if the env contents are structurally wrong.
121
+ * - `definitions` statically declares the tool names this factory
122
+ * contributes so callers (e.g. the deploy-time capability walk) can
123
+ * enumerate them without instantiating the factory.
124
+ * - `factory(env)` returns a `ToolBundle`. Invoked once per agent
125
+ * instantiation; the bundle's lifetime is tied to that agent.
126
+ *
127
+ * The returned object is the same callable as the supplied `factory`
128
+ * with `id`, a frozen `requires` array, and a frozen `definitions`
129
+ * array attached.
130
+ */
131
+ export declare function defineTool<EnvReq extends BaseEnv = BaseEnv>(opts: {
132
+ id: string;
133
+ requires?: readonly string[];
134
+ definitions: readonly ToolDeclaration[];
135
+ factory: ToolFactory<EnvReq>;
136
+ }): AnnotatedToolFactory<EnvReq>;
137
+ /**
138
+ * A plugin contributes capabilities (extra tools, middleware, anything
139
+ * a host plugin protocol defines) without producing a `ToolBundle`
140
+ * itself. Plugins are first-class entries in an `interchange.tools`
141
+ * module alongside `AnnotatedToolFactory` exports.
142
+ *
143
+ * The shape the factory returns is host-defined: tool packages that
144
+ * accept plugins read `env.plugins` and dispatch by structural shape
145
+ * (or by an explicit kind marker the host agrees on). The agent
146
+ * runtime does not interpret plugin shapes; it only delivers them.
147
+ *
148
+ * The marker is a `Symbol.for`-registered key (PLUGIN_MARKER) so a
149
+ * duck-typed loader can separate plugins from `AnnotatedToolFactory`s
150
+ * without re-running `defineTool`/`definePlugin` against each export.
151
+ * Using a registered symbol (rather than a string key like `_plugin`)
152
+ * prevents third-party objects from accidentally satisfying the
153
+ * marker check by happening to have a property of the same name.
154
+ */
155
+ export type PluginFactory<EnvReq extends BaseEnv, Result> = (env: EnvReq) => Result;
156
+ /**
157
+ * Registered symbol that tags `AnnotatedPluginFactory` values. Exported
158
+ * so consumers that need to introspect plugin factories directly can
159
+ * read the marker without re-registering the key.
160
+ */
161
+ export declare const PLUGIN_MARKER: unique symbol;
162
+ export interface AnnotatedPluginMeta {
163
+ readonly id: string;
164
+ readonly requires: readonly string[];
165
+ /**
166
+ * Static declaration of the tool names this plugin contributes at
167
+ * runtime, so a caller can enumerate the plugin's tool grant surface
168
+ * WITHOUT instantiating it (which for a plugin like LSP would start a
169
+ * language-server subprocess). A plugin adds its tools indirectly -- it
170
+ * hands a host-defined shape to the tool package that consumes
171
+ * `env.plugins`, which then registers the plugin's tools under its own
172
+ * bundle -- so the plugin's contributed tool names are otherwise
173
+ * invisible until run time. The deploy-time capability walk reads this
174
+ * field to authorize a plugin-contributed tool the same way it
175
+ * authorizes a factory-declared tool. Empty when the plugin contributes
176
+ * no standalone tool (middleware-only plugins).
177
+ */
178
+ readonly definitions: readonly ToolDeclaration[];
179
+ readonly [PLUGIN_MARKER]: true;
180
+ }
181
+ export type AnnotatedPluginFactory<EnvReq extends BaseEnv = BaseEnv, Result = unknown> = PluginFactory<EnvReq, Result> & AnnotatedPluginMeta;
182
+ /**
183
+ * Constant on every plugin instance returned by `definePlugin`. Hosts
184
+ * that need to distinguish plugin instances from arbitrary objects
185
+ * received via `env.plugins` check this field before duck-typing on
186
+ * shape. The string value is the operative form of the contract — the
187
+ * value-side marker exists because the factory-side symbol marker
188
+ * (PLUGIN_MARKER above) is only visible to code that imported it,
189
+ * while the kind string travels through pure-JSON inspection too.
190
+ */
191
+ export declare const TOOL_PLUGIN_KIND: "tool-plugin";
192
+ /** Type-level form of the kind marker. */
193
+ export type ToolPluginKind = typeof TOOL_PLUGIN_KIND;
194
+ /**
195
+ * Predicate hosts use to confirm a value off `env.plugins` was minted
196
+ * by `definePlugin` rather than happening to satisfy a shape-based
197
+ * check. Returns true iff the value is an object carrying the
198
+ * literal `kind: "tool-plugin"` marker.
199
+ */
200
+ export declare function isToolPluginInstance(value: unknown): value is Record<string, unknown> & {
201
+ kind: ToolPluginKind;
202
+ };
203
+ /**
204
+ * Define a plugin factory. The plugin's `Result` is host-defined and
205
+ * surfaces in `env.plugins` for the host-side tool factories that
206
+ * consume it. The returned instance is tagged with
207
+ * `kind: "tool-plugin"` so hosts can identify plugin instances
208
+ * structurally without falling back to duck-typing on the result's
209
+ * own shape.
210
+ *
211
+ * `id` must be package-namespaced — same rule as `defineTool` — so
212
+ * audit provenance threads through plugins as cleanly as through
213
+ * tools.
214
+ */
215
+ export declare function definePlugin<Result extends object, EnvReq extends BaseEnv = BaseEnv>(opts: {
216
+ id: string;
217
+ requires?: readonly string[];
218
+ /**
219
+ * Static declaration of the tool names this plugin contributes at run
220
+ * time. Omit for a middleware-only plugin that adds no standalone tool.
221
+ * See `AnnotatedPluginMeta.definitions`.
222
+ */
223
+ definitions?: readonly ToolDeclaration[];
224
+ factory: PluginFactory<EnvReq, Result>;
225
+ }): AnnotatedPluginFactory<EnvReq, Result & {
226
+ kind: ToolPluginKind;
227
+ }>;
228
+ /** Type predicate distinguishing plugin factories from tool factories. */
229
+ export declare function isAnnotatedPluginFactory(value: unknown): value is AnnotatedPluginFactory;
230
+ /**
231
+ * Build a `ToolRunner` that dispatches by tool name. Throws
232
+ * `DuplicateToolError` at construction if any two tools share a name.
233
+ *
234
+ * At call time, unknown tool names and exceptions from handlers are
235
+ * converted to `ToolResult { isError: true }` so the contract on
236
+ * `ToolRunner.run` ("must not throw") is upheld.
237
+ */
238
+ export declare function createToolRunner(tools: AgentTool[]): AgentToolRunner;