@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.
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Tool schemas in the subset of JSON Schema that apfel's guided generation
3
+ * enforces, for the tool step's arguments (see adapter.ts, `stepSchema`).
4
+ * apfel rejects the keywords it can't enforce with HTTP 400.
5
+ */
6
+ type JsonSchema = Record<string, unknown>;
7
+ /**
8
+ * The schema in the subset guided generation enforces: types, properties,
9
+ * required, items, enum, const and descriptions. Local `$ref`s are inlined,
10
+ * `anyOf`/`oneOf`/type unions become their first non-null choice, and
11
+ * constraints it can't enforce (lengths, patterns, formats…) are dropped.
12
+ */
13
+ export declare function toGuidedSchema(schema: unknown, root?: JsonSchema, depth?: number): JsonSchema;
14
+ /**
15
+ * Arguments written under `toGuidedSchema`, without the nulls it allows in
16
+ * optional fields: a null is a field the model left out.
17
+ */
18
+ export declare function withoutNulls(value: unknown, schema: JsonSchema): unknown;
19
+ export {};
20
+ //# sourceMappingURL=guided-schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"guided-schema.d.ts","sourceRoot":"","sources":["../src/guided-schema.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,KAAK,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;AAoBzC;;;;;GAKG;AACH,wBAAgB,cAAc,CAC5B,MAAM,EAAE,OAAO,EACf,IAAI,GAAE,UAA2C,EACjD,KAAK,SAAI,GACR,UAAU,CA4EZ;AA2BD;;;GAGG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,GAAG,OAAO,CAuBxE"}
@@ -0,0 +1,136 @@
1
+ /**
2
+ * Tool schemas in the subset of JSON Schema that apfel's guided generation
3
+ * enforces, for the tool step's arguments (see adapter.ts, `stepSchema`).
4
+ * apfel rejects the keywords it can't enforce with HTTP 400.
5
+ */
6
+ /** Keywords guided generation can enforce; apfel rejects others with 400. */
7
+ const KEPT = ["type", "description", "enum", "const"];
8
+ const isObject = (value) => !!value && typeof value === "object" && !Array.isArray(value);
9
+ /** The schema a local `$ref` (`#/$defs/X`, `#/definitions/X`) points to. */
10
+ function resolveRef(ref, root) {
11
+ if (!ref.startsWith("#/"))
12
+ return undefined;
13
+ let node = root;
14
+ for (const key of ref.slice(2).split("/")) {
15
+ node = isObject(node)
16
+ ? node[key.replace(/~1/g, "/").replace(/~0/g, "~")]
17
+ : undefined;
18
+ }
19
+ return isObject(node) ? node : undefined;
20
+ }
21
+ /**
22
+ * The schema in the subset guided generation enforces: types, properties,
23
+ * required, items, enum, const and descriptions. Local `$ref`s are inlined,
24
+ * `anyOf`/`oneOf`/type unions become their first non-null choice, and
25
+ * constraints it can't enforce (lengths, patterns, formats…) are dropped.
26
+ */
27
+ export function toGuidedSchema(schema, root = isObject(schema) ? schema : {}, depth = 0) {
28
+ if (!isObject(schema) || depth > 12)
29
+ return { type: "string" };
30
+ if (typeof schema.$ref === "string") {
31
+ const target = resolveRef(schema.$ref, root);
32
+ return target
33
+ ? toGuidedSchema({ ...target, ...withoutKey(schema, "$ref") }, root, depth + 1)
34
+ : { type: "string" };
35
+ }
36
+ const choices = (schema.anyOf ?? schema.oneOf);
37
+ if (Array.isArray(choices)) {
38
+ const choice = choices.find((c) => isObject(c) && c.type !== "null") ?? choices[0];
39
+ return toGuidedSchema({
40
+ ...(isObject(choice) ? choice : {}),
41
+ ...(schema.description !== undefined && {
42
+ description: schema.description
43
+ })
44
+ }, root, depth + 1);
45
+ }
46
+ if (Array.isArray(schema.allOf)) {
47
+ const merged = schema.allOf.reduce((all, part) => (isObject(part) ? mergeSchemas(all, part) : all), withoutKey(schema, "allOf"));
48
+ return toGuidedSchema(merged, root, depth + 1);
49
+ }
50
+ const out = {};
51
+ for (const key of KEPT) {
52
+ if (schema[key] !== undefined)
53
+ out[key] = schema[key];
54
+ }
55
+ if (Array.isArray(out.type)) {
56
+ out.type = out.type.find((t) => t !== "null") ?? "string";
57
+ }
58
+ if (out.type === undefined) {
59
+ if (isObject(schema.properties))
60
+ out.type = "object";
61
+ else if (schema.items !== undefined)
62
+ out.type = "array";
63
+ else if (Array.isArray(out.enum))
64
+ out.type = typeof out.enum[0];
65
+ else if (out.const === undefined)
66
+ out.type = "string";
67
+ }
68
+ if (out.type === "object") {
69
+ const properties = isObject(schema.properties) ? schema.properties : {};
70
+ out.properties = Object.fromEntries(Object.entries(properties).map(([key, value]) => [
71
+ key,
72
+ toGuidedSchema(value, root, depth + 1)
73
+ ]));
74
+ out.required = Array.isArray(schema.required)
75
+ ? schema.required.filter((key) => typeof key === "string" && key in properties)
76
+ : [];
77
+ // An optional field can also be null, as in the Prompt API adapter
78
+ // (Gemini Nano wrote 0 for coordinates it didn't have). apfel reads a
79
+ // nullable field as optional, and takes only strings in an enum, so an
80
+ // enum gets no null of its own. `withoutNulls` drops any null written.
81
+ const required = new Set(out.required);
82
+ for (const [key, value] of Object.entries(out.properties)) {
83
+ if (!required.has(key))
84
+ value.type = [value.type, "null"];
85
+ }
86
+ }
87
+ if (out.type === "array") {
88
+ out.items = toGuidedSchema(schema.items ?? {}, root, depth + 1);
89
+ }
90
+ return out;
91
+ }
92
+ function withoutKey(schema, key) {
93
+ const rest = { ...schema };
94
+ delete rest[key];
95
+ return rest;
96
+ }
97
+ function mergeSchemas(a, b) {
98
+ const merged = { ...a, ...b };
99
+ if (isObject(a.properties) || isObject(b.properties)) {
100
+ merged.properties = {
101
+ ...(isObject(a.properties) ? a.properties : {}),
102
+ ...(isObject(b.properties) ? b.properties : {})
103
+ };
104
+ }
105
+ if (Array.isArray(a.required) || Array.isArray(b.required)) {
106
+ merged.required = [
107
+ ...new Set([
108
+ ...(Array.isArray(a.required) ? a.required : []),
109
+ ...(Array.isArray(b.required) ? b.required : [])
110
+ ])
111
+ ];
112
+ }
113
+ return merged;
114
+ }
115
+ /**
116
+ * Arguments written under `toGuidedSchema`, without the nulls it allows in
117
+ * optional fields: a null is a field the model left out.
118
+ */
119
+ export function withoutNulls(value, schema) {
120
+ if (Array.isArray(value)) {
121
+ const items = isObject(schema.items) ? schema.items : {};
122
+ return value.map((item) => withoutNulls(item, items));
123
+ }
124
+ if (!isObject(value))
125
+ return value;
126
+ const properties = isObject(schema.properties) ? schema.properties : {};
127
+ const required = Array.isArray(schema.required) ? schema.required : [];
128
+ return Object.fromEntries(Object.entries(value).flatMap(([key, field]) => field === null && !required.includes(key)
129
+ ? []
130
+ : [
131
+ [
132
+ key,
133
+ withoutNulls(field, isObject(properties[key]) ? properties[key] : {})
134
+ ]
135
+ ]));
136
+ }
package/dist/index.d.ts CHANGED
@@ -1,6 +1,9 @@
1
1
  /**
2
2
  * TanStack AI adapter for Apple's on-device Foundation Model, served by apfel.
3
+ * The model catalog (model-meta.ts) is exported here too.
3
4
  */
