@statelyai/agent 2.0.0-alpha.28 → 2.0.0-alpha.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.
Files changed (80) hide show
  1. package/dist/ai-sdk.cjs +150 -27
  2. package/dist/ai-sdk.d.cts +59 -36
  3. package/dist/ai-sdk.d.mts +59 -36
  4. package/dist/ai-sdk.mjs +148 -24
  5. package/dist/{decision-RQ0iv0oZ.mjs → decision-BHkvuImD.mjs} +74 -8
  6. package/dist/{decision-CmM8-HHz.cjs → decision-CIb7ddIv.cjs} +113 -30
  7. package/dist/{text-logic-f5F5sCdc.cjs → event-log-BZRfBRd7.cjs} +814 -7
  8. package/dist/{text-logic-CIqHAmBi.mjs → event-log-DJCMHek_.mjs} +680 -4
  9. package/dist/{event-log-store-BIPOz-di.d.mts → event-log-store-CIIJohjj.d.mts} +90 -5
  10. package/dist/{event-log-store-2xzFmgr2.d.cts → event-log-store-CLAC9q2Y.d.cts} +90 -5
  11. package/dist/event-log-store-DZR4IIWe.cjs +95 -0
  12. package/dist/event-log-store-DsP6GCRh.mjs +84 -0
  13. package/dist/index.cjs +576 -58
  14. package/dist/index.d.cts +104 -510
  15. package/dist/index.d.mts +104 -510
  16. package/dist/index.mjs +547 -32
  17. package/dist/log.cjs +77 -20
  18. package/dist/log.d.cts +5 -5
  19. package/dist/log.d.mts +5 -5
  20. package/dist/log.mjs +58 -3
  21. package/dist/machines.cjs +1 -1
  22. package/dist/machines.d.cts +15 -15
  23. package/dist/machines.d.mts +15 -15
  24. package/dist/machines.mjs +1 -1
  25. package/dist/model-source-BCKg4qJ1.cjs +44 -0
  26. package/dist/model-source-BaHwJ1wK.mjs +39 -0
  27. package/dist/openai.cjs +11 -11
  28. package/dist/openai.d.cts +28 -26
  29. package/dist/openai.d.mts +28 -26
  30. package/dist/openai.mjs +3 -3
  31. package/dist/otel.cjs +5 -4
  32. package/dist/otel.d.cts +5 -7
  33. package/dist/otel.d.mts +5 -7
  34. package/dist/otel.mjs +5 -3
  35. package/dist/{run-agent-xNTxpt82.mjs → run-agent-BpHqkaZw.mjs} +181 -44
  36. package/dist/{run-agent-COXXc0RW.d.mts → run-agent-CAVt5R-r.d.mts} +95 -30
  37. package/dist/{run-agent-ByO-Bxyd.d.cts → run-agent-CTlMc3Jb.d.cts} +95 -30
  38. package/dist/{run-agent-Dmf8GV2d.cjs → run-agent-ctLCvNgL.cjs} +230 -88
  39. package/dist/{setup-agent-BuanDXeh.mjs → setup-agent-7rV_cujV.mjs} +15 -9
  40. package/dist/setup-agent-CGELn1qZ.d.mts +577 -0
  41. package/dist/setup-agent-CLPCkXsu.d.cts +577 -0
  42. package/dist/{setup-agent-DV4cTou-.cjs → setup-agent-DoUk7EdC.cjs} +31 -20
  43. package/dist/tanstack-ai.cjs +627 -0
  44. package/dist/tanstack-ai.d.cts +204 -0
  45. package/dist/tanstack-ai.d.mts +204 -0
  46. package/dist/tanstack-ai.mjs +618 -0
  47. package/dist/testing.cjs +204 -985
  48. package/dist/testing.d.cts +132 -347
  49. package/dist/testing.d.mts +132 -347
  50. package/dist/testing.mjs +191 -973
  51. package/dist/{text-logic-IjfYy9DP.d.cts → text-logic-BRYOPWvY.d.mts} +52 -12
  52. package/dist/{text-logic-D-czVPYp.d.mts → text-logic-DmciuVUQ.d.cts} +52 -12
  53. package/dist/{types-BgNbNKnJ.d.mts → types-Dp5_Ru4R.d.cts} +2 -2
  54. package/dist/{types-hR5f5YXa.d.cts → types-Dp5_Ru4R.d.mts} +2 -2
  55. package/dist/usage-BkM3z02Y.d.mts +17 -0
  56. package/dist/usage-Cm_ovz9V.d.cts +17 -0
  57. package/dist/{utils-_q7F-AZu.d.mts → utils-BnXdWih3.d.mts} +1 -2
  58. package/dist/{utils-o4YRdHJX.d.cts → utils-ebVpCYoF.d.cts} +1 -2
  59. package/dist/validate.cjs +239 -8
  60. package/dist/validate.d.cts +110 -3
  61. package/dist/validate.d.mts +110 -3
  62. package/dist/validate.mjs +238 -7
  63. package/dist/verify-COFWyprJ.mjs +1181 -0
  64. package/dist/verify-CVlsTZ-V.cjs +1222 -0
  65. package/dist/verify-Cn86rT8l.d.mts +414 -0
  66. package/dist/verify-Dzwd4Mvk.d.cts +414 -0
  67. package/package.json +44 -31
  68. package/readme.md +40 -28
  69. package/skills/add-evals/SKILL.md +129 -0
  70. package/skills/add-traces/SKILL.md +135 -0
  71. package/skills/generate-machine/SKILL.md +143 -69
  72. package/skills/graph-engineering/SKILL.md +33 -0
  73. package/skills/improve-machine/SKILL.md +241 -0
  74. package/skills/improve-machine/reference.md +101 -0
  75. package/skills/loop-to-machine/SKILL.md +183 -0
  76. package/skills/loop-to-machine/policy.md +158 -0
  77. package/dist/event-log-store-2pAFiWuc.mjs +0 -576
  78. package/dist/event-log-store-C-COhIXP.cjs +0 -702
  79. package/dist/usage-BdEjFuIi.mjs +0 -5
  80. package/dist/usage-efOQPdf_.cjs +0 -10
