expo-ai-kit 0.9.0 → 0.11.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/README.md +96 -2
- package/ai.d.ts +1 -0
- package/ai.js +5 -0
- package/android/src/main/java/expo/modules/aikit/ExpoAiKitModule.kt +14 -0
- package/build/ExpoAiKitModule.d.ts +4 -0
- package/build/ExpoAiKitModule.d.ts.map +1 -1
- package/build/ExpoAiKitModule.js.map +1 -1
- package/build/ai/convert.d.ts +55 -0
- package/build/ai/convert.d.ts.map +1 -0
- package/build/ai/convert.js +270 -0
- package/build/ai/convert.js.map +1 -0
- package/build/ai/index.d.ts +66 -0
- package/build/ai/index.d.ts.map +1 -0
- package/build/ai/index.js +273 -0
- package/build/ai/index.js.map +1 -0
- package/build/index.d.ts +43 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js +59 -0
- package/build/index.js.map +1 -1
- package/build/rag.d.ts +116 -0
- package/build/rag.d.ts.map +1 -0
- package/build/rag.js +189 -0
- package/build/rag.js.map +1 -0
- package/build/types.d.ts +13 -0
- package/build/types.d.ts.map +1 -1
- package/build/types.js.map +1 -1
- package/ios/ExpoAiKitModule.swift +77 -0
- package/package.json +19 -3
- package/src/ExpoAiKitModule.ts +4 -0
- package/src/ai/convert.ts +309 -0
- package/src/ai/index.ts +338 -0
- package/src/index.ts +66 -0
- package/src/rag.ts +262 -0
- package/src/types.ts +18 -0
package/README.md
CHANGED
|
@@ -7,8 +7,12 @@ On-device AI for Expo & React Native — run LLMs locally. No API keys, no cloud
|
|
|
7
7
|
|
|
8
8
|
Runs **Apple Foundation Models** (iOS 26+), **ML Kit** (Android), and downloadable
|
|
9
9
|
**Gemma 4** (E2B / E4B, iOS + Android via [LiteRT-LM](https://ai.google.dev/edge/litert-lm))
|
|
10
|
-
— with streaming, structured output, tool calling,
|
|
11
|
-
all on-device.
|
|
10
|
+
— with streaming, structured output, tool calling, **embeddings & on-device RAG**,
|
|
11
|
+
cancellation, and runtime model switching, all on-device.
|
|
12
|
+
|
|
13
|
+
**New in 0.11:** a [**Vercel AI SDK provider**](#vercel-ai-sdk) — use `generateText`,
|
|
14
|
+
`streamText`, and `embed` from the [AI SDK](https://ai-sdk.dev) with on-device models
|
|
15
|
+
via `expo-ai-kit/ai`. Same `useChat`-style code you'd write for OpenAI, no API key.
|
|
12
16
|
|
|
13
17
|
## Install
|
|
14
18
|
|
|
@@ -98,6 +102,94 @@ Omit a tool's `execute` to gate it yourself: the loop stops with `finishReason:
|
|
|
98
102
|
and hands you the proposed call to confirm before running. Keep tool sets small and parameter
|
|
99
103
|
schemas flat — on-device models pick tools more reliably that way.
|
|
100
104
|
|
|
105
|
+
## Embeddings & RAG
|
|
106
|
+
|
|
107
|
+
Turn text into vectors for semantic search and retrieval-augmented generation — find the
|
|
108
|
+
chunks of your own documents most relevant to a question, then feed them to the model. All
|
|
109
|
+
on-device: your data never leaves the phone.
|
|
110
|
+
|
|
111
|
+
```tsx
|
|
112
|
+
import { embed, chunkText, createVectorStore, sendMessage } from 'expo-ai-kit';
|
|
113
|
+
|
|
114
|
+
// 1. Index your document once: split → embed → store
|
|
115
|
+
const chunks = chunkText(document); // overlapping, sentence-aware chunks
|
|
116
|
+
const { embeddings } = await embed(chunks); // one vector per chunk
|
|
117
|
+
const store = createVectorStore<{ text: string }>();
|
|
118
|
+
store.addMany(chunks.map((text, i) => ({ id: `c${i}`, vector: embeddings[i], metadata: { text } })));
|
|
119
|
+
|
|
120
|
+
// 2. At query time: embed the question, retrieve the top matches, answer from them
|
|
121
|
+
const { embeddings: [q] } = await embed([question]);
|
|
122
|
+
const context = store.search(q, { topK: 4 }).map((h) => h.metadata!.text).join('\n\n');
|
|
123
|
+
|
|
124
|
+
const { text } = await sendMessage([
|
|
125
|
+
{ role: 'system', content: `Answer using only this context:\n${context}` },
|
|
126
|
+
{ role: 'user', content: question },
|
|
127
|
+
]);
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
`embed()` is backed by Apple's `NLContextualEmbedding` — a **zero-download, OS-maintained**
|
|
131
|
+
model (iOS 17+, works even without Apple Intelligence). **iOS-only for now**; on Android it
|
|
132
|
+
throws `DEVICE_NOT_SUPPORTED` (MediaPipe support is planned). The toolkit —
|
|
133
|
+
`chunkText`, `cosineSimilarity`, and the `createVectorStore` (add / search top-k / `toJSON`
|
|
134
|
+
for persistence) — is pure JS and works on **both platforms with any vector source**.
|
|
135
|
+
|
|
136
|
+
## Vercel AI SDK
|
|
137
|
+
|
|
138
|
+
expo-ai-kit ships a first-class [AI SDK](https://ai-sdk.dev) provider: point `model:` at
|
|
139
|
+
`expoAiKit()` and the AI SDK's `generateText` / `streamText` / `generateObject` / `embed`
|
|
140
|
+
run **on-device** — reuse code, examples, and patterns written for cloud providers, with
|
|
141
|
+
no API key and no data leaving the phone.
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
npm i ai # AI SDK 6+ (the provider implements LanguageModelV3; AI SDK 7 accepts it too)
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
```tsx
|
|
148
|
+
import { generateText, streamText, embed } from 'ai';
|
|
149
|
+
import { expoAiKit } from 'expo-ai-kit/ai';
|
|
150
|
+
|
|
151
|
+
// Text — the active on-device model (OS built-in by default)
|
|
152
|
+
const { text } = await generateText({
|
|
153
|
+
model: expoAiKit(),
|
|
154
|
+
prompt: 'Capital of France?',
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
// Streaming — pass a specific model id to activate it first
|
|
158
|
+
const result = streamText({
|
|
159
|
+
model: expoAiKit('gemma-e2b'), // must be downloaded; settings ride along:
|
|
160
|
+
// model: expoAiKit('gemma-e2b', { generation: { temperature: 0.7 } }),
|
|
161
|
+
prompt: 'Write a short story',
|
|
162
|
+
});
|
|
163
|
+
for await (const chunk of result.textStream) setText((t) => t + chunk);
|
|
164
|
+
|
|
165
|
+
// Embeddings (iOS) — the same NLContextualEmbedding behind embed()
|
|
166
|
+
const { embedding } = await embed({
|
|
167
|
+
model: expoAiKit.embeddingModel(),
|
|
168
|
+
value: 'sunny day at the beach',
|
|
169
|
+
});
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Tool calling and `generateObject` work too — they ride the exact same prompt protocol as
|
|
173
|
+
the core `generateText`/`generateObject`, so behavior is identical either way.
|
|
174
|
+
|
|
175
|
+
**On-device caveats** (the honest fine print):
|
|
176
|
+
|
|
177
|
+
- **One generation at a time.** Concurrent calls reject with `INFERENCE_BUSY` — on-device
|
|
178
|
+
models share a single KV-cache. Await one call before starting the next.
|
|
179
|
+
- **Sampling is fixed at model activation**, not per call. A per-call `temperature`/`topK`
|
|
180
|
+
is reported as an AI SDK warning and ignored; set it via `expoAiKit(id, { generation })`
|
|
181
|
+
or `setModel()`.
|
|
182
|
+
- **Streaming buffers when tools or JSON output are requested** — the tool-call envelope
|
|
183
|
+
has to be parsed whole, not leaked as text deltas. Plain-text streaming is token-by-token.
|
|
184
|
+
- **No token usage numbers** (on-device runtimes don't report them) and no image/file inputs
|
|
185
|
+
(vision is on the roadmap). `embeddingModel()` is iOS-only, like `embed()`.
|
|
186
|
+
- React Native needs the AI SDK's usual polyfills (e.g. `web-streams-polyfill`,
|
|
187
|
+
`structuredClone`, `TextEncoder`) — see the docs for a copy-paste setup.
|
|
188
|
+
|
|
189
|
+
The provider is a thin wrapper over the same core functions below — mix and match freely.
|
|
190
|
+
The core stays **zero-dependency**; `@ai-sdk/provider` is an optional peer used only for
|
|
191
|
+
types when you import `expo-ai-kit/ai`.
|
|
192
|
+
|
|
101
193
|
## Downloadable models
|
|
102
194
|
|
|
103
195
|
Beyond the OS built-ins, you can download open models (via LiteRT-LM) and switch to them at
|
|
@@ -166,6 +258,8 @@ file persists on disk, so its `'downloaded'` status survives restarts once re-re
|
|
|
166
258
|
## API
|
|
167
259
|
|
|
168
260
|
Inference: `isAvailable`, `sendMessage`, `streamMessage`, `generateObject`, `generateText`.
|
|
261
|
+
Embeddings & RAG: `embed`, `chunkText`, `cosineSimilarity`, `createVectorStore`.
|
|
262
|
+
AI SDK provider (`expo-ai-kit/ai`): `expoAiKit`, `createExpoAiKit`.
|
|
169
263
|
Models: `getBuiltInModels`, `getDownloadableModels`, `getRecommendedModel`,
|
|
170
264
|
`downloadModel`, `cancelDownload`, `deleteModel`, `setModel`, `unloadModel`, `getActiveModel`.
|
|
171
265
|
Custom models: `registerModel`, `unregisterModel`, `getRegisteredModels`, `fetchModelMetadata`.
|
package/ai.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './build/ai';
|
package/ai.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// Subpath entry: `import { expoAiKit } from 'expo-ai-kit/ai'`.
|
|
2
|
+
// A root shim (instead of a package.json "exports" map) so resolution works
|
|
3
|
+
// across Metro, Node, and TypeScript without changing how the existing
|
|
4
|
+
// entry points resolve.
|
|
5
|
+
export * from './build/ai';
|
|
@@ -128,6 +128,20 @@ class ExpoAiKitModule : Module() {
|
|
|
128
128
|
activeStreamJobs.remove(sessionId)
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
+
// ==================================================================
|
|
132
|
+
// Embeddings
|
|
133
|
+
// ==================================================================
|
|
134
|
+
// iOS-only for now (Apple NLContextualEmbedding). The JS layer guards the
|
|
135
|
+
// platform and throws before reaching native, so this is a defensive stub
|
|
136
|
+
// that honors the same "CODE::reason" error contract. Android support via
|
|
137
|
+
// MediaPipe Text Embedder is planned.
|
|
138
|
+
AsyncFunction("embed") { _: List<String> ->
|
|
139
|
+
throw RuntimeException(
|
|
140
|
+
"DEVICE_NOT_SUPPORTED::On-device embeddings are iOS-only for now " +
|
|
141
|
+
"(Apple NLContextualEmbedding); Android support via MediaPipe is planned"
|
|
142
|
+
)
|
|
143
|
+
}
|
|
144
|
+
|
|
131
145
|
// ==================================================================
|
|
132
146
|
// Model discovery
|
|
133
147
|
// ==================================================================
|
|
@@ -18,6 +18,10 @@ export interface ExpoAiKitNativeModule {
|
|
|
18
18
|
sendMessage(messages: LLMMessage[], systemPrompt: string, sessionId: string): Promise<LLMResponse>;
|
|
19
19
|
startStreaming(messages: LLMMessage[], systemPrompt: string, sessionId: string): Promise<void>;
|
|
20
20
|
stopStreaming(sessionId: string): Promise<void>;
|
|
21
|
+
embed(texts: string[]): Promise<{
|
|
22
|
+
embeddings: number[][];
|
|
23
|
+
dimensions: number;
|
|
24
|
+
}>;
|
|
21
25
|
getBuiltInModels(): BuiltInModel[];
|
|
22
26
|
getDownloadableModelStatus(modelId: string): Promise<DownloadableModelStatus>;
|
|
23
27
|
getDeviceRamBytes(): number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ExpoAiKitModule.d.ts","sourceRoot":"","sources":["../src/ExpoAiKitModule.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAC3D,OAAO,EACL,YAAY,EACZ,uBAAuB,EACvB,UAAU,EACV,WAAW,EACX,cAAc,EACd,0BAA0B,EAC1B,qBAAqB,EACtB,MAAM,SAAS,CAAC;AAEjB,MAAM,MAAM,qBAAqB,GAAG;IAClC,aAAa,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,CAAC;IAC/C,kBAAkB,EAAE,CAAC,KAAK,EAAE,0BAA0B,KAAK,IAAI,CAAC;IAChE,kBAAkB,EAAE,CAAC,KAAK,EAAE,qBAAqB,KAAK,IAAI,CAAC;CAC5D,CAAC;AAEF,8FAA8F;AAC9F,MAAM,MAAM,sBAAsB,GAAG;IACnC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,WAAW,qBAAqB;IAEpC,WAAW,IAAI,OAAO,CAAC;IAEvB,WAAW,CACT,QAAQ,EAAE,UAAU,EAAE,EACtB,YAAY,EAAE,MAAM,EACpB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,WAAW,CAAC,CAAC;IACxB,cAAc,CACZ,QAAQ,EAAE,UAAU,EAAE,EACtB,YAAY,EAAE,MAAM,EACpB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC,CAAC;IAEjB,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"ExpoAiKitModule.d.ts","sourceRoot":"","sources":["../src/ExpoAiKitModule.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAC3D,OAAO,EACL,YAAY,EACZ,uBAAuB,EACvB,UAAU,EACV,WAAW,EACX,cAAc,EACd,0BAA0B,EAC1B,qBAAqB,EACtB,MAAM,SAAS,CAAC;AAEjB,MAAM,MAAM,qBAAqB,GAAG;IAClC,aAAa,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,CAAC;IAC/C,kBAAkB,EAAE,CAAC,KAAK,EAAE,0BAA0B,KAAK,IAAI,CAAC;IAChE,kBAAkB,EAAE,CAAC,KAAK,EAAE,qBAAqB,KAAK,IAAI,CAAC;CAC5D,CAAC;AAEF,8FAA8F;AAC9F,MAAM,MAAM,sBAAsB,GAAG;IACnC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,WAAW,qBAAqB;IAEpC,WAAW,IAAI,OAAO,CAAC;IAEvB,WAAW,CACT,QAAQ,EAAE,UAAU,EAAE,EACtB,YAAY,EAAE,MAAM,EACpB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,WAAW,CAAC,CAAC;IACxB,cAAc,CACZ,QAAQ,EAAE,UAAU,EAAE,EACtB,YAAY,EAAE,MAAM,EACpB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC,CAAC;IAEjB,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAIhD,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC;QAAE,UAAU,EAAE,MAAM,EAAE,EAAE,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAGhF,gBAAgB,IAAI,YAAY,EAAE,CAAC;IAGnC,0BAA0B,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,uBAAuB,CAAC,CAAC;IAC9E,iBAAiB,IAAI,MAAM,CAAC;IAM5B,QAAQ,CACN,OAAO,EAAE,MAAM,EACf,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,sBAAsB,GACjC,OAAO,CAAC,IAAI,CAAC,CAAC;IACjB,cAAc,IAAI,MAAM,CAAC;IAGzB,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAG7B,aAAa,CACX,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,IAAI,CAAC,CAAC;IAEjB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/C,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAG5C,WAAW,CAAC,CAAC,SAAS,MAAM,qBAAqB,EAC/C,SAAS,EAAE,CAAC,EACZ,QAAQ,EAAE,qBAAqB,CAAC,CAAC,CAAC,GACjC,iBAAiB,CAAC;CACtB;AAED,QAAA,MAAM,eAAe,uBACoC,CAAC;AAE1D,eAAe,eAAe,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ExpoAiKitModule.js","sourceRoot":"","sources":["../src/ExpoAiKitModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"ExpoAiKitModule.js","sourceRoot":"","sources":["../src/ExpoAiKitModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAuFxD,MAAM,eAAe,GACnB,mBAAmB,CAAwB,WAAW,CAAC,CAAC;AAE1D,eAAe,eAAe,CAAC","sourcesContent":["import { requireNativeModule } from 'expo-modules-core';\nimport type { EventSubscription } from 'expo-modules-core';\nimport {\n BuiltInModel,\n DownloadableModelStatus,\n LLMMessage,\n LLMResponse,\n LLMStreamEvent,\n ModelDownloadProgressEvent,\n ModelStateChangeEvent,\n} from './types';\n\nexport type ExpoAiKitModuleEvents = {\n onStreamToken: (event: LLMStreamEvent) => void;\n onDownloadProgress: (event: ModelDownloadProgressEvent) => void;\n onModelStateChange: (event: ModelStateChangeEvent) => void;\n};\n\n/** Generation parameters passed to native. All fields optional; -1 / absent means \"unset\". */\nexport type NativeGenerationConfig = {\n temperature?: number;\n topK?: number;\n topP?: number;\n seed?: number;\n maxTokens?: number;\n};\n\nexport interface ExpoAiKitNativeModule {\n // Existing inference API\n isAvailable(): boolean;\n // sessionId lets stopStreaming() cancel an in-flight (non-streaming) generation too.\n sendMessage(\n messages: LLMMessage[],\n systemPrompt: string,\n sessionId: string\n ): Promise<LLMResponse>;\n startStreaming(\n messages: LLMMessage[],\n systemPrompt: string,\n sessionId: string\n ): Promise<void>;\n // Cancels either a streaming session or a sendMessage session by id.\n stopStreaming(sessionId: string): Promise<void>;\n\n // Embeddings. iOS-only (Apple NLContextualEmbedding); the JS layer guards the\n // platform, so this is never reached on Android/web.\n embed(texts: string[]): Promise<{ embeddings: number[][]; dimensions: number }>;\n\n // Model discovery\n getBuiltInModels(): BuiltInModel[];\n // Async: iOS reads actor-isolated state (so it bridges as a Promise); Android\n // returns synchronously. Callers must await — see getDownloadableModels.\n getDownloadableModelStatus(modelId: string): Promise<DownloadableModelStatus>;\n getDeviceRamBytes(): number;\n\n // Model selection & memory management\n // setModel is async: switching to a downloadable model loads it into memory.\n // Auto-unloads the previous downloadable model (only one loaded at a time).\n // `generation` carries best-effort sampling defaults for the session.\n setModel(\n modelId: string,\n minRamBytes: number,\n backend: string,\n generation: NativeGenerationConfig\n ): Promise<void>;\n getActiveModel(): string;\n // Explicitly free memory from the loaded downloadable model.\n // Reverts to the platform built-in model.\n unloadModel(): Promise<void>;\n\n // Model lifecycle (downloadable models only)\n downloadModel(\n modelId: string,\n url: string,\n sha256: string\n ): Promise<void>;\n // Cancels an in-flight download for the given model (no-op if none).\n cancelDownload(modelId: string): Promise<void>;\n deleteModel(modelId: string): Promise<void>;\n\n // Event subscription\n addListener<K extends keyof ExpoAiKitModuleEvents>(\n eventName: K,\n listener: ExpoAiKitModuleEvents[K]\n ): EventSubscription;\n}\n\nconst ExpoAiKitModule =\n requireNativeModule<ExpoAiKitNativeModule>('ExpoAiKit');\n\nexport default ExpoAiKitModule;\n"]}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { LanguageModelV3CallOptions, LanguageModelV3ToolResultOutput, LanguageModelV3Usage, SharedV3Warning } from '@ai-sdk/provider';
|
|
2
|
+
import { type LLMMessage } from '../types';
|
|
3
|
+
/**
|
|
4
|
+
* On-device runtimes report no token counts, so every usage field is
|
|
5
|
+
* `undefined` (the shape the spec prescribes for "unknown").
|
|
6
|
+
*/
|
|
7
|
+
export declare const EMPTY_USAGE: LanguageModelV3Usage;
|
|
8
|
+
/** Unique-enough id for stream parts / tool calls within a session. */
|
|
9
|
+
export declare function newPartId(prefix: string): string;
|
|
10
|
+
/** Everything doGenerate/doStream needs, derived from LanguageModelV3CallOptions. */
|
|
11
|
+
export type ConvertedCall = {
|
|
12
|
+
/** The conversation in our core protocol, instructions merged into the system message. */
|
|
13
|
+
messages: LLMMessage[];
|
|
14
|
+
/** Names of function tools offered to the model ([] when none / toolChoice 'none'). */
|
|
15
|
+
toolNames: string[];
|
|
16
|
+
/** True when responseFormat is JSON — output should be extracted as JSON. */
|
|
17
|
+
jsonMode: boolean;
|
|
18
|
+
/** Spec warnings for settings we cannot honor on-device. */
|
|
19
|
+
warnings: SharedV3Warning[];
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Map the AI SDK's call options onto our core `LLMMessage[]` protocol.
|
|
23
|
+
*
|
|
24
|
+
* - System messages are merged into one leading system message, with the tool
|
|
25
|
+
* instruction (tools.ts) and/or JSON-Schema instruction (structured.ts)
|
|
26
|
+
* appended — the same prompt protocol as generateText / generateObject.
|
|
27
|
+
* - Assistant tool-call parts are re-rendered as the `{"tool", "arguments"}`
|
|
28
|
+
* envelope and tool results as formatToolResult() text, so multi-step AI SDK
|
|
29
|
+
* conversations round-trip through the exact format the model was taught.
|
|
30
|
+
* - Per-call sampling settings can't be honored (fixed at setModel) and are
|
|
31
|
+
* reported as spec warnings instead of being silently dropped.
|
|
32
|
+
*
|
|
33
|
+
* @throws {ModelError} DEVICE_NOT_SUPPORTED on file/image prompt parts —
|
|
34
|
+
* on-device text models have no media input (vision is on the roadmap).
|
|
35
|
+
*/
|
|
36
|
+
export declare function convertCallOptions(options: LanguageModelV3CallOptions): ConvertedCall;
|
|
37
|
+
/** What the model's raw text turned out to be, per the offered capabilities. */
|
|
38
|
+
export type ExtractedOutput = {
|
|
39
|
+
kind: 'text';
|
|
40
|
+
text: string;
|
|
41
|
+
} | {
|
|
42
|
+
kind: 'tool-call';
|
|
43
|
+
toolName: string;
|
|
44
|
+
input: string;
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* Interpret raw model output for the AI SDK: detect a tool-call envelope when
|
|
48
|
+
* tools were offered (reusing parseToolCall), or extract/clean the JSON value
|
|
49
|
+
* in json mode (reusing extractJson). Single-shot — the repair loops live in
|
|
50
|
+
* the core generateText/generateObject, not in the provider.
|
|
51
|
+
*/
|
|
52
|
+
export declare function extractOutput(text: string, toolNames: string[], jsonMode: boolean): ExtractedOutput;
|
|
53
|
+
/** Unwrap the spec's tool-result output union into a plain value for formatToolResult. */
|
|
54
|
+
export declare function unwrapToolOutput(output: LanguageModelV3ToolResultOutput): unknown;
|
|
55
|
+
//# sourceMappingURL=convert.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"convert.d.ts","sourceRoot":"","sources":["../../src/ai/convert.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,0BAA0B,EAE1B,+BAA+B,EAC/B,oBAAoB,EACpB,eAAe,EAChB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAA+B,KAAK,UAAU,EAAgB,MAAM,UAAU,CAAC;AAkBtF;;;GAGG;AACH,eAAO,MAAM,WAAW,EAAE,oBAYzB,CAAC;AAGF,uEAAuE;AACvE,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAEhD;AAED,qFAAqF;AACrF,MAAM,MAAM,aAAa,GAAG;IAC1B,0FAA0F;IAC1F,QAAQ,EAAE,UAAU,EAAE,CAAC;IACvB,uFAAuF;IACvF,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,6EAA6E;IAC7E,QAAQ,EAAE,OAAO,CAAC;IAClB,4DAA4D;IAC5D,QAAQ,EAAE,eAAe,EAAE,CAAC;CAC7B,CAAC;AAeF;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,0BAA0B,GAAG,aAAa,CAqIrF;AAED,gFAAgF;AAChF,MAAM,MAAM,eAAe,GACvB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC9B;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAE3D;;;;;GAKG;AACH,wBAAgB,aAAa,CAC3B,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,EAAE,EACnB,QAAQ,EAAE,OAAO,GAChB,eAAe,CAsBjB;AAED,0FAA0F;AAC1F,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,+BAA+B,GAAG,OAAO,CAiBjF"}
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import { ModelError } from '../types';
|
|
2
|
+
import { buildSchemaInstruction, extractJson } from '../structured';
|
|
3
|
+
import { buildToolInstruction, formatToolResult, parseToolCall } from '../tools';
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
// Pure helpers for the Vercel AI SDK provider (src/ai/index.ts).
|
|
6
|
+
//
|
|
7
|
+
// Like structured.ts and tools.ts, this module imports no native module so its
|
|
8
|
+
// logic can be unit-tested in plain Node. It maps the AI SDK's LanguageModelV3
|
|
9
|
+
// call options onto our LLMMessage[] protocol — reusing the exact same tool
|
|
10
|
+
// instruction / tool-call envelope as generateText() and the same JSON-Schema
|
|
11
|
+
// instruction / extraction as generateObject(), so a model behaves identically
|
|
12
|
+
// whether it's driven through the AI SDK or through the core API.
|
|
13
|
+
//
|
|
14
|
+
// Only *type* imports from '@ai-sdk/provider' are allowed here (they compile
|
|
15
|
+
// away), keeping the published JS free of runtime dependencies.
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
/**
|
|
18
|
+
* On-device runtimes report no token counts, so every usage field is
|
|
19
|
+
* `undefined` (the shape the spec prescribes for "unknown").
|
|
20
|
+
*/
|
|
21
|
+
export const EMPTY_USAGE = {
|
|
22
|
+
inputTokens: {
|
|
23
|
+
total: undefined,
|
|
24
|
+
noCache: undefined,
|
|
25
|
+
cacheRead: undefined,
|
|
26
|
+
cacheWrite: undefined,
|
|
27
|
+
},
|
|
28
|
+
outputTokens: {
|
|
29
|
+
total: undefined,
|
|
30
|
+
text: undefined,
|
|
31
|
+
reasoning: undefined,
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
let idCounter = 0;
|
|
35
|
+
/** Unique-enough id for stream parts / tool calls within a session. */
|
|
36
|
+
export function newPartId(prefix) {
|
|
37
|
+
return `${prefix}_${Date.now()}_${++idCounter}`;
|
|
38
|
+
}
|
|
39
|
+
// Per-call sampling knobs the AI SDK passes that on-device runtimes fix at
|
|
40
|
+
// setModel() time (LiteRT-LM builds its sampler at conversation creation).
|
|
41
|
+
const FIXED_AT_SETMODEL = [
|
|
42
|
+
'temperature',
|
|
43
|
+
'topP',
|
|
44
|
+
'topK',
|
|
45
|
+
'seed',
|
|
46
|
+
'maxOutputTokens',
|
|
47
|
+
'stopSequences',
|
|
48
|
+
'presencePenalty',
|
|
49
|
+
'frequencyPenalty',
|
|
50
|
+
];
|
|
51
|
+
/**
|
|
52
|
+
* Map the AI SDK's call options onto our core `LLMMessage[]` protocol.
|
|
53
|
+
*
|
|
54
|
+
* - System messages are merged into one leading system message, with the tool
|
|
55
|
+
* instruction (tools.ts) and/or JSON-Schema instruction (structured.ts)
|
|
56
|
+
* appended — the same prompt protocol as generateText / generateObject.
|
|
57
|
+
* - Assistant tool-call parts are re-rendered as the `{"tool", "arguments"}`
|
|
58
|
+
* envelope and tool results as formatToolResult() text, so multi-step AI SDK
|
|
59
|
+
* conversations round-trip through the exact format the model was taught.
|
|
60
|
+
* - Per-call sampling settings can't be honored (fixed at setModel) and are
|
|
61
|
+
* reported as spec warnings instead of being silently dropped.
|
|
62
|
+
*
|
|
63
|
+
* @throws {ModelError} DEVICE_NOT_SUPPORTED on file/image prompt parts —
|
|
64
|
+
* on-device text models have no media input (vision is on the roadmap).
|
|
65
|
+
*/
|
|
66
|
+
export function convertCallOptions(options) {
|
|
67
|
+
const warnings = [];
|
|
68
|
+
for (const setting of FIXED_AT_SETMODEL) {
|
|
69
|
+
if (options[setting] != null) {
|
|
70
|
+
warnings.push({
|
|
71
|
+
type: 'unsupported',
|
|
72
|
+
feature: setting,
|
|
73
|
+
details: 'On-device sampling is fixed when the model is activated (setModel / provider settings), not per call.',
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
// --- Tools → the generateText() text protocol -----------------------------
|
|
78
|
+
const toolSet = {};
|
|
79
|
+
const toolChoice = options.toolChoice ?? { type: 'auto' };
|
|
80
|
+
if (options.tools && toolChoice.type !== 'none') {
|
|
81
|
+
for (const tool of options.tools) {
|
|
82
|
+
if (tool.type === 'function') {
|
|
83
|
+
toolSet[tool.name] = {
|
|
84
|
+
description: tool.description ?? '',
|
|
85
|
+
parameters: toLocalSchema(tool.inputSchema),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
warnings.push({
|
|
90
|
+
type: 'unsupported',
|
|
91
|
+
feature: `provider tool "${tool.id}"`,
|
|
92
|
+
details: 'expo-ai-kit runs on-device models; provider-executed tools are not available.',
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
const toolNames = Object.keys(toolSet);
|
|
98
|
+
let toolInstruction = toolNames.length > 0 ? buildToolInstruction(toolSet) : '';
|
|
99
|
+
if (toolNames.length > 0 && toolChoice.type === 'required') {
|
|
100
|
+
toolInstruction += '\n- For this request you MUST call one of the tools.';
|
|
101
|
+
warnings.push({
|
|
102
|
+
type: 'compatibility',
|
|
103
|
+
feature: "toolChoice: 'required'",
|
|
104
|
+
details: 'Enforced via a prompt nudge — on-device models have no constrained decoding yet.',
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
else if (toolNames.length > 0 && toolChoice.type === 'tool') {
|
|
108
|
+
toolInstruction += `\n- For this request you MUST call the tool "${toolChoice.toolName}".`;
|
|
109
|
+
warnings.push({
|
|
110
|
+
type: 'compatibility',
|
|
111
|
+
feature: `toolChoice: { type: 'tool' }`,
|
|
112
|
+
details: 'Enforced via a prompt nudge — on-device models have no constrained decoding yet.',
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
// --- responseFormat json → the generateObject() instruction ---------------
|
|
116
|
+
let jsonMode = false;
|
|
117
|
+
let schemaInstruction = '';
|
|
118
|
+
if (options.responseFormat?.type === 'json') {
|
|
119
|
+
if (toolNames.length > 0) {
|
|
120
|
+
// The SDK never combines them; if a caller does, tools win.
|
|
121
|
+
warnings.push({
|
|
122
|
+
type: 'other',
|
|
123
|
+
message: 'responseFormat: json is ignored when tools are provided.',
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
jsonMode = true;
|
|
128
|
+
schemaInstruction = options.responseFormat.schema
|
|
129
|
+
? buildSchemaInstruction(toLocalSchema(options.responseFormat.schema))
|
|
130
|
+
: 'Respond with ONLY a valid JSON value — no prose, no markdown code fences.';
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
// --- Prompt → LLMMessage[] -------------------------------------------------
|
|
134
|
+
const systemParts = [];
|
|
135
|
+
const messages = [];
|
|
136
|
+
for (const message of options.prompt) {
|
|
137
|
+
switch (message.role) {
|
|
138
|
+
case 'system': {
|
|
139
|
+
systemParts.push(message.content);
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
142
|
+
case 'user': {
|
|
143
|
+
const text = message.content
|
|
144
|
+
.map((part) => {
|
|
145
|
+
if (part.type === 'text')
|
|
146
|
+
return part.text;
|
|
147
|
+
throw unsupportedPart(part.type);
|
|
148
|
+
})
|
|
149
|
+
.join('');
|
|
150
|
+
messages.push({ role: 'user', content: text });
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
case 'assistant': {
|
|
154
|
+
const chunks = [];
|
|
155
|
+
for (const part of message.content) {
|
|
156
|
+
if (part.type === 'text') {
|
|
157
|
+
chunks.push(part.text);
|
|
158
|
+
}
|
|
159
|
+
else if (part.type === 'tool-call') {
|
|
160
|
+
// Re-render past calls in the envelope the model was taught, so
|
|
161
|
+
// multi-step conversations stay in-protocol.
|
|
162
|
+
chunks.push(JSON.stringify({ tool: part.toolName, arguments: part.input ?? {} }));
|
|
163
|
+
}
|
|
164
|
+
else if (part.type === 'reasoning') {
|
|
165
|
+
// Model-internal; not replayed.
|
|
166
|
+
}
|
|
167
|
+
else if (part.type === 'tool-result') {
|
|
168
|
+
// Provider-executed results never occur here (we execute nothing).
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
throw unsupportedPart(part.type);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
const text = chunks.join('\n');
|
|
175
|
+
if (text !== '')
|
|
176
|
+
messages.push({ role: 'assistant', content: text });
|
|
177
|
+
break;
|
|
178
|
+
}
|
|
179
|
+
case 'tool': {
|
|
180
|
+
const chunks = [];
|
|
181
|
+
for (const part of message.content) {
|
|
182
|
+
if (part.type === 'tool-result') {
|
|
183
|
+
chunks.push(formatToolResult(part.toolName, unwrapToolOutput(part.output)));
|
|
184
|
+
}
|
|
185
|
+
// tool-approval-response: we never emit approval requests, so none
|
|
186
|
+
// can legitimately appear; ignore defensively.
|
|
187
|
+
}
|
|
188
|
+
if (chunks.length > 0)
|
|
189
|
+
messages.push({ role: 'user', content: chunks.join('\n\n') });
|
|
190
|
+
break;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
const instructions = [toolInstruction, schemaInstruction].filter((s) => s !== '');
|
|
195
|
+
const system = [...systemParts, ...instructions].join('\n\n');
|
|
196
|
+
if (system !== '') {
|
|
197
|
+
messages.unshift({ role: 'system', content: system });
|
|
198
|
+
}
|
|
199
|
+
return { messages, toolNames, jsonMode, warnings };
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Interpret raw model output for the AI SDK: detect a tool-call envelope when
|
|
203
|
+
* tools were offered (reusing parseToolCall), or extract/clean the JSON value
|
|
204
|
+
* in json mode (reusing extractJson). Single-shot — the repair loops live in
|
|
205
|
+
* the core generateText/generateObject, not in the provider.
|
|
206
|
+
*/
|
|
207
|
+
export function extractOutput(text, toolNames, jsonMode) {
|
|
208
|
+
if (toolNames.length > 0) {
|
|
209
|
+
const parsed = parseToolCall(text, toolNames);
|
|
210
|
+
if (parsed.kind === 'tool') {
|
|
211
|
+
return {
|
|
212
|
+
kind: 'tool-call',
|
|
213
|
+
toolName: parsed.toolName,
|
|
214
|
+
input: safeStringify(parsed.args ?? {}),
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
// 'unknown-tool' deliberately falls through to text: the provider is
|
|
218
|
+
// single-shot, so surfacing the raw output beats silently retrying.
|
|
219
|
+
return { kind: 'text', text };
|
|
220
|
+
}
|
|
221
|
+
if (jsonMode) {
|
|
222
|
+
const parsed = extractJson(text);
|
|
223
|
+
if (parsed.ok)
|
|
224
|
+
return { kind: 'text', text: safeStringify(parsed.value) };
|
|
225
|
+
return { kind: 'text', text }; // let the SDK report the parse failure honestly
|
|
226
|
+
}
|
|
227
|
+
return { kind: 'text', text };
|
|
228
|
+
}
|
|
229
|
+
/** Unwrap the spec's tool-result output union into a plain value for formatToolResult. */
|
|
230
|
+
export function unwrapToolOutput(output) {
|
|
231
|
+
switch (output.type) {
|
|
232
|
+
case 'text':
|
|
233
|
+
return output.value;
|
|
234
|
+
case 'json':
|
|
235
|
+
return output.value;
|
|
236
|
+
case 'error-text':
|
|
237
|
+
return { error: output.value };
|
|
238
|
+
case 'error-json':
|
|
239
|
+
return { error: output.value };
|
|
240
|
+
case 'execution-denied':
|
|
241
|
+
return { error: `Tool execution denied${output.reason ? `: ${output.reason}` : ''}` };
|
|
242
|
+
case 'content':
|
|
243
|
+
return output.value
|
|
244
|
+
.map((part) => (part.type === 'text' ? part.text : `[${part.type} omitted]`))
|
|
245
|
+
.join('\n');
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* The spec's JSONSchema7 and our pragmatic JSONSchema subset are structurally
|
|
250
|
+
* compatible for prompting purposes (both serialize to the same JSON); this
|
|
251
|
+
* cast localizes the type-system disagreement to one place.
|
|
252
|
+
*/
|
|
253
|
+
function toLocalSchema(schema) {
|
|
254
|
+
if (typeof schema === 'object' && schema !== null)
|
|
255
|
+
return schema;
|
|
256
|
+
return {};
|
|
257
|
+
}
|
|
258
|
+
function unsupportedPart(type) {
|
|
259
|
+
return new ModelError('DEVICE_NOT_SUPPORTED', '', `expo-ai-kit AI SDK provider: "${type}" prompt parts are not supported — ` +
|
|
260
|
+
'on-device models take text only (vision input is on the roadmap).');
|
|
261
|
+
}
|
|
262
|
+
function safeStringify(value) {
|
|
263
|
+
try {
|
|
264
|
+
return JSON.stringify(value) ?? String(value);
|
|
265
|
+
}
|
|
266
|
+
catch {
|
|
267
|
+
return String(value);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
//# sourceMappingURL=convert.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"convert.js","sourceRoot":"","sources":["../../src/ai/convert.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n LanguageModelV3CallOptions,\n LanguageModelV3FunctionTool,\n LanguageModelV3ToolResultOutput,\n LanguageModelV3Usage,\n SharedV3Warning,\n} from '@ai-sdk/provider';\nimport { ModelError, type JSONSchema, type LLMMessage, type ToolSet } from '../types';\nimport { buildSchemaInstruction, extractJson } from '../structured';\nimport { buildToolInstruction, formatToolResult, parseToolCall } from '../tools';\n\n// ---------------------------------------------------------------------------\n// Pure helpers for the Vercel AI SDK provider (src/ai/index.ts).\n//\n// Like structured.ts and tools.ts, this module imports no native module so its\n// logic can be unit-tested in plain Node. It maps the AI SDK's LanguageModelV3\n// call options onto our LLMMessage[] protocol — reusing the exact same tool\n// instruction / tool-call envelope as generateText() and the same JSON-Schema\n// instruction / extraction as generateObject(), so a model behaves identically\n// whether it's driven through the AI SDK or through the core API.\n//\n// Only *type* imports from '@ai-sdk/provider' are allowed here (they compile\n// away), keeping the published JS free of runtime dependencies.\n// ---------------------------------------------------------------------------\n\n/**\n * On-device runtimes report no token counts, so every usage field is\n * `undefined` (the shape the spec prescribes for \"unknown\").\n */\nexport const EMPTY_USAGE: LanguageModelV3Usage = {\n inputTokens: {\n total: undefined,\n noCache: undefined,\n cacheRead: undefined,\n cacheWrite: undefined,\n },\n outputTokens: {\n total: undefined,\n text: undefined,\n reasoning: undefined,\n },\n};\n\nlet idCounter = 0;\n/** Unique-enough id for stream parts / tool calls within a session. */\nexport function newPartId(prefix: string): string {\n return `${prefix}_${Date.now()}_${++idCounter}`;\n}\n\n/** Everything doGenerate/doStream needs, derived from LanguageModelV3CallOptions. */\nexport type ConvertedCall = {\n /** The conversation in our core protocol, instructions merged into the system message. */\n messages: LLMMessage[];\n /** Names of function tools offered to the model ([] when none / toolChoice 'none'). */\n toolNames: string[];\n /** True when responseFormat is JSON — output should be extracted as JSON. */\n jsonMode: boolean;\n /** Spec warnings for settings we cannot honor on-device. */\n warnings: SharedV3Warning[];\n};\n\n// Per-call sampling knobs the AI SDK passes that on-device runtimes fix at\n// setModel() time (LiteRT-LM builds its sampler at conversation creation).\nconst FIXED_AT_SETMODEL = [\n 'temperature',\n 'topP',\n 'topK',\n 'seed',\n 'maxOutputTokens',\n 'stopSequences',\n 'presencePenalty',\n 'frequencyPenalty',\n] as const;\n\n/**\n * Map the AI SDK's call options onto our core `LLMMessage[]` protocol.\n *\n * - System messages are merged into one leading system message, with the tool\n * instruction (tools.ts) and/or JSON-Schema instruction (structured.ts)\n * appended — the same prompt protocol as generateText / generateObject.\n * - Assistant tool-call parts are re-rendered as the `{\"tool\", \"arguments\"}`\n * envelope and tool results as formatToolResult() text, so multi-step AI SDK\n * conversations round-trip through the exact format the model was taught.\n * - Per-call sampling settings can't be honored (fixed at setModel) and are\n * reported as spec warnings instead of being silently dropped.\n *\n * @throws {ModelError} DEVICE_NOT_SUPPORTED on file/image prompt parts —\n * on-device text models have no media input (vision is on the roadmap).\n */\nexport function convertCallOptions(options: LanguageModelV3CallOptions): ConvertedCall {\n const warnings: SharedV3Warning[] = [];\n\n for (const setting of FIXED_AT_SETMODEL) {\n if (options[setting] != null) {\n warnings.push({\n type: 'unsupported',\n feature: setting,\n details:\n 'On-device sampling is fixed when the model is activated (setModel / provider settings), not per call.',\n });\n }\n }\n\n // --- Tools → the generateText() text protocol -----------------------------\n const toolSet: ToolSet = {};\n const toolChoice = options.toolChoice ?? { type: 'auto' };\n if (options.tools && toolChoice.type !== 'none') {\n for (const tool of options.tools) {\n if (tool.type === 'function') {\n toolSet[tool.name] = {\n description: tool.description ?? '',\n parameters: toLocalSchema((tool as LanguageModelV3FunctionTool).inputSchema),\n };\n } else {\n warnings.push({\n type: 'unsupported',\n feature: `provider tool \"${tool.id}\"`,\n details: 'expo-ai-kit runs on-device models; provider-executed tools are not available.',\n });\n }\n }\n }\n const toolNames = Object.keys(toolSet);\n\n let toolInstruction = toolNames.length > 0 ? buildToolInstruction(toolSet) : '';\n if (toolNames.length > 0 && toolChoice.type === 'required') {\n toolInstruction += '\\n- For this request you MUST call one of the tools.';\n warnings.push({\n type: 'compatibility',\n feature: \"toolChoice: 'required'\",\n details: 'Enforced via a prompt nudge — on-device models have no constrained decoding yet.',\n });\n } else if (toolNames.length > 0 && toolChoice.type === 'tool') {\n toolInstruction += `\\n- For this request you MUST call the tool \"${toolChoice.toolName}\".`;\n warnings.push({\n type: 'compatibility',\n feature: `toolChoice: { type: 'tool' }`,\n details: 'Enforced via a prompt nudge — on-device models have no constrained decoding yet.',\n });\n }\n\n // --- responseFormat json → the generateObject() instruction ---------------\n let jsonMode = false;\n let schemaInstruction = '';\n if (options.responseFormat?.type === 'json') {\n if (toolNames.length > 0) {\n // The SDK never combines them; if a caller does, tools win.\n warnings.push({\n type: 'other',\n message: 'responseFormat: json is ignored when tools are provided.',\n });\n } else {\n jsonMode = true;\n schemaInstruction = options.responseFormat.schema\n ? buildSchemaInstruction(toLocalSchema(options.responseFormat.schema))\n : 'Respond with ONLY a valid JSON value — no prose, no markdown code fences.';\n }\n }\n\n // --- Prompt → LLMMessage[] -------------------------------------------------\n const systemParts: string[] = [];\n const messages: LLMMessage[] = [];\n\n for (const message of options.prompt) {\n switch (message.role) {\n case 'system': {\n systemParts.push(message.content);\n break;\n }\n case 'user': {\n const text = message.content\n .map((part) => {\n if (part.type === 'text') return part.text;\n throw unsupportedPart(part.type);\n })\n .join('');\n messages.push({ role: 'user', content: text });\n break;\n }\n case 'assistant': {\n const chunks: string[] = [];\n for (const part of message.content) {\n if (part.type === 'text') {\n chunks.push(part.text);\n } else if (part.type === 'tool-call') {\n // Re-render past calls in the envelope the model was taught, so\n // multi-step conversations stay in-protocol.\n chunks.push(JSON.stringify({ tool: part.toolName, arguments: part.input ?? {} }));\n } else if (part.type === 'reasoning') {\n // Model-internal; not replayed.\n } else if (part.type === 'tool-result') {\n // Provider-executed results never occur here (we execute nothing).\n } else {\n throw unsupportedPart(part.type);\n }\n }\n const text = chunks.join('\\n');\n if (text !== '') messages.push({ role: 'assistant', content: text });\n break;\n }\n case 'tool': {\n const chunks: string[] = [];\n for (const part of message.content) {\n if (part.type === 'tool-result') {\n chunks.push(formatToolResult(part.toolName, unwrapToolOutput(part.output)));\n }\n // tool-approval-response: we never emit approval requests, so none\n // can legitimately appear; ignore defensively.\n }\n if (chunks.length > 0) messages.push({ role: 'user', content: chunks.join('\\n\\n') });\n break;\n }\n }\n }\n\n const instructions = [toolInstruction, schemaInstruction].filter((s) => s !== '');\n const system = [...systemParts, ...instructions].join('\\n\\n');\n if (system !== '') {\n messages.unshift({ role: 'system', content: system });\n }\n\n return { messages, toolNames, jsonMode, warnings };\n}\n\n/** What the model's raw text turned out to be, per the offered capabilities. */\nexport type ExtractedOutput =\n | { kind: 'text'; text: string }\n | { kind: 'tool-call'; toolName: string; input: string };\n\n/**\n * Interpret raw model output for the AI SDK: detect a tool-call envelope when\n * tools were offered (reusing parseToolCall), or extract/clean the JSON value\n * in json mode (reusing extractJson). Single-shot — the repair loops live in\n * the core generateText/generateObject, not in the provider.\n */\nexport function extractOutput(\n text: string,\n toolNames: string[],\n jsonMode: boolean\n): ExtractedOutput {\n if (toolNames.length > 0) {\n const parsed = parseToolCall(text, toolNames);\n if (parsed.kind === 'tool') {\n return {\n kind: 'tool-call',\n toolName: parsed.toolName,\n input: safeStringify(parsed.args ?? {}),\n };\n }\n // 'unknown-tool' deliberately falls through to text: the provider is\n // single-shot, so surfacing the raw output beats silently retrying.\n return { kind: 'text', text };\n }\n\n if (jsonMode) {\n const parsed = extractJson(text);\n if (parsed.ok) return { kind: 'text', text: safeStringify(parsed.value) };\n return { kind: 'text', text }; // let the SDK report the parse failure honestly\n }\n\n return { kind: 'text', text };\n}\n\n/** Unwrap the spec's tool-result output union into a plain value for formatToolResult. */\nexport function unwrapToolOutput(output: LanguageModelV3ToolResultOutput): unknown {\n switch (output.type) {\n case 'text':\n return output.value;\n case 'json':\n return output.value;\n case 'error-text':\n return { error: output.value };\n case 'error-json':\n return { error: output.value };\n case 'execution-denied':\n return { error: `Tool execution denied${output.reason ? `: ${output.reason}` : ''}` };\n case 'content':\n return output.value\n .map((part) => (part.type === 'text' ? part.text : `[${part.type} omitted]`))\n .join('\\n');\n }\n}\n\n/**\n * The spec's JSONSchema7 and our pragmatic JSONSchema subset are structurally\n * compatible for prompting purposes (both serialize to the same JSON); this\n * cast localizes the type-system disagreement to one place.\n */\nfunction toLocalSchema(schema: unknown): JSONSchema {\n if (typeof schema === 'object' && schema !== null) return schema as JSONSchema;\n return {};\n}\n\nfunction unsupportedPart(type: string): ModelError {\n return new ModelError(\n 'DEVICE_NOT_SUPPORTED',\n '',\n `expo-ai-kit AI SDK provider: \"${type}\" prompt parts are not supported — ` +\n 'on-device models take text only (vision input is on the roadmap).'\n );\n}\n\nfunction safeStringify(value: unknown): string {\n try {\n return JSON.stringify(value) ?? String(value);\n } catch {\n return String(value);\n }\n}\n"]}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { EmbeddingModelV3, LanguageModelV3 } from '@ai-sdk/provider';
|
|
2
|
+
import { type SetModelOptions } from '../types';
|
|
3
|
+
/**
|
|
4
|
+
* Sentinel model id meaning "whatever model is currently active" — the OS
|
|
5
|
+
* built-in by default, or whatever the app last activated via setModel().
|
|
6
|
+
* The provider never switches models for this id.
|
|
7
|
+
*/
|
|
8
|
+
export declare const AUTO_MODEL_ID = "auto";
|
|
9
|
+
/** Model id for the built-in embedding model (Apple NLContextualEmbedding, iOS 17+). */
|
|
10
|
+
export declare const EMBEDDING_MODEL_ID = "apple-nl-contextual";
|
|
11
|
+
/**
|
|
12
|
+
* Per-instance model settings, applied when this instance activates its model
|
|
13
|
+
* (same shape as {@link setModel}'s options). If the model is already active,
|
|
14
|
+
* it is NOT reloaded — on-device model loads are expensive, so sampling config
|
|
15
|
+
* effectively belongs to whoever activated the model first.
|
|
16
|
+
*/
|
|
17
|
+
export type ExpoAiKitModelSettings = SetModelOptions;
|
|
18
|
+
export interface ExpoAiKitProvider {
|
|
19
|
+
/** Shorthand for {@link ExpoAiKitProvider.languageModel}. */
|
|
20
|
+
(modelId?: string, settings?: ExpoAiKitModelSettings): LanguageModelV3;
|
|
21
|
+
/**
|
|
22
|
+
* A LanguageModelV3 over the on-device model. Pass any model id accepted by
|
|
23
|
+
* setModel() ('apple-fm', 'mlkit', 'gemma-e2b', a registerModel() id, …) to
|
|
24
|
+
* have the provider activate it before generating, or omit / pass 'auto' to
|
|
25
|
+
* use whatever model is already active.
|
|
26
|
+
*/
|
|
27
|
+
languageModel(modelId?: string, settings?: ExpoAiKitModelSettings): LanguageModelV3;
|
|
28
|
+
/** An EmbeddingModelV3 over embed() — iOS-only (Apple NLContextualEmbedding). */
|
|
29
|
+
embeddingModel(modelId?: string): EmbeddingModelV3;
|
|
30
|
+
/** @deprecated Spec alias for {@link ExpoAiKitProvider.embeddingModel}. */
|
|
31
|
+
textEmbeddingModel(modelId?: string): EmbeddingModelV3;
|
|
32
|
+
/** expo-ai-kit has no image models; always throws MODEL_NOT_FOUND. */
|
|
33
|
+
imageModel(modelId: string): never;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Create an expo-ai-kit provider for the Vercel AI SDK.
|
|
37
|
+
*
|
|
38
|
+
* ```ts
|
|
39
|
+
* import { generateText, streamText } from 'ai';
|
|
40
|
+
* import { expoAiKit } from 'expo-ai-kit/ai';
|
|
41
|
+
*
|
|
42
|
+
* const { text } = await generateText({
|
|
43
|
+
* model: expoAiKit(), // the active on-device model
|
|
44
|
+
* prompt: 'Capital of France?',
|
|
45
|
+
* });
|
|
46
|
+
*
|
|
47
|
+
* const result = streamText({
|
|
48
|
+
* model: expoAiKit('gemma-e2b'), // activates gemma-e2b first (must be downloaded)
|
|
49
|
+
* prompt: 'Write a short story',
|
|
50
|
+
* });
|
|
51
|
+
* ```
|
|
52
|
+
*
|
|
53
|
+
* On-device caveats (all documented in the guide):
|
|
54
|
+
* - One generation at a time — concurrent calls reject with INFERENCE_BUSY.
|
|
55
|
+
* - Per-call sampling (temperature, topK, …) is reported as an unsupported-
|
|
56
|
+
* setting warning; sampling is fixed when the model is activated.
|
|
57
|
+
* - Tool calling and JSON output ride the same prompt protocol as
|
|
58
|
+
* generateText()/generateObject() — single-shot here, since the AI SDK owns
|
|
59
|
+
* the loop. Streaming buffers when tools/JSON are requested (the envelope
|
|
60
|
+
* must be parsed whole, not surfaced as text deltas).
|
|
61
|
+
* - embeddingModel() is iOS-only for now; Android throws DEVICE_NOT_SUPPORTED.
|
|
62
|
+
*/
|
|
63
|
+
export declare function createExpoAiKit(): ExpoAiKitProvider;
|
|
64
|
+
/** The default provider instance. */
|
|
65
|
+
export declare const expoAiKit: ExpoAiKitProvider;
|
|
66
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/ai/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,gBAAgB,EAChB,eAAe,EAMhB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EAAc,KAAK,eAAe,EAAE,MAAM,UAAU,CAAC;AAkB5D;;;;GAIG;AACH,eAAO,MAAM,aAAa,SAAS,CAAC;AAEpC,wFAAwF;AACxF,eAAO,MAAM,kBAAkB,wBAAwB,CAAC;AAExD;;;;;GAKG;AACH,MAAM,MAAM,sBAAsB,GAAG,eAAe,CAAC;AAErD,MAAM,WAAW,iBAAiB;IAChC,6DAA6D;IAC7D,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,sBAAsB,GAAG,eAAe,CAAC;IACvE;;;;;OAKG;IACH,aAAa,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,sBAAsB,GAAG,eAAe,CAAC;IACpF,iFAAiF;IACjF,cAAc,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,gBAAgB,CAAC;IACnD,2EAA2E;IAC3E,kBAAkB,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,gBAAgB,CAAC;IACvD,sEAAsE;IACtE,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,KAAK,CAAC;CACpC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,eAAe,IAAI,iBAAiB,CAyBnD;AAED,qCAAqC;AACrC,eAAO,MAAM,SAAS,EAAE,iBAAqC,CAAC"}
|