@theokit/agents 10.1.0 → 11.0.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/CHANGELOG.md +312 -0
- package/LICENSE +2 -2
- package/README.md +6 -5
- package/dist/{agent-compiler-CIPQkehU.d.ts → agent-compiler-tetgj6zR.d.ts} +3 -172
- package/dist/{agent-handle-Dgi4ZGbg.d.ts → agent-handle-rEWdERuv.d.ts} +113 -2
- package/dist/auth.js +1 -1
- package/dist/{bridge-entry-emr2PSXC.d.ts → bridge-entry-DRAzQ7UA.d.ts} +32 -200
- package/dist/bridge.d.ts +7 -6
- package/dist/bridge.js +3 -3
- package/dist/{chunk-CKRM5Q2K.js → chunk-4EHZG6KN.js} +122 -16
- package/dist/chunk-4EHZG6KN.js.map +1 -0
- package/dist/{chunk-M6HMASZC.js → chunk-7PNUTDBQ.js} +109 -14
- package/dist/chunk-7PNUTDBQ.js.map +1 -0
- package/dist/{chunk-QXGSF6WX.js → chunk-D2EFYZBV.js} +1 -1
- package/dist/{chunk-QXGSF6WX.js.map → chunk-D2EFYZBV.js.map} +1 -1
- package/dist/{chunk-4VHCH6IZ.js → chunk-LLIERPF3.js} +14 -1
- package/dist/chunk-LLIERPF3.js.map +1 -0
- package/dist/{chunk-QJN2LLPF.js → chunk-T5MBTKA2.js} +275 -164
- package/dist/chunk-T5MBTKA2.js.map +1 -0
- package/dist/client-react.d.ts +24 -2
- package/dist/client-react.js +2 -1
- package/dist/client-react.js.map +1 -1
- package/dist/client.d.ts +99 -5
- package/dist/client.js +3 -1
- package/dist/config.d.ts +2 -2
- package/dist/config.js +2 -2
- package/dist/{define-agent-BO5QSjV8.d.ts → define-agent-BnH1MBxs.d.ts} +16 -1
- package/dist/{delegation-scoring-CDvtrYKd.d.ts → delegation-scoring-CQtF2Zaf.d.ts} +345 -11
- package/dist/hooks.js +2 -2
- package/dist/hooks.js.map +1 -1
- package/dist/index.d.ts +22 -9
- package/dist/index.js +8 -3
- package/dist/index.js.map +1 -1
- package/dist/mcp-health.d.ts +66 -1
- package/dist/mcp-health.js +32 -1
- package/dist/mcp-health.js.map +1 -1
- package/dist/pty.js.map +1 -1
- package/dist/session.d.ts +58 -1
- package/dist/session.js +34 -1
- package/dist/session.js.map +1 -1
- package/dist/testing.d.ts +3 -2
- package/dist/testing.js +1 -1
- package/dist/tools.d.ts +4 -2
- package/dist/tools.js +4 -4
- package/dist/tools.js.map +1 -1
- package/dist/types-C16Wuh9E.d.ts +173 -0
- package/package.json +23 -10
- package/dist/chunk-4VHCH6IZ.js.map +0 -1
- package/dist/chunk-CKRM5Q2K.js.map +0 -1
- package/dist/chunk-M6HMASZC.js.map +0 -1
- package/dist/chunk-QJN2LLPF.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,317 @@
|
|
|
1
1
|
# @theokit/agents
|
|
2
2
|
|
|
3
|
+
## 11.0.0
|
|
4
|
+
|
|
5
|
+
### Major Changes
|
|
6
|
+
|
|
7
|
+
- bbdfc15: The server's raw error text no longer reaches the browser by default.
|
|
8
|
+
|
|
9
|
+
Every failure the framework reported to a browser carried the server's own words: a tool handler's
|
|
10
|
+
stderr verbatim in `tool-output-error.errorText`, a run failure's message verbatim in
|
|
11
|
+
`error.errorText` — whatever a driver, an HTTP client or a filesystem call put in the exception.
|
|
12
|
+
`ai@7`, speaking the same UIMessage protocol, masks by default and says why in its own comment:
|
|
13
|
+
"prevent leaking server error details to the client by default". There was no equivalent here, and
|
|
14
|
+
no seam to add one.
|
|
15
|
+
|
|
16
|
+
Both are masked now, through one `onError` hook on the serving boundary
|
|
17
|
+
(`streamAgentUIMessages`, `streamAgentTurnInProcess`, and `mountAgent` by pass-through), defaulting
|
|
18
|
+
to a fixed string. The full text is not lost: it still reaches the server's logs and the
|
|
19
|
+
`agent.run` span, and the hook receives it — what stops is it reaching a browser unless the host
|
|
20
|
+
decides otherwise.
|
|
21
|
+
|
|
22
|
+
**A tool's error text masks by the same default as a run's**, which the report that raised this
|
|
23
|
+
deliberately left open. The deciding fact is that masking costs the model nothing: the presenter is
|
|
24
|
+
downstream of the SDK loop, observing events the model has already consumed, so the copy being
|
|
25
|
+
masked is the browser's and only the browser's. Two different defaults for "server text reaching a
|
|
26
|
+
browser" would be a rule nobody could hold.
|
|
27
|
+
|
|
28
|
+
The failure `code` keeps travelling on its own data part, so consumers still distinguish failures
|
|
29
|
+
without matching on text — masking that removed the discriminator would push them back into the
|
|
30
|
+
habit that part exists to have removed.
|
|
31
|
+
|
|
32
|
+
**Breaking** for `@theokit/agents`: an application that read the server's message out of
|
|
33
|
+
`errorText` now reads `'An error occurred.'`. Pass `onError: (e) => e.message` to restore the old
|
|
34
|
+
behaviour explicitly, which is the point — it becomes a decision instead of a default.
|
|
35
|
+
|
|
36
|
+
- d4da51b: The agent route `generateAgentRoutes` mounts now speaks the wire this framework's clients read.
|
|
37
|
+
|
|
38
|
+
There were TWO SSE encoders for agent runs and they did not agree. The durable one writes
|
|
39
|
+
`data: <UIMessageChunk>` and a terminal `data: [DONE]`; this one wrote
|
|
40
|
+
`event: <type>` + `data: <framework StreamEvent>` — snake_case agent events rather than kebab-case
|
|
41
|
+
wire chunks — and no terminator at all.
|
|
42
|
+
|
|
43
|
+
`parseWireStream` validates each `data:` payload against `wireChunkSchema` and drops what fails
|
|
44
|
+
through a `warn` whose default sink is a no-op. So a `TheoApp` app mounted with `agentRuntime`
|
|
45
|
+
served `POST {route}/chat` in a format none of its own clients could read: zero chunks, no
|
|
46
|
+
assistant message, and a run reporting success with an empty answer — silent at every layer.
|
|
47
|
+
|
|
48
|
+
The events go through `presentUIMessageStream` now, the same translator `mountAgent` uses, so there
|
|
49
|
+
is one wire and one place that produces it. It also terminates: the missing `finish` chunk is what
|
|
50
|
+
a client keys "completed" on, so without it a finished run and a dropped connection were
|
|
51
|
+
indistinguishable there too.
|
|
52
|
+
|
|
53
|
+
**Breaking** for anyone who built their own reader against the old framework-event format on this
|
|
54
|
+
route. That is the trade the fix makes: a wire only a bespoke consumer could read, for the one every
|
|
55
|
+
client in this framework already speaks.
|
|
56
|
+
|
|
57
|
+
### Minor Changes
|
|
58
|
+
|
|
59
|
+
- a896e4a: A conversation survives a reload: `chatId` can be supplied and read.
|
|
60
|
+
|
|
61
|
+
`AgentClient` drew `#chatId` in its field declaration and offered no way to supply one or to read
|
|
62
|
+
the one it drew. The id is not decorative — the HTTP transport sends it as the top-level `id`, which
|
|
63
|
+
the server reads as the session id — so every `new AgentClient(...)` started a new conversation, and
|
|
64
|
+
reloading the page silently abandoned the thread the server still held.
|
|
65
|
+
|
|
66
|
+
```ts
|
|
67
|
+
const client = new AgentClient(transport, undefined, {
|
|
68
|
+
chatId: localStorage.getItem('chat') ?? undefined,
|
|
69
|
+
})
|
|
70
|
+
localStorage.setItem('chat', client.chatId)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Both halves matter. Reading without supplying lets an application persist an id it can never
|
|
74
|
+
restore; supplying without reading leaves it nothing to persist.
|
|
75
|
+
|
|
76
|
+
The default is unchanged and deliberately so: two clients built with no id are still two
|
|
77
|
+
conversations. Sharing one by default would let two unrelated tabs write into the same thread, which
|
|
78
|
+
is the opposite defect and the more dangerous one.
|
|
79
|
+
|
|
80
|
+
- d9e98e0: A model that takes no credential can delegate.
|
|
81
|
+
|
|
82
|
+
`createDelegateTool` refused to construct when any target was a `SubAgentSpec` and
|
|
83
|
+
`defaults.apiKey` was empty, and `delegate()` refused the same way deeper in its own call stack.
|
|
84
|
+
Both read "non-empty string" as the definition of authenticated — a safe reading while every
|
|
85
|
+
provider held a key, and no longer one now that a keyless provider (a model on the developer's own
|
|
86
|
+
machine) is reachable.
|
|
87
|
+
|
|
88
|
+
`apiKey: null` says the provider takes no credential. It is a distinct value from `''` on purpose:
|
|
89
|
+
an empty string is also what an unset environment variable produces, so accepting THAT would turn a
|
|
90
|
+
typo into an unauthenticated run. `undefined` still means the caller supplied nothing and is still
|
|
91
|
+
refused — at startup rather than at the model's first call, which is what the guard was for.
|
|
92
|
+
|
|
93
|
+
The refusal now names the option, so a reader who hits it is not left choosing between a fabricated
|
|
94
|
+
value and giving up.
|
|
95
|
+
|
|
96
|
+
- da4db56: A run the server still holds can be reached after a page reload.
|
|
97
|
+
|
|
98
|
+
The whole durable-reconnect machinery — sequence ids on every frame, the `RunEventCache`, the
|
|
99
|
+
`Last-Event-ID` replay, the `x-theokit-run-id` header — was built and reachable, and one link made
|
|
100
|
+
it unusable in the case with the highest user cost. The reconnect key lived in a private in-memory
|
|
101
|
+
field, so a reloaded page built a fresh transport with an empty cell and `reconnectToStream`
|
|
102
|
+
returned `null` before it reached the network. The run was alive, cached, replayable, and
|
|
103
|
+
unreachable; the user got an empty thread instead of the answer the server had already finished.
|
|
104
|
+
|
|
105
|
+
`HttpTransport` takes a `runIdStore` now — `{ get(): string | undefined; set(id): void }` —
|
|
106
|
+
defaulting to an in-memory cell, which is exactly what the private field was. Nothing changes for a
|
|
107
|
+
caller who passes nothing, including the reconnect-within-one-page-lifetime case that already
|
|
108
|
+
worked.
|
|
109
|
+
|
|
110
|
+
The MEDIUM is deliberately the consumer's decision. `sessionStorage` matches a run's lifetime better
|
|
111
|
+
than `localStorage`, and either would be a client library writing to browser storage nobody asked it
|
|
112
|
+
to write to, with privacy and SSR consequences. So the seam is injected and the package stores
|
|
113
|
+
nothing it was not handed a place for.
|
|
114
|
+
|
|
115
|
+
Deliberately NOT included: reconnecting automatically on load. This makes a cached run _reachable_;
|
|
116
|
+
reaching for it is a product decision nobody has asked for.
|
|
117
|
+
|
|
118
|
+
- 3762c7d: An agent can declare a ceiling on the tool-calling turns of a single run, and the served agent obeys
|
|
119
|
+
it. `AgentBuilder.create().maxIterations(5)` and `defineAgent({ maxIterations: 5 })` are new; the
|
|
120
|
+
ceiling `@Agent({ maxIterations })` and `@MainLoop({ maxIterations })` already accepted now reaches
|
|
121
|
+
the runtime as well.
|
|
122
|
+
|
|
123
|
+
The number was written by every authoring path and read by none of them once the agent was served:
|
|
124
|
+
the only code enforcing a ceiling was the reflective loop, which no served path calls. The adapter
|
|
125
|
+
now lowers `CompiledAgentOptions.maxIterations` to the SDK's `SendOptions.maxIterations` — its
|
|
126
|
+
documented per-send ceiling — on both the streaming path and the handle `toAgentFactory` serves,
|
|
127
|
+
where a caller's own value still wins for that turn.
|
|
128
|
+
|
|
129
|
+
An agent that declares no ceiling is untouched: the key is omitted entirely, so the SDK's own default
|
|
130
|
+
still applies and nothing about that run changes. A value that is not a positive integer is refused
|
|
131
|
+
where it was written rather than at the first send.
|
|
132
|
+
|
|
133
|
+
- 5f90ddd: A run that was cut short says so. The terminal `done` frame and the turn metadata a client reads off
|
|
134
|
+
`UIMessage.metadata` now carry an optional `stopReason` — `'step_limit'` when the loop ran out of
|
|
135
|
+
tool-calling turns while the model still wanted more, `'no_progress'` when the doom-loop guard
|
|
136
|
+
stopped it repeating identical tool calls. The observability span `agent.run` records the same value
|
|
137
|
+
as `stop.reason`.
|
|
138
|
+
|
|
139
|
+
Both outcomes reached the caller as an ordinary `done` before this, identical in every field to a run
|
|
140
|
+
that finished on its own, so a surface could not tell "the agent answered" from "the agent was cut off
|
|
141
|
+
with a tool call still pending". The SDK reported both on its `RunResult`; the adapter's locally-typed
|
|
142
|
+
`wait()` declared no field to read them from, so nothing read them.
|
|
143
|
+
|
|
144
|
+
This is not the rare case it looks like: the SDK's iteration budget defaults to 8, so every served run
|
|
145
|
+
needing a ninth tool-calling turn was being truncated and reported as finished — including runs of
|
|
146
|
+
agents that never declared a ceiling.
|
|
147
|
+
|
|
148
|
+
Two reasons rather than a `truncated` flag, because they demand opposite reactions: `step_limit` means
|
|
149
|
+
re-sending continues the work, `no_progress` means re-sending repeats the loop that was just cut.
|
|
150
|
+
Nothing here re-sends — the SDK owns continuation; this reports the outcome.
|
|
151
|
+
|
|
152
|
+
A run that finishes on its own is unchanged: the field is absent, not `undefined`, so absence keeps
|
|
153
|
+
meaning "the agent finished" and a consumer that has never heard of `stopReason` receives exactly what
|
|
154
|
+
it received before.
|
|
155
|
+
|
|
156
|
+
- c131170: A run whose connection drops mid-answer is reported as interrupted instead of finished. The agent
|
|
157
|
+
client used to settle a dropped stream in `status: 'done'` with `error` undefined and half a sentence
|
|
158
|
+
on screen — the spinner stopped, the error surface stayed empty, and the truncated turn was committed
|
|
159
|
+
to the thread as a completed one. `reconnect()` and the durable replay route were fully built and
|
|
160
|
+
unreachable, because the only trigger a consumer has for them is a status that never arrived.
|
|
161
|
+
|
|
162
|
+
`consumeChunkStream` (and `consumeUIMessageStream`) now return a `ChunkStreamOutcome` saying whether
|
|
163
|
+
the stream carried its terminal `finish` chunk and how many chunks crossed. When it did not,
|
|
164
|
+
`AgentClient` settles `status: 'error'` with an `AgentStreamInterruptedError` — a `TheokitAgentError`
|
|
165
|
+
with `code: 'AGENT_STREAM_INTERRUPTED'` and `isRetryable: true`, so `isTransientError` sees it and a
|
|
166
|
+
consumer decides on the type instead of on message text. The text already received stays on screen,
|
|
167
|
+
and `send()`'s existing rule keeps the truncated turn out of history.
|
|
168
|
+
|
|
169
|
+
The status is `'error'` rather than a new `'interrupted'` member on purpose: a new member fixes the
|
|
170
|
+
lie only for consumers who update their switch, while every other surface keeps rendering a finished
|
|
171
|
+
turn. Reusing `'error'` fixes it for all of them at once, and the reason for it lives in the typed
|
|
172
|
+
error where this framework already puts error discrimination.
|
|
173
|
+
|
|
174
|
+
This is a different axis from `stopReason` (#379), not another member of it. `stopReason` says why the
|
|
175
|
+
RUN stopped and rides the terminal frame's metadata; an interruption is the absence of that frame,
|
|
176
|
+
where the client cannot know why the run stopped because it never heard.
|
|
177
|
+
|
|
178
|
+
A stream that ends on its terminal `finish` chunk is unchanged, down to the fields on the snapshot.
|
|
179
|
+
A custom transport that never emitted `finish` — which no framework producer does, since
|
|
180
|
+
`presentUIMessageStream` emits it on every path including the error one — will now be reported as
|
|
181
|
+
interrupted, which is what it always was.
|
|
182
|
+
|
|
183
|
+
- e29e22e: `mcpInventory()`, from `@theokit/agents/mcp-health`: the per-server status of the agent's MCP
|
|
184
|
+
servers — `loaded`, `failed` or `ignored`, each with its reason.
|
|
185
|
+
|
|
186
|
+
`loadMcpJson` reads the configuration file; this reads what was observed. A server that failed its
|
|
187
|
+
handshake and a server the loader refused both appear, which is what a `/mcp`-style command needs and
|
|
188
|
+
what a configuration read cannot give.
|
|
189
|
+
|
|
190
|
+
Tool-level enumeration is not included and is not planned here: the resolved tool table lives inside
|
|
191
|
+
`@theokit/sdk`'s agent loop and no run event carries it.
|
|
192
|
+
|
|
193
|
+
- a5c6353: `readThreadHistory()` from `@theokit/agents/session`: read one thread's stored history with three
|
|
194
|
+
answers — `present`, `absent`, or `unreadable` with the reason.
|
|
195
|
+
|
|
196
|
+
Applications had two. Catching for a brand-new thread (which has no transcript yet) is mandatory, and
|
|
197
|
+
that catch also swallowed parse and permission failures, so a damaged conversation rendered as an
|
|
198
|
+
empty successful one.
|
|
199
|
+
|
|
200
|
+
`absent` still does not distinguish a lost conversation from a new one, and the type says so: the
|
|
201
|
+
thread id is minted client-side and nothing records that it was issued. That distinction belongs to
|
|
202
|
+
the caller who knows whether the id was restored from storage or freshly minted.
|
|
203
|
+
|
|
204
|
+
### Patch Changes
|
|
205
|
+
|
|
206
|
+
- c8022db: Four separate defects the SAST gate was reporting, each fixed at its cause.
|
|
207
|
+
|
|
208
|
+
**A generated property key could not contain a backslash.** The typed app-client emits a route
|
|
209
|
+
segment that is not a plain identifier as a quoted key, escaping the quote but not the escape
|
|
210
|
+
character — so a segment ending in `\` produced `'trail\'`, whose trailing backslash escapes the
|
|
211
|
+
closing quote and swallows the rest of the emitted line. A backslash is a legal POSIX filename
|
|
212
|
+
character, so it reaches this code from `server/routes/`.
|
|
213
|
+
|
|
214
|
+
**An internal error could forge log entries.** `sendError` logs an `INTERNAL_ERROR` with its
|
|
215
|
+
message and request id, and an exception message can be built from request data. A newline inside
|
|
216
|
+
it reached the log verbatim, which is enough to append lines of one's own — a fabricated entry
|
|
217
|
+
sitting in the log looking exactly like a real one. Both values are now rendered as one line.
|
|
218
|
+
|
|
219
|
+
**Stripping TOTP padding was quadratic.** `base32Decode` removed trailing `=` with an anchored
|
|
220
|
+
`/=+$/`, which retries from every start position, so a long run of `=` followed by anything else
|
|
221
|
+
costs O(n²) — on an authentication path. The comment defending it argued the input was short
|
|
222
|
+
enough ("10..50 chars typical"), which is an expectation rather than a bound. A scan back from the
|
|
223
|
+
end is linear and needs no such argument.
|
|
224
|
+
|
|
225
|
+
**The hook-output fence escaped only the first `<`.** `fenceHookOutput` neutralises an early
|
|
226
|
+
fence-close by escaping its `<`, using a form of `replace` that stops at the first occurrence. The
|
|
227
|
+
fence contains exactly one today, so nothing was wrong — and nothing said so, which made the
|
|
228
|
+
correctness of a prompt-injection guard depend on a property of a string literal several lines
|
|
229
|
+
away. `replaceAll` removes the dependency.
|
|
230
|
+
|
|
231
|
+
Behaviour is otherwise unchanged: an identifier-safe key, a message without newlines, a normal
|
|
232
|
+
base32 secret and a well-formed hook output all produce exactly what they produced before.
|
|
233
|
+
|
|
234
|
+
- 0e9e6dc: A human-in-the-loop tool call is one call on the wire again. A `@HumanInTheLoop` tool used to cross
|
|
235
|
+
as TWO `tool-input-available` chunks under two different `toolCallId`s — the approval id the HITL
|
|
236
|
+
plugin mints for its `approve/${approvalId}` callback, and the runtime tool-call id the SDK mints
|
|
237
|
+
when it dispatches the tool. Neither producer can adopt the other's id: the SDK's `pre_tool_call`
|
|
238
|
+
context carries `name`, `args`, `agentId` and `runId` and no call id at all, so the plugin has
|
|
239
|
+
nothing to key on, and the approval has to be published before the tool exists.
|
|
240
|
+
|
|
241
|
+
The translator correlates them now, so one logical call is announced once and its result carries the
|
|
242
|
+
same id. `tool-approval-request` keeps the plugin's id in `approvalId` — the callback URL is
|
|
243
|
+
unchanged and the same value still resolves the pause — and names the call it gates in `toolCallId`,
|
|
244
|
+
which is what that field was always for.
|
|
245
|
+
|
|
246
|
+
What this was costing: a consumer counting tool calls counted two, a UI grouping blocks by
|
|
247
|
+
`toolCallId` rendered two cards for one call and left a permanently pending approval part next to the
|
|
248
|
+
completed one, and the `agent.hitl` observability span opened on the approval id was never closed by
|
|
249
|
+
a result arriving under the runtime id — so its duration approximated the whole run instead of the
|
|
250
|
+
human's wait. That span now closes at the resume and carries `hitl.resume_observed: true`; the
|
|
251
|
+
end-of-run sweep that marks the opposite is back to being the exceptional path it describes, reached
|
|
252
|
+
when a pause genuinely never resumes (the client disconnected, the run failed mid-pause).
|
|
253
|
+
|
|
254
|
+
Ungated tools are untouched — the correlation is identity for a call no approval ever claims.
|
|
255
|
+
|
|
256
|
+
- 4411a59: A web application can now render a human-in-the-loop approval prompt. `useAgent` returns
|
|
257
|
+
`pendingApprovals` — one entry per decision the run is parked on, carrying the `approvalId` that
|
|
258
|
+
`approve()` takes, the gated tool's name, the arguments it is about to run with, the question
|
|
259
|
+
declared on the gate, and the window before it settles itself.
|
|
260
|
+
|
|
261
|
+
Before this the hook exposed the settle half of the gate and no way to reach the other half. The
|
|
262
|
+
store dropped the `tool-approval-request` frame on the way in, so its whole snapshot while a human
|
|
263
|
+
was deciding was `messages`, `thread`, `status: 'streaming'` and `error` — and the paused tool sat in
|
|
264
|
+
`state: 'input-available'`, which is exactly what an ungated tool looks like while it runs. An
|
|
265
|
+
application could not tell "working" from "waiting for you", and could not have named the decision if
|
|
266
|
+
it could. The only path left was polling `GET /api/agents/<name>/approvals` out of band.
|
|
267
|
+
|
|
268
|
+
The transcript carries it too: the gated call's own part moves to `state: 'approval-requested'` with
|
|
269
|
+
the id under `approval.id` while the decision is outstanding, and leaves that state when it is
|
|
270
|
+
settled. That is the ai-sdk reader's own vocabulary, not a new one — the differential oracle compares
|
|
271
|
+
the two readers on the paused run and the denied run and they reconstruct identically.
|
|
272
|
+
|
|
273
|
+
What the gate is asking travels as a transient `data-approval` part rather than on the approval frame
|
|
274
|
+
itself. The frame is shared vocabulary and `ai`'s validator for it is strict: a `question` added
|
|
275
|
+
there would not give an ai-sdk client a poorer prompt, it would delete the whole approval frame for
|
|
276
|
+
that client and re-create this defect on the other side of the wire. The tool's name and its
|
|
277
|
+
resolved input are not repeated anywhere — the `tool-input-available` frame already announces both
|
|
278
|
+
under the same call id, and both readers fold the frames into one part.
|
|
279
|
+
|
|
280
|
+
`approve(approvalId, decision)` is unchanged; what changes is that the store now hands the id over.
|
|
281
|
+
A tool with no gate produces exactly the same frames and exactly the same snapshot as before, with
|
|
282
|
+
`pendingApprovals` empty.
|
|
283
|
+
|
|
284
|
+
- 3126e58: A tool that failed reaches the caller as a tool that failed.
|
|
285
|
+
|
|
286
|
+
A tool whose handler threw — including one that threw on every attempt until its retries ran out —
|
|
287
|
+
crossed the wire as `tool-output-available`, the SUCCESS part of the UIMessage protocol, with the
|
|
288
|
+
error message sitting in the field a UI renders as the tool's answer, on a run that terminated with
|
|
289
|
+
an ordinary `done`. Nothing on the wire told a failed call from a call that worked, so a consumer
|
|
290
|
+
watching for a failure never fired, and a UI printed the failure as the result.
|
|
291
|
+
|
|
292
|
+
The failure signal was in hand the whole time. `@theokit/sdk` catches whatever a handler throws and
|
|
293
|
+
reports the call with `{stdout, stderr, exitCode}` — a non-zero code for a throw, a hook block, a
|
|
294
|
+
human denial, a timeout or an unknown tool — under `status: 'completed'`, which is the SDK's word
|
|
295
|
+
for "the call is over", not for "the call worked". Both translation sites read the status and
|
|
296
|
+
hardcoded `isError: false`, and the timeline dedup then dropped the only report carrying the exit
|
|
297
|
+
code as a duplicate of the report that structurally cannot carry one: the completion delta's payload
|
|
298
|
+
is a rendered string, and the message carrying the code always arrives second.
|
|
299
|
+
|
|
300
|
+
The exit code now travels. A failed call reaches the wire as `tool-output-error` with the message in
|
|
301
|
+
`errorText` — the presenter branch that emits it already existed and was never reachable from a
|
|
302
|
+
served run. A completion is held for one report rather than emitted immediately, so the second report
|
|
303
|
+
can contribute its exit code to the first instead of being discarded; exactly one result per call
|
|
304
|
+
still reaches the wire, and one that ends the run is flushed rather than held forever.
|
|
305
|
+
|
|
306
|
+
A call that succeeded is unchanged, chunk for chunk: it emits `tool-output-available` with the same
|
|
307
|
+
rendered output, under the same id, exactly once. A completion nobody reported an exit code for is
|
|
308
|
+
not called a failure — the `[stderr]` prefix in the rendered text is a string convention, and
|
|
309
|
+
classifying failures by matching error text is a mistake this codebase has already paid for once.
|
|
310
|
+
|
|
311
|
+
- Updated dependencies [bbdfc15]
|
|
312
|
+
- Updated dependencies [4411a59]
|
|
313
|
+
- @theokit/presenter@0.8.0
|
|
314
|
+
|
|
3
315
|
## 10.1.0
|
|
4
316
|
|
|
5
317
|
### Minor Changes
|
package/LICENSE
CHANGED
|
@@ -137,8 +137,8 @@
|
|
|
137
137
|
|
|
138
138
|
6. Trademarks. This License does not grant permission to use the trade
|
|
139
139
|
names, trademarks, service marks, or product names of the Licensor,
|
|
140
|
-
except as required for
|
|
141
|
-
reproducing the content of the NOTICE file.
|
|
140
|
+
except as required for reasonable and customary use in describing the
|
|
141
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
142
142
|
|
|
143
143
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
144
144
|
agreed to in writing, Licensor provides the Work (and each
|
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@ there is state or orchestration to hold, and forwarding the rest unchanged.
|
|
|
14
14
|
> **Enriching never reduces.** A symbol the SDK exposes and this layer does not forward is
|
|
15
15
|
> unreachable to whoever consumes the layer — and the only legal way out for them is to reimplement
|
|
16
16
|
> it. That happened: when `./auth` exported one value against the SDK's nineteen, a downstream
|
|
17
|
-
> product rewrote ~120 lines of credential mechanics
|
|
17
|
+
> product rewrote ~120 lines of credential mechanics in the
|
|
18
18
|
> repository root now requires a written decision per SDK symbol, so the next gap breaks CI instead
|
|
19
19
|
> of quietly costing someone a week.
|
|
20
20
|
|
|
@@ -29,11 +29,11 @@ you mount HTTP surfaces).
|
|
|
29
29
|
|
|
30
30
|
## Subpath map
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
Twenty entry points. Import the one you need — the barrel is not the API.
|
|
33
33
|
|
|
34
34
|
| Subpath | What lives there |
|
|
35
35
|
|---|---|
|
|
36
|
-
| `.` |
|
|
36
|
+
| `.` | `AgentBuilder.create()` — the authoring surface — plus the error hierarchy and shared types |
|
|
37
37
|
| `./bridge` | The SDK seam — `createSdkAgentStream`, event translation, the agent builder internals |
|
|
38
38
|
| `./testing` | Test seams: compiled-agent inspection, mock streams |
|
|
39
39
|
| `./sandbox` | Sandbox backends, postures, `sandboxWritePolicy` |
|
|
@@ -41,6 +41,7 @@ Nineteen entry points. Import the one you need — the barrel is not the API.
|
|
|
41
41
|
| `./interactive` | The interactive-shell backend contract |
|
|
42
42
|
| `./pty` | The node-pty implementation of that contract |
|
|
43
43
|
| `./auth` | Credential store, OAuth/device flow, `resolveCredential`, `assertSecureModes` |
|
|
44
|
+
| `./config` | Agent configuration, trust posture, and the instruction tree (`loadInstructionTree`) |
|
|
44
45
|
| `./tools` | Built-in tool factories (read, list, grep, git, patch, edit, shell, …) |
|
|
45
46
|
| `./client` | Transports for driving an agent from a client |
|
|
46
47
|
| `./client/react` | `useAgent` and the React bindings |
|
|
@@ -78,8 +79,8 @@ data, and a user cannot tell an explicit choice from a precedence fallback.
|
|
|
78
79
|
See [`CHANGELOG.md`](./CHANGELOG.md) — shipped inside this package, so `node_modules` answers the
|
|
79
80
|
question without a round trip to the repository.
|
|
80
81
|
|
|
81
|
-
For "which symbol delivers capability X, and in which version did it land", the
|
|
82
|
-
|
|
82
|
+
For "which symbol delivers capability X, and in which version did it land", the CHANGELOG entry that
|
|
83
|
+
shipped the symbol is the answer — every entry names the version it landed in.
|
|
83
84
|
|
|
84
85
|
## Boundaries this package keeps
|
|
85
86
|
|
|
@@ -1,175 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { InlineSkill, SystemPromptResolver, SettingSource, MemorySettings, SkillsSettings, ContextSettings } from '@theokit/sdk';
|
|
2
2
|
import { TheokitAgentError } from '@theokit/sdk/errors';
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Provider-agnostic extended-thinking knob (M1 reasoning-visibility). The common set autocompletes;
|
|
7
|
-
* `(string & {})` accepts provider-specific values forward-compat (mirrors `AgentRunErrorCode`) — the
|
|
8
|
-
* SDK validates the value against the model's catalog. Defined in this leaf module so every layer
|
|
9
|
-
* (`@Agent` config, compiler, runner, sdk-adapter) imports it without an import cycle.
|
|
10
|
-
*/
|
|
11
|
-
type ReasoningEffort = 'minimal' | 'low' | 'medium' | 'high' | 'xhigh' | (string & {});
|
|
12
|
-
/** Scalar agent configuration. */
|
|
13
|
-
interface AgentOptions {
|
|
14
|
-
/** Unique agent name (kebab-case). */
|
|
15
|
-
name: string;
|
|
16
|
-
/** HTTP route prefix (e.g., '/api/agents/support'). */
|
|
17
|
-
route: string;
|
|
18
|
-
/** LLM model identifier (e.g., 'claude-sonnet-4-5-20250929'). */
|
|
19
|
-
model?: string;
|
|
20
|
-
/** Extended-thinking effort; mapped to the SDK `ModelSelection.params` so the provider reasons. */
|
|
21
|
-
reasoningEffort?: ReasoningEffort;
|
|
22
|
-
/**
|
|
23
|
-
* Opt-in (default false): convert inline `<think>…</think>` in the text stream into `thinking`
|
|
24
|
-
* events (M2) — for models that emit reasoning as inline tags (qwen/deepseek) rather than via a
|
|
25
|
-
* native reasoning param. Off by default since a code assistant may emit literal `<think>` in text.
|
|
26
|
-
*/
|
|
27
|
-
parseThinkTags?: boolean;
|
|
28
|
-
/**
|
|
29
|
-
* Opt-in (default false): strip a leaked Hermes `<function=…></tool_call>` tool-call dialect out of
|
|
30
|
-
* the visible text (theocode#32) — for models (qwen/qwen3-coder) that intermittently emit tool calls
|
|
31
|
-
* as text instead of native `tool_calls`. Off by default since a code assistant may emit a literal
|
|
32
|
-
* `<function=` in answer/code text. Sibling of {@link parseThinkTags}.
|
|
33
|
-
*/
|
|
34
|
-
stripToolDialect?: boolean;
|
|
35
|
-
/**
|
|
36
|
-
* Opt-in (default false): recover a leaked Hermes `<function=…></tool_call>` tool-call dialect so the
|
|
37
|
-
* call actually EXECUTES (theokit#58 follow-up). Where {@link stripToolDialect} only hides the leaked
|
|
38
|
-
* block from the visible text, this enables the SDK's `extractToolCallsFromContent` on the chat route,
|
|
39
|
-
* so a `chat_completions` finish with ZERO native `tool_calls` has its text scanned for the dialect and
|
|
40
|
-
* any recovered calls are dispatched by the loop. For models (qwen/qwen3-coder via OpenRouter) that
|
|
41
|
-
* leak tool calls as text. Off by default (a code assistant may print a literal `<function=`); fail-open.
|
|
42
|
-
* Has effect only when {@link AgentOptions} routes a provider via `providers.routes`. Sibling of
|
|
43
|
-
* {@link stripToolDialect} — typically enabled together.
|
|
44
|
-
*/
|
|
45
|
-
recoverLeakedToolCalls?: boolean;
|
|
46
|
-
/** Enable SSE streaming (default: true). */
|
|
47
|
-
stream?: boolean;
|
|
48
|
-
/** Maximum loop iterations before forcing a terminal response. */
|
|
49
|
-
maxIterations?: number;
|
|
50
|
-
/** Timeout in milliseconds for the entire agent run. */
|
|
51
|
-
timeoutMs?: number;
|
|
52
|
-
/**
|
|
53
|
-
* System prompt for the agent. Either a static string OR a
|
|
54
|
-
* {@link SystemPromptResolver} computed per request (V4-L.1, Axis-B) — the SDK
|
|
55
|
-
* invokes the resolver each send with the run's `SystemPromptContext` (cwd, etc.).
|
|
56
|
-
*/
|
|
57
|
-
systemPrompt?: string | SystemPromptResolver;
|
|
58
|
-
}
|
|
59
|
-
/** Configuration stored by @MainLoop() decorator. */
|
|
60
|
-
interface MainLoopOptions {
|
|
61
|
-
/** Execution strategy. */
|
|
62
|
-
strategy?: 'simple-chat' | 'plan-act-reflect' | 'react';
|
|
63
|
-
/** Maximum iterations for this loop. */
|
|
64
|
-
maxIterations?: number;
|
|
65
|
-
/** Timeout in milliseconds. */
|
|
66
|
-
timeoutMs?: number;
|
|
67
|
-
}
|
|
68
|
-
/** Internal representation of a resolved @MainLoop. */
|
|
69
|
-
interface MainLoopMeta {
|
|
70
|
-
propertyKey: string | symbol;
|
|
71
|
-
strategy: 'simple-chat' | 'plan-act-reflect' | 'react';
|
|
72
|
-
maxIterations?: number;
|
|
73
|
-
timeoutMs?: number;
|
|
74
|
-
}
|
|
75
|
-
/** Configuration stored by @Toolbox() decorator. */
|
|
76
|
-
interface ToolboxOptions {
|
|
77
|
-
/** Namespace prefix for all tools in this toolbox (e.g., 'support'). */
|
|
78
|
-
namespace?: string;
|
|
79
|
-
}
|
|
80
|
-
/** Configuration stored by @Tool() decorator. */
|
|
81
|
-
interface ToolOptions {
|
|
82
|
-
/** Tool name (surfaced to LLM). */
|
|
83
|
-
name: string;
|
|
84
|
-
/** LLM-facing description. */
|
|
85
|
-
description: string;
|
|
86
|
-
/** Zod input schema — compiled to JSON Schema via defineTool(). */
|
|
87
|
-
input: z.ZodType;
|
|
88
|
-
/** Risk level (informational — feeds manifest + UI). */
|
|
89
|
-
risk?: 'low' | 'medium' | 'high';
|
|
90
|
-
}
|
|
91
|
-
/** Budget configuration for @Budget() decorator. */
|
|
92
|
-
interface BudgetOptions {
|
|
93
|
-
/** Maximum cost in USD for this scope. */
|
|
94
|
-
maxCostUsd: number;
|
|
95
|
-
/** Rolling window for budget tracking. */
|
|
96
|
-
window?: 'daily' | 'monthly';
|
|
97
|
-
}
|
|
98
|
-
/** Approval configuration for @RequiresApproval() decorator. */
|
|
99
|
-
interface ApprovalOptions {
|
|
100
|
-
/** Reason shown to the approver. */
|
|
101
|
-
reason: string;
|
|
102
|
-
}
|
|
103
|
-
/** Policy handler function type. */
|
|
104
|
-
type PolicyHandler = (user: {
|
|
105
|
-
roles: string[];
|
|
106
|
-
}) => boolean;
|
|
107
|
-
/**
|
|
108
|
-
* M53 — moved here from the `@HumanInTheLoop` decorator, which is being deleted: the type is
|
|
109
|
-
* consumed by `compileHitlGates` and the toolbox capability, not by the decorator alone.
|
|
110
|
-
*/
|
|
111
|
-
type TimeoutAction = 'abort' | 'proceed' | 'retry';
|
|
112
|
-
interface HumanInTheLoopOptions {
|
|
113
|
-
/** Question shown to the human approver. */
|
|
114
|
-
question: string;
|
|
115
|
-
/** Timeout in milliseconds before onTimeout fires (default: 300_000 = 5 min). */
|
|
116
|
-
timeout?: number;
|
|
117
|
-
/** Action when timeout expires (default: 'abort'). */
|
|
118
|
-
onTimeout?: TimeoutAction;
|
|
119
|
-
/** Show the tool input to the approver (default: true). */
|
|
120
|
-
showInput?: boolean;
|
|
121
|
-
/**
|
|
122
|
-
* M20 — an optional JSON-schema descriptor of the custom payload the approver may attach (edited
|
|
123
|
-
* args, a review note). Carried into the `approval_required` event + `GET /approvals` so the UI
|
|
124
|
-
* knows what to collect. A plain JSON object, not a live Zod schema (keeps the wire serializable).
|
|
125
|
-
*/
|
|
126
|
-
payloadSchema?: Record<string, unknown>;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
type McpServersMap = Record<string, McpServerConfig>;
|
|
130
|
-
/** M53 — moved from the `@ProjectContext` decorator being deleted; read by the compiler. */
|
|
131
|
-
type IndexStrategy = 'tree-sitter' | 'regex' | 'none';
|
|
132
|
-
type RelevanceStrategy = 'git-history' | 'import-graph' | 'semantic' | 'manual';
|
|
133
|
-
interface ProjectContextOptions {
|
|
134
|
-
/** Files that mark the project root (searched upward from cwd). */
|
|
135
|
-
rootMarkers?: string[];
|
|
136
|
-
/** How to index the codebase for structural understanding. */
|
|
137
|
-
indexStrategy?: IndexStrategy;
|
|
138
|
-
/** Maximum files to include in context per request. */
|
|
139
|
-
maxFilesInContext?: number;
|
|
140
|
-
/** How to rank file relevance when selecting context. */
|
|
141
|
-
relevanceStrategy?: RelevanceStrategy;
|
|
142
|
-
/** Glob patterns to exclude from indexing and context. */
|
|
143
|
-
ignorePatterns?: string[];
|
|
144
|
-
/** File extensions to include in indexing (default: all text files). */
|
|
145
|
-
includeExtensions?: string[];
|
|
146
|
-
}
|
|
147
|
-
type CheckpointStrategy = 'after-tool-call' | 'after-iteration' | 'manual';
|
|
148
|
-
type CheckpointStorage = 'memory' | 'filesystem' | 'drizzle' | 'redis';
|
|
149
|
-
interface CheckpointOptions {
|
|
150
|
-
/** Where to persist checkpoints. */
|
|
151
|
-
storage?: CheckpointStorage;
|
|
152
|
-
/** When to auto-checkpoint (default: 'after-tool-call'). */
|
|
153
|
-
strategy?: CheckpointStrategy;
|
|
154
|
-
/** Maximum checkpoints to retain per run (rolling window). */
|
|
155
|
-
maxCheckpoints?: number;
|
|
156
|
-
/** Time-to-live in ms before checkpoints expire (default: 3_600_000 = 1h). */
|
|
157
|
-
ttl?: number;
|
|
158
|
-
}
|
|
159
|
-
type MemoryProvider = 'built-in' | 'honcho' | 'supermemory' | 'mem0';
|
|
160
|
-
type MemoryScope = 'per-user' | 'per-agent' | 'per-tenant' | 'global';
|
|
161
|
-
interface MemoryOptions {
|
|
162
|
-
/** Memory provider backend. */
|
|
163
|
-
provider?: MemoryProvider;
|
|
164
|
-
/** Enable semantic search via embeddings. */
|
|
165
|
-
embeddings?: boolean;
|
|
166
|
-
/** Enable full-text search (FTS5). */
|
|
167
|
-
fts?: boolean;
|
|
168
|
-
/** Memory isolation scope (default: 'per-user'). */
|
|
169
|
-
scope?: MemoryScope;
|
|
170
|
-
/** Maximum facts to retain per scope (0 = unlimited). */
|
|
171
|
-
maxFacts?: number;
|
|
172
|
-
}
|
|
3
|
+
import { R as ReasoningEffort, a as MemoryOptions, P as ProjectContextOptions, M as McpServersMap, H as HumanInTheLoopOptions, C as CheckpointOptions, T as ToolOptions, A as ApprovalOptions, B as BudgetOptions } from './types-C16Wuh9E.js';
|
|
173
4
|
|
|
174
5
|
/**
|
|
175
6
|
* M9 (theokit-ai-first) — guardrail contract + typed errors.
|
|
@@ -400,4 +231,4 @@ interface CompiledAgentOptions {
|
|
|
400
231
|
skillsResolver?: SkillsSelection;
|
|
401
232
|
}
|
|
402
233
|
|
|
403
|
-
export { type
|
|
234
|
+
export { type CompiledAgentOptions as C, type Guardrail as G, type SkillsSelection as S, type ToolWalkResult as T, type CompiledTool as a, CostBudgetExceededError as b, type GuardrailAction as c, type GuardrailPhase as d, type GuardrailResult as e, GuardrailViolationError as f, type SkillsRequestContext as g, type ToolboxWalkResult as h, compileTools as i, resolveEnabledSkills as r };
|