@@ -0,0 +1,618 @@
1
+ import { L as parseProviderOutput, R as providerOutputSchema, X as getJsonSchemaSync, Y as getJsonSchema, ct as AgentTruncatedError, j as getAgentOutputMode, tt as isStandardSchema, w as AGENT_USAGE_TOKEN_FIELDS } from "./event-log-DJCMHek_.mjs";
2
+ import { c as renderDecisionAttempts } from "./decision-BHkvuImD.mjs";
3
+ import { t as resolveModelSource } from "./model-source-BaHwJ1wK.mjs";
4
+ import { chat, maxIterations } from "@tanstack/ai";
5
+ //#region src/tanstack-ai/index.ts
6
+ /**
7
+ * The TanStack AI adapter: builds the `{ generateText, streamText, decide }`
8
+ * executor set consumed by `createAgentRuntime`/`executeAgentRequest`, mapped
9
+ * onto TanStack AI's `chat()` activity and any text adapter
10
+ * (`openaiText(...)`, `anthropicText(...)`, `geminiText(...)`, …).
11
+ *
12
+ * `@tanstack/ai` is an OPTIONAL peer dependency, imported only here. No
13
+ * provider package (`@tanstack/ai-openai`, …) is a dependency: the host passes
14
+ * its own text adapters. Compare `@statelyai/agent/ai-sdk`, the same contract
15
+ * over the Vercel AI SDK.
16
+ */
17
+ /** Thrown for a message part TanStack AI's model messages cannot carry —
18
+ * dropping it would silently change what the model sees. */
19
+ function unsupportedPart(role, type) {
20
+ throw new Error(`createTanStackAiExecutors: a ${role} message part of type '${type}' has no TanStack AI equivalent. Send text, image, tool-call, or tool-result parts, or convert it before the request.`);
21
+ }
22
+ /** A `DataContent`/`URL` image as a TanStack AI content source: an http(s)
23
+ * string or `URL` is a `url` source; a `data:` URL, a bare base64 string, and
24
+ * raw bytes are a base64 `data` source. */
25
+ function toImagePart(image, mediaType) {
26
+ const mimeType = mediaType ?? "image/jpeg";
27
+ if (image instanceof URL || typeof image === "string" && /^https?:/.test(image)) return {
28
+ type: "image",
29
+ source: {
30
+ type: "url",
31
+ value: String(image)
32
+ }
33
+ };
34
+ if (typeof image === "string") {
35
+ const dataUrl = /^data:([^;,]+)?(?:;base64)?,(.*)$/.exec(image);
36
+ return {
37
+ type: "image",
38
+ source: {
39
+ type: "data",
40
+ value: dataUrl ? dataUrl[2] : image,
41
+ mimeType: dataUrl?.[1] ?? mimeType
42
+ }
43
+ };
44
+ }
45
+ const bytes = image instanceof Uint8Array ? image : new Uint8Array(image);
46
+ const encode = globalThis.btoa;
47
+ if (!encode) throw new Error("createTanStackAiExecutors: binary image parts need a global `btoa` to base64-encode. Pass the image as a URL or a base64 string instead.");
48
+ let binary = "";
49
+ for (const byte of bytes) binary += String.fromCharCode(byte);
50
+ return {
51
+ type: "image",
52
+ source: {
53
+ type: "data",
54
+ value: encode(binary),
55
+ mimeType
56
+ }
57
+ };
58
+ }
59
+ /** One tool message per `ToolResultPart` — the tool role is one message per result. */
60
+ function toToolMessage(part) {
61
+ return {
62
+ role: "tool",
63
+ toolCallId: part.toolCallId,
64
+ content: part.output.type === "text" || part.output.type === "error-text" ? part.output.value : JSON.stringify(part.output.value)
65
+ };
66
+ }
67
+ /**
68
+ * Maps `AgentTextRequest.messages`/`system`/`prompt` onto `chat()`'s
69
+ * `systemPrompts` and `messages`. TanStack AI's model messages have no
70
+ * `system` role — a provider adapter may forward one as a user turn — so every
71
+ * system message, and the request's `system`, becomes a system prompt.
72
+ */
73
+ function toTanStackAiMessages(request) {
74
+ const systemPrompts = request.system ? [request.system] : [];
75
+ if (!request.messages) return {
76
+ systemPrompts,
77
+ messages: [{
78
+ role: "user",
79
+ content: request.prompt ?? ""
80
+ }]
81
+ };
82
+ return {
83
+ systemPrompts,
84
+ messages: request.messages.flatMap((message) => {
85
+ switch (message.role) {
86
+ case "system":
87
+ systemPrompts.push(message.content);
88
+ return [];
89
+ case "user":
90
+ if (typeof message.content === "string") return [{
91
+ role: "user",
92
+ content: message.content
93
+ }];
94
+ return [{
95
+ role: "user",
96
+ content: message.content.map((part) => {
97
+ switch (part.type) {
98
+ case "text": return {
99
+ type: "text",
100
+ content: part.text
101
+ };
102
+ case "image": return toImagePart(part.image, part.mediaType);
103
+ default: return unsupportedPart("user", part.type);
104
+ }
105
+ })
106
+ }];
107
+ case "assistant": {
108
+ if (typeof message.content === "string") return [{
109
+ role: "assistant",
110
+ content: message.content
111
+ }];
112
+ let text = "";
113
+ const toolCalls = [];
114
+ const trailing = [];
115
+ for (const part of message.content) switch (part.type) {
116
+ case "text":
117
+ text += part.text;
118
+ break;
119
+ case "tool-call":
120
+ toolCalls.push({
121
+ id: part.toolCallId,
122
+ type: "function",
123
+ function: {
124
+ name: part.toolName,
125
+ arguments: JSON.stringify(part.input ?? {})
126
+ }
127
+ });
128
+ break;
129
+ case "tool-result":
130
+ trailing.push(toToolMessage(part));
131
+ break;
132
+ default: unsupportedPart("assistant", part.type);
133
+ }
134
+ return [{
135
+ role: "assistant",
136
+ content: text || null,
137
+ ...toolCalls.length > 0 ? { toolCalls } : {}
138
+ }, ...trailing];
139
+ }
140
+ case "tool": return message.content.map(toToolMessage);
141
+ }
142
+ })
143
+ };
144
+ }
145
+ /** A tool `description` may be a string or, for an AI SDK v7 tool, a function
146
+ * of the call's context. TanStack AI tools carry the static form. */
147
+ function staticDescription(descriptor) {
148
+ if (typeof descriptor === "function") return "";
149
+ return typeof descriptor.description === "string" ? descriptor.description : "";
150
+ }
151
+ /**
152
+ * One TanStack AI tool per `AgentTools` entry. A tool with an `execute` (or a
153
+ * bare function) is a server tool `chat()` runs in its loop; a tool without one
154
+ * is a client tool, so the run stops at its call and hands the call back.
155
+ * Input schemas travel as JSON Schema.
156
+ */
157
+ function toTanStackAiTools(tools) {
158
+ return Object.entries(tools).flatMap(([name, descriptor]) => {
159
+ if (!descriptor) return [];
160
+ const inputSchema = typeof descriptor === "function" ? void 0 : descriptor.inputSchema;
161
+ const execute = typeof descriptor === "function" ? descriptor : descriptor.execute;
162
+ return [{
163
+ name,
164
+ description: staticDescription(descriptor),
165
+ inputSchema: (isStandardSchema(inputSchema) ? getJsonSchemaSync(inputSchema) : void 0) ?? { type: "object" },
166
+ ...typeof execute === "function" ? { execute: (input) => execute(input) } : {}
167
+ }];
168
+ });
169
+ }
170
+ /** One client tool per candidate decision event: no `execute`, so the run
171
+ * stops at the model's first call — the "tool-per-event" recipe. */
172
+ function toTanStackAiEventTools(events) {
173
+ return events.map((event) => ({
174
+ name: event.toolName,
175
+ description: `Choose the '${event.type}' move.`,
176
+ inputSchema: getJsonSchemaSync(event.inputSchema) ?? { type: "object" }
177
+ }));
178
+ }
179
+ /** Drops keys whose value is `undefined`, so a spread cannot erase what it lands on. */
180
+ function defined(settings) {
181
+ return Object.fromEntries(Object.entries(settings).filter(([, value]) => value !== void 0));
182
+ }
183
+ /**
184
+ * Maps the request's portable generation settings and tool choice onto the
185
+ * `modelOptions` of the adapter's provider. TanStack AI has no portable
186
+ * sampling or tool-choice options — each provider adapter names its own — so
187
+ * the mapping is per provider (`adapter.name`):
188
+ *
189
+ * - `openai` (Responses API): `temperature`, `top_p`, `max_output_tokens`,
190
+ * `tool_choice`. The API has no `top_k`, `seed`, or `stop`; those are dropped.
191
+ * - `anthropic`: `temperature`, `top_p`, `top_k`, `max_tokens`,
192
+ * `stop_sequences`, `tool_choice`. `seed` is dropped.
193
+ * - `gemini`: `temperature`, `topP`, `topK`, `maxOutputTokens`, `seed`,
194
+ * `stopSequences`, `toolConfig.functionCallingConfig`.
195
+ *
196
+ * Any other provider gets none of them: map them yourself through the
197
+ * `settings` option, which receives the request.
198
+ */
199
+ function toTanStackAiModelOptions(adapter, request) {
200
+ const { toolChoice } = request;
201
+ switch (adapter.name) {
202
+ case "openai": return defined({
203
+ temperature: request.temperature,
204
+ top_p: request.topP,
205
+ max_output_tokens: request.maxOutputTokens,
206
+ tool_choice: typeof toolChoice === "object" ? {
207
+ type: "function",
208
+ name: toolChoice.name
209
+ } : toolChoice
210
+ });
211
+ case "anthropic": return defined({
212
+ temperature: request.temperature,
213
+ top_p: request.topP,
214
+ top_k: request.topK,
215
+ max_tokens: request.maxOutputTokens,
216
+ stop_sequences: request.stopSequences,
217
+ tool_choice: typeof toolChoice === "object" ? {
218
+ type: "tool",
219
+ name: toolChoice.name
220
+ } : toolChoice === "required" ? { type: "any" } : toolChoice === void 0 ? void 0 : { type: toolChoice }
221
+ });
222
+ case "gemini": return defined({
223
+ temperature: request.temperature,
224
+ topP: request.topP,
225
+ topK: request.topK,
226
+ maxOutputTokens: request.maxOutputTokens,
227
+ seed: request.seed,
228
+ stopSequences: request.stopSequences,
229
+ toolConfig: toolChoice === void 0 ? void 0 : { functionCallingConfig: typeof toolChoice === "object" ? {
230
+ mode: "ANY",
231
+ allowedFunctionNames: [toolChoice.name]
232
+ } : { mode: toolChoice === "required" ? "ANY" : toolChoice.toUpperCase() } }
233
+ });
234
+ default: return {};
235
+ }
236
+ }
237
+ /**
238
+ * Messages for a decision request, with prior failed `attempts` rendered as
239
+ * appended user messages so retries converge. Mirrors the AI SDK adapter's
240
+ * `toDecisionMessages`.
241
+ */
242
+ function toDecisionMessages(request) {
243
+ const mapped = toTanStackAiMessages(request);
244
+ for (const attempt of renderDecisionAttempts(request)) mapped.messages.push({
245
+ role: "user",
246
+ content: attempt.content
247
+ });
248
+ return mapped;
249
+ }
250
+ /**
251
+ * Folds TanStack AI's `TokenUsage` onto the flat {@link AgentCallUsage} field
252
+ * names core aggregates. A field the provider omitted stays omitted, so it
253
+ * never contributes a `0` to a run's partial sums.
254
+ */
255
+ function toAgentCallUsage(usage) {
256
+ if (!usage) return;
257
+ const reasoningTokens = usage.completionTokensDetails?.reasoningTokens;
258
+ const cachedInputTokens = usage.promptTokensDetails?.cachedTokens;
259
+ return defined({
260
+ inputTokens: usage.promptTokens,
261
+ outputTokens: usage.completionTokens,
262
+ totalTokens: usage.totalTokens,
263
+ reasoningTokens,
264
+ cachedInputTokens
265
+ });
266
+ }
267
+ /** Adds two model turns' usage together, field by field. A field neither
268
+ * reported stays absent. */
269
+ function addUsage(total, next) {
270
+ if (!total || !next) return total ?? next;
271
+ const sum = {};
272
+ for (const field of AGENT_USAGE_TOKEN_FIELDS) if (total[field] !== void 0 || next[field] !== void 0) sum[field] = (total[field] ?? 0) + (next[field] ?? 0);
273
+ return sum;
274
+ }
275
+ /**
276
+ * Maps a TanStack AI finish reason onto the portable {@link AgentFinishReason}.
277
+ * Anything unrecognized, including a missing reason, lands on `'other'`.
278
+ */
279
+ function toAgentFinishReason(finishReason) {
280
+ switch (finishReason) {
281
+ case "stop": return "stop";
282
+ case "length": return "length";
283
+ case "tool_calls": return "tool-calls";
284
+ case "content_filter": return "content-filter";
285
+ default: return "other";
286
+ }
287
+ }
288
+ function toToolResultOutput(content) {
289
+ if (typeof content !== "string") return {
290
+ type: "json",
291
+ value: content
292
+ };
293
+ try {
294
+ return {
295
+ type: "json",
296
+ value: JSON.parse(content)
297
+ };
298
+ } catch {
299
+ return {
300
+ type: "text",
301
+ value: content
302
+ };
303
+ }
304
+ }
305
+ /** A `RUN_ERROR` chunk as the `Error` it reports, `code` included. */
306
+ var TanStackAiRunError = class extends Error {
307
+ code;
308
+ constructor(message, code) {
309
+ super(message);
310
+ this.name = "TanStackAiRunError";
311
+ this.code = code;
312
+ }
313
+ };
314
+ /**
315
+ * Reads a `chat()` stream to the end. Text arrives as `TEXT_MESSAGE_CONTENT`
316
+ * deltas; every model turn ends in its own `RUN_FINISHED`, so a tool loop's
317
+ * usage is summed and the last finish reason wins. A provider failure arrives
318
+ * as a `RUN_ERROR` chunk rather than a throw, and is thrown here.
319
+ */
320
+ async function readRun(stream, onDelta) {
321
+ const summary = {
322
+ text: "",
323
+ structured: void 0,
324
+ usage: void 0,
325
+ finishReason: "other",
326
+ toolCalls: [],
327
+ messages: [],
328
+ chunks: []
329
+ };
330
+ const args = /* @__PURE__ */ new Map();
331
+ let assistant;
332
+ let results;
333
+ const flushAssistant = () => {
334
+ if (assistant?.length) summary.messages.push({
335
+ role: "assistant",
336
+ content: assistant
337
+ });
338
+ assistant = void 0;
339
+ };
340
+ const flushResults = () => {
341
+ if (results?.length) summary.messages.push({
342
+ role: "tool",
343
+ content: results
344
+ });
345
+ results = void 0;
346
+ };
347
+ const assistantParts = () => {
348
+ flushResults();
349
+ return assistant ??= [];
350
+ };
351
+ for await (const chunk of stream) {
352
+ summary.chunks.push(chunk);
353
+ switch (chunk.type) {
354
+ case "TEXT_MESSAGE_CONTENT": {
355
+ summary.text += chunk.delta;
356
+ onDelta?.(chunk.delta);
357
+ const parts = assistantParts();
358
+ const last = parts.at(-1);
359
+ if (last?.type === "text") last.text += chunk.delta;
360
+ else parts.push({
361
+ type: "text",
362
+ text: chunk.delta
363
+ });
364
+ break;
365
+ }
366
+ case "TOOL_CALL_START": {
367
+ const part = {
368
+ type: "tool-call",
369
+ toolCallId: chunk.toolCallId,
370
+ toolName: chunk.toolCallName,
371
+ input: {}
372
+ };
373
+ assistantParts().push(part);
374
+ args.set(chunk.toolCallId, {
375
+ part,
376
+ json: ""
377
+ });
378
+ break;
379
+ }
380
+ case "TOOL_CALL_ARGS": {
381
+ const call = args.get(chunk.toolCallId);
382
+ if (call) call.json += chunk.delta;
383
+ break;
384
+ }
385
+ case "TOOL_CALL_RESULT":
386
+ flushAssistant();
387
+ (results ??= []).push({
388
+ type: "tool-result",
389
+ toolCallId: chunk.toolCallId,
390
+ toolName: args.get(chunk.toolCallId)?.part.toolName ?? "",
391
+ output: toToolResultOutput(chunk.content)
392
+ });
393
+ break;
394
+ case "CUSTOM":
395
+ if (chunk.name === "structured-output.complete") summary.structured = chunk.value?.object;
396
+ break;
397
+ case "RUN_FINISHED":
398
+ summary.finishReason = toAgentFinishReason(chunk.finishReason ?? (chunk.metadata?.tanstack)?.finishReason);
399
+ if (chunk.usage && !Array.isArray(chunk.usage)) summary.usage = addUsage(summary.usage, toAgentCallUsage(chunk.usage));
400
+ break;
401
+ case "RUN_ERROR": throw new TanStackAiRunError(chunk.message, chunk.code ?? chunk.error?.code);
402
+ }
403
+ }
404
+ for (const { part, json } of args.values()) part.input = json ? JSON.parse(json) : {};
405
+ flushAssistant();
406
+ flushResults();
407
+ summary.toolCalls = [...args.values()].map(({ part }) => ({
408
+ toolCallId: part.toolCallId,
409
+ toolName: part.toolName,
410
+ input: part.input
411
+ }));
412
+ return summary;
413
+ }
414
+ function isAdapterWithSettings(entry) {
415
+ return "adapter" in entry && !("kind" in entry);
416
+ }
417
+ /** The text adapter inside a map entry, whichever form it takes. */
418
+ function toTextAdapter(entry) {
419
+ return isAdapterWithSettings(entry) ? entry.adapter : entry;
420
+ }
421
+ function truncated(request, info, partialOutput, cause) {
422
+ return new AgentTruncatedError(`createTanStackAiExecutors: request '${request.name ?? "(unnamed)"}' hit the output token limit — its structured output never completed. Raise \`maxOutputTokens\`, shorten the input, or ask for a smaller output schema.`, {
423
+ requestName: request.name ?? "(unnamed)",
424
+ ...info?.requestId !== void 0 ? { requestId: info.requestId } : {},
425
+ ...partialOutput !== void 0 ? { partialOutput } : {},
426
+ ...cause !== void 0 ? { cause } : {}
427
+ });
428
+ }
429
+ function isTruncationError(error) {
430
+ const { code, message } = error ?? {};
431
+ return code === "max_tokens" || code === "incomplete" || message === "max_output_tokens";
432
+ }
433
+ function usageField(usage) {
434
+ return usage ? { usage } : {};
435
+ }
436
+ /**
437
+ * Runs one `chat()` call to the end. `chat()` takes an `AbortController`, not
438
+ * a signal, so the runtime's signal is bridged to one — and unhooked once the
439
+ * run settles, so a long-lived signal does not collect a listener per request.
440
+ * An aborted `chat()` stream just ends, so the abort is surfaced as the throw
441
+ * it is.
442
+ */
443
+ async function runChat(options, signal, onDelta) {
444
+ const controller = new AbortController();
445
+ const abort = () => controller.abort(signal?.reason);
446
+ if (signal?.aborted) abort();
447
+ else signal?.addEventListener("abort", abort, { once: true });
448
+ let run;
449
+ try {
450
+ run = await readRun(chat({
451
+ ...options,
452
+ abortController: controller
453
+ }), onDelta);
454
+ } finally {
455
+ signal?.removeEventListener("abort", abort);
456
+ }
457
+ if (signal?.aborted) throw signal.reason ?? /* @__PURE__ */ new Error("createTanStackAiExecutors: the request was aborted.");
458
+ return run;
459
+ }
460
+ /**
461
+ * A request's tool choice belongs to its first model turn. `chat()` reuses
462
+ * `modelOptions` for every turn of its tool loop, so a forced choice left in
463
+ * place would force another tool call after each result, and the request could
464
+ * never answer. From the second turn on, the choice's keys go back to what the
465
+ * host set, or away.
466
+ */
467
+ function toolChoiceOnFirstTurn(keys, later) {
468
+ return {
469
+ name: "statelyai:tool-choice-first-turn",
470
+ onConfig: (ctx, config) => {
471
+ if (ctx.iteration === 0) return;
472
+ const modelOptions = { ...config.modelOptions };
473
+ for (const key of keys) if (key in later) modelOptions[key] = later[key];
474
+ else delete modelOptions[key];
475
+ return { modelOptions };
476
+ }
477
+ };
478
+ }
479
+ function createTanStackAiExecutors(source) {
480
+ const options = resolveModelSource("createTanStackAiExecutors", source);
481
+ const resolve = (request, portable) => {
482
+ let adapter;
483
+ let entrySettings;
484
+ if (options.resolveModel) adapter = options.resolveModel(request.model);
485
+ else {
486
+ const entry = options.models?.[request.model];
487
+ if (!options.models) throw new Error(`createTanStackAiExecutors: no model resolver configured for '${request.model}'.`);
488
+ if (!entry) throw new Error(`createTanStackAiExecutors: unknown model '${request.model}'.`);
489
+ adapter = toTextAdapter(entry);
490
+ entrySettings = isAdapterWithSettings(entry) ? entry.settings : void 0;
491
+ }
492
+ const host = (typeof options.settings === "function" ? options.settings(request) : options.settings) ?? {};
493
+ const merged = {
494
+ ...host,
495
+ ...entrySettings
496
+ };
497
+ const base = {
498
+ ...host.modelOptions,
499
+ ...entrySettings?.modelOptions
500
+ };
501
+ const generation = toTanStackAiModelOptions(adapter, {
502
+ ...portable,
503
+ toolChoice: void 0
504
+ });
505
+ const firstTurn = toTanStackAiModelOptions(adapter, portable);
506
+ const choiceKeys = Object.keys(firstTurn).filter((key) => !(key in generation));
507
+ return {
508
+ adapter,
509
+ settings: {
510
+ ...merged,
511
+ modelOptions: {
512
+ ...base,
513
+ ...firstTurn
514
+ },
515
+ ...choiceKeys.length > 0 ? { middleware: [...merged.middleware ?? [], toolChoiceOnFirstTurn(choiceKeys, {
516
+ ...base,
517
+ ...generation
518
+ })] } : {}
519
+ }
520
+ };
521
+ };
522
+ const loopBound = (request) => maxIterations(typeof request.maxSteps === "number" && Number.isFinite(request.maxSteps) ? Math.max(1, Math.floor(request.maxSteps)) : 1);
523
+ const generateText = async (request, info) => {
524
+ const { adapter, settings } = resolve(request, request);
525
+ const tools = toTanStackAiTools(request.tools);
526
+ const structured = getAgentOutputMode(request.outputSchema) === "structured";
527
+ const outputSchema = structured ? await getJsonSchema(providerOutputSchema(request.outputSchema, { reasoning: request.includeReasoning })) : void 0;
528
+ let run;
529
+ try {
530
+ run = await runChat({
531
+ ...settings,
532
+ adapter,
533
+ ...toTanStackAiMessages(request),
534
+ ...tools.length > 0 ? { tools } : {},
535
+ ...outputSchema ? {
536
+ outputSchema,
537
+ stream: true
538
+ } : {},
539
+ agentLoopStrategy: loopBound(request)
540
+ }, info?.signal);
541
+ } catch (error) {
542
+ throw structured && isTruncationError(error) ? truncated(request, info, void 0, error) : error;
543
+ }
544
+ if (outputSchema) {
545
+ if (run.finishReason === "length") throw truncated(request, info, run.structured ?? run.text);
546
+ const parsed = parseProviderOutput(request, run.structured !== void 0 ? run.structured : JSON.parse(run.text));
547
+ return {
548
+ result: parsed.result,
549
+ ...typeof parsed.reasoning === "string" ? { reasoning: parsed.reasoning } : {},
550
+ ...usageField(run.usage),
551
+ finishReason: run.finishReason,
552
+ toolCalls: run.toolCalls,
553
+ messages: run.messages,
554
+ raw: run.chunks
555
+ };
556
+ }
557
+ return {
558
+ result: run.text,
559
+ ...usageField(run.usage),
560
+ finishReason: run.finishReason,
561
+ toolCalls: run.toolCalls,
562
+ messages: run.messages,
563
+ raw: run.chunks
564
+ };
565
+ };
566
+ const streamText = async (request, info) => {
567
+ if (getAgentOutputMode(request.outputSchema) === "structured") throw new Error("createTanStackAiExecutors: streamText streams text — a request declaring a structured output schema must be routed to generateText.");
568
+ const { adapter, settings } = resolve(request, request);
569
+ const tools = toTanStackAiTools(request.tools ?? {});
570
+ const run = await runChat({
571
+ ...settings,
572
+ adapter,
573
+ ...toTanStackAiMessages(request),
574
+ ...tools.length > 0 ? { tools } : {},
575
+ agentLoopStrategy: loopBound(request)
576
+ }, info?.signal, (delta) => info?.onChunk?.(delta));
577
+ return {
578
+ result: run.text,
579
+ ...usageField(run.usage),
580
+ finishReason: run.finishReason,
581
+ messages: run.messages,
582
+ raw: run.chunks
583
+ };
584
+ };
585
+ const decide = async (request, info) => {
586
+ const { adapter, settings } = resolve(request, {
587
+ ...request,
588
+ toolChoice: "required"
589
+ });
590
+ const run = await runChat({
591
+ ...settings,
592
+ adapter,
593
+ ...toDecisionMessages(request),
594
+ tools: toTanStackAiEventTools(request.events),
595
+ agentLoopStrategy: maxIterations(1)
596
+ }, info?.signal);
597
+ const toolCall = run.toolCalls[0];
598
+ if (!toolCall) throw new Error("createTanStackAiExecutors: decide — model did not call an event tool.");
599
+ const chosenEvent = request.events.find((event) => event.toolName === toolCall.toolName);
600
+ if (!chosenEvent) throw new Error(`createTanStackAiExecutors: decide — model called unknown tool '${toolCall.toolName}'.`);
601
+ return {
602
+ event: {
603
+ ...toolCall.input && typeof toolCall.input === "object" ? toolCall.input : {},
604
+ type: chosenEvent.type
605
+ },
606
+ ...usageField(run.usage),
607
+ finishReason: run.finishReason,
608
+ raw: run.chunks
609
+ };
610
+ };
611
+ return {
612
+ generateText,
613
+ streamText,
614
+ decide
615
+ };
616
+ }
617
+ //#endregion
618
+ export { createTanStackAiExecutors, toAgentCallUsage, toAgentFinishReason, toDecisionMessages, toTanStackAiEventTools, toTanStackAiMessages, toTanStackAiModelOptions, toTanStackAiTools, toTextAdapter };