@adia-ai/agent 0.8.29 → 0.8.30
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/CHANGELOG.md +40 -0
- package/README.md +384 -11
- package/agent.d.ts +44 -3
- package/agent.d.ts.map +1 -1
- package/agent.js +172 -8
- package/agent.js.map +1 -1
- package/events.d.ts +46 -2
- package/events.d.ts.map +1 -1
- package/events.js +30 -1
- package/events.js.map +1 -1
- package/guardrail.d.ts +68 -0
- package/guardrail.d.ts.map +1 -0
- package/guardrail.js +79 -0
- package/guardrail.js.map +1 -0
- package/index.d.ts +15 -7
- package/index.d.ts.map +1 -1
- package/index.js +7 -3
- package/index.js.map +1 -1
- package/integrations.d.ts +19 -2
- package/integrations.d.ts.map +1 -1
- package/integrations.js +6 -1
- package/integrations.js.map +1 -1
- package/loop.d.ts +19 -8
- package/loop.d.ts.map +1 -1
- package/loop.js +176 -9
- package/loop.js.map +1 -1
- package/mcp-transport.d.ts +128 -0
- package/mcp-transport.d.ts.map +1 -0
- package/mcp-transport.js +361 -0
- package/mcp-transport.js.map +1 -0
- package/mcp.d.ts +120 -0
- package/mcp.d.ts.map +1 -0
- package/mcp.js +264 -0
- package/mcp.js.map +1 -0
- package/package.json +1 -1
- package/resource.d.ts +28 -3
- package/resource.d.ts.map +1 -1
- package/resource.js +43 -5
- package/resource.js.map +1 -1
- package/session.d.ts +18 -0
- package/session.d.ts.map +1 -1
- package/session.js +40 -0
- package/session.js.map +1 -1
- package/stub.d.ts +8 -0
- package/stub.d.ts.map +1 -1
- package/stub.js +13 -0
- package/stub.js.map +1 -1
- package/tools.d.ts +6 -0
- package/tools.d.ts.map +1 -1
- package/tools.js.map +1 -1
- package/trace.d.ts +50 -0
- package/trace.d.ts.map +1 -0
- package/trace.js +60 -0
- package/trace.js.map +1 -0
- package/workflow.d.ts +113 -11
- package/workflow.d.ts.map +1 -1
- package/workflow.js +93 -15
- package/workflow.js.map +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,45 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.8.30] — 2026-08-07
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- **MCP-client mode — an MCP server as a tool/resource source (gh#666).** `createAgent({ integrations: [mcpServer({ url, headers })] })` connects over streamable-HTTP, lists the server's tools, and registers each one through the tier-2 integration registry (`register` fail-fast on a name collision, `buildToolDispatch` as the one ToolDef builder). From there they are ordinary `ToolDef`s: the guardrail chain sees the calls, the tracer records the executions, `checkInput` runs at the boundary, failures come back as `isError` tool_results. Additive throughout — an agent declaring no `integrations` produces the same request, events, and session as before (the byte-identity baseline is untouched).
|
|
7
|
+
- **Lazy, memoized connection.** `createAgent` stays synchronous; the handshake happens on the first turn, or eagerly via the new `agent.connect()`, which reports `{ url, serverInfo, protocolVersion, tools, resources }` per server. `agent.close()` ends the sessions. A failed connect is not cached — the next turn retries.
|
|
8
|
+
- **Failure semantics.** Unreachable server, non-2xx, an unspeakable protocol version, a failed `tools/list`, a tool-name collision, or more tools than `maxTools` all **throw `McpConnectionError`** (infrastructure faults the model cannot work around); a failed `tools/call`, a mid-session disconnect, or a server-reported `isError` reaches the model as an `isError` tool_result and the loop continues. There is no path that silently yields an empty tool list. `maxTools` (default `MAX_INTEGRATIONS`) throws instead of dropping the overflow the way `resolveIntegrations`' cap does, which is why MCP registration does not route through `resolveIntegrations` (it has neither a persona list nor an env key).
|
|
9
|
+
- **Resources map onto the existing two modes.** Every server resource is `tool` mode by default (`read_<name>`); `attach: [name|uri]` promotes the ones the app wants inlined every turn. **gh#671 is ruled here: no third `'internal'` resource mode.** A mode declares who invokes and when, and engine-internal retrieval names no mediated party — its dependency is already `services` and its visibility already `ctx.trace` in the `workflow` phase. Recorded in the README.
|
|
10
|
+
- **No new runtime dependency.** The streamable-HTTP client is hand-rolled (~200 lines, `fetch`-only, browser-safe): initialize + `notifications/initialized` + `tools/list` + `tools/call` + `resources/list` + `resources/read`, session-header echo, protocol-version negotiation, cursor pagination, and JSON *or* SSE response bodies. Verified against the real in-repo `packages/a2ui/mcp` SDK server over HTTP (30 tools, 4 resources, tool call and resource read). **stdio is unsupported** — it needs a child process, so it is Node-only by construction. Browser use needs a CORS-enabled server or the host's existing proxy; this package ships no proxy, and `fetchImpl` is the injection seam.
|
|
11
|
+
- **Per-request deadline, default 60s** (`McpTransportOptions.timeoutMs`, `DEFAULT_MCP_TIMEOUT_MS`; `0` disables). It covers the fetch AND the wait for the answer on an SSE body, so a server that accepts a POST and then says nothing fails instead of hanging. 60s rather than a handshake-sized number because an MCP `tools/call` may run an LLM server-side (the in-repo a2ui server's `generate_ui` does). A connect timeout throws; a call timeout reaches the model as an `isError` tool_result.
|
|
12
|
+
- **Abort plumbed end to end** — `SendOpts.signal` reaches an in-flight `tools/call` / `resources/read`, so a user cancellation cancels the server request rather than abandoning it. The signal of the turn that triggers a connect cancels that handshake only; it never stays attached to the resulting connection.
|
|
13
|
+
- **Failures leave no session open** — a source that fails after its handshake closes its own session, a failing second source closes the first one's, and `close()` during an in-flight connect wins (the late arrival closes what it opened and rejects rather than reinstalling itself). `close()` does not spend the agent: a later `send()` reconnects.
|
|
14
|
+
- **New trace phase `mcp`** (`connect` / `connect:failed`) on `TRACE_PHASES`; contributed tools still execute under `tool`.
|
|
15
|
+
- **New test fixture** `mcp-fixture.mjs` — an in-process MCP server behind a `fetch`-shaped function, covering both response body shapes.
|
|
16
|
+
- **Attach-mode resources are contained, not trusted (behavior change).** `attachLayers` now escapes the two structural sequences (`<resource`, `</resource`) in a resource BODY and the attribute specials in its `name` / `description`. A body containing `</resource>` previously closed its own envelope, and everything after it read as prompt — a file, a database row, or an MCP server could write instructions into the system prompt that way. Only those sequences are escaped: attached bodies are catalog JSON, component markup and docs, and mangling every `<` would degrade the data the model reads every turn for a property those two sequences already give. Applies to every attach resource, hand-declared or MCP. A prompt only changes if it contained one of those sequences.
|
|
17
|
+
- **`ResourceDef.get` receives `(input, ctx)`** — `ctx.signal` is the turn's cancellation, so a resource read can be aborted. Additive: a one-argument `get` is unaffected.
|
|
18
|
+
- **`IntegrationManifest.validation: 'local' | 'remote'`** (default `'local'`, behavior unchanged). `'remote'` says the executor is the authoritative validator, so registration accepts the schema as written instead of enforcing `checkInput`'s allowlist — an MCP server's schema reaches the model verbatim rather than being trimmed to a locally-enforceable subset that describes the tool less accurately.
|
|
19
|
+
- **W2 hardening — guardrails, tracing, session memory, partial tool input (gh#665).** All additive; a config that declares none of them produces the same requests, the same events, and the same session as before.
|
|
20
|
+
- **Guardrails** (`defineGuardrail`, `AgentConfig.guardrails`) — the deterministic layer between the model and the world, declared once at `createAgent`. `toolCall` guardrails allow / rewrite / deny a call before it executes (a deny feeds the model an `isError` tool_result naming the guardrail; the loop continues); `output` guardrails allow / rewrite / deny the model's completed text for **the round that ends the turn** — text from a round that also called tools is intermediate and passes through unguarded (README records the caveat) — where a deny ends the turn with no assistant text and the synthetic `stopReason: 'guardrail_denied'`. A rewrite or denial is followed by a `{ text: '', snapshot }` event: a UI folding text events by appending must treat an empty delta as a replace from `snapshot`. They chain in declaration order — a rewrite feeds the next, the first deny short-circuits. `onToolCall` is now exactly one guardrail of the `toolCall` kind, unchanged in behavior and wording, running ahead of the declared chain. New render-only `guardrail` event.
|
|
21
|
+
- **Tracing** (`AgentConfig.trace`, `createTracer`, `consoleSink`, `TRACE_PHASES`) — structured records for prompt assembly, each provider call and response, each tool execution, each workflow step verdict, each guardrail intervention, and the turn's token usage. Two consumers of one record: a render-only `trace` event on the stream and a pluggable sink (`true` → console). Off by default; a throwing sink warns instead of failing the turn. No exporter protocol, no dependency.
|
|
22
|
+
- **Session memory** (`Session.memory`, `remember()`, `ToolContext.remember`) — a per-session JSON-safe key/value slot written ONLY through `reduce()` via the new `memory` event (a null value deletes a key). A tool asks for a write through `ctx.remember(patch)`; the loop yields the event after that tool's result, and drains anything still queued before every `done` — so a final-round tool's write, or a guardrail's (guardrails receive the same context), is emitted rather than silently dropped. Non-JSON-safe patches throw at the write, naming the path (`assertJsonSafe`). Cross-session persistence stays a seam: memory serializes with the Session, storage remains the caller's.
|
|
23
|
+
- **Partial tool-input deltas** — the new `@adia-ai/llm` `tool_use_delta` chunk surfaces as a render-only `tool_input_delta` event (`id`, `name`, `partial`, `snapshot`). The snapshot is raw, incomplete JSON; nothing executes off it, and the complete `tool_use` chunk is unchanged. `scriptClient` gained `ScriptedTurn.toolUseDeltas` to script the path.
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
- **Workflow contract repairs — the five W1 findings (gh#665).** Additive except where noted:
|
|
27
|
+
- **`until` may return a VERDICT** — `{ accepted, reason, detail, data }`. Only an object with a boolean `accepted` is read as a verdict; **every other return is read for its truthiness**, so a pre-verdict predicate (`r => r.messages.length`, `r => r.id`) keeps meaning what it always meant rather than being treated as a malformed verdict and silently flipping to rejected. The reason lands on the attempt, so consumers stop recomputing why a step was rejected.
|
|
28
|
+
- **`WorkflowAttempt` gains `round`, and optional `reason` / `detail` / `data` / `gateRejected`.** `round` is always present — a consumer doing an exact deep-equal on an attempt object sees one new key.
|
|
29
|
+
- **Workflow-level exit gate** — `defineWorkflow(name, steps, { exitGate })`, the check every accepted result passes through, declared once instead of restated per step. A **terminal step (no `until`) is not exit-gated**: the floor of a ladder is the answer of last resort. Declare `until: () => true` on the last step to gate it like the others.
|
|
30
|
+
- **Same-step retry** — `WorkflowStep.retry = { maxRounds }` re-runs a step against its own rejection verdict before falling through. `ctx.round` (1-based) and `ctx.feedback` (the previous verdict) reach `run`; each round is its own `attempts` entry. An exit-gate rejection consumes a round like any other and arrives on the next round's `ctx.feedback` carrying `gateRejected: true` (the marker is on the Verdict, not only on the attempt).
|
|
31
|
+
- **Services channel** — `WorkflowContext.services` is what a run may USE (llmAdapter, clients, stores), separate from `input`, which is what it is ABOUT. Declared on `AgentConfig.services` and/or per run; per-run wins. `input` is unchanged and not deprecated for consumer-owned shapes.
|
|
32
|
+
- **`agent.run`'s fourth argument may be an options object** (`{ onEvent, services, signal }`) as well as the v0 bare `onEvent` function.
|
|
33
|
+
|
|
34
|
+
### Changed (v0 → v1, previously recorded)
|
|
35
|
+
- **No longer experimental — the A2UI pipeline migration validated the contracts (gh#664).** `packages/a2ui/compose` now declares its engine escalation ladder as a `defineWorkflow` (`strategies/cascade.js`, reachable as the reserved engine name `auto`), and the A2UI MCP surface declares its corpus reads as `mode:'tool'` resources and `refine_ui` as a `defineTool`. Three contracts changed under that pressure, all additive:
|
|
36
|
+
- **Workflow predicates may be async, and `until` receives the context.** `until?: (result, ctx) => boolean | Promise<boolean>`, `when?: (ctx) => boolean | Promise<boolean>`. A real acceptance gate for a generated surface reads catalog JSON off disk; a sync-only predicate forced every consumer to pre-resolve its gate outside the workflow, which is where cascades get duplicated.
|
|
37
|
+
- **`WorkflowResult.attempts`** — `{ step, accepted, result }` per step that ran, in order. The escalation trace was previously unrecoverable from the result: consumers could see who won but not who was tried or why the ladder climbed.
|
|
38
|
+
- **`ResourceDef.inputSchema`** (tool-mode only; throws on an attach-mode resource) and a `get` that may return a non-string, JSON-serialized on the way out. Without the schema a tool-mode resource was a zero-argument read, which cannot express `search_chunks(query, kind, limit)`.
|
|
39
|
+
|
|
40
|
+
### Maintenance
|
|
41
|
+
- **`src/` touched in this release window** (14 file(s), e.g. `src/agent.ts`) — carried by the entries above.
|
|
42
|
+
|
|
3
43
|
## [0.8.29] — 2026-08-06
|
|
4
44
|
|
|
5
45
|
### Maintenance
|
package/README.md
CHANGED
|
@@ -6,8 +6,22 @@ Composable chat-agent harness. Assemble an agent from four declared parts —
|
|
|
6
6
|
serializable `Session`, and the one shared event reducer; it renders nothing
|
|
7
7
|
(pair it with `web-modules/chat` or any UI).
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
The contracts are validated by a real consumer: the A2UI generation pipeline
|
|
10
|
+
runs on them ([gh#664](https://github.com/adiahealth/gen-ui-kit/issues/664))
|
|
11
|
+
— its engine escalation ladder is a workflow
|
|
12
|
+
(`packages/a2ui/compose/strategies/cascade.js`), its corpus reads are
|
|
13
|
+
tool-mode resources, and its refiner is a defined tool. What that migration
|
|
14
|
+
could not express became this package's hardening pass
|
|
15
|
+
([gh#665](https://github.com/adiahealth/gen-ui-kit/issues/665)): workflow
|
|
16
|
+
verdicts, exit gates, same-step retry, a services channel, guardrails,
|
|
17
|
+
tracing, session memory, and partial tool-input deltas.
|
|
18
|
+
|
|
19
|
+
Deferred, with reason: **`@genui/adia-producer`'s bridge keeps its own loop.**
|
|
20
|
+
The retry seam covers per-tier round budgets, but the bridge also owns a
|
|
21
|
+
turn-wide `maxProviderCallsPerTurn` budget spanning steps, a genui `TurnTrace`,
|
|
22
|
+
and `OrchestrationExhaustedError` semantics that come from `@genui/producer`
|
|
23
|
+
— none of which this package models, and modelling them here would import
|
|
24
|
+
another package's orchestration vocabulary into a general harness.
|
|
11
25
|
|
|
12
26
|
## Install
|
|
13
27
|
|
|
@@ -40,13 +54,17 @@ const agent = createAgent({
|
|
|
40
54
|
workflows: [
|
|
41
55
|
defineWorkflow('generate', [
|
|
42
56
|
{ name: 'zettel', run: zettel, until: r => r.strategy === 'composition-match' },
|
|
43
|
-
{ name: 'free-form', run: freeForm, until: r => r.ok },
|
|
57
|
+
{ name: 'free-form', run: freeForm, until: async r => r.ok && await passesGate(r) },
|
|
44
58
|
{ name: 'monolithic', run: monolithic }, // terminal — always accepted
|
|
45
59
|
]),
|
|
46
60
|
],
|
|
47
61
|
resources: [
|
|
48
62
|
defineResource({ name: 'visit-summary', mode: 'attach', get: fetchSummary }), // app-controlled
|
|
49
|
-
defineResource({
|
|
63
|
+
defineResource({ // model-controlled
|
|
64
|
+
name: 'care-plan', mode: 'tool',
|
|
65
|
+
inputSchema: { type: 'object', properties: { section: { type: 'string' } } },
|
|
66
|
+
get: ({ section }) => readPlan(section), // non-string returns are JSON-serialized
|
|
67
|
+
}),
|
|
50
68
|
],
|
|
51
69
|
maxToolRounds: 8,
|
|
52
70
|
});
|
|
@@ -64,9 +82,101 @@ await agent.run(session, 'generate', { intent });
|
|
|
64
82
|
| Part | Declared with | What the harness does |
|
|
65
83
|
|---|---|---|
|
|
66
84
|
| Prompt | `promptLayer(name, text, {cache})` | Static layers first, `cache_control` on the last cached one (Anthropic block array; joined string elsewhere) |
|
|
67
|
-
| Tools | `defineTool({name, inputSchema, execute\|endpoint})` | JSON-schema check at the call boundary, then the execute-and-feed-back loop (bounded by `maxToolRounds
|
|
68
|
-
| Workflows | `defineWorkflow(name, steps)` | Sequential cascade — `until` accepts, absent `until` is terminal, `when` skips |
|
|
69
|
-
| Resources | `defineResource({name, mode, get})` | `attach` → dynamic prompt layer each turn; `tool` → a read-only `read_<name>` tool |
|
|
85
|
+
| Tools | `defineTool({name, inputSchema, execute\|endpoint})` | JSON-schema check at the call boundary, then the execute-and-feed-back loop (bounded by `maxToolRounds`) |
|
|
86
|
+
| Workflows | `defineWorkflow(name, steps, {exitGate})` | Sequential cascade — `until` accepts, absent `until` is terminal, `when` skips, `retry` re-runs the same step |
|
|
87
|
+
| Resources | `defineResource({name, mode, inputSchema, get})` | `attach` → dynamic prompt layer each turn; `tool` → a read-only `read_<name>` tool taking `inputSchema`'s arguments |
|
|
88
|
+
|
|
89
|
+
**The attach envelope contains what it wraps.** An attach-mode resource
|
|
90
|
+
inlines somebody else's bytes into the system prompt, so a body containing
|
|
91
|
+
`</resource>` would otherwise close its own envelope and everything after it
|
|
92
|
+
would read as prompt — a file, a database row, or an MCP server could write
|
|
93
|
+
instructions into the system prompt that way. `attachLayers` escapes the two
|
|
94
|
+
structural sequences (`<resource` and `</resource`) in the body and the
|
|
95
|
+
attribute specials in `name` / `description`. Only those: attached bodies here
|
|
96
|
+
are catalog JSON, component markup and docs, and mangling every `<` in them to
|
|
97
|
+
close one hole would degrade the data the model reads on every turn for a
|
|
98
|
+
containment property those two sequences already give. Everything else arrives
|
|
99
|
+
byte-for-byte. This applies to every attach resource, hand-declared or MCP.
|
|
100
|
+
|
|
101
|
+
A resource's `get` receives `(input, ctx)` — `ctx.signal` is the turn's
|
|
102
|
+
cancellation. Existing one-argument `get`s are unaffected.
|
|
103
|
+
|
|
104
|
+
Three cross-cutting layers ride on top: **guardrails** (veto/rewrite),
|
|
105
|
+
**tracing** (structured records of what the harness did), and **session
|
|
106
|
+
memory** (a JSON-safe slot on the Session). Tools and resources may also
|
|
107
|
+
arrive from **an MCP server** instead of being hand-declared — see
|
|
108
|
+
[MCP-client mode](#mcp-client-mode).
|
|
109
|
+
|
|
110
|
+
### Workflows in detail
|
|
111
|
+
|
|
112
|
+
`until` and `when` may both be async, and `until` also receives the context
|
|
113
|
+
(`until(result, ctx)`) — the acceptance gate for a generated artifact
|
|
114
|
+
usually has to read something. The result carries the whole run:
|
|
115
|
+
|
|
116
|
+
```js
|
|
117
|
+
const { result, step, accepted, attempts } = await agent.run(session, 'generate', { intent });
|
|
118
|
+
// attempts: [{ step:'zettel', accepted:false, reason:'no-emission', round:1, result:… },
|
|
119
|
+
// { step:'free-form', accepted:true, round:1, result:… }]
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
`attempts` lists every step round that RAN (a `when`-skipped step doesn't
|
|
123
|
+
appear), in order, with its verdict — that list is the escalation trace a UI
|
|
124
|
+
renders. `accepted` is false only when the last step also rejected; give the
|
|
125
|
+
ladder a terminal step (no `until`) if you want a guaranteed answer.
|
|
126
|
+
|
|
127
|
+
**Verdicts.** `until` may return a verdict object —
|
|
128
|
+
`{ accepted, reason, detail, data }` — or **any other value, read for its
|
|
129
|
+
truthiness**, which is what a pre-verdict predicate (`r => r.messages.length`)
|
|
130
|
+
already did. Only an object with a boolean `accepted` is treated as a verdict.
|
|
131
|
+
The reason lands on the attempt, so a consumer renders why the ladder climbed
|
|
132
|
+
instead of recomputing it:
|
|
133
|
+
|
|
134
|
+
```js
|
|
135
|
+
until: (r) => r.messages.length
|
|
136
|
+
? { accepted: true }
|
|
137
|
+
: { accepted: false, reason: 'no-emission', detail: 'engine emitted nothing' },
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
**Exit gate.** The check every step's result must pass is declared once on
|
|
141
|
+
the workflow instead of restated in every `until`:
|
|
142
|
+
|
|
143
|
+
```js
|
|
144
|
+
defineWorkflow('generate', steps, {
|
|
145
|
+
exitGate: async (result, ctx) => await validatesAgainstCatalog(result, ctx.input),
|
|
146
|
+
});
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
It runs after a step's own `until` accepted, and a rejection falls the ladder
|
|
150
|
+
through exactly like an `until` rejection (the attempt carries
|
|
151
|
+
`gateRejected: true` and `reason: 'gate-rejected'`). A **terminal step — one
|
|
152
|
+
with no `until` — is not exit-gated**: the floor of a ladder is the answer of
|
|
153
|
+
last resort. Give the last step `until: () => true` if you want it gated too.
|
|
154
|
+
|
|
155
|
+
**Same-step retry.** A step may retry against its own rejection before
|
|
156
|
+
falling through:
|
|
157
|
+
|
|
158
|
+
```js
|
|
159
|
+
{ name: 'repair', retry: { maxRounds: 3 },
|
|
160
|
+
run: (ctx) => generate(ctx.input, ctx.feedback), // ctx.round is 1-based
|
|
161
|
+
until: (r) => r.valid ? { accepted: true } : { accepted: false, reason: 'invalid', data: r.errors } }
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
`ctx.feedback` is the previous round's verdict; each round is its own entry
|
|
165
|
+
in `attempts`. `maxRounds` counts total runs (2 = one retry). A workflow
|
|
166
|
+
**exit-gate** rejection consumes a round exactly like an `until` rejection,
|
|
167
|
+
and reaches the next round as `ctx.feedback` carrying `gateRejected: true` —
|
|
168
|
+
so a repair round can tell the gate's objection from the step's own.
|
|
169
|
+
|
|
170
|
+
**Services.** `ctx.input` is what the run is ABOUT; `ctx.services` is what it
|
|
171
|
+
may USE. Declare them on the agent, per run, or both (per-run wins):
|
|
172
|
+
|
|
173
|
+
```js
|
|
174
|
+
createAgent({ …, services: { llmAdapter, store } });
|
|
175
|
+
await agent.run(session, 'generate', { intent }, { services: { llmAdapter: turnAdapter } });
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Services used to ride `input`, and still may — nothing breaks — but a new
|
|
179
|
+
consumer should keep `input` the subject and put dependencies here.
|
|
70
180
|
|
|
71
181
|
## Events
|
|
72
182
|
|
|
@@ -77,17 +187,30 @@ can react to — the loop throws for nothing tool-shaped.
|
|
|
77
187
|
```
|
|
78
188
|
{ type:'message', role:'user', text }
|
|
79
189
|
{ type:'text', text, snapshot } · { type:'thinking', text }
|
|
190
|
+
{ type:'tool_input_delta', id, name, partial, snapshot }
|
|
80
191
|
{ type:'tool_use', id, name, input } · { type:'tool_result', id, name, output, isError? }
|
|
81
192
|
{ type:'step', workflow, step, data? }
|
|
82
193
|
{ type:'progress', stage } · { type:'surface', surfaceId, line }
|
|
194
|
+
{ type:'guardrail', name, target, action, reason?, toolName?, toolUseId? }
|
|
195
|
+
{ type:'trace', record } · { type:'memory', patch }
|
|
83
196
|
{ type:'done', text, usage, stopReason } · { type:'error', error }
|
|
84
197
|
```
|
|
85
198
|
|
|
86
|
-
`stopReason` passes through raw from the provider; the loop adds exactly
|
|
87
|
-
synthetic
|
|
199
|
+
`stopReason` passes through raw from the provider; the loop adds exactly two
|
|
200
|
+
synthetic values — `max_tool_rounds` when the bound is hit, and
|
|
201
|
+
`guardrail_denied` when an output guardrail vetoed the turn.
|
|
202
|
+
|
|
203
|
+
`thinking` / `step` / `error` / `progress` / `surface` / `tool_input_delta` /
|
|
204
|
+
`guardrail` / `trace` are render-only — `reduce()` folds them into no session
|
|
205
|
+
change. `memory` is the one new event that DOES change the session.
|
|
88
206
|
|
|
89
|
-
|
|
90
|
-
|
|
207
|
+
### Partial tool input
|
|
208
|
+
|
|
209
|
+
While a provider streams a tool call's arguments, each fragment surfaces as
|
|
210
|
+
`tool_input_delta` (Anthropic `input_json_delta`, OpenAI partial argument
|
|
211
|
+
strings; Gemini streams complete calls and emits none). `snapshot` is the raw
|
|
212
|
+
JSON so far — **incomplete and unparseable until the matching `tool_use`
|
|
213
|
+
arrives**. Render progress from it; execute only on `tool_use`.
|
|
91
214
|
|
|
92
215
|
### Progress events (CHAT-HARNESS law 3)
|
|
93
216
|
|
|
@@ -144,6 +267,250 @@ loop as `agent.send()`. A silent-apply message yields nothing and never
|
|
|
144
267
|
touches the provider — the caller already applied it directly (e.g. to a
|
|
145
268
|
surface's data-model store).
|
|
146
269
|
|
|
270
|
+
## MCP-client mode
|
|
271
|
+
|
|
272
|
+
Point the agent at an [MCP](https://modelcontextprotocol.io) server and its
|
|
273
|
+
tools and resources join the declared ones. Nothing downstream knows the
|
|
274
|
+
difference: the calls go through the guardrail chain, the tracer records the
|
|
275
|
+
executions, `checkInput` runs at the boundary, a failure comes back as an
|
|
276
|
+
`isError` tool_result. MCP is a *source* of tools, not a second kind of tool.
|
|
277
|
+
|
|
278
|
+
```js
|
|
279
|
+
import { createAgent, mcpServer } from '@adia-ai/agent';
|
|
280
|
+
|
|
281
|
+
const agent = createAgent({
|
|
282
|
+
llm: { model: 'claude-sonnet-4-6', proxyUrl: '/api/chat' },
|
|
283
|
+
integrations: [
|
|
284
|
+
mcpServer({
|
|
285
|
+
url: 'https://mcp.example.com/mcp',
|
|
286
|
+
headers: { authorization: `Bearer ${token}` }, // v1 auth: headers only
|
|
287
|
+
prefix: 'wiki_', // namespace its tool names
|
|
288
|
+
tools: ['search', 'fetch'], // allowlist; absent = everything it lists
|
|
289
|
+
maxTools: 24, // loud cap, default MAX_INTEGRATIONS (16)
|
|
290
|
+
attach: ['style-guide'], // resources to inline every turn
|
|
291
|
+
timeoutMs: 60_000, // per request; 0 disables (default 60s)
|
|
292
|
+
}),
|
|
293
|
+
],
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
const report = await agent.connect(); // optional — otherwise the first turn does it
|
|
297
|
+
// [{ url, serverInfo, protocolVersion, tools: [...], resources: [{name, uri, mode}] }]
|
|
298
|
+
await agent.close(); // ends the MCP session(s)
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
**Connection is lazy and memoized.** `createAgent` is synchronous and a
|
|
302
|
+
constructor that reaches the network is a constructor that can fail, so the
|
|
303
|
+
handshake happens on the first turn — or eagerly on `agent.connect()`, which
|
|
304
|
+
returns what each server offered. `agent.tools` lists the declared set until
|
|
305
|
+
the sources connect, and everything afterwards. A failed connect is not
|
|
306
|
+
cached as a verdict: the next turn tries again.
|
|
307
|
+
|
|
308
|
+
**Failure is loud where it must be and model-visible where it can be.**
|
|
309
|
+
|
|
310
|
+
| When | What happens |
|
|
311
|
+
|---|---|
|
|
312
|
+
| Server unreachable, non-2xx, unspeakable protocol version, `tools/list` fails | `McpConnectionError` **thrown** out of `connect()` / `send()`. An infrastructure fault the model cannot work around — the same class as a missing `@adia-ai/llm`, never a silent empty tool list |
|
|
313
|
+
| Two sources (or a source and a declared tool) contribute the same tool name | `McpConnectionError` at connect — set a `prefix` |
|
|
314
|
+
| More tools listed than `maxTools` | `McpConnectionError` naming the count. `resolveIntegrations`' cap silently drops the overflow; this one refuses to serve two thirds of a server without saying so |
|
|
315
|
+
| A `tools/call` fails, times out, is aborted, or the server goes away mid-session | `isError` tool_result (`McpCallError`'s message). The model sees it and the loop continues |
|
|
316
|
+
| The server answers `isError: true` | Same — the error text reaches the model as an error, never as prose that reads like an answer |
|
|
317
|
+
| An **attach-mode** resource's `resources/read` fails mid-session | `McpCallError` **thrown out of `send()`** during prompt assembly. There is no turn to degrade into: the prompt the model was going to see is the thing that could not be built |
|
|
318
|
+
|
|
319
|
+
**Nothing waits forever.** Every round trip carries `timeoutMs` (default
|
|
320
|
+
`DEFAULT_MCP_TIMEOUT_MS`, 60 s), covering both the fetch and the wait for the
|
|
321
|
+
answer to arrive on an SSE body — a server that accepts a POST and then says
|
|
322
|
+
nothing is a real failure mode and is otherwise indistinguishable from a hang.
|
|
323
|
+
The default has to clear the slowest legitimate answer, and for MCP that is a
|
|
324
|
+
tool that thinks (the in-repo a2ui server runs an LLM inside `tools/call`), so
|
|
325
|
+
it is generous rather than handshake-sized; raise it per source for a slower
|
|
326
|
+
server, or set `0` if you own the clock through `signal`. A timeout during
|
|
327
|
+
connect throws; a timeout during a call reaches the model like any other call
|
|
328
|
+
failure.
|
|
329
|
+
|
|
330
|
+
**Abort is plumbed end to end.** `agent.send(session, text, { signal })` puts
|
|
331
|
+
that signal on the turn's `ctx`, and an MCP `tools/call` or `resources/read`
|
|
332
|
+
carries it to the wire — a user who cancels mid-answer cancels the server call
|
|
333
|
+
instead of leaving it running and ignored. Aborting the turn that happens to
|
|
334
|
+
be doing the connect cancels the handshake too; the signal does *not* stay
|
|
335
|
+
attached to the resulting connection, so a later turn is unaffected.
|
|
336
|
+
|
|
337
|
+
**Failures leave nothing open.** If a source fails after its handshake
|
|
338
|
+
succeeded, its session is closed before the error propagates; if the second of
|
|
339
|
+
two sources fails, the first one's session is closed too. `close()` during an
|
|
340
|
+
in-flight connect wins — the late arrival closes what it opened and rejects
|
|
341
|
+
with "the agent was closed while connecting" rather than quietly reinstalling
|
|
342
|
+
itself. `close()` ends the sessions but does not spend the agent: a later
|
|
343
|
+
`send()` reconnects.
|
|
344
|
+
|
|
345
|
+
**Schemas pass through verbatim.** A server's JSON Schema reaches the model
|
|
346
|
+
unmodified, including keywords `checkInput` cannot enforce. The server is the
|
|
347
|
+
authoritative validator (its rejection returns as an error result), so a
|
|
348
|
+
faithful copy of a contract someone else owns beats a locally-enforceable
|
|
349
|
+
subset that describes the tool less accurately. The manifest states this with
|
|
350
|
+
the new `IntegrationManifest.validation: 'remote'` rather than leaving it
|
|
351
|
+
implied; `'local'` (the default, and every hand-declared integration) keeps
|
|
352
|
+
the strict registration allowlist, because there the only validator is us.
|
|
353
|
+
|
|
354
|
+
**Transport is streamable-HTTP, hand-rolled, no new dependency.**
|
|
355
|
+
`@modelcontextprotocol/sdk` brings a server framework, stdio/websocket
|
|
356
|
+
transports, an auth stack and zod for a client subset that is six JSON-RPC
|
|
357
|
+
methods over one POST endpoint; this package stays dependency-free apart from
|
|
358
|
+
`@adia-ai/llm`. The SDK still belongs in `packages/a2ui/mcp`, which *serves*.
|
|
359
|
+
**stdio is not supported** — it needs a child process, so it is Node-only by
|
|
360
|
+
construction and no in-repo consumer wants it; a `mcpServerStdio()` Node entry
|
|
361
|
+
point is the shape to add if one does.
|
|
362
|
+
|
|
363
|
+
**In a browser, the server must be reachable by `fetch`** — meaning it sends
|
|
364
|
+
CORS headers for your origin, or you put it behind the same proxy you already
|
|
365
|
+
route `@adia-ai/llm` through (`proxyUrl`) and point `url` at that path. This
|
|
366
|
+
package deliberately ships no proxy: a server-side pass-through is the host
|
|
367
|
+
app's, and forwarding `mcp-session-id` and `mcp-protocol-version` both ways is
|
|
368
|
+
all it has to do. `fetchImpl` is the injection seam (tests use it for the
|
|
369
|
+
in-process server; a host can use it to route through its own client). The
|
|
370
|
+
in-repo `packages/a2ui/mcp` server sends no CORS headers today, so reaching
|
|
371
|
+
it from a page means proxying it; from Node it works directly.
|
|
372
|
+
|
|
373
|
+
### MCP resources, and the gh#671 ruling
|
|
374
|
+
|
|
375
|
+
A server's resources map onto the two existing modes and **no new one**:
|
|
376
|
+
|
|
377
|
+
- **`tool` (default)** — the harness cannot know which of a remote server's
|
|
378
|
+
resources belong in every prompt, and attaching all of them grows the
|
|
379
|
+
system prompt without bound. So each becomes a `read_<name>` tool.
|
|
380
|
+
- **`attach`** — the APP promotes specific resources by name or URI
|
|
381
|
+
(`attach: ['style-guide']`), and those are fetched and inlined every turn.
|
|
382
|
+
|
|
383
|
+
That is exactly MCP's own application-controlled vs. model-controlled
|
|
384
|
+
distinction, expressed with the modes the harness already has.
|
|
385
|
+
|
|
386
|
+
[gh#671](https://github.com/adiahealth/gen-ui-kit/issues/671) asked whether
|
|
387
|
+
engine-internal retrieval (zettel and free-form reading corpus content on
|
|
388
|
+
their own schedule inside a workflow step's `run`) needs a third mode —
|
|
389
|
+
`'internal'`. **Ruled no, and W3 did not need one either.** A resource mode
|
|
390
|
+
is a declaration of *who invokes it and when*, and both existing modes name a
|
|
391
|
+
party the harness mediates: `attach` = the harness fetches, `tool` = the model
|
|
392
|
+
asks. `'internal'` would name "engine code, whenever it likes", which is not
|
|
393
|
+
a mediation — it is a function call. Declaring it would put into the resource
|
|
394
|
+
contract a thing the model can never call and the harness never fetches,
|
|
395
|
+
buying nothing but a second way to spell `await`. The two things the issue
|
|
396
|
+
actually wanted are already contracts: the dependency is declared through
|
|
397
|
+
**`services`** (W2), and the invocation is visible through **`ctx.trace`** in
|
|
398
|
+
the `workflow` phase. Server-pushed MCP resources do not need it either —
|
|
399
|
+
every read is either app-scheduled or model-asked.
|
|
400
|
+
|
|
401
|
+
**Everything a server returns is DATA.** A tool description, a resource body,
|
|
402
|
+
an error string — a description that reads like an instruction is still a
|
|
403
|
+
description. It lands in the tool spec or inside a `<resource>` envelope and
|
|
404
|
+
decides nothing about what the harness does. Guardrails are how you constrain
|
|
405
|
+
what a remote tool may actually do.
|
|
406
|
+
|
|
407
|
+
## Guardrails
|
|
408
|
+
|
|
409
|
+
The deterministic layer between the model and the world — code that runs
|
|
410
|
+
outside the model's context and cannot be talked past. Declared once at
|
|
411
|
+
`createAgent`, two attach points, both optional:
|
|
412
|
+
|
|
413
|
+
```js
|
|
414
|
+
import { defineGuardrail } from '@adia-ai/agent';
|
|
415
|
+
|
|
416
|
+
createAgent({
|
|
417
|
+
llm: { … },
|
|
418
|
+
guardrails: [
|
|
419
|
+
defineGuardrail({
|
|
420
|
+
name: 'scope-to-tenant',
|
|
421
|
+
// Before the call executes: allow (or return nothing), rewrite, deny.
|
|
422
|
+
toolCall: (call, ctx) => ({ action: 'rewrite', input: { ...call.input, tenantId } }),
|
|
423
|
+
}),
|
|
424
|
+
defineGuardrail({
|
|
425
|
+
name: 'redact',
|
|
426
|
+
// On the model's completed text for the round.
|
|
427
|
+
output: (text) => ({ action: 'rewrite', text: text.replace(SSN, '[redacted]') }),
|
|
428
|
+
}),
|
|
429
|
+
],
|
|
430
|
+
});
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
Guardrails run in declaration order and chain: a rewrite feeds the next one,
|
|
434
|
+
the first `deny` short-circuits.
|
|
435
|
+
|
|
436
|
+
- **Tool-call deny** → the model gets an `isError` tool_result naming the
|
|
437
|
+
guardrail and its reason, and the loop continues. Nothing executes.
|
|
438
|
+
- **Output deny** → the turn ends with no assistant text and
|
|
439
|
+
`stopReason: 'guardrail_denied'`; the reason rides the `guardrail` event.
|
|
440
|
+
- **Output rewrite** → the rewritten text is what `done` carries and what the
|
|
441
|
+
transcript keeps. A final `{ text: '', snapshot: <rewritten> }` event
|
|
442
|
+
follows, and a denial sends `{ text: '', snapshot: '' }`.
|
|
443
|
+
**A UI that folds `text` events by APPENDING the delta must treat an empty
|
|
444
|
+
delta as a replace from `snapshot`, or it will keep showing the original
|
|
445
|
+
while the transcript says otherwise** — `web-modules/chat`'s
|
|
446
|
+
`wireAgentEvents` does exactly this via `chatShell.setStreamedText()`.
|
|
447
|
+
|
|
448
|
+
Two scope limits worth knowing before you rely on the output chain:
|
|
449
|
+
|
|
450
|
+
- **Output guardrails see the text of the round that ENDS the turn.** Text
|
|
451
|
+
from a round that also called tools is intermediate reasoning the model
|
|
452
|
+
supersedes, and denying it has no coherent answer for the tool calls
|
|
453
|
+
emitted beside it — so it passes through unguarded and reaches the
|
|
454
|
+
transcript. Put the check on `toolCall` if what matters is what the model
|
|
455
|
+
is about to DO.
|
|
456
|
+
- **A guardrail may `ctx.remember(patch)`** — it receives the same context a
|
|
457
|
+
tool does. The write is drained as a `memory` event before `done`, never
|
|
458
|
+
dropped.
|
|
459
|
+
|
|
460
|
+
`onToolCall` is now exactly one guardrail of the `toolCall` kind: it still
|
|
461
|
+
works, still denies with its original wording, and runs ahead of the declared
|
|
462
|
+
chain.
|
|
463
|
+
|
|
464
|
+
A guardrail decides from the call and the harness's own context. Text
|
|
465
|
+
produced by a model, a tool, or a resource is DATA — a guardrail that lets
|
|
466
|
+
such text talk it into allowing something is not a guardrail.
|
|
467
|
+
|
|
468
|
+
## Tracing
|
|
469
|
+
|
|
470
|
+
Off by default. `trace: true` uses the console sink; a function IS the sink.
|
|
471
|
+
Every record is also a `trace` event on the stream:
|
|
472
|
+
|
|
473
|
+
```js
|
|
474
|
+
const agent = createAgent({ llm: { … }, trace: (record) => myLogger.debug(record) });
|
|
475
|
+
// { phase, name, at, sessionId?, data? }
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
`phase` is the closed vocabulary (`TRACE_PHASES`, guarded by
|
|
479
|
+
`isTracePhase()`): `prompt` (assembly: layer count, cached blocks, chars),
|
|
480
|
+
`request` / `response` (per provider call: round, model, tool count, stop
|
|
481
|
+
reason, ms), `tool` (per execution: name, id, ms, isError), `workflow`
|
|
482
|
+
(`step:start` / `step:verdict` with the verdict reason), `guardrail` (each
|
|
483
|
+
non-allow decision), `usage` (the turn's token totals), `mcp` (`connect` /
|
|
484
|
+
`connect:failed` per source — a tool a server contributed then executes
|
|
485
|
+
under `tool` like any other). A sink that throws
|
|
486
|
+
warns and the turn continues. There is no exporter protocol and no
|
|
487
|
+
dependency — an OpenTelemetry bridge is a caller-side function.
|
|
488
|
+
|
|
489
|
+
## Session memory
|
|
490
|
+
|
|
491
|
+
Per-session, JSON-safe, and written ONLY through `reduce()` — the same
|
|
492
|
+
single-writer rule the messages obey:
|
|
493
|
+
|
|
494
|
+
```js
|
|
495
|
+
import { remember } from '@adia-ai/agent';
|
|
496
|
+
|
|
497
|
+
session = reduce(session, remember({ patientId: 'p_7', tone: 'brief' }));
|
|
498
|
+
session.memory; // { patientId: 'p_7', tone: 'brief' }
|
|
499
|
+
session = reduce(session, remember({ tone: null })); // null deletes the key
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
A tool writes by ASKING: `ctx.remember(patch)` queues the patch, and the loop
|
|
503
|
+
yields it as a `memory` event right after that tool's result — and drains
|
|
504
|
+
anything still queued before `done`, so a final-round tool's write (or a
|
|
505
|
+
guardrail's, which shares the context) is never dropped. The asker never
|
|
506
|
+
touches state.
|
|
507
|
+
|
|
508
|
+
A patch that would not survive `JSON.stringify` → `JSON.parse` unchanged (a
|
|
509
|
+
Date, a function, `undefined`, a class instance) throws at the write, naming
|
|
510
|
+
the path. Memory rides the Session through `toJSON`/`fromJSON`; **where that
|
|
511
|
+
JSON is stored stays the caller's** — cross-session persistence is a seam
|
|
512
|
+
this package deliberately does not implement.
|
|
513
|
+
|
|
147
514
|
## Byte-identity baseline (CHAT-HARNESS law 2)
|
|
148
515
|
|
|
149
516
|
Every axis on `AgentConfig` is optional, and absence must produce a
|
|
@@ -170,3 +537,9 @@ like any other pinned contract.
|
|
|
170
537
|
`scriptClient(turns)` is a deterministic `LLMClient`: script `[{text, toolUse}]`
|
|
171
538
|
turns and drive the whole loop keylessly. It also records every `ChatOpts` it
|
|
172
539
|
was called with (`client.calls`) so tests can assert what reached the wire.
|
|
540
|
+
|
|
541
|
+
`mcp-fixture.mjs`'s `createTestMcpServer({ tools, resources, … })` is the
|
|
542
|
+
same idea for MCP: an in-process server behind a `fetch`-shaped function
|
|
543
|
+
(`fetchImpl`), speaking real JSON-RPC with session headers, cursor
|
|
544
|
+
pagination, `isError` results, and either a JSON or an SSE response body —
|
|
545
|
+
no network, no ports, no SDK.
|
package/agent.d.ts
CHANGED
|
@@ -15,7 +15,10 @@ import { type ResourceDef } from './resource.js';
|
|
|
15
15
|
import { type Session } from './session.js';
|
|
16
16
|
import type { AgentEvent } from './events.js';
|
|
17
17
|
import { type ClientMessage } from './frame.js';
|
|
18
|
+
import { type Guardrail } from './guardrail.js';
|
|
19
|
+
import { type McpServerReport, type McpSource } from './mcp.js';
|
|
18
20
|
import type { ToolContext, ToolDef } from './tools.js';
|
|
21
|
+
import { type TraceConfig } from './trace.js';
|
|
19
22
|
import { type Workflow, type WorkflowResult } from './workflow.js';
|
|
20
23
|
export interface AgentConfig {
|
|
21
24
|
/** Client defaults (model, proxyUrl, apiKey…) handed to createClient(),
|
|
@@ -31,12 +34,38 @@ export interface AgentConfig {
|
|
|
31
34
|
workflows?: Array<Workflow<never, never>> | Workflow[];
|
|
32
35
|
resources?: ResourceDef[];
|
|
33
36
|
maxToolRounds?: number;
|
|
34
|
-
/** Gate every tool call; false denies it (isError feedback, loop continues).
|
|
37
|
+
/** Gate every tool call; false denies it (isError feedback, loop continues).
|
|
38
|
+
* Superseded by `guardrails` — this is now adapted into exactly one
|
|
39
|
+
* guardrail of the `toolCall` kind and runs FIRST, ahead of the declared
|
|
40
|
+
* chain. Kept because it is the v0 seam consumers already call. */
|
|
35
41
|
onToolCall?: (call: ToolUse, ctx: ToolContext) => boolean | Promise<boolean>;
|
|
42
|
+
/** The deterministic layer between the model and the world: veto or
|
|
43
|
+
* rewrite tool calls and model output (see guardrail.ts). */
|
|
44
|
+
guardrails?: Guardrail[];
|
|
45
|
+
/** Structured tracing: `true` → the console sink, a function → that sink.
|
|
46
|
+
* Absent → no trace events, no console output, no request difference. */
|
|
47
|
+
trace?: TraceConfig;
|
|
48
|
+
/** Dependencies every workflow run may USE (llmAdapter, clients, stores).
|
|
49
|
+
* Reaches steps as `ctx.services`, merged with per-run services. */
|
|
50
|
+
services?: Record<string, unknown>;
|
|
51
|
+
/** External sources whose tools and resources join the declared ones —
|
|
52
|
+
* today, MCP servers (`mcpServer({ url })`). Connected LAZILY on the
|
|
53
|
+
* first turn and memoized, because `createAgent` is synchronous and a
|
|
54
|
+
* constructor that reaches the network is a constructor that can fail.
|
|
55
|
+
* Call `agent.connect()` to do it eagerly and inspect the result. */
|
|
56
|
+
integrations?: McpSource[];
|
|
36
57
|
}
|
|
37
58
|
export interface SendOpts {
|
|
38
59
|
signal?: AbortSignal;
|
|
39
60
|
}
|
|
61
|
+
/** `agent.run`'s options — the fourth argument may still be a bare
|
|
62
|
+
* `onEvent` function (the v0 shape). */
|
|
63
|
+
export interface RunOpts {
|
|
64
|
+
onEvent?: (event: AgentEvent) => void;
|
|
65
|
+
/** Per-run dependencies, merged over `AgentConfig.services`. */
|
|
66
|
+
services?: Record<string, unknown>;
|
|
67
|
+
signal?: AbortSignal;
|
|
68
|
+
}
|
|
40
69
|
export interface Agent {
|
|
41
70
|
createSession(id?: string): Session;
|
|
42
71
|
send(session: Session, text: string, opts?: SendOpts): AsyncGenerator<AgentEvent>;
|
|
@@ -45,9 +74,21 @@ export interface Agent {
|
|
|
45
74
|
* A silent-apply message (shouldRunTurn ⇒ false) yields nothing and
|
|
46
75
|
* never touches the loop — the caller already applied it directly. */
|
|
47
76
|
sendClientMessage(session: Session, msg: ClientMessage, opts?: SendOpts): AsyncGenerator<AgentEvent>;
|
|
48
|
-
run<TResult = unknown>(session: Session, workflowName: string, input?: unknown,
|
|
49
|
-
/** The assembled tool set
|
|
77
|
+
run<TResult = unknown>(session: Session, workflowName: string, input?: unknown, opts?: RunOpts | ((event: AgentEvent) => void)): Promise<WorkflowResult<TResult>>;
|
|
78
|
+
/** The assembled tool set — declared tools, tool-mode resources, and (once
|
|
79
|
+
* the sources have connected) everything MCP contributed. Before the
|
|
80
|
+
* first turn or an explicit `connect()`, it is the declared set. */
|
|
50
81
|
tools: ToolDef[];
|
|
82
|
+
/** Connect the declared `integrations` now and report what each server
|
|
83
|
+
* offered. Idempotent and memoized: the turn loop awaits the same
|
|
84
|
+
* promise. Rejects with `McpConnectionError` when a server is
|
|
85
|
+
* unreachable, mis-speaks the protocol, or collides with a declared tool
|
|
86
|
+
* name — never resolves to a quietly empty tool list. */
|
|
87
|
+
connect(): Promise<McpServerReport[]>;
|
|
88
|
+
/** Close every MCP session (best effort). A Node process that exits
|
|
89
|
+
* without it leaves sessions the server times out on its own; a browser
|
|
90
|
+
* page unload is the same. */
|
|
91
|
+
close(): Promise<void>;
|
|
51
92
|
}
|
|
52
93
|
export declare function createAgent(config: AgentConfig): Agent;
|
|
53
94
|
//# sourceMappingURL=agent.d.ts.map
|
package/agent.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["src/agent.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAYjE,OAAO,EAAiB,KAAK,WAAW,EAAE,MAAM,aAAa,CAAC;AAC9D,OAAO,EAA+B,KAAK,WAAW,EAAE,MAAM,eAAe,CAAC;AAC9E,OAAO,EAAiB,KAAK,OAAO,EAAE,MAAM,cAAc,CAAC;AAC3D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EAAqC,KAAK,aAAa,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["src/agent.ts"],"names":[],"mappings":"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"}
|