expo-ai-kit 0.5.0 → 0.7.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/build/types.d.ts CHANGED
@@ -129,6 +129,179 @@ export type LLMStreamEvent = {
129
129
  * Callback function for streaming events.
130
130
  */
131
131
  export type LLMStreamCallback = (event: LLMStreamEvent) => void;
132
+ /**
133
+ * The set of JSON Schema primitive `type` values understood by
134
+ * {@link generateObject}'s local validator.
135
+ */
136
+ export type JSONSchemaType = 'object' | 'array' | 'string' | 'number' | 'integer' | 'boolean' | 'null';
137
+ /**
138
+ * A JSON Schema describing the shape you want {@link generateObject} to return.
139
+ *
140
+ * A pragmatic subset is enforced locally — `type`, `properties`, `required`,
141
+ * `items`, and `enum` — which covers most extraction shapes. Any other JSON
142
+ * Schema keywords you include (e.g. `description`, `minLength`) are still sent
143
+ * to the model to guide it, but are not validated on-device. Keep schemas small:
144
+ * on-device models follow flat, shallow shapes far more reliably than deeply
145
+ * nested ones.
146
+ */
147
+ export type JSONSchema = {
148
+ /** Expected JSON type (or a union of types). */
149
+ type?: JSONSchemaType | JSONSchemaType[];
150
+ /** Human-readable hint passed through to the model. */
151
+ description?: string;
152
+ /** For `object` schemas: the schema of each named property. */
153
+ properties?: Record<string, JSONSchema>;
154
+ /** For `object` schemas: property names that must be present. */
155
+ required?: string[];
156
+ /** For `array` schemas: the schema each element must satisfy. */
157
+ items?: JSONSchema;
158
+ /** Restrict the value to this set of literals. */
159
+ enum?: ReadonlyArray<string | number | boolean | null>;
160
+ /** Other JSON Schema keywords are accepted and forwarded to the model. */
161
+ [key: string]: unknown;
162
+ };
163
+ /**
164
+ * Options for {@link generateObject}.
165
+ */
166
+ export type GenerateObjectOptions = {
167
+ /**
168
+ * System prompt used when the messages array has no system message. Defaults
169
+ * to a structured-output-oriented instruction. If a system message is present
170
+ * in the array, this is ignored (the schema instruction is appended to it).
171
+ */
172
+ systemPrompt?: string;
173
+ /**
174
+ * Abort the request. Behaves like {@link LLMSendOptions.signal} — the returned
175
+ * promise rejects with an INFERENCE_CANCELLED {@link ModelError}.
176
+ */
177
+ signal?: AbortSignal;
178
+ /**
179
+ * How many times to re-prompt the model when its output is not valid JSON or
180
+ * does not match the schema. Each repair feeds the error back to the model.
181
+ * Defaults to 2 (i.e. up to 3 generations total).
182
+ */
183
+ maxRepairAttempts?: number;
184
+ };
185
+ /**
186
+ * Result of {@link generateObject}.
187
+ */
188
+ export type GenerateObjectResult<T = unknown> = {
189
+ /** The parsed value, validated against the schema. */
190
+ object: T;
191
+ /** The raw model output that produced `object` (useful for debugging). */
192
+ text: string;
193
+ };
194
+ /**
195
+ * A tool (function) the model may call to fetch data or take an action.
196
+ *
197
+ * The model never runs anything itself — it *proposes* a call (a name + JSON
198
+ * arguments), {@link generateText} validates the arguments against `parameters`,
199
+ * and only then invokes your `execute`. The result is fed back into the
200
+ * conversation so the model can use it to produce its final answer.
201
+ *
202
+ * @typeParam TArgs - Shape of the validated arguments passed to `execute`.
203
+ * @typeParam TResult - What `execute` returns (serialized back to the model).
204
+ */
205
+ export type Tool<TArgs = any, TResult = any> = {
206
+ /**
207
+ * What the tool does and when to use it. This is how the model decides
208
+ * whether a request matches this tool — make it specific and action-oriented.
209
+ */
210
+ description: string;
211
+ /**
212
+ * JSON Schema for the tool's arguments. The model is told to conform to it,
213
+ * and the args it proposes are validated against it (same pragmatic subset as
214
+ * {@link generateObject}) before `execute` runs. Keep it small and shallow.
215
+ */
216
+ parameters: JSONSchema;
217
+ /**
218
+ * Runs the tool with the validated arguments and returns a result.
219
+ *
220
+ * **Optional on purpose.** If you omit it, {@link generateText} does not run
221
+ * anything — it stops with `finishReason: 'tool-calls'` and hands you the
222
+ * proposed call so you can confirm, gate, or execute it yourself.
223
+ */
224
+ execute?: (args: TArgs) => TResult | Promise<TResult>;
225
+ };
226
+ /** A map of tool name → {@link Tool}, passed to {@link generateText}. */
227
+ export type ToolSet = Record<string, Tool>;
228
+ /** A tool invocation the model proposed (name + validated arguments). */
229
+ export type ToolCall = {
230
+ /** The tool's key in the {@link ToolSet}. */
231
+ toolName: string;
232
+ /** Arguments the model supplied, validated against the tool's `parameters`. */
233
+ args: unknown;
234
+ };
235
+ /** The outcome of running a {@link ToolCall} via its `execute`. */
236
+ export type ToolResult = {
237
+ /** The tool's key in the {@link ToolSet}. */
238
+ toolName: string;
239
+ /** The arguments that were passed to `execute`. */
240
+ args: unknown;
241
+ /** Whatever `execute` returned (or `{ error }` if it threw). */
242
+ result: unknown;
243
+ };
244
+ /** One model round-trip in the {@link generateText} loop. */
245
+ export type StepResult = {
246
+ /** Assistant text produced this step (empty when the step only called a tool). */
247
+ text: string;
248
+ /** Tool calls proposed this step (at most one in the current protocol). */
249
+ toolCalls: ToolCall[];
250
+ /** Results of the tool calls executed this step. */
251
+ toolResults: ToolResult[];
252
+ };
253
+ /**
254
+ * Why {@link generateText} stopped.
255
+ *
256
+ * - `'stop'`: the model produced a final text answer.
257
+ * - `'tool-calls'`: stopped because a proposed tool has no `execute` — the call
258
+ * is returned for you to handle (human-in-the-loop).
259
+ * - `'max-steps'`: hit the `maxSteps` cap while still calling tools. Raise the cap.
260
+ */
261
+ export type GenerateTextFinishReason = 'stop' | 'tool-calls' | 'max-steps';
262
+ /**
263
+ * Options for {@link generateText}.
264
+ */
265
+ export type GenerateTextOptions = {
266
+ /** Tools the model may call. Omit (or pass `{}`) for a plain text generation. */
267
+ tools?: ToolSet;
268
+ /**
269
+ * Maximum number of model round-trips (each call + tool execution is one step).
270
+ * Bounds the tool-calling chain so it can't run away. Defaults to 5.
271
+ */
272
+ maxSteps?: number;
273
+ /**
274
+ * System prompt used when the messages array has no system message. The tool
275
+ * instructions are appended to it (or to the array's system message if present).
276
+ */
277
+ systemPrompt?: string;
278
+ /**
279
+ * Abort the request. Behaves like {@link LLMSendOptions.signal} — the returned
280
+ * promise rejects with an INFERENCE_CANCELLED {@link ModelError}.
281
+ */
282
+ signal?: AbortSignal;
283
+ /**
284
+ * How many times to re-prompt within a step when the model emits a malformed
285
+ * tool call, an unknown tool name, or arguments that fail schema validation.
286
+ * Defaults to 2. If it still can't comply, `generateText` throws INFERENCE_FAILED.
287
+ */
288
+ maxRepairAttempts?: number;
289
+ };
290
+ /**
291
+ * Result of {@link generateText}.
292
+ */
293
+ export type GenerateTextResult = {
294
+ /** The final assistant text (empty if it stopped on a tool call without `execute`). */
295
+ text: string;
296
+ /** Every step taken, in order — useful for tracing or debugging. */
297
+ steps: StepResult[];
298
+ /** All tool calls across every step, flattened. */
299
+ toolCalls: ToolCall[];
300
+ /** All tool results across every step, flattened. */
301
+ toolResults: ToolResult[];
302
+ /** Why generation stopped. See {@link GenerateTextFinishReason}. */
303
+ finishReason: GenerateTextFinishReason;
304
+ };
132
305
  /**
133
306
  * A built-in model provided by the OS (e.g. Apple Foundation Models, ML Kit).
134
307
  * These are always available on supported devices -- no download needed.
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AA+NA;;GAEG;AACH,MAAM,OAAO,UAAW,SAAQ,KAAK;IACnC,IAAI,CAAiB;IACrB,OAAO,CAAS;IAEhB,YAAY,IAAoB,EAAE,OAAe,EAAE,OAAgB;QACjE,KAAK,CAAC,OAAO,IAAI,GAAG,IAAI,KAAK,OAAO,EAAE,CAAC,CAAC;QACxC,IAAI,CAAC,IAAI,GAAG,YAAY,CAAC;QACzB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACzB,CAAC;CACF","sourcesContent":["/**\n * Hardware backend for on-device model inference.\n *\n * - 'auto': Try GPU first, fall back to CPU (default)\n * - 'gpu': Force GPU — faster (~40-50 tok/s) but needs more memory\n * - 'cpu': Force CPU — slower (~2-5 tok/s) but works on low-RAM devices\n */\nexport type InferenceBackend = 'auto' | 'gpu' | 'cpu';\n\n/**\n * Sampling / generation parameters applied to a model session.\n *\n * Support is per-backend (on-device runtimes expose different knobs), so these\n * are best-effort — unsupported fields are ignored rather than erroring:\n *\n * | field | Gemma (LiteRT-LM) | Apple Foundation Models | ML Kit |\n * |-------------|:-----------------:|:-----------------------:|:------:|\n * | temperature | ✓ | ✓ | — |\n * | topK | ✓ | — | — |\n * | topP | ✓ | — | — |\n * | seed | ✓ (iOS only) | — | — |\n * | maxTokens | — | ✓ (max output) | — |\n *\n * Notes:\n * - Gemma/LiteRT-LM has no per-generation output-token cap (its `maxNumTokens`\n * is the total KV-cache size, set at load), so `maxTokens` is not honored\n * there. Its sampler (topK/topP/temperature[/seed]) is fixed at conversation\n * creation, which is why generation config lives on setModel() rather than\n * per-call. `seed` is currently wired on iOS only.\n * - The ML Kit built-in (Android default) does not yet apply generation config;\n * it uses its own defaults.\n */\nexport type GenerationConfig = {\n /** Sampling temperature. Lower = more deterministic. Typically 0.0–2.0. */\n temperature?: number;\n /** Nucleus sampling: number of top logits to consider. Must be > 0. */\n topK?: number;\n /** Nucleus sampling: cumulative probability threshold in [0, 1]. */\n topP?: number;\n /** RNG seed for reproducible sampling (Gemma only). */\n seed?: number;\n /** Maximum number of output tokens to generate (Apple FM / ML Kit only). */\n maxTokens?: number;\n};\n\n/**\n * Options for setModel.\n */\nexport type SetModelOptions = {\n /** Hardware backend to use for inference. Defaults to 'auto'. */\n backend?: InferenceBackend;\n /**\n * Default sampling parameters for this model session. Applied when the model\n * is activated and used for all subsequent sendMessage/streamMessage calls\n * until the next setModel(). See {@link GenerationConfig} for per-backend support.\n */\n generation?: GenerationConfig;\n};\n\n/**\n * Role in a conversation message.\n */\nexport type LLMRole = 'system' | 'user' | 'assistant';\n\n/**\n * A single message in a conversation.\n */\nexport type LLMMessage = {\n role: LLMRole;\n content: string;\n};\n\n/**\n * Options for sendMessage.\n */\nexport type LLMSendOptions = {\n /**\n * Default system prompt to use if no system message is provided in the messages array.\n * If a system message exists in the array, this is ignored.\n */\n systemPrompt?: string;\n /**\n * Abort the request. When the signal fires, the returned promise rejects with\n * an INFERENCE_CANCELLED {@link ModelError}.\n *\n * Note: on-device, non-streaming generation cannot always be interrupted\n * mid-decode — abort always unblocks the caller, but the model may keep\n * computing in the background until it finishes, during which a new\n * sendMessage/streamMessage will throw INFERENCE_BUSY. To truly interrupt a\n * long generation, prefer streamMessage().stop().\n */\n signal?: AbortSignal;\n};\n\n/**\n * Response from sendMessage.\n */\nexport type LLMResponse = {\n /** The generated response text */\n text: string;\n};\n\n/**\n * Options for streamMessage.\n */\nexport type LLMStreamOptions = {\n /**\n * Default system prompt to use if no system message is provided in the messages array.\n * If a system message exists in the array, this is ignored.\n */\n systemPrompt?: string;\n};\n\n/**\n * Handle returned by streamMessage.\n */\nexport type LLMStreamHandle = {\n /** Resolves with the final text when streaming completes or is stopped. */\n promise: Promise<LLMResponse>;\n /** Stop streaming. Resolves `promise` with the text accumulated so far. */\n stop: () => void;\n};\n\n/**\n * Event payload for streaming tokens.\n */\nexport type LLMStreamEvent = {\n /** Unique identifier for this streaming session */\n sessionId: string;\n /** The token/chunk of text received */\n token: string;\n /** Accumulated text so far */\n accumulatedText: string;\n /** Whether this is the final chunk */\n isDone: boolean;\n};\n\n/**\n * Callback function for streaming events.\n */\nexport type LLMStreamCallback = (event: LLMStreamEvent) => void;\n\n\n// ============================================================================\n// Model Types\n// ============================================================================\n\n/**\n * A built-in model provided by the OS (e.g. Apple Foundation Models, ML Kit).\n * These are always available on supported devices -- no download needed.\n */\nexport type BuiltInModel = {\n /** Unique model identifier (e.g. 'apple-fm', 'mlkit') */\n id: string;\n /** Human-readable model name */\n name: string;\n /** Whether this model is available on the current device/OS */\n available: boolean;\n /** Platform this model is associated with */\n platform: 'ios' | 'android';\n /** Maximum context window in tokens */\n contextWindow: number;\n};\n\n/**\n * A downloadable model that the user manages (download, load, delete).\n * These require explicit download before use.\n */\nexport type DownloadableModel = {\n /** Unique model identifier (e.g. 'gemma-e2b', 'gemma-e4b') */\n id: string;\n /** Human-readable model name */\n name: string;\n /** Parameter count label (e.g. '2.3B') */\n parameterCount: string;\n /** Download file size in bytes */\n sizeBytes: number;\n /** Maximum context window in tokens */\n contextWindow: number;\n /** Minimum device RAM in bytes required to run */\n minRamBytes: number;\n /** Whether this device meets the model's minimum RAM requirement */\n meetsRequirements: boolean;\n /** Current lifecycle status */\n status: DownloadableModelStatus;\n};\n\n/**\n * Lifecycle status of a downloadable model.\n *\n * - 'not-downloaded': Model file is not on disk\n * - 'downloading': Model file is being downloaded\n * - 'downloaded': Model file is on disk but not loaded into memory. Call\n * setModel() to load it. This survives app restarts, so use it to decide\n * whether a (re-)download is needed.\n * - 'loading': File is on disk, model is being loaded into memory for inference\n * - 'ready': Model is loaded in memory and ready for inference\n */\nexport type DownloadableModelStatus =\n | 'not-downloaded'\n | 'downloading'\n | 'downloaded'\n | 'loading'\n | 'ready';\n\n/**\n * Error codes for model-related operations.\n */\nexport type ModelErrorCode =\n | 'MODEL_NOT_FOUND'\n | 'MODEL_NOT_DOWNLOADED'\n | 'DOWNLOAD_FAILED'\n | 'DOWNLOAD_CORRUPT'\n | 'DOWNLOAD_STORAGE_FULL'\n | 'DOWNLOAD_CANCELLED'\n | 'INFERENCE_OOM'\n | 'INFERENCE_FAILED'\n | 'INFERENCE_BUSY'\n | 'INFERENCE_CANCELLED'\n | 'MODEL_LOAD_FAILED'\n | 'DEVICE_NOT_SUPPORTED'\n | 'UNKNOWN';\n\n/**\n * Structured error for model operations.\n */\nexport class ModelError extends Error {\n code: ModelErrorCode;\n modelId: string;\n\n constructor(code: ModelErrorCode, modelId: string, message?: string) {\n super(message ?? `${code}: ${modelId}`);\n this.name = 'ModelError';\n this.code = code;\n this.modelId = modelId;\n }\n}\n\n/**\n * Event payload for model download progress.\n */\nexport type ModelDownloadProgressEvent = {\n /** Model being downloaded */\n modelId: string;\n /** Download progress from 0 to 1 */\n progress: number;\n};\n\n/**\n * Event payload for model state changes.\n */\nexport type ModelStateChangeEvent = {\n /** Model whose state changed */\n modelId: string;\n /** New status */\n status: DownloadableModelStatus;\n};\n"]}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAyaA;;GAEG;AACH,MAAM,OAAO,UAAW,SAAQ,KAAK;IACnC,IAAI,CAAiB;IACrB,OAAO,CAAS;IAEhB,YAAY,IAAoB,EAAE,OAAe,EAAE,OAAgB;QACjE,KAAK,CAAC,OAAO,IAAI,GAAG,IAAI,KAAK,OAAO,EAAE,CAAC,CAAC;QACxC,IAAI,CAAC,IAAI,GAAG,YAAY,CAAC;QACzB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACzB,CAAC;CACF","sourcesContent":["/**\n * Hardware backend for on-device model inference.\n *\n * - 'auto': Try GPU first, fall back to CPU (default)\n * - 'gpu': Force GPU — faster (~40-50 tok/s) but needs more memory\n * - 'cpu': Force CPU — slower (~2-5 tok/s) but works on low-RAM devices\n */\nexport type InferenceBackend = 'auto' | 'gpu' | 'cpu';\n\n/**\n * Sampling / generation parameters applied to a model session.\n *\n * Support is per-backend (on-device runtimes expose different knobs), so these\n * are best-effort — unsupported fields are ignored rather than erroring:\n *\n * | field | Gemma (LiteRT-LM) | Apple Foundation Models | ML Kit |\n * |-------------|:-----------------:|:-----------------------:|:------:|\n * | temperature | ✓ | ✓ | — |\n * | topK | ✓ | — | — |\n * | topP | ✓ | — | — |\n * | seed | ✓ (iOS only) | — | — |\n * | maxTokens | — | ✓ (max output) | — |\n *\n * Notes:\n * - Gemma/LiteRT-LM has no per-generation output-token cap (its `maxNumTokens`\n * is the total KV-cache size, set at load), so `maxTokens` is not honored\n * there. Its sampler (topK/topP/temperature[/seed]) is fixed at conversation\n * creation, which is why generation config lives on setModel() rather than\n * per-call. `seed` is currently wired on iOS only.\n * - The ML Kit built-in (Android default) does not yet apply generation config;\n * it uses its own defaults.\n */\nexport type GenerationConfig = {\n /** Sampling temperature. Lower = more deterministic. Typically 0.0–2.0. */\n temperature?: number;\n /** Nucleus sampling: number of top logits to consider. Must be > 0. */\n topK?: number;\n /** Nucleus sampling: cumulative probability threshold in [0, 1]. */\n topP?: number;\n /** RNG seed for reproducible sampling (Gemma only). */\n seed?: number;\n /** Maximum number of output tokens to generate (Apple FM / ML Kit only). */\n maxTokens?: number;\n};\n\n/**\n * Options for setModel.\n */\nexport type SetModelOptions = {\n /** Hardware backend to use for inference. Defaults to 'auto'. */\n backend?: InferenceBackend;\n /**\n * Default sampling parameters for this model session. Applied when the model\n * is activated and used for all subsequent sendMessage/streamMessage calls\n * until the next setModel(). See {@link GenerationConfig} for per-backend support.\n */\n generation?: GenerationConfig;\n};\n\n/**\n * Role in a conversation message.\n */\nexport type LLMRole = 'system' | 'user' | 'assistant';\n\n/**\n * A single message in a conversation.\n */\nexport type LLMMessage = {\n role: LLMRole;\n content: string;\n};\n\n/**\n * Options for sendMessage.\n */\nexport type LLMSendOptions = {\n /**\n * Default system prompt to use if no system message is provided in the messages array.\n * If a system message exists in the array, this is ignored.\n */\n systemPrompt?: string;\n /**\n * Abort the request. When the signal fires, the returned promise rejects with\n * an INFERENCE_CANCELLED {@link ModelError}.\n *\n * Note: on-device, non-streaming generation cannot always be interrupted\n * mid-decode — abort always unblocks the caller, but the model may keep\n * computing in the background until it finishes, during which a new\n * sendMessage/streamMessage will throw INFERENCE_BUSY. To truly interrupt a\n * long generation, prefer streamMessage().stop().\n */\n signal?: AbortSignal;\n};\n\n/**\n * Response from sendMessage.\n */\nexport type LLMResponse = {\n /** The generated response text */\n text: string;\n};\n\n/**\n * Options for streamMessage.\n */\nexport type LLMStreamOptions = {\n /**\n * Default system prompt to use if no system message is provided in the messages array.\n * If a system message exists in the array, this is ignored.\n */\n systemPrompt?: string;\n};\n\n/**\n * Handle returned by streamMessage.\n */\nexport type LLMStreamHandle = {\n /** Resolves with the final text when streaming completes or is stopped. */\n promise: Promise<LLMResponse>;\n /** Stop streaming. Resolves `promise` with the text accumulated so far. */\n stop: () => void;\n};\n\n/**\n * Event payload for streaming tokens.\n */\nexport type LLMStreamEvent = {\n /** Unique identifier for this streaming session */\n sessionId: string;\n /** The token/chunk of text received */\n token: string;\n /** Accumulated text so far */\n accumulatedText: string;\n /** Whether this is the final chunk */\n isDone: boolean;\n};\n\n/**\n * Callback function for streaming events.\n */\nexport type LLMStreamCallback = (event: LLMStreamEvent) => void;\n\n\n// ============================================================================\n// Structured Output (generateObject)\n// ============================================================================\n\n/**\n * The set of JSON Schema primitive `type` values understood by\n * {@link generateObject}'s local validator.\n */\nexport type JSONSchemaType =\n | 'object'\n | 'array'\n | 'string'\n | 'number'\n | 'integer'\n | 'boolean'\n | 'null';\n\n/**\n * A JSON Schema describing the shape you want {@link generateObject} to return.\n *\n * A pragmatic subset is enforced locally — `type`, `properties`, `required`,\n * `items`, and `enum` — which covers most extraction shapes. Any other JSON\n * Schema keywords you include (e.g. `description`, `minLength`) are still sent\n * to the model to guide it, but are not validated on-device. Keep schemas small:\n * on-device models follow flat, shallow shapes far more reliably than deeply\n * nested ones.\n */\nexport type JSONSchema = {\n /** Expected JSON type (or a union of types). */\n type?: JSONSchemaType | JSONSchemaType[];\n /** Human-readable hint passed through to the model. */\n description?: string;\n /** For `object` schemas: the schema of each named property. */\n properties?: Record<string, JSONSchema>;\n /** For `object` schemas: property names that must be present. */\n required?: string[];\n /** For `array` schemas: the schema each element must satisfy. */\n items?: JSONSchema;\n /** Restrict the value to this set of literals. */\n enum?: ReadonlyArray<string | number | boolean | null>;\n /** Other JSON Schema keywords are accepted and forwarded to the model. */\n [key: string]: unknown;\n};\n\n/**\n * Options for {@link generateObject}.\n */\nexport type GenerateObjectOptions = {\n /**\n * System prompt used when the messages array has no system message. Defaults\n * to a structured-output-oriented instruction. If a system message is present\n * in the array, this is ignored (the schema instruction is appended to it).\n */\n systemPrompt?: string;\n /**\n * Abort the request. Behaves like {@link LLMSendOptions.signal} — the returned\n * promise rejects with an INFERENCE_CANCELLED {@link ModelError}.\n */\n signal?: AbortSignal;\n /**\n * How many times to re-prompt the model when its output is not valid JSON or\n * does not match the schema. Each repair feeds the error back to the model.\n * Defaults to 2 (i.e. up to 3 generations total).\n */\n maxRepairAttempts?: number;\n};\n\n/**\n * Result of {@link generateObject}.\n */\nexport type GenerateObjectResult<T = unknown> = {\n /** The parsed value, validated against the schema. */\n object: T;\n /** The raw model output that produced `object` (useful for debugging). */\n text: string;\n};\n\n\n// ============================================================================\n// Tool / Function Calling (generateText)\n// ============================================================================\n\n/**\n * A tool (function) the model may call to fetch data or take an action.\n *\n * The model never runs anything itself — it *proposes* a call (a name + JSON\n * arguments), {@link generateText} validates the arguments against `parameters`,\n * and only then invokes your `execute`. The result is fed back into the\n * conversation so the model can use it to produce its final answer.\n *\n * @typeParam TArgs - Shape of the validated arguments passed to `execute`.\n * @typeParam TResult - What `execute` returns (serialized back to the model).\n */\nexport type Tool<TArgs = any, TResult = any> = {\n /**\n * What the tool does and when to use it. This is how the model decides\n * whether a request matches this tool — make it specific and action-oriented.\n */\n description: string;\n /**\n * JSON Schema for the tool's arguments. The model is told to conform to it,\n * and the args it proposes are validated against it (same pragmatic subset as\n * {@link generateObject}) before `execute` runs. Keep it small and shallow.\n */\n parameters: JSONSchema;\n /**\n * Runs the tool with the validated arguments and returns a result.\n *\n * **Optional on purpose.** If you omit it, {@link generateText} does not run\n * anything — it stops with `finishReason: 'tool-calls'` and hands you the\n * proposed call so you can confirm, gate, or execute it yourself.\n */\n execute?: (args: TArgs) => TResult | Promise<TResult>;\n};\n\n/** A map of tool name → {@link Tool}, passed to {@link generateText}. */\nexport type ToolSet = Record<string, Tool>;\n\n/** A tool invocation the model proposed (name + validated arguments). */\nexport type ToolCall = {\n /** The tool's key in the {@link ToolSet}. */\n toolName: string;\n /** Arguments the model supplied, validated against the tool's `parameters`. */\n args: unknown;\n};\n\n/** The outcome of running a {@link ToolCall} via its `execute`. */\nexport type ToolResult = {\n /** The tool's key in the {@link ToolSet}. */\n toolName: string;\n /** The arguments that were passed to `execute`. */\n args: unknown;\n /** Whatever `execute` returned (or `{ error }` if it threw). */\n result: unknown;\n};\n\n/** One model round-trip in the {@link generateText} loop. */\nexport type StepResult = {\n /** Assistant text produced this step (empty when the step only called a tool). */\n text: string;\n /** Tool calls proposed this step (at most one in the current protocol). */\n toolCalls: ToolCall[];\n /** Results of the tool calls executed this step. */\n toolResults: ToolResult[];\n};\n\n/**\n * Why {@link generateText} stopped.\n *\n * - `'stop'`: the model produced a final text answer.\n * - `'tool-calls'`: stopped because a proposed tool has no `execute` — the call\n * is returned for you to handle (human-in-the-loop).\n * - `'max-steps'`: hit the `maxSteps` cap while still calling tools. Raise the cap.\n */\nexport type GenerateTextFinishReason = 'stop' | 'tool-calls' | 'max-steps';\n\n/**\n * Options for {@link generateText}.\n */\nexport type GenerateTextOptions = {\n /** Tools the model may call. Omit (or pass `{}`) for a plain text generation. */\n tools?: ToolSet;\n /**\n * Maximum number of model round-trips (each call + tool execution is one step).\n * Bounds the tool-calling chain so it can't run away. Defaults to 5.\n */\n maxSteps?: number;\n /**\n * System prompt used when the messages array has no system message. The tool\n * instructions are appended to it (or to the array's system message if present).\n */\n systemPrompt?: string;\n /**\n * Abort the request. Behaves like {@link LLMSendOptions.signal} — the returned\n * promise rejects with an INFERENCE_CANCELLED {@link ModelError}.\n */\n signal?: AbortSignal;\n /**\n * How many times to re-prompt within a step when the model emits a malformed\n * tool call, an unknown tool name, or arguments that fail schema validation.\n * Defaults to 2. If it still can't comply, `generateText` throws INFERENCE_FAILED.\n */\n maxRepairAttempts?: number;\n};\n\n/**\n * Result of {@link generateText}.\n */\nexport type GenerateTextResult = {\n /** The final assistant text (empty if it stopped on a tool call without `execute`). */\n text: string;\n /** Every step taken, in order — useful for tracing or debugging. */\n steps: StepResult[];\n /** All tool calls across every step, flattened. */\n toolCalls: ToolCall[];\n /** All tool results across every step, flattened. */\n toolResults: ToolResult[];\n /** Why generation stopped. See {@link GenerateTextFinishReason}. */\n finishReason: GenerateTextFinishReason;\n};\n\n\n// ============================================================================\n// Model Types\n// ============================================================================\n\n/**\n * A built-in model provided by the OS (e.g. Apple Foundation Models, ML Kit).\n * These are always available on supported devices -- no download needed.\n */\nexport type BuiltInModel = {\n /** Unique model identifier (e.g. 'apple-fm', 'mlkit') */\n id: string;\n /** Human-readable model name */\n name: string;\n /** Whether this model is available on the current device/OS */\n available: boolean;\n /** Platform this model is associated with */\n platform: 'ios' | 'android';\n /** Maximum context window in tokens */\n contextWindow: number;\n};\n\n/**\n * A downloadable model that the user manages (download, load, delete).\n * These require explicit download before use.\n */\nexport type DownloadableModel = {\n /** Unique model identifier (e.g. 'gemma-e2b', 'gemma-e4b') */\n id: string;\n /** Human-readable model name */\n name: string;\n /** Parameter count label (e.g. '2.3B') */\n parameterCount: string;\n /** Download file size in bytes */\n sizeBytes: number;\n /** Maximum context window in tokens */\n contextWindow: number;\n /** Minimum device RAM in bytes required to run */\n minRamBytes: number;\n /** Whether this device meets the model's minimum RAM requirement */\n meetsRequirements: boolean;\n /** Current lifecycle status */\n status: DownloadableModelStatus;\n};\n\n/**\n * Lifecycle status of a downloadable model.\n *\n * - 'not-downloaded': Model file is not on disk\n * - 'downloading': Model file is being downloaded\n * - 'downloaded': Model file is on disk but not loaded into memory. Call\n * setModel() to load it. This survives app restarts, so use it to decide\n * whether a (re-)download is needed.\n * - 'loading': File is on disk, model is being loaded into memory for inference\n * - 'ready': Model is loaded in memory and ready for inference\n */\nexport type DownloadableModelStatus =\n | 'not-downloaded'\n | 'downloading'\n | 'downloaded'\n | 'loading'\n | 'ready';\n\n/**\n * Error codes for model-related operations.\n */\nexport type ModelErrorCode =\n | 'MODEL_NOT_FOUND'\n | 'MODEL_NOT_DOWNLOADED'\n | 'DOWNLOAD_FAILED'\n | 'DOWNLOAD_CORRUPT'\n | 'DOWNLOAD_STORAGE_FULL'\n | 'DOWNLOAD_CANCELLED'\n | 'INFERENCE_OOM'\n | 'INFERENCE_FAILED'\n | 'INFERENCE_BUSY'\n | 'INFERENCE_CANCELLED'\n | 'MODEL_LOAD_FAILED'\n | 'DEVICE_NOT_SUPPORTED'\n | 'UNKNOWN';\n\n/**\n * Structured error for model operations.\n */\nexport class ModelError extends Error {\n code: ModelErrorCode;\n modelId: string;\n\n constructor(code: ModelErrorCode, modelId: string, message?: string) {\n super(message ?? `${code}: ${modelId}`);\n this.name = 'ModelError';\n this.code = code;\n this.modelId = modelId;\n }\n}\n\n/**\n * Event payload for model download progress.\n */\nexport type ModelDownloadProgressEvent = {\n /** Model being downloaded */\n modelId: string;\n /** Download progress from 0 to 1 */\n progress: number;\n};\n\n/**\n * Event payload for model state changes.\n */\nexport type ModelStateChangeEvent = {\n /** Model whose state changed */\n modelId: string;\n /** New status */\n status: DownloadableModelStatus;\n};\n"]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "expo-ai-kit",
3
- "version": "0.5.0",
4
- "description": "On-device AI for Expo apps — run Gemma 4, Apple Foundation Models, and ML Kit locally with zero API keys",
3
+ "version": "0.7.0",
4
+ "description": "On-device AI for Expo — run Gemma 4, Apple Foundation Models & ML Kit locally: streaming, structured output, tool calling, zero API keys",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",
7
7
  "files": [
@@ -13,6 +13,7 @@
13
13
  "android/build.gradle",
14
14
  "scripts/install-litertlm.sh",
15
15
  "src",
16
+ "!src/__tests__",
16
17
  "expo-module.config.json"
17
18
  ],
18
19
  "scripts": {
@@ -43,6 +44,10 @@
43
44
  "apple-foundation-models",
44
45
  "ml-kit",
45
46
  "local-inference",
47
+ "structured-output",
48
+ "tool-calling",
49
+ "function-calling",
50
+ "json-schema",
46
51
  "expo-ai-kit",
47
52
  "ExpoAiKit"
48
53
  ],
package/src/index.ts CHANGED
@@ -14,7 +14,29 @@ import {
14
14
  ModelError,
15
15
  ModelErrorCode,
16
16
  SetModelOptions,
17
+ JSONSchema,
18
+ GenerateObjectOptions,
19
+ GenerateObjectResult,
20
+ GenerateTextOptions,
21
+ GenerateTextResult,
22
+ ToolCall,
23
+ ToolResult,
24
+ StepResult,
17
25
  } from './types';
26
+ import {
27
+ buildSchemaInstruction,
28
+ buildSchemaRepair,
29
+ extractJson,
30
+ validateAgainstSchema,
31
+ REPAIR_INVALID_JSON,
32
+ } from './structured';
33
+ import {
34
+ buildToolInstruction,
35
+ parseToolCall,
36
+ buildUnknownToolRepair,
37
+ buildToolArgsRepair,
38
+ formatToolResult,
39
+ } from './tools';
18
40
  import { MODEL_REGISTRY, getRegistryEntry } from './models';
19
41
 
20
42
  export * from './types';
@@ -23,6 +45,9 @@ export * from './models';
23
45
  const DEFAULT_SYSTEM_PROMPT =
24
46
  'You are a helpful, friendly assistant. Answer the user directly and concisely.';
25
47
 
48
+ const DEFAULT_OBJECT_SYSTEM_PROMPT =
49
+ 'You output structured data as JSON. Follow the provided JSON Schema exactly.';
50
+
26
51
  let streamIdCounter = 0;
27
52
  function generateSessionId(): string {
28
53
  return `gen_${Date.now()}_${++streamIdCounter}`;
@@ -382,6 +407,328 @@ export function streamMessage(
382
407
  return { promise, stop };
383
408
  }
384
409
 
410
+ /**
411
+ * Generate a typed object instead of free text.
412
+ *
413
+ * You describe the shape you want with a JSON Schema. expo-ai-kit appends a
414
+ * strict instruction to the system prompt, runs the on-device model, extracts
415
+ * the JSON from its output (tolerating prose and ```json fences), validates it
416
+ * against the schema, and — on a parse error or schema mismatch — feeds the
417
+ * error back and re-prompts up to `maxRepairAttempts` times.
418
+ *
419
+ * Works on every backend (Apple Foundation Models, ML Kit, Gemma) because it is
420
+ * orchestrated over {@link sendMessage}: it honors the same single-flight guard,
421
+ * `AbortSignal`, and `systemPrompt` semantics. Keep schemas small and shallow —
422
+ * on-device models follow flat shapes far more reliably than deeply nested ones.
423
+ *
424
+ * @param messages - The conversation, same shape as {@link sendMessage}.
425
+ * @param schema - A JSON Schema describing the desired result.
426
+ * @param options - Optional settings (systemPrompt, signal, maxRepairAttempts).
427
+ * @returns `{ object, text }` — the validated value and the raw output.
428
+ * @throws {ModelError} INFERENCE_FAILED if no schema-valid JSON is produced
429
+ * after the repair attempts. Also propagates INFERENCE_BUSY / INFERENCE_CANCELLED
430
+ * from the underlying generation.
431
+ *
432
+ * @example
433
+ * ```ts
434
+ * type Recipe = { title: string; minutes: number; ingredients: string[] };
435
+ *
436
+ * const { object } = await generateObject<Recipe>(
437
+ * [{ role: 'user', content: 'A quick weeknight pasta.' }],
438
+ * {
439
+ * type: 'object',
440
+ * properties: {
441
+ * title: { type: 'string' },
442
+ * minutes: { type: 'integer' },
443
+ * ingredients: { type: 'array', items: { type: 'string' } },
444
+ * },
445
+ * required: ['title', 'minutes', 'ingredients'],
446
+ * },
447
+ * );
448
+ * object.title; // typed Recipe
449
+ * ```
450
+ */
451
+ export async function generateObject<T = unknown>(
452
+ messages: LLMMessage[],
453
+ schema: JSONSchema,
454
+ options?: GenerateObjectOptions
455
+ ): Promise<GenerateObjectResult<T>> {
456
+ if (Platform.OS !== 'ios' && Platform.OS !== 'android') {
457
+ throw new ModelError(
458
+ 'DEVICE_NOT_SUPPORTED',
459
+ '',
460
+ 'generateObject is only available on iOS and Android'
461
+ );
462
+ }
463
+ if (!messages || messages.length === 0) {
464
+ throw new Error('messages array cannot be empty');
465
+ }
466
+ if (!schema || typeof schema !== 'object') {
467
+ throw new Error('schema must be a JSON Schema object');
468
+ }
469
+
470
+ const maxRepairAttempts = Math.max(0, options?.maxRepairAttempts ?? 2);
471
+ const instruction = buildSchemaInstruction(schema);
472
+
473
+ // Inject the schema instruction. If the caller supplied a system message we
474
+ // append to it (sendMessage reads system from the array); otherwise we carry
475
+ // the instruction via the systemPrompt option, which sendMessage applies when
476
+ // the array has no system message — including on the repair turns we append.
477
+ const sysIdx = messages.findIndex((m) => m.role === 'system');
478
+ let working: LLMMessage[];
479
+ let systemPrompt: string | undefined;
480
+ if (sysIdx >= 0) {
481
+ working = messages.map((m, i) =>
482
+ i === sysIdx ? { role: m.role, content: `${m.content}\n\n${instruction}` } : m
483
+ );
484
+ systemPrompt = undefined; // the array carries the system message
485
+ } else {
486
+ working = [...messages];
487
+ systemPrompt = `${options?.systemPrompt ?? DEFAULT_OBJECT_SYSTEM_PROMPT}\n\n${instruction}`;
488
+ }
489
+
490
+ let lastText = '';
491
+ for (let attempt = 0; attempt <= maxRepairAttempts; attempt++) {
492
+ const { text } = await sendMessage(working, { systemPrompt, signal: options?.signal });
493
+ lastText = text;
494
+
495
+ const parsed = extractJson(text);
496
+ if (parsed.ok) {
497
+ const errors = validateAgainstSchema(parsed.value, schema);
498
+ if (errors.length === 0) {
499
+ return { object: parsed.value as T, text };
500
+ }
501
+ if (attempt < maxRepairAttempts) {
502
+ working = [
503
+ ...working,
504
+ { role: 'assistant', content: text },
505
+ { role: 'user', content: buildSchemaRepair(errors) },
506
+ ];
507
+ }
508
+ } else if (attempt < maxRepairAttempts) {
509
+ working = [
510
+ ...working,
511
+ { role: 'assistant', content: text },
512
+ { role: 'user', content: REPAIR_INVALID_JSON },
513
+ ];
514
+ }
515
+ }
516
+
517
+ throw new ModelError(
518
+ 'INFERENCE_FAILED',
519
+ getActiveModel(),
520
+ `generateObject: model did not return schema-valid JSON after ${maxRepairAttempts + 1} attempt(s). ` +
521
+ `Last output: ${lastText.slice(0, 200)}`
522
+ );
523
+ }
524
+
525
+ /**
526
+ * Generate text, optionally letting the model call tools (functions) you provide.
527
+ *
528
+ * Unlike {@link generateObject} (where the JSON *is* the answer), tool calling is
529
+ * a loop: the model proposes a call, expo-ai-kit validates the arguments against
530
+ * the tool's `parameters`, runs your `execute`, feeds the result back, and lets
531
+ * the model continue — until it produces a plain-text answer or the `maxSteps`
532
+ * budget is reached. With no `tools`, this is a single text generation.
533
+ *
534
+ * Orchestrated in JS over {@link sendMessage}, so it works on every backend
535
+ * (Apple Foundation Models, ML Kit, Gemma) and inherits the single-flight guard,
536
+ * `AbortSignal`, and `systemPrompt` semantics. On-device models are imperfect at
537
+ * tool selection, so the loop is defensive: malformed calls, unknown tool names,
538
+ * and schema-invalid arguments are re-prompted up to `maxRepairAttempts` times,
539
+ * and a tool with no `execute` stops the loop and returns the proposed call for
540
+ * you to gate. Keep tool sets small and `parameters` flat for best reliability.
541
+ *
542
+ * @param messages - The conversation, same shape as {@link sendMessage}.
543
+ * @param options - Tools, `maxSteps`, `systemPrompt`, `signal`, `maxRepairAttempts`.
544
+ * @returns `{ text, steps, toolCalls, toolResults, finishReason }`.
545
+ * @throws {ModelError} INFERENCE_FAILED if the model keeps proposing an unknown
546
+ * tool or schema-invalid arguments after the repair attempts. Also propagates
547
+ * INFERENCE_BUSY / INFERENCE_CANCELLED from the underlying generation.
548
+ *
549
+ * @example
550
+ * ```ts
551
+ * const { text } = await generateText(
552
+ * [{ role: 'user', content: 'What should I wear in Paris today?' }],
553
+ * {
554
+ * tools: {
555
+ * getWeather: {
556
+ * description: 'Get the current weather for a city.',
557
+ * parameters: {
558
+ * type: 'object',
559
+ * properties: { city: { type: 'string' } },
560
+ * required: ['city'],
561
+ * },
562
+ * execute: async ({ city }: { city: string }) => fetchWeather(city),
563
+ * },
564
+ * },
565
+ * },
566
+ * );
567
+ * ```
568
+ *
569
+ * @example
570
+ * ```ts
571
+ * // Human-in-the-loop: omit `execute` to gate the call yourself.
572
+ * const res = await generateText(messages, {
573
+ * tools: { deleteAccount: { description: '…', parameters: { type: 'object' } } },
574
+ * });
575
+ * if (res.finishReason === 'tool-calls') {
576
+ * const call = res.toolCalls[0]; // confirm with the user before running
577
+ * }
578
+ * ```
579
+ */
580
+ export async function generateText(
581
+ messages: LLMMessage[],
582
+ options?: GenerateTextOptions
583
+ ): Promise<GenerateTextResult> {
584
+ if (Platform.OS !== 'ios' && Platform.OS !== 'android') {
585
+ throw new ModelError(
586
+ 'DEVICE_NOT_SUPPORTED',
587
+ '',
588
+ 'generateText is only available on iOS and Android'
589
+ );
590
+ }
591
+ if (!messages || messages.length === 0) {
592
+ throw new Error('messages array cannot be empty');
593
+ }
594
+
595
+ const tools = options?.tools ?? {};
596
+ const toolNames = Object.keys(tools);
597
+ const maxSteps = Math.max(1, options?.maxSteps ?? 5);
598
+ const maxRepairAttempts = Math.max(0, options?.maxRepairAttempts ?? 2);
599
+
600
+ // Inject the tool instruction the same way generateObject injects its schema
601
+ // instruction: into the array's system message if present, else via the
602
+ // systemPrompt option. With no tools, this is a plain single-shot generation.
603
+ const instruction = toolNames.length > 0 ? buildToolInstruction(tools) : '';
604
+ const sysIdx = messages.findIndex((m) => m.role === 'system');
605
+ let working: LLMMessage[];
606
+ let systemPrompt: string | undefined;
607
+ if (instruction === '') {
608
+ working = [...messages];
609
+ systemPrompt = options?.systemPrompt;
610
+ } else if (sysIdx >= 0) {
611
+ working = messages.map((m, i) =>
612
+ i === sysIdx ? { role: m.role, content: `${m.content}\n\n${instruction}` } : m
613
+ );
614
+ systemPrompt = undefined; // the array carries the system message
615
+ } else {
616
+ working = [...messages];
617
+ systemPrompt = `${options?.systemPrompt ?? DEFAULT_SYSTEM_PROMPT}\n\n${instruction}`;
618
+ }
619
+
620
+ const steps: StepResult[] = [];
621
+ const allToolCalls: ToolCall[] = [];
622
+ const allToolResults: ToolResult[] = [];
623
+
624
+ for (let step = 0; step < maxSteps; step++) {
625
+ // One model round-trip, with an inner repair loop for malformed/invalid calls.
626
+ let call: ToolCall | null = null;
627
+ let text = '';
628
+
629
+ for (let repair = 0; ; repair++) {
630
+ const r = await sendMessage(working, { systemPrompt, signal: options?.signal });
631
+ text = r.text;
632
+
633
+ if (toolNames.length === 0) break; // no tools → this is the final answer
634
+
635
+ const parsed = parseToolCall(text, toolNames);
636
+ if (parsed.kind === 'text') break; // plain answer, no tool call
637
+
638
+ if (parsed.kind === 'unknown-tool') {
639
+ if (repair >= maxRepairAttempts) {
640
+ throw new ModelError(
641
+ 'INFERENCE_FAILED',
642
+ getActiveModel(),
643
+ `generateText: model called unknown tool "${parsed.toolName}" after ${maxRepairAttempts + 1} attempt(s).`
644
+ );
645
+ }
646
+ working = [
647
+ ...working,
648
+ { role: 'assistant', content: text },
649
+ { role: 'user', content: buildUnknownToolRepair(parsed.toolName, toolNames) },
650
+ ];
651
+ continue;
652
+ }
653
+
654
+ // parsed.kind === 'tool' — validate the proposed args before executing.
655
+ const errors = validateAgainstSchema(parsed.args, tools[parsed.toolName].parameters);
656
+ if (errors.length === 0) {
657
+ call = { toolName: parsed.toolName, args: parsed.args };
658
+ break;
659
+ }
660
+ if (repair >= maxRepairAttempts) {
661
+ throw new ModelError(
662
+ 'INFERENCE_FAILED',
663
+ getActiveModel(),
664
+ `generateText: arguments for "${parsed.toolName}" failed schema validation after ` +
665
+ `${maxRepairAttempts + 1} attempt(s): ${errors.slice(0, 4).join('; ')}`
666
+ );
667
+ }
668
+ working = [
669
+ ...working,
670
+ { role: 'assistant', content: text },
671
+ { role: 'user', content: buildToolArgsRepair(parsed.toolName, errors) },
672
+ ];
673
+ }
674
+
675
+ // No tool call this step → the model produced its final text answer.
676
+ if (!call) {
677
+ steps.push({ text, toolCalls: [], toolResults: [] });
678
+ return {
679
+ text,
680
+ steps,
681
+ toolCalls: allToolCalls,
682
+ toolResults: allToolResults,
683
+ finishReason: 'stop',
684
+ };
685
+ }
686
+
687
+ allToolCalls.push(call);
688
+ const tool = tools[call.toolName];
689
+
690
+ // No execute → hand the proposed call back to the caller (human-in-the-loop).
691
+ if (typeof tool.execute !== 'function') {
692
+ steps.push({ text, toolCalls: [call], toolResults: [] });
693
+ return {
694
+ text,
695
+ steps,
696
+ toolCalls: allToolCalls,
697
+ toolResults: allToolResults,
698
+ finishReason: 'tool-calls',
699
+ };
700
+ }
701
+
702
+ // Run the tool. A thrown error is fed back as the result so the model can recover.
703
+ let result: unknown;
704
+ try {
705
+ result = await tool.execute(call.args);
706
+ } catch (e) {
707
+ result = { error: String((e as any)?.message ?? e) };
708
+ }
709
+ const toolResult: ToolResult = { toolName: call.toolName, args: call.args, result };
710
+ allToolResults.push(toolResult);
711
+ steps.push({ text, toolCalls: [call], toolResults: [toolResult] });
712
+
713
+ // Feed the call + result back into the conversation for the next step.
714
+ working = [
715
+ ...working,
716
+ { role: 'assistant', content: text },
717
+ { role: 'user', content: formatToolResult(call.toolName, result) },
718
+ ];
719
+ }
720
+
721
+ // Step budget exhausted while still calling tools — no final answer was
722
+ // produced. Signal it via finishReason so the caller can raise maxSteps.
723
+ return {
724
+ text: '',
725
+ steps,
726
+ toolCalls: allToolCalls,
727
+ toolResults: allToolResults,
728
+ finishReason: 'max-steps',
729
+ };
730
+ }
731
+
385
732
  // ============================================================================
386
733
  // Model Management API
387
734
  // ============================================================================