@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/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
- "version": "0.1.2",
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
- "types": "./src/index.ts",
9
- "default": "./src/index.ts"
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.0.0",
14
- "@intx/mime": "0.0.0",
15
- "@intx/storage-isogit": "0.0.0",
16
- "@intx/types": "0.0.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
- });