@tanstack-ai-browser/apfel 0.1.0 → 0.2.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 CHANGED
@@ -1,6 +1,8 @@
1
1
  # @tanstack-ai-browser/apfel
2
2
 
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.
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.
4
+
5
+ It's a thin pass-through: TanStack's OpenAI-compatible Chat Completions adapter (`@tanstack/ai-openai/compatible`) pointed at apfel. Tool calls go to apfel as `tools` and come back as apfel parses them; structured output goes as `response_format`, which apfel holds the model to with Apple's guided generation. The adapter adds an availability check and renames `modelOptions` for the request.
4
6
 
5
7
  ```sh
6
8
  npm install @tanstack-ai-browser/apfel @tanstack/ai
@@ -8,41 +10,40 @@ npm install @tanstack-ai-browser/apfel @tanstack/ai
8
10
 
9
11
  ```ts
10
12
  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
- })
13
+ import { apfelText } from "@tanstack-ai-browser/apfel"
14
+
15
+ const adapter = apfelText()
16
+
17
+ if ((await adapter.availability()) === "available") {
18
+ const stream = chat({ adapter, messages, threadId, tools })
25
19
  }
26
20
  ```
27
21
 
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.
22
+ Start apfel with `apfel --serve --cors`.
29
23
 
30
- ## Tool calls
24
+ ## Images
31
25
 
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:
26
+ Image parts go to apfel as OpenAI `image_url` parts, so `adapter.inputModalities` lists `"image"` for Apple's model. Apple's model reads images natively on macOS 27 and later; on older macOS, apfel passes it only the text and labels it reads from the image.
33
27
 
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.
28
+ ## Config
36
29
 
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`.
30
+ - `baseURL`: apfel's API, up to `/v1`. It defaults to `http://127.0.0.1:11434/v1`.
31
+ - `defaultHeaders`: headers for every request, such as a bearer token.
38
32
 
39
- ## Usage
33
+ ## Availability
40
34
 
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`).
35
+ - `adapter.availability()` resolves to `"available"` when apfel lists the model within 10 seconds, and to `"unavailable"` otherwise.
36
+ - `adapter.create({ signal? })` makes the same check. It resolves to the adapter, or rejects. Nothing loads, since apfel runs the model outside the page.
42
37
 
43
- ```ts
44
- // For example, in chat() middleware:
45
- const stats = { name: "stats", onUsage: (ctx, usage) => console.log(usage) }
46
- ```
38
+ ## Model options
39
+
40
+ `modelOptions` are the request fields apfel takes, with OpenAI's names, sent as given (as TanStack's adapters do with their provider's fields): `max_tokens`, `temperature`, `top_p` and `seed`.
41
+
42
+ ## Exports
43
+
44
+ - `@tanstack-ai-browser/apfel`:
45
+ - `apfelText(config?)` for Apple's model, or `apfelText(model, config?)` for another model apfel serves
46
+ - the types `ApfelConfig`, `ApfelModel`, `ApfelTextAdapter`, `ApfelTextModelOptions` and `Availability`
47
+ - the catalog: `APFEL_CHAT_MODELS` and `APFEL_MODEL_META`, with the types `ApfelChatModel` and `ApfelModelMeta`
47
48
 
48
- **Status:** early (0.0.x); the API may change. See the [repository README](https://github.com/daveycodez/tanstack-ai-browser#readme).
49
+ **Status:** early (0.x). The API may change.
package/dist/adapter.d.ts CHANGED
@@ -1,98 +1,45 @@
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. */
1
+ import type { OpenAICompatibleChatAdapter } from "@tanstack/ai-openai/compatible";
2
+ import type { ApfelChatModel } from "./model-meta.js";
3
+ export type ApfelConfig = {
4
+ /** apfel's OpenAI-compatible API, up to `/v1`. `http://127.0.0.1:11434/v1` by default. */
12
5
  baseURL?: string;
13
6
  /** Headers sent with every request, such as apfel's bearer token. */
14
7
  defaultHeaders?: Record<string, string>;
