@tanstack-ai-browser/apfel 0.0.0-stage → 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 daveycodez
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,48 @@
1
- # Temporary Holding Version
1
+ # @tanstack-ai-browser/apfel
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ A [TanStack AI](https://tanstack.com/ai) adapter for Apple's on-device Foundation Model, served on your Mac by [apfel](https://apfel.franzai.com)'s OpenAI-compatible server. The browser streams straight from apfel's server, at the `baseURL` you give it.
4
+
5
+ ```sh
6
+ npm install @tanstack-ai-browser/apfel @tanstack/ai
7
+ ```
8
+
9
+ ```ts
10
+ import { chat } from "@tanstack/ai"
11
+ import {
12
+ APFEL_BASE_URL,
13
+ apfelText,
14
+ isApfelAvailable
15
+ } from "@tanstack-ai-browser/apfel"
16
+
17
+ const config = { baseURL: APFEL_BASE_URL }
18
+
19
+ if (await isApfelAvailable("apple-foundationmodel", config)) {
20
+ const stream = chat({
21
+ adapter: apfelText("apple-foundationmodel", config),
22
+ messages,
23
+ threadId
24
+ })
25
+ }
26
+ ```
27
+
28
+ `baseURL` is apfel's OpenAI-compatible API, up to `/v1`; `APFEL_BASE_URL` is apfel's default address. `defaultHeaders` adds headers to every request, such as a bearer token. Apple's model doesn't reason, so `chat({ reasoning })` is a type error with this adapter.
29
+
30
+ ## Tool calls
31
+
32
+ Tools work as with any adapter: pass them to `chat({ tools })`, MCP tools included, and `chat()` runs them. Apple's model picks the right tool but often gets its arguments wrong, renaming fields (`limit` for `numResults`) or dropping optional ones it was asked to set. So the adapter repairs every tool call before `chat()` runs it, using only the tool's JSON Schema:
33
+
34
+ 1. For each optional field, apfel's guided generation answers yes or no: does the request give it a value?
35
+ 2. The arguments are written again, constrained to the schema, with those fields required. Fields the model already named and typed right are kept as it wrote them.
36
+
37
+ Schema keywords the on-device model can't enforce (`minLength`, `pattern`, `format`…) are dropped for the repair, `$ref`s are inlined, and `anyOf`/`oneOf` take their first non-null choice. If a repair request fails, the model's own arguments are used. Repair adds two short requests per tool call (one for a tool without optional fields), about a second and a half in all; it's in `usage.providerUsageDetails.toolRepairMs`.
38
+
39
+ ## Usage
40
+
41
+ Each call's token counts are in `RUN_FINISHED`'s `usage`, as with any TanStack AI adapter, with timings the adapter measured in `usage.providerUsageDetails` (`ApfelProviderUsageDetails`: `firstTokenMs`, `decodeMs`, `decodeTokens`, `toolRepairMs`).
42
+
43
+ ```ts
44
+ // For example, in chat() middleware:
45
+ const stats = { name: "stats", onUsage: (ctx, usage) => console.log(usage) }
46
+ ```
47
+
48
+ **Status:** early (0.0.x); the API may change. See the [repository README](https://github.com/daveycodez/tanstack-ai-browser#readme).
@@ -0,0 +1,98 @@
1
+ import type { AdapterYieldChunk, TextOptions } from "@tanstack/ai";
2
+ import { BaseTextAdapter } from "@tanstack/ai/adapters";
3
+ import type { StructuredOutputOptions, StructuredOutputResult } from "@tanstack/ai/adapters";
4
+ /** Where apfel's server listens by default. */
5
+ export declare const APFEL_BASE_URL = "http://127.0.0.1:11434/v1";
6
+ /** The models apfel serves: Apple's on-device Foundation Model. */
7
+ export declare const APFEL_TEXT_MODELS: readonly ["apple-foundationmodel"];
8
+ /** A model apfel serves. */
9
+ export type ApfelTextModel = (typeof APFEL_TEXT_MODELS)[number] | (string & {});
10
+ export type ApfelTextConfig = {
11
+ /** apfel's OpenAI-compatible API, up to `/v1`. `APFEL_BASE_URL` by default. */
12
+ baseURL?: string;
13
+ /** Headers sent with every request, such as apfel's bearer token. */
14
+ defaultHeaders?: Record<string, string>;
15
+ };
16
+ /**
17
+ * Timings of one model call, in milliseconds, in `RUN_FINISHED`'s
18
+ * `usage.providerUsageDetails`. The adapter measures them as the stream
19
+ * arrives; apfel itself reports token counts only.
20
+ */
21
+ export type ApfelProviderUsageDetails = {
22
+ /** From sending the request to the first generated text. */
23
+ firstTokenMs?: number;
24
+ /** From the first generated text to the last. */
25
+ decodeMs?: number;
26
+ /**
27
+ * Tokens generated after the first chunk, estimated: apfel streams a few
28
+ * tokens per chunk, and the first chunk's share of the characters stands
29
+ * in for its share of the tokens.
30
+ */
31
+ decodeTokens?: number;
32
+ /**
33
+ * Time spent rewriting the tool calls' arguments under their schemas,
34
+ * after the reply ended (see the README's "Tool calls").
35
+ */
36
+ toolRepairMs?: number;
37
+ };
38
+ /**
39
+ * Request fields apfel takes for Apple's model. apfel rejects parameters
40
+ * the model doesn't support (stop, penalties, n…) with HTTP 400.
41
+ */
42
+ export type ApfelModelOptions = {
43
+ temperature?: number;
44
+ max_tokens?: number;
45
+ seed?: number;
46
+ };
47
+ type ApfelReasoning = never;
48
+ type MetadataByModality = {
49
+ text: unknown;
50
+ image: unknown;
51
+ audio: unknown;
52
+ video: unknown;
53
+ document: unknown;
54
+ };
55
+ export declare class ApfelTextAdapter<TModel extends string> extends BaseTextAdapter<TModel, ApfelModelOptions, readonly ["text"], MetadataByModality, ReadonlyArray<string>, unknown, never, ApfelReasoning> {
56
+ readonly kind: "text";
57
+ readonly name: "apfel";
58
+ readonly provider: "apfel";
59
+ readonly api: "openai-completions";
60
+ readonly inputModalities: readonly ["text"];
61
+ private readonly baseURL;
62
+ private readonly headers;
63
+ constructor(config: ApfelTextConfig, model: TModel);
64
+ chatStream(options: TextOptions<ApfelModelOptions>): AsyncIterable<AdapterYieldChunk>;
65
+ /**
66
+ * A tool call's arguments, written again under the tool's schema with
67
+ * guided generation. Undefined if the tool takes no arguments or a request
68
+ * failed: the model's own arguments are used then.
69
+ */
70
+ private repairArguments;
71
+ /**
72
+ * A JSON answer for `chat({ outputSchema })`, from apfel's JSON mode:
73
+ * `response_format: json_schema` constrains Apple's model to the schema
74
+ * (guided generation). `chat()` validates the result.
75
+ */
76
+ structuredOutput(options: StructuredOutputOptions<ApfelModelOptions>): Promise<StructuredOutputResult<unknown>>;
77
+ }
78
+ /**
79
+ * A TanStack AI text adapter for Apple's on-device Foundation Model, served
80
+ * by apfel.
81
+ *
82
+ * @example
83
+ * ```ts
84
+ * chat({
85
+ * adapter: apfelText("apple-foundationmodel", { baseURL: APFEL_BASE_URL }),
86
+ * messages,
87
+ * threadId
88
+ * })
89
+ * ```
90
+ */
91
+ export declare function apfelText<TModel extends ApfelTextModel>(model: TModel, config?: ApfelTextConfig): ApfelTextAdapter<TModel>;
92
+ /**
93
+ * Whether apfel is running and serves the model: true if `GET /v1/models`
94
+ * lists it within 10 seconds.
95
+ */
96
+ export declare function isApfelAvailable(model: ApfelTextModel, config?: ApfelTextConfig): Promise<boolean>;
97
+ export {};
98
+ //# sourceMappingURL=adapter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EACV,iBAAiB,EAEjB,WAAW,EAGZ,MAAM,cAAc,CAAA;AACrB,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAA;AACvD,OAAO,KAAK,EACV,uBAAuB,EACvB,sBAAsB,EACvB,MAAM,uBAAuB,CAAA;AAU9B,+CAA+C;AAC/C,eAAO,MAAM,cAAc,8BAA8B,CAAA;AAEzD,mEAAmE;AACnE,eAAO,MAAM,iBAAiB,oCAAqC,CAAA;AAEnE,4BAA4B;AAC5B,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,iBAAiB,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC,CAAA;AAE/E,MAAM,MAAM,eAAe,GAAG;IAC5B,+EAA+E;IAC/E,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,qEAAqE;IACrE,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CACxC,CAAA;AAED;;;;GAIG;AACH,MAAM,MAAM,yBAAyB,GAAG;IACtC,4DAA4D;IAC5D,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,iDAAiD;IACjD,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB,CAAA;AAED;;;GAGG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,IAAI,CAAC,EAAE,MAAM,CAAA;CACd,CAAA;AAED,KAAK,cAAc,GAAG,KAAK,CAAA;AAE3B,KAAK,kBAAkB,GAAG;IACxB,IAAI,EAAE,OAAO,CAAA;IACb,KAAK,EAAE,OAAO,CAAA;IACd,KAAK,EAAE,OAAO,CAAA;IACd,KAAK,EAAE,OAAO,CAAA;IACd,QAAQ,EAAE,OAAO,CAAA;CAClB,CAAA;AA0OD,qBAAa,gBAAgB,CAAC,MAAM,SAAS,MAAM,CAAE,SAAQ,eAAe,CAC1E,MAAM,EACN,iBAAiB,EACjB,SAAS,CAAC,MAAM,CAAC,EACjB,kBAAkB,EAClB,aAAa,CAAC,MAAM,CAAC,EACrB,OAAO,EACP,KAAK,EACL,cAAc,CACf;IACC,SAAkB,IAAI,EAAG,MAAM,CAAS;IACxC,QAAQ,CAAC,IAAI,EAAG,OAAO,CAAS;IAChC,SAAkB,QAAQ,EAAG,OAAO,CAAS;IAC7C,SAAkB,GAAG,EAAG,oBAAoB,CAAS;IACrD,SAAkB,eAAe,oBAAoB;IACrD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAQ;IAChC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAwB;gBAEpC,MAAM,EAAE,eAAe,EAAE,KAAK,EAAE,MAAM;IAS3C,UAAU,CACf,OAAO,EAAE,WAAW,CAAC,iBAAiB,CAAC,GACtC,aAAa,CAAC,iBAAiB,CAAC;IAyUnC;;;;OAIG;YACW,eAAe;IAgE7B;;;;OAIG;IACG,gBAAgB,CACpB,OAAO,EAAE,uBAAuB,CAAC,iBAAiB,CAAC,GAClD,OAAO,CAAC,sBAAsB,CAAC,OAAO,CAAC,CAAC;CAsE5C;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,SAAS,CAAC,MAAM,SAAS,cAAc,EACrD,KAAK,EAAE,MAAM,EACb,MAAM,GAAE,eAAoB,GAC3B,gBAAgB,CAAC,MAAM,CAAC,CAE1B;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,CACpC,KAAK,EAAE,cAAc,EACrB,MAAM,GAAE,eAAoB,oBAc7B"}