4
- export { APFEL_BASE_URL, APFEL_TEXT_MODELS, ApfelTextAdapter, apfelText, isApfelAvailable } from "./adapter.js";
5
- export type { ApfelModelOptions, ApfelProviderUsageDetails, ApfelTextConfig, ApfelTextModel } from "./adapter.js";
5
+ export { apfelText } from "./adapter.js";
6
+ export type { ApfelConfig, ApfelModel, ApfelTextAdapter, ApfelTextModelOptions, Availability } from "./adapter.js";
7
+ export { APFEL_CHAT_MODELS, APFEL_MODEL_META } from "./model-meta.js";
8
+ export type { ApfelChatModel, ApfelModelMeta } from "./model-meta.js";
6
9
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EACL,cAAc,EACd,iBAAiB,EACjB,gBAAgB,EAChB,SAAS,EACT,gBAAgB,EACjB,MAAM,cAAc,CAAA;AACrB,YAAY,EACV,iBAAiB,EACjB,yBAAyB,EACzB,eAAe,EACf,cAAc,EACf,MAAM,cAAc,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AACxC,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AAClH,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAA;AACrE,YAAY,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA"}
package/dist/index.js CHANGED
@@ -1,4 +1,6 @@
1
1
  /**
2
2
  * TanStack AI adapter for Apple's on-device Foundation Model, served by apfel.
3
+ * The model catalog (model-meta.ts) is exported here too.
3
4
  */