15
8
  };
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;
9
+ /** Per-call `modelOptions`: the request fields apfel takes, with OpenAI's names, sent as given. */
10
+ export type ApfelTextModelOptions = {
11
+ /** Most tokens to generate. */
44
12
  max_tokens?: number;
13
+ temperature?: number;
14
+ top_p?: number;
45
15
  seed?: number;
46
16
  };
47
- type ApfelReasoning = never;
48
- type MetadataByModality = {
49
- text: unknown;
50
- image: unknown;
51
- audio: unknown;
52
- video: unknown;
53
- document: unknown;
17
+ /** The Prompt API's availability values; apfel's model is "available" or "unavailable". */
18
+ export type Availability = "unavailable" | "downloadable" | "downloading" | "available";
19
+ /** TanStack's OpenAI-compatible adapter for apfel, with apfel's availability. */
20
+ export type ApfelTextAdapter = OpenAICompatibleChatAdapter<ApfelModel, ApfelTextModelOptions> & {
21
+ /** Text and images for Apple's model; not known for another model. */
22
+ readonly inputModalities: ReadonlyArray<"text" | "image"> | undefined;
23
+ /** "available" if apfel answers and serves the model, else "unavailable". */
24
+ availability(signal?: AbortSignal): Promise<Availability>;
25
+ /** Resolves to this adapter if apfel serves the model, else rejects. Nothing loads. */
26
+ create(options?: {
27
+ signal?: AbortSignal;
28
+ }): Promise<ApfelTextAdapter>;
54
29
  };
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
- }
30
+ /** A model apfel serves: Apple's ("apple-foundationmodel", the default), or any other name. */
31
+ export type ApfelModel = ApfelChatModel | (string & {});
78
32
  /**
79
- * A TanStack AI text adapter for Apple's on-device Foundation Model, served
80
- * by apfel.
33
+ * An adapter for a model apfel serves: Apple's on-device model by default.
81
34
  *
82
35
  * @example
83
36
  * ```ts
84
- * chat({
85
- * adapter: apfelText("apple-foundationmodel", { baseURL: APFEL_BASE_URL }),
86
- * messages,
87
- * threadId
88
- * })
37
+ * const adapter = apfelText()
38
+ * if ((await adapter.availability()) === "available") {
39
+ * chat({ adapter, messages, tools })
40
+ * }
89
41
  * ```
90
42
  */
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 {};
43
+ export declare function apfelText(config?: ApfelConfig): ApfelTextAdapter;
44
+ export declare function apfelText(model: ApfelModel, config?: ApfelConfig): ApfelTextAdapter;
98
45
  //# sourceMappingURL=adapter.d.ts.map
@@ -1 +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"}
1
+ {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,gCAAgC,CAAA;AAGjF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAErD,MAAM,MAAM,WAAW,GAAG;IACxB,0FAA0F;IAC1F,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,qEAAqE;IACrE,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CACxC,CAAA;AAED,mGAAmG;AACnG,MAAM,MAAM,qBAAqB,GAAG;IAClC,+BAA+B;IAC/B,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,IAAI,CAAC,EAAE,MAAM,CAAA;CACd,CAAA;AAED,2FAA2F;AAC3F,MAAM,MAAM,YAAY,GAAG,aAAa,GAAG,cAAc,GAAG,aAAa,GAAG,WAAW,CAAA;AAEvF,iFAAiF;AACjF,MAAM,MAAM,gBAAgB,GAAG,2BAA2B,CAAC,UAAU,EAAE,qBAAqB,CAAC,GAAG;IAC9F,sEAAsE;IACtE,QAAQ,CAAC,eAAe,EAAE,aAAa,CAAC,MAAM,GAAG,OAAO,CAAC,GAAG,SAAS,CAAA;IACrE,6EAA6E;IAC7E,YAAY,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC,CAAA;IACzD,uFAAuF;IACvF,MAAM,CAAC,OAAO,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,WAAW,CAAA;KAAE,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAA;CACtE,CAAA;AAID,+FAA+F;AAC/F,MAAM,MAAM,UAAU,GAAG,cAAc,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC,CAAA;AAEvD;;;;;;;;;;GAUG;AACH,wBAAgB,SAAS,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,gBAAgB,CAAA;AACjE,wBAAgB,SAAS,CAAC,KAAK,EAAE,UAAU,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,gBAAgB,CAAA"}