@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
package/dist/tool.js
ADDED
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
// Tool registration and dispatch.
|
|
2
|
+
//
|
|
3
|
+
// Two registration shapes are supported for per-tool authoring:
|
|
4
|
+
//
|
|
5
|
+
// `tool({ definition, handler })` - handler receives the full
|
|
6
|
+
// ToolCall and returns the full
|
|
7
|
+
// ToolResult. Use when the
|
|
8
|
+
// handler needs the callId or
|
|
9
|
+
// wants to set isError/detail/
|
|
10
|
+
// pendingMarker.
|
|
11
|
+
//
|
|
12
|
+
// `stringTool({ definition, handler })` - sugar for the common case of
|
|
13
|
+
// "compute a string from the
|
|
14
|
+
// parsed arguments." The callId
|
|
15
|
+
// is filled in from the
|
|
16
|
+
// surrounding ToolCall, and
|
|
17
|
+
// isError is false unless the
|
|
18
|
+
// handler throws.
|
|
19
|
+
//
|
|
20
|
+
// `createToolRunner(tools)` builds a `ToolRunner` that dispatches by tool
|
|
21
|
+
// name. Per the ToolRunner contract (packages/types/src/runtime.ts), `run`
|
|
22
|
+
// must not throw -- unknown tool names and handler exceptions are surfaced
|
|
23
|
+
// as `ToolResult` with `isError: true` so the model sees them and can
|
|
24
|
+
// recover.
|
|
25
|
+
//
|
|
26
|
+
// `defineTool({ id, requires?, factory })` is the env-DI factory shape.
|
|
27
|
+
// It produces an
|
|
28
|
+
// `AnnotatedToolFactory` whose `factory(env)` returns a `ToolBundle`
|
|
29
|
+
// exposing a set of tool definitions, a dispatcher, and an optional
|
|
30
|
+
// disposer. Bundle-style (rather than per-tool) factory shapes match
|
|
31
|
+
// the existing posix-tools and mail-tools ergonomics; a package that
|
|
32
|
+
// wants per-tool granularity wraps each tool in its own single-
|
|
33
|
+
// definition bundle.
|
|
34
|
+
import { validateNamespacedId } from "./namespace.js";
|
|
35
|
+
export function tool(args) {
|
|
36
|
+
return { kind: "full", definition: args.definition, handler: args.handler };
|
|
37
|
+
}
|
|
38
|
+
export function stringTool(args) {
|
|
39
|
+
return {
|
|
40
|
+
kind: "string",
|
|
41
|
+
definition: args.definition,
|
|
42
|
+
handler: args.handler,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Adapt a pre-built ToolRunner (e.g. the one returned by
|
|
47
|
+
* `createPosixTools`) into a list of AgentTools that can be passed to
|
|
48
|
+
* `createAgent({ tools })`. Each definition becomes a full-handler
|
|
49
|
+
* AgentTool that delegates to the runner's `run`.
|
|
50
|
+
*
|
|
51
|
+
* Use this when integrating tool packages whose public surface is a
|
|
52
|
+
* single ToolRunner rather than individual handlers.
|
|
53
|
+
*/
|
|
54
|
+
export function fromToolRunner(runner) {
|
|
55
|
+
return runner.definitions.map((definition) => ({
|
|
56
|
+
kind: "full",
|
|
57
|
+
definition,
|
|
58
|
+
handler: (call, signal) => runner.run(call, signal),
|
|
59
|
+
}));
|
|
60
|
+
}
|
|
61
|
+
export class DuplicateToolError extends Error {
|
|
62
|
+
toolName;
|
|
63
|
+
constructor(toolName) {
|
|
64
|
+
super(`duplicate tool name: ${toolName}`);
|
|
65
|
+
this.name = "DuplicateToolError";
|
|
66
|
+
this.toolName = toolName;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Map a tool's static approval mark to the `GrantEffect` floor its
|
|
71
|
+
* `tool:<name>` grant carries: an `ask`-marked tool floors at `ask` (its
|
|
72
|
+
* invocation must clear an approval gate), every other tool floors at
|
|
73
|
+
* `allow`.
|
|
74
|
+
*
|
|
75
|
+
* This is the single canonical derivation of a tool's authorization floor
|
|
76
|
+
* from its declaration. Both the deploy-time capability walk (hub-side)
|
|
77
|
+
* and the per-step tool authorization (sidecar-side) route through here so
|
|
78
|
+
* a pinned tool loaded in the child derives the SAME floor the walk would
|
|
79
|
+
* have derived from an inline declaration. A divergence between the two
|
|
80
|
+
* sites would let a pinned `ask` tool authorize as `allow` (or vice
|
|
81
|
+
* versa), so the mapping lives in exactly one place.
|
|
82
|
+
*/
|
|
83
|
+
export function toolApprovalEffect(declaration) {
|
|
84
|
+
return declaration.approval === "ask" ? "ask" : "allow";
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Define a tool bundle factory.
|
|
88
|
+
*
|
|
89
|
+
* - `id` must be package-namespaced ("@vendor/pkg/name" or
|
|
90
|
+
* "pkg/name"). Bare ids throw `Error` at definition time.
|
|
91
|
+
* - `requires` enumerates the env keys this factory touches beyond
|
|
92
|
+
* `BaseEnv`'s six core fields. The runtime `validateEnv` checks
|
|
93
|
+
* presence; the factory itself may also fail loud at construction
|
|
94
|
+
* if the env contents are structurally wrong.
|
|
95
|
+
* - `definitions` statically declares the tool names this factory
|
|
96
|
+
* contributes so callers (e.g. the deploy-time capability walk) can
|
|
97
|
+
* enumerate them without instantiating the factory.
|
|
98
|
+
* - `factory(env)` returns a `ToolBundle`. Invoked once per agent
|
|
99
|
+
* instantiation; the bundle's lifetime is tied to that agent.
|
|
100
|
+
*
|
|
101
|
+
* The returned object is the same callable as the supplied `factory`
|
|
102
|
+
* with `id`, a frozen `requires` array, and a frozen `definitions`
|
|
103
|
+
* array attached.
|
|
104
|
+
*/
|
|
105
|
+
export function defineTool(opts) {
|
|
106
|
+
validateNamespacedId(opts.id);
|
|
107
|
+
const requires = Object.freeze([
|
|
108
|
+
...(opts.requires ?? []),
|
|
109
|
+
]);
|
|
110
|
+
const definitions = Object.freeze([
|
|
111
|
+
...opts.definitions,
|
|
112
|
+
]);
|
|
113
|
+
// Wrap the caller's factory rather than mutating it. A caller that
|
|
114
|
+
// shares a factory function across multiple `defineTool` calls
|
|
115
|
+
// (e.g. registering the same constructor under two ids in different
|
|
116
|
+
// bundles) needs each `AnnotatedToolFactory` to be a distinct
|
|
117
|
+
// identity with its own metadata; a direct `Object.assign` on
|
|
118
|
+
// `opts.factory` would let the second call silently overwrite the
|
|
119
|
+
// first's annotations.
|
|
120
|
+
const wrapped = (env) => opts.factory(env);
|
|
121
|
+
return Object.assign(wrapped, {
|
|
122
|
+
id: opts.id,
|
|
123
|
+
requires,
|
|
124
|
+
definitions,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Registered symbol that tags `AnnotatedPluginFactory` values. Exported
|
|
129
|
+
* so consumers that need to introspect plugin factories directly can
|
|
130
|
+
* read the marker without re-registering the key.
|
|
131
|
+
*/
|
|
132
|
+
export const PLUGIN_MARKER = Symbol.for("@intx/agent.plugin");
|
|
133
|
+
/**
|
|
134
|
+
* Constant on every plugin instance returned by `definePlugin`. Hosts
|
|
135
|
+
* that need to distinguish plugin instances from arbitrary objects
|
|
136
|
+
* received via `env.plugins` check this field before duck-typing on
|
|
137
|
+
* shape. The string value is the operative form of the contract — the
|
|
138
|
+
* value-side marker exists because the factory-side symbol marker
|
|
139
|
+
* (PLUGIN_MARKER above) is only visible to code that imported it,
|
|
140
|
+
* while the kind string travels through pure-JSON inspection too.
|
|
141
|
+
*/
|
|
142
|
+
export const TOOL_PLUGIN_KIND = "tool-plugin";
|
|
143
|
+
/**
|
|
144
|
+
* Predicate hosts use to confirm a value off `env.plugins` was minted
|
|
145
|
+
* by `definePlugin` rather than happening to satisfy a shape-based
|
|
146
|
+
* check. Returns true iff the value is an object carrying the
|
|
147
|
+
* literal `kind: "tool-plugin"` marker.
|
|
148
|
+
*/
|
|
149
|
+
export function isToolPluginInstance(value) {
|
|
150
|
+
if (value === null || typeof value !== "object")
|
|
151
|
+
return false;
|
|
152
|
+
if (!("kind" in value))
|
|
153
|
+
return false;
|
|
154
|
+
return value.kind === TOOL_PLUGIN_KIND;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Define a plugin factory. The plugin's `Result` is host-defined and
|
|
158
|
+
* surfaces in `env.plugins` for the host-side tool factories that
|
|
159
|
+
* consume it. The returned instance is tagged with
|
|
160
|
+
* `kind: "tool-plugin"` so hosts can identify plugin instances
|
|
161
|
+
* structurally without falling back to duck-typing on the result's
|
|
162
|
+
* own shape.
|
|
163
|
+
*
|
|
164
|
+
* `id` must be package-namespaced — same rule as `defineTool` — so
|
|
165
|
+
* audit provenance threads through plugins as cleanly as through
|
|
166
|
+
* tools.
|
|
167
|
+
*/
|
|
168
|
+
export function definePlugin(opts) {
|
|
169
|
+
validateNamespacedId(opts.id);
|
|
170
|
+
const requires = Object.freeze([
|
|
171
|
+
...(opts.requires ?? []),
|
|
172
|
+
]);
|
|
173
|
+
const definitions = Object.freeze([
|
|
174
|
+
...(opts.definitions ?? []),
|
|
175
|
+
]);
|
|
176
|
+
const wrapped = (env) => {
|
|
177
|
+
const instance = opts.factory(env);
|
|
178
|
+
// Re-stamping the marker is harmless if the factory chose to set
|
|
179
|
+
// it itself; otherwise we add it. Either way the returned value
|
|
180
|
+
// carries the contract.
|
|
181
|
+
return Object.assign(instance, { kind: TOOL_PLUGIN_KIND });
|
|
182
|
+
};
|
|
183
|
+
return Object.assign(wrapped, {
|
|
184
|
+
id: opts.id,
|
|
185
|
+
requires,
|
|
186
|
+
definitions,
|
|
187
|
+
[PLUGIN_MARKER]: true,
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
/** Type predicate distinguishing plugin factories from tool factories. */
|
|
191
|
+
export function isAnnotatedPluginFactory(value) {
|
|
192
|
+
if (typeof value !== "function")
|
|
193
|
+
return false;
|
|
194
|
+
if (!(PLUGIN_MARKER in value))
|
|
195
|
+
return false;
|
|
196
|
+
// `PLUGIN_MARKER in value` narrows `value` to include the symbol
|
|
197
|
+
// key, so the index access below is type-safe without a cast.
|
|
198
|
+
return value[PLUGIN_MARKER] === true;
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Build a `ToolRunner` that dispatches by tool name. Throws
|
|
202
|
+
* `DuplicateToolError` at construction if any two tools share a name.
|
|
203
|
+
*
|
|
204
|
+
* At call time, unknown tool names and exceptions from handlers are
|
|
205
|
+
* converted to `ToolResult { isError: true }` so the contract on
|
|
206
|
+
* `ToolRunner.run` ("must not throw") is upheld.
|
|
207
|
+
*/
|
|
208
|
+
export function createToolRunner(tools) {
|
|
209
|
+
const byName = new Map();
|
|
210
|
+
for (const t of tools) {
|
|
211
|
+
if (byName.has(t.definition.name)) {
|
|
212
|
+
throw new DuplicateToolError(t.definition.name);
|
|
213
|
+
}
|
|
214
|
+
byName.set(t.definition.name, t);
|
|
215
|
+
}
|
|
216
|
+
const definitions = tools.map((t) => t.definition);
|
|
217
|
+
return {
|
|
218
|
+
definitions,
|
|
219
|
+
async run(call, signal) {
|
|
220
|
+
const found = byName.get(call.name);
|
|
221
|
+
if (found === undefined) {
|
|
222
|
+
return {
|
|
223
|
+
callId: call.id,
|
|
224
|
+
content: `unknown tool: ${call.name}`,
|
|
225
|
+
isError: true,
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
try {
|
|
229
|
+
if (found.kind === "full") {
|
|
230
|
+
return await found.handler(call, signal);
|
|
231
|
+
}
|
|
232
|
+
const text = await found.handler(call.arguments, signal);
|
|
233
|
+
return { callId: call.id, content: text };
|
|
234
|
+
}
|
|
235
|
+
catch (err) {
|
|
236
|
+
return {
|
|
237
|
+
callId: call.id,
|
|
238
|
+
content: err instanceof Error ? err.message : String(err),
|
|
239
|
+
isError: true,
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
},
|
|
243
|
+
};
|
|
244
|
+
}
|
package/package.json
CHANGED
|
@@ -1,19 +1,38 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@intx/agent",
|
|
3
|
-
"
|
|
3
|
+
"description": "In-process agent runtime: construct an agent, send it a message, get a reply",
|
|
4
|
+
"version": "0.3.0",
|
|
4
5
|
"license": "LGPL-2.1-only",
|
|
5
6
|
"type": "module",
|
|
6
7
|
"exports": {
|
|
7
8
|
".": {
|
|
8
|
-
"
|
|
9
|
-
"
|
|
9
|
+
"intx-src": "./src/index.ts",
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"default": "./dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./testing": {
|
|
14
|
+
"intx-src": "./src/testing/index.ts",
|
|
15
|
+
"types": "./dist/testing/index.d.ts",
|
|
16
|
+
"default": "./dist/testing/index.js"
|
|
10
17
|
}
|
|
11
18
|
},
|
|
12
19
|
"dependencies": {
|
|
13
|
-
"@intx/inference": "0.
|
|
14
|
-
"@intx/
|
|
15
|
-
"@intx/
|
|
16
|
-
"@intx/types": "0.
|
|
20
|
+
"@intx/inference": "0.3.0",
|
|
21
|
+
"@intx/log": "0.3.0",
|
|
22
|
+
"@intx/mime": "0.3.0",
|
|
23
|
+
"@intx/types": "0.3.0",
|
|
17
24
|
"arktype": "^2.1.29"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@intx/storage-isogit": "0.3.0"
|
|
28
|
+
},
|
|
29
|
+
"files": [
|
|
30
|
+
"dist",
|
|
31
|
+
"README.md",
|
|
32
|
+
"LICENSE"
|
|
33
|
+
],
|
|
34
|
+
"sideEffects": false,
|
|
35
|
+
"publishConfig": {
|
|
36
|
+
"access": "public"
|
|
18
37
|
}
|
|
19
38
|
}
|
package/src/agent.test.ts
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import { describe, test, expect } from "bun:test";
|
|
2
|
-
|
|
3
|
-
import type { ContextStore, InferenceSource } from "@intx/types/runtime";
|
|
4
|
-
|
|
5
|
-
import { AgentConfigError, createAgent } from "./agent";
|
|
6
|
-
|
|
7
|
-
const SOURCE: InferenceSource = {
|
|
8
|
-
id: "anthropic:claude-3-5-sonnet",
|
|
9
|
-
provider: "anthropic",
|
|
10
|
-
baseURL: "https://api.anthropic.com",
|
|
11
|
-
apiKey: "sk-test",
|
|
12
|
-
model: "claude-3-5-sonnet",
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
function stubContextStore(): ContextStore {
|
|
16
|
-
// Storage-validation tests reject before touching the store; the stub
|
|
17
|
-
// never has any of its methods called.
|
|
18
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- test stub, never invoked
|
|
19
|
-
return {} as ContextStore;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
describe("createAgent storage configuration", () => {
|
|
23
|
-
test("rejects when both contextStore and contextDir are given", async () => {
|
|
24
|
-
await expect(
|
|
25
|
-
createAgent({
|
|
26
|
-
contextStore: stubContextStore(),
|
|
27
|
-
contextDir: "/tmp/agent-config-1",
|
|
28
|
-
sources: [SOURCE],
|
|
29
|
-
defaultSource: SOURCE.id,
|
|
30
|
-
systemPrompt: "test",
|
|
31
|
-
tools: [],
|
|
32
|
-
}),
|
|
33
|
-
).rejects.toBeInstanceOf(AgentConfigError);
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
test("rejects when neither contextStore nor contextDir is given", async () => {
|
|
37
|
-
await expect(
|
|
38
|
-
createAgent({
|
|
39
|
-
sources: [SOURCE],
|
|
40
|
-
defaultSource: SOURCE.id,
|
|
41
|
-
systemPrompt: "test",
|
|
42
|
-
tools: [],
|
|
43
|
-
}),
|
|
44
|
-
).rejects.toBeInstanceOf(AgentConfigError);
|
|
45
|
-
});
|
|
46
|
-
});
|