4
- export { APFEL_BASE_URL, APFEL_TEXT_MODELS, ApfelTextAdapter, apfelText, isApfelAvailable } from "./adapter.js";
5
+ export { apfelText } from "./adapter.js";
6
+ export { APFEL_CHAT_MODELS, APFEL_MODEL_META } from "./model-meta.js";
@@ -0,0 +1,23 @@
1
+ /**
2
+ * The models apfel serves: Apple's on-device Foundation Model, with what it
3
+ * supports. It doesn't reason, so `chat()` offers no `reasoning` option for it.
4
+ * It reads images on macOS 27 and later; on older macOS apfel gives it only
5
+ * text read from the image.
6
+ */
7
+ /** What a model apfel serves supports. */
8
+ export type ApfelModelMeta = {
9
+ /** The model's id, as apfel lists it. */
10
+ name: string;
11
+ supports: {
12
+ input: ReadonlyArray<"text" | "image">;
13
+ output: ReadonlyArray<"text">;
14
+ capabilities: ReadonlyArray<"tools">;
15
+ };
16
+ };
17
+ /** The models apfel serves: Apple's on-device Foundation Model. */
18
+ export declare const APFEL_CHAT_MODELS: readonly ["apple-foundationmodel"];
19
+ /** A model apfel serves. */
20
+ export type ApfelChatModel = (typeof APFEL_CHAT_MODELS)[number];
21
+ /** Each model's metadata, by name. */
22
+ export declare const APFEL_MODEL_META: Readonly<Record<ApfelChatModel, ApfelModelMeta>>;
23
+ //# sourceMappingURL=model-meta.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"model-meta.d.ts","sourceRoot":"","sources":["../src/model-meta.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,0CAA0C;AAC1C,MAAM,MAAM,cAAc,GAAG;IAC3B,yCAAyC;IACzC,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE;QACR,KAAK,EAAE,aAAa,CAAC,MAAM,GAAG,OAAO,CAAC,CAAA;QACtC,MAAM,EAAE,aAAa,CAAC,MAAM,CAAC,CAAA;QAC7B,YAAY,EAAE,aAAa,CAAC,OAAO,CAAC,CAAA;KACrC,CAAA;CACF,CAAA;AAWD,mEAAmE;AACnE,eAAO,MAAM,iBAAiB,oCAAyC,CAAA;AAEvE,4BAA4B;AAC5B,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,iBAAiB,CAAC,CAAC,MAAM,CAAC,CAAA;AAE/D,sCAAsC;AACtC,eAAO,MAAM,gBAAgB,EAAE,QAAQ,CAAC,MAAM,CAAC,cAAc,EAAE,cAAc,CAAC,CAE7E,CAAA"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * The models apfel serves: Apple's on-device Foundation Model, with what it
3
+ * supports. It doesn't reason, so `chat()` offers no `reasoning` option for it.
4
+ * It reads images on macOS 27 and later; on older macOS apfel gives it only
5
+ * text read from the image.
6
+ */
7
+ const APPLE_FOUNDATION_MODEL = {
8
+ name: "apple-foundationmodel",
9
+ supports: {
10
+ input: ["text", "image"],
11
+ output: ["text"],
12
+ capabilities: ["tools"]
13
+ }
14
+ };
15
+ /** The models apfel serves: Apple's on-device Foundation Model. */
16
+ export const APFEL_CHAT_MODELS = [APPLE_FOUNDATION_MODEL.name];
17
+ /** Each model's metadata, by name. */
18
+ export const APFEL_MODEL_META = {
19
+ [APPLE_FOUNDATION_MODEL.name]: APPLE_FOUNDATION_MODEL
20
+ };
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Tool calls by prompt, a copy of the Prompt API adapter's
3
+ * (`@tanstack-ai-browser/prompt-api`'s `tools.ts`): keep the two
4
+ * identical. Apple's model writes malformed
5
+ * native tool calls that apfel can't parse, so the system prompt lists the
6
+ * tools as JSON and asks for a call as JSON in a ```tool_call fence; results
7
+ * go back in a ```tool_result fence. The adapter reads calls out of the
8
+ * streamed text.
9
+ */
10
+ import type { ModelMessage, Tool } from "@tanstack/ai";
11
+ /** A tool call read from the model's text. */
12
+ export type ParsedToolCall = {
13
+ name: string;
14
+ arguments: Record<string, unknown>;
15
+ };
16
+ /**
17
+ * The system prompt with the tools and how to call them; unchanged without
18
+ * tools.
19
+ */
20
+ export declare function toolSystemPrompt(system: string, tools: ReadonlyArray<Tool>): string;
21
+ /** Earlier tool calls in the conversation, as the model would have written them. */
22
+ export declare function toolCallFence(calls: NonNullable<ModelMessage["toolCalls"]>): string;
23
+ /** A tool's result line for a ```tool_result fence. */
24
+ export declare function toolResultLine(id: string, name: string, content: string, error?: string): string;
25
+ /** Tool results as the model reads them. */
26
+ export declare const toolResultFence: (lines: ReadonlyArray<string>) => string;
27
+ /**
28
+ * The first tool call in a fence's body: a JSON object (or an array of them,
29
+ * or one per line) with `name` (or `tool`, `tool_name`) and `arguments` (or
30
+ * `parameters`), the arguments possibly a JSON string, maybe wrapped in a
31
+ * `tool_call` or `function` key; or a Python-style call, `[name(key="v")]`;
32
+ * or `call:name{key:value}`.
33
+ */
34
+ export declare function parseToolCall(body: string): ParsedToolCall | undefined;
35
+ /**
36
+ * Reads a streamed reply: `push` each piece and get back the text that can
37
+ * be shown now; text inside a ```tool_call fence (or another call's
38
+ * delimiters: `<tool_call>`, `<|tool_call>`, `<|tool_call_start|>`, or a
39
+ * Python-style `[name(…)]`) is held back. The first
40
+ * fence that closes ends the reply (stop generating then): `fence` is its
41
+ * text, `call` the call in it, if it holds one (else the fence is a failed
42
+ * call, for `repairToolCall`), and `trailing` the text after it. Other code
43
+ * fences are text. `end` gives the rest, reading an unclosed fence as the
44
+ * reply's last.
45
+ */
46
+ export declare function toolCallReader(): {
47
+ readonly call: ParsedToolCall | undefined;
48
+ readonly fence: string | undefined;
49
+ /** Text that came after the fence: shown after the call. */
50
+ readonly trailing: string;
51
+ push(piece: string): string;
52
+ end(): string;
53
+ };
54
+ /** Why a call doesn't fit the tools (an unknown tool, or arguments that fail its schema), if it doesn't. */
55
+ export declare function invalidToolCall(call: ParsedToolCall, tools: ReadonlyArray<Tool>): string | undefined;
56
+ /** Why a reply's ```tool_call fence isn't a valid call, if it isn't. */
57
+ export declare const toolCallError: (call: ParsedToolCall | undefined, tools: ReadonlyArray<Tool>) => string | undefined;
58
+ /** The message after a failed call, asking for it again. */
59
+ export declare const repairPrompt: (reason: string) => string;
60
+ /**
61
+ * One repair of a failed tool call, with output held to a schema (as webgllm
62
+ * repairs): the tool's arguments if `name` is a tool, else first the tool's
63
+ * name, then its arguments. `generate(schema)` asks the model, with the
64
+ * conversation, its failed attempt and `repairPrompt`, for JSON that fits
65
+ * `schema`, and returns it parsed. Undefined if the repair fails too.
66
+ */
67
+ export declare function repairToolCall(tools: ReadonlyArray<Tool>, name: string | undefined, generate: (schema: Record<string, unknown>) => Promise<unknown>): Promise<ParsedToolCall | undefined>;
68
+ //# sourceMappingURL=tools.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,cAAc,CAAA;AAEtD,8CAA8C;AAC9C,MAAM,MAAM,cAAc,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,CAAA;AAiBjF;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,CAAC,IAAI,CAAC,GAAG,MAAM,CAiCnF;AAED,oFAAoF;AACpF,wBAAgB,aAAa,CAAC,KAAK,EAAE,WAAW,CAAC,YAAY,CAAC,WAAW,CAAC,CAAC,GAAG,MAAM,CAWnF;AAED,uDAAuD;AACvD,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAUhG;AAED,4CAA4C;AAC5C,eAAO,MAAM,eAAe,UAAW,aAAa,CAAC,MAAM,CAAC,WAAqD,CAAA;AAEjH;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS,CA0CtE;AAyHD;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc;aA4CtB,IAAI;aAGJ,KAAK;IAGT,4DAA4D;aACxD,QAAQ;IAGZ,IAAI,QAAQ,MAAM,GAAG,MAAM;IAI3B,GAAG,IAAI,MAAM;EAkBhB;AA2DD,4GAA4G;AAC5G,wBAAgB,eAAe,CAC7B,IAAI,EAAE,cAAc,EACpB,KAAK,EAAE,aAAa,CAAC,IAAI,CAAC,GACzB,MAAM,GAAG,SAAS,CAKpB;AAED,wEAAwE;AACxE,eAAO,MAAM,aAAa,SAClB,cAAc,GAAG,SAAS,SACzB,aAAa,CAAC,IAAI,CAAC,uBAIsB,CAAA;AAElD,4DAA4D;AAC5D,eAAO,MAAM,YAAY,WAAY,MAAM,WACgB,CAAA;AAE3D;;;;;;GAMG;AACH,wBAAsB,cAAc,CAClC,KAAK,EAAE,aAAa,CAAC,IAAI,CAAC,EAC1B,IAAI,EAAE,MAAM,GAAG,SAAS,EACxB,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,OAAO,CAAC,GAC9D,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC,CA2BrC"}