@tanstack-ai-browser/afm 0.0.0-stage → 0.2.2
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 +21 -0
- package/Package.swift +33 -0
- package/README.md +75 -2
- package/dist/adapter.d.ts +107 -0
- package/dist/adapter.d.ts.map +1 -0
- package/dist/adapter.js +522 -0
- package/dist/electron.d.ts +29 -0
- package/dist/electron.d.ts.map +1 -0
- package/dist/electron.js +136 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/json-value.d.ts +9 -0
- package/dist/json-value.d.ts.map +1 -0
- package/dist/json-value.js +19 -0
- package/dist/messages.d.ts +12 -0
- package/dist/messages.d.ts.map +1 -0
- package/dist/messages.js +29 -0
- package/dist/model-meta.d.ts +23 -0
- package/dist/model-meta.d.ts.map +1 -0
- package/dist/model-meta.js +20 -0
- package/dist/native.d.ts +175 -0
- package/dist/native.d.ts.map +1 -0
- package/dist/native.js +21 -0
- package/dist/schema.d.ts +17 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +133 -0
- package/dist/validate.d.ts +2 -0
- package/dist/validate.d.ts.map +1 -0
- package/dist/validate.js +124 -0
- package/electron/AfmNode.swift +81 -0
- package/electron/afm.c +94 -0
- package/electron/afm.node +0 -0
- package/electron/afm.node.sha256 +1 -0
- package/electron/build.mjs +54 -0
- package/electron/preload.cjs +35 -0
- package/electron/sources-hash.mjs +40 -0
- package/ios/Sources/AfmCore/Bridge.swift +354 -0
- package/ios/Sources/AfmCore/Conversation.swift +49 -0
- package/ios/Sources/AfmCore/Schema.swift +90 -0
- package/ios/Sources/AfmPlugin/AfmPlugin.swift +89 -0
- package/package.json +97 -3
- package/src/adapter.test.ts +428 -0
- package/src/adapter.ts +616 -0
- package/src/electron.ts +193 -0
- package/src/index.ts +28 -0
- package/src/json-value.test.ts +26 -0
- package/src/json-value.ts +17 -0
- package/src/messages.ts +37 -0
- package/src/model-meta.ts +37 -0
- package/src/native-build.test.ts +13 -0
- package/src/native.ts +128 -0
- package/src/schema.test.ts +146 -0
- package/src/schema.ts +153 -0
- package/src/validate.test.ts +38 -0
- package/src/validate.ts +112 -0
package/dist/electron.js
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
const PACKAGE = "@tanstack-ai-browser/afm";
|
|
2
|
+
const CALL = "afm:call";
|
|
3
|
+
const EVENT = "afm:event";
|
|
4
|
+
const appPages = (url) => {
|
|
5
|
+
try {
|
|
6
|
+
const { protocol, hostname } = new URL(url);
|
|
7
|
+
if (protocol !== "http:" && protocol !== "https:")
|
|
8
|
+
return true;
|
|
9
|
+
return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "[::1]";
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
const unpacked = (file) => file.replace(/app\.asar(?=[\\/])/, "app.asar.unpacked");
|
|
16
|
+
let served = false;
|
|
17
|
+
/**
|
|
18
|
+
* Runs Apple's on-device model for the app's pages, where `afmText()` finds it.
|
|
19
|
+
* Call it once in the main process, before opening windows. On a Mac before
|
|
20
|
+
* macOS 26, or elsewhere, the pages hear the model is unavailable.
|
|
21
|
+
*/
|
|
22
|
+
export function serveAfm(options = {}) {
|
|
23
|
+
const appGuardrails = options.guardrails;
|
|
24
|
+
if (served)
|
|
25
|
+
return;
|
|
26
|
+
served = true;
|
|
27
|
+
const node = globalThis.process;
|
|
28
|
+
const { createRequire } = node.getBuiltinModule("node:module");
|
|
29
|
+
const path = node.getBuiltinModule("node:path");
|
|
30
|
+
// Electron answers `require("electron")` from anywhere in the main process.
|
|
31
|
+
const electron = createRequire(node.execPath)("electron");
|
|
32
|
+
const { app } = electron;
|
|
33
|
+
const allow = options.allow ?? appPages;
|
|
34
|
+
let directory = options.directory;
|
|
35
|
+
if (!directory) {
|
|
36
|
+
try {
|
|
37
|
+
const fromApp = createRequire(path.join(app.getAppPath(), "package.json"));
|
|
38
|
+
directory = path.join(path.dirname(fromApp.resolve(`${PACKAGE}/package.json`)), "electron");
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
console.error(`Apple's on-device model is unavailable: ${PACKAGE} isn't in the app's node_modules (pass serveAfm's directory)`, error);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
let addon;
|
|
45
|
+
if (directory && node.platform === "darwin") {
|
|
46
|
+
try {
|
|
47
|
+
addon = createRequire(node.execPath)(unpacked(path.join(directory, "afm.node")));
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
console.error("Apple's on-device model is unavailable: its addon didn't load", error);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
// The native sessions, by native id: `${frame id}/${the page's id}`.
|
|
54
|
+
const owners = new Map();
|
|
55
|
+
const frameIdOf = (frame) => `${frame.processId}:${frame.frameToken}`;
|
|
56
|
+
const endSessions = (ended) => {
|
|
57
|
+
for (const [id, owner] of owners) {
|
|
58
|
+
if (!ended(owner))
|
|
59
|
+
continue;
|
|
60
|
+
owners.delete(id);
|
|
61
|
+
addon?.call("deleteSession", JSON.stringify({ sessionId: id }));
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
// A page's sessions end with its document: a navigation, or its contents closing.
|
|
65
|
+
const watched = new WeakSet();
|
|
66
|
+
const watch = (contents) => {
|
|
67
|
+
if (watched.has(contents))
|
|
68
|
+
return;
|
|
69
|
+
watched.add(contents);
|
|
70
|
+
const contentsId = contents.id;
|
|
71
|
+
contents.on("did-start-navigation", ({ isSameDocument, frame }) => {
|
|
72
|
+
if (isSameDocument || !frame)
|
|
73
|
+
return;
|
|
74
|
+
const frameId = frameIdOf(frame);
|
|
75
|
+
endSessions((owner) => owner.frameId === frameId);
|
|
76
|
+
});
|
|
77
|
+
contents.once("destroyed", () => endSessions((owner) => owner.contentsId === contentsId));
|
|
78
|
+
};
|
|
79
|
+
electron.ipcMain.handle(CALL, (event, method, args) => {
|
|
80
|
+
const frame = event.senderFrame;
|
|
81
|
+
if (!frame || !allow(frame.url))
|
|
82
|
+
throw new Error("This page may not use Apple's on-device model");
|
|
83
|
+
if (!addon) {
|
|
84
|
+
if (method === "availability")
|
|
85
|
+
return { available: false, reason: "unsupportedOS" };
|
|
86
|
+
throw new Error("Apple's on-device model needs macOS 26 on a Mac with Apple silicon");
|
|
87
|
+
}
|
|
88
|
+
const options = { ...(typeof args === "object" && args !== null ? args : {}) };
|
|
89
|
+
// The app's guardrails, where the page names none.
|
|
90
|
+
if ((method === "createSession" || method === "availability") && options.guardrails === undefined && appGuardrails) {
|
|
91
|
+
options.guardrails = appGuardrails;
|
|
92
|
+
}
|
|
93
|
+
// The frame's own session: another frame's can't be reached.
|
|
94
|
+
if (typeof options.sessionId === "string") {
|
|
95
|
+
const frameId = frameIdOf(frame);
|
|
96
|
+
const id = `${frameId}/${options.sessionId}`;
|
|
97
|
+
if (method === "createSession") {
|
|
98
|
+
owners.set(id, { frame, frameId, contentsId: event.sender.id, sessionId: options.sessionId });
|
|
99
|
+
watch(event.sender);
|
|
100
|
+
}
|
|
101
|
+
else if (method === "deleteSession") {
|
|
102
|
+
owners.delete(id);
|
|
103
|
+
}
|
|
104
|
+
options.sessionId = id;
|
|
105
|
+
}
|
|
106
|
+
const reply = JSON.parse(addon.call(method, JSON.stringify(options)));
|
|
107
|
+
if (reply.error)
|
|
108
|
+
throw new Error(reply.error);
|
|
109
|
+
return reply.result ?? null;
|
|
110
|
+
});
|
|
111
|
+
// Each event to its session's frame only, with the page's id, while its page is allowed.
|
|
112
|
+
addon?.setListener((json) => {
|
|
113
|
+
const event = JSON.parse(json);
|
|
114
|
+
const owner = typeof event.sessionId === "string" ? owners.get(event.sessionId) : undefined;
|
|
115
|
+
if (!owner || owner.frame.isDestroyed() || owner.frame.detached || !allow(owner.frame.url))
|
|
116
|
+
return;
|
|
117
|
+
owner.frame.send(EVENT, { ...event, sessionId: owner.sessionId });
|
|
118
|
+
});
|
|
119
|
+
// The preload, in every session's pages: window.afm.
|
|
120
|
+
if (!directory)
|
|
121
|
+
return;
|
|
122
|
+
const preload = path.join(directory, "preload.cjs");
|
|
123
|
+
const preloaded = new WeakSet();
|
|
124
|
+
const addPreload = (session) => {
|
|
125
|
+
if (preloaded.has(session))
|
|
126
|
+
return;
|
|
127
|
+
preloaded.add(session);
|
|
128
|
+
session.registerPreloadScript({ type: "frame", id: "tanstack-ai-browser-afm", filePath: preload });
|
|
129
|
+
};
|
|
130
|
+
// Now if the app is ready, so a window opened next has it.
|
|
131
|
+
if (app.isReady())
|
|
132
|
+
addPreload(electron.session.defaultSession);
|
|
133
|
+
else
|
|
134
|
+
void app.whenReady().then(() => addPreload(electron.session.defaultSession));
|
|
135
|
+
app.on("session-created", addPreload);
|
|
136
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TanStack AI adapter for Apple's on-device Foundation Model, through the
|
|
3
|
+
* Foundation Models framework, with its native tool calling: a Capacitor
|
|
4
|
+
* plugin for iOS (`Afm`), Electron's main process on macOS (`serveAfm()`), or
|
|
5
|
+
* any host implementing `AfmNative`. The model catalog (model-meta.ts) is
|
|
6
|
+
* exported here too.
|
|
7
|
+
*/
|
|
8
|
+
export { AfmTextAdapter, afmText } from "./adapter.js";
|
|
9
|
+
export type { Availability, AfmConfig, AfmTextModelOptions } from "./adapter.js";
|
|
10
|
+
export { Afm } from "./native.js";
|
|
11
|
+
export { serveAfm } from "./electron.js";
|
|
12
|
+
export type { ServeAfmOptions } from "./electron.js";
|
|
13
|
+
export type { AfmGuardrails, AfmNative, NativeAvailability, NativeEvent, NativeGenerationSchema, NativeMessage, NativeProperty, NativeSchema, NativeTool, NativeUsage, UnavailableReason } from "./native.js";
|
|
14
|
+
export { toNativeSchema } from "./schema.js";
|
|
15
|
+
export { AFM_CHAT_MODELS, AFM_MODEL_META } from "./model-meta.js";
|
|
16
|
+
export type { AfmChatModel, AfmModelMeta } from "./model-meta.js";
|
|
17
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AACtD,YAAY,EAAE,YAAY,EAAE,SAAS,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAA;AAChF,OAAO,EAAE,GAAG,EAAE,MAAM,aAAa,CAAA;AACjC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AACxC,YAAY,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AACpD,YAAY,EACV,aAAa,EACb,SAAS,EACT,kBAAkB,EAClB,WAAW,EACX,sBAAsB,EACtB,aAAa,EACb,cAAc,EACd,YAAY,EACZ,UAAU,EACV,WAAW,EACX,iBAAiB,EAClB,MAAM,aAAa,CAAA;AACpB,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAC5C,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AACjE,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TanStack AI adapter for Apple's on-device Foundation Model, through the
|
|
3
|
+
* Foundation Models framework, with its native tool calling: a Capacitor
|
|
4
|
+
* plugin for iOS (`Afm`), Electron's main process on macOS (`serveAfm()`), or
|
|
5
|
+
* any host implementing `AfmNative`. The model catalog (model-meta.ts) is
|
|
6
|
+
* exported here too.
|
|
7
|
+
*/
|
|
8
|
+
export { AfmTextAdapter, afmText } from "./adapter.js";
|
|
9
|
+
export { Afm } from "./native.js";
|
|
10
|
+
export { serveAfm } from "./electron.js";
|
|
11
|
+
export { toNativeSchema } from "./schema.js";
|
|
12
|
+
export { AFM_CHAT_MODELS, AFM_MODEL_META } from "./model-meta.js";
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The JSON value in a model's text answer: the whole trimmed text, or, when the
|
|
3
|
+
* model wrapped it in a Markdown code fence, everything inside the fence. Any
|
|
4
|
+
* value: an object, an array, a string, a number, a boolean or null. Never by
|
|
5
|
+
* brace positions, which would cut an array down to one of its objects, or a
|
|
6
|
+
* string that contains braces out of its quotes.
|
|
7
|
+
*/
|
|
8
|
+
export declare function parseJsonValue(text: string): unknown;
|
|
9
|
+
//# sourceMappingURL=json-value.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"json-value.d.ts","sourceRoot":"","sources":["../src/json-value.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CASpD"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The JSON value in a model's text answer: the whole trimmed text, or, when the
|
|
3
|
+
* model wrapped it in a Markdown code fence, everything inside the fence. Any
|
|
4
|
+
* value: an object, an array, a string, a number, a boolean or null. Never by
|
|
5
|
+
* brace positions, which would cut an array down to one of its objects, or a
|
|
6
|
+
* string that contains braces out of its quotes.
|
|
7
|
+
*/
|
|
8
|
+
export function parseJsonValue(text) {
|
|
9
|
+
const trimmed = text.trim();
|
|
10
|
+
try {
|
|
11
|
+
return JSON.parse(trimmed);
|
|
12
|
+
}
|
|
13
|
+
catch (error) {
|
|
14
|
+
const fenced = /^```[\w-]*[^\S\n]*\n([\s\S]*?)\n?```$/.exec(trimmed);
|
|
15
|
+
if (fenced)
|
|
16
|
+
return JSON.parse(fenced[1].trim());
|
|
17
|
+
throw error;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TanStack AI's messages as a native session's conversation (`NativeMessage`):
|
|
3
|
+
* text, the assistant's tool calls, and the tools' results. The framework
|
|
4
|
+
* reads text only here, so other parts are left out.
|
|
5
|
+
*/
|
|
6
|
+
import type { ModelMessage } from "@tanstack/ai";
|
|
7
|
+
import type { NativeMessage } from "./native.js";
|
|
8
|
+
/** A message's text: its string, or its text parts joined. */
|
|
9
|
+
export declare function contentText(content: ModelMessage["content"]): string;
|
|
10
|
+
/** The messages as a session's conversation, each tool result named by its call's tool. */
|
|
11
|
+
export declare function toNativeMessages(messages: ReadonlyArray<ModelMessage>): Array<NativeMessage>;
|
|
12
|
+
//# sourceMappingURL=messages.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../src/messages.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AAEhD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAEhD,8DAA8D;AAC9D,wBAAgB,WAAW,CAAC,OAAO,EAAE,YAAY,CAAC,SAAS,CAAC,GAAG,MAAM,CAOpE;AAED,2FAA2F;AAC3F,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,aAAa,CAAC,YAAY,CAAC,GAAG,KAAK,CAAC,aAAa,CAAC,CAgB5F"}
|
package/dist/messages.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/** A message's text: its string, or its text parts joined. */
|
|
2
|
+
export function contentText(content) {
|
|
3
|
+
if (typeof content === "string")
|
|
4
|
+
return content;
|
|
5
|
+
if (!Array.isArray(content))
|
|
6
|
+
return "";
|
|
7
|
+
return content
|
|
8
|
+
.map((part) => (part.type === "text" ? part.content : ""))
|
|
9
|
+
.filter(Boolean)
|
|
10
|
+
.join("\n");
|
|
11
|
+
}
|
|
12
|
+
/** The messages as a session's conversation, each tool result named by its call's tool. */
|
|
13
|
+
export function toNativeMessages(messages) {
|
|
14
|
+
const toolNames = new Map();
|
|
15
|
+
return messages.map((m) => {
|
|
16
|
+
if (m.role === "tool") {
|
|
17
|
+
const id = m.toolCallId ?? "";
|
|
18
|
+
return { role: "tool", toolCallId: id, name: m.name ?? toolNames.get(id) ?? "", text: contentText(m.content) };
|
|
19
|
+
}
|
|
20
|
+
if (m.role === "assistant") {
|
|
21
|
+
const toolCalls = (m.toolCalls ?? []).map((call) => {
|
|
22
|
+
toolNames.set(call.id, call.function.name);
|
|
23
|
+
return { id: call.id, name: call.function.name, arguments: call.function.arguments || "{}" };
|
|
24
|
+
});
|
|
25
|
+
return { role: "assistant", text: contentText(m.content), ...(toolCalls.length > 0 && { toolCalls }) };
|
|
26
|
+
}
|
|
27
|
+
return { role: "user", text: contentText(m.content) };
|
|
28
|
+
});
|
|
29
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The model the framework runs: Apple's system language model
|
|
3
|
+
* (`SystemLanguageModel.default`), the on-device model of Apple Intelligence.
|
|
4
|
+
* It calls tools natively, and holds structured answers to a schema. It doesn't
|
|
5
|
+
* reason, so `chat()` offers no `reasoning` option for it.
|
|
6
|
+
*/
|
|
7
|
+
/** What a Foundation Models model supports. */
|
|
8
|
+
export type AfmModelMeta = {
|
|
9
|
+
/** The model's name here. */
|
|
10
|
+
name: string;
|
|
11
|
+
supports: {
|
|
12
|
+
input: ReadonlyArray<"text">;
|
|
13
|
+
output: ReadonlyArray<"text">;
|
|
14
|
+
capabilities: ReadonlyArray<"tools" | "structured-output">;
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
/** The models the framework runs: Apple's system language model. */
|
|
18
|
+
export declare const AFM_CHAT_MODELS: readonly ["system"];
|
|
19
|
+
/** A model the framework runs. */
|
|
20
|
+
export type AfmChatModel = (typeof AFM_CHAT_MODELS)[number];
|
|
21
|
+
/** Each model's metadata, by name. */
|
|
22
|
+
export declare const AFM_MODEL_META: Readonly<Record<AfmChatModel, AfmModelMeta>>;
|
|
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,+CAA+C;AAC/C,MAAM,MAAM,YAAY,GAAG;IACzB,6BAA6B;IAC7B,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE;QACR,KAAK,EAAE,aAAa,CAAC,MAAM,CAAC,CAAA;QAC5B,MAAM,EAAE,aAAa,CAAC,MAAM,CAAC,CAAA;QAC7B,YAAY,EAAE,aAAa,CAAC,OAAO,GAAG,mBAAmB,CAAC,CAAA;KAC3D,CAAA;CACF,CAAA;AAWD,oEAAoE;AACpE,eAAO,MAAM,eAAe,qBAA+B,CAAA;AAE3D,kCAAkC;AAClC,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,eAAe,CAAC,CAAC,MAAM,CAAC,CAAA;AAE3D,sCAAsC;AACtC,eAAO,MAAM,cAAc,EAAE,QAAQ,CAAC,MAAM,CAAC,YAAY,EAAE,YAAY,CAAC,CAEvE,CAAA"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The model the framework runs: Apple's system language model
|
|
3
|
+
* (`SystemLanguageModel.default`), the on-device model of Apple Intelligence.
|
|
4
|
+
* It calls tools natively, and holds structured answers to a schema. It doesn't
|
|
5
|
+
* reason, so `chat()` offers no `reasoning` option for it.
|
|
6
|
+
*/
|
|
7
|
+
const SYSTEM_MODEL = {
|
|
8
|
+
name: "system",
|
|
9
|
+
supports: {
|
|
10
|
+
input: ["text"],
|
|
11
|
+
output: ["text"],
|
|
12
|
+
capabilities: ["tools", "structured-output"]
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
/** The models the framework runs: Apple's system language model. */
|
|
16
|
+
export const AFM_CHAT_MODELS = [SYSTEM_MODEL.name];
|
|
17
|
+
/** Each model's metadata, by name. */
|
|
18
|
+
export const AFM_MODEL_META = {
|
|
19
|
+
[SYSTEM_MODEL.name]: SYSTEM_MODEL
|
|
20
|
+
};
|
package/dist/native.d.ts
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The native side of the adapter: Apple's Foundation Models framework, reached
|
|
3
|
+
* through the Capacitor plugin on iOS (ios/Sources), or the Electron main
|
|
4
|
+
* process (`serveAfm()`, whose preload puts it at `window.afm`). Any host can
|
|
5
|
+
* stand in for them by implementing `AfmNative`.
|
|
6
|
+
*/
|
|
7
|
+
import type { PluginListenerHandle } from "@capacitor/core";
|
|
8
|
+
/** Why the model can't run: Apple's reasons, or an OS before 26. */
|
|
9
|
+
export type UnavailableReason = "deviceNotEligible" | "appleIntelligenceNotEnabled" | "modelNotReady" | "unsupportedOS" | "unavailable";
|
|
10
|
+
export type NativeAvailability = {
|
|
11
|
+
available: boolean;
|
|
12
|
+
reason?: UnavailableReason;
|
|
13
|
+
/** Tokens a session holds (iOS/macOS 26.4 and later). */
|
|
14
|
+
contextSize?: number;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* A schema as the framework takes it: JSON Schema read into an ordered form
|
|
18
|
+
* (`schema.ts`), properties in the order given, and named definitions for
|
|
19
|
+
* `reference`s.
|
|
20
|
+
*/
|
|
21
|
+
export type NativeSchema = {
|
|
22
|
+
kind: "object";
|
|
23
|
+
name: string;
|
|
24
|
+
description?: string;
|
|
25
|
+
properties: Array<NativeProperty>;
|
|
26
|
+
} | {
|
|
27
|
+
kind: "array";
|
|
28
|
+
items: NativeSchema;
|
|
29
|
+
minimumElements?: number;
|
|
30
|
+
maximumElements?: number;
|
|
31
|
+
} | {
|
|
32
|
+
kind: "string";
|
|
33
|
+
choices?: Array<string>;
|
|
34
|
+
constant?: string;
|
|
35
|
+
} | {
|
|
36
|
+
kind: "integer" | "number";
|
|
37
|
+
minimum?: number;
|
|
38
|
+
maximum?: number;
|
|
39
|
+
} | {
|
|
40
|
+
kind: "boolean";
|
|
41
|
+
} | {
|
|
42
|
+
kind: "null";
|
|
43
|
+
} | {
|
|
44
|
+
kind: "anyOf";
|
|
45
|
+
name: string;
|
|
46
|
+
description?: string;
|
|
47
|
+
choices: Array<NativeSchema>;
|
|
48
|
+
} | {
|
|
49
|
+
kind: "reference";
|
|
50
|
+
name: string;
|
|
51
|
+
};
|
|
52
|
+
export type NativeProperty = {
|
|
53
|
+
name: string;
|
|
54
|
+
description?: string;
|
|
55
|
+
optional: boolean;
|
|
56
|
+
schema: NativeSchema;
|
|
57
|
+
};
|
|
58
|
+
/** A root schema and the definitions its references name. */
|
|
59
|
+
export type NativeGenerationSchema = {
|
|
60
|
+
root: NativeSchema;
|
|
61
|
+
definitions: Array<NativeSchema>;
|
|
62
|
+
};
|
|
63
|
+
export type NativeTool = {
|
|
64
|
+
name: string;
|
|
65
|
+
description: string;
|
|
66
|
+
parameters: NativeGenerationSchema;
|
|
67
|
+
};
|
|
68
|
+
/** A message of the conversation a session starts from. */
|
|
69
|
+
export type NativeMessage = {
|
|
70
|
+
role: "user";
|
|
71
|
+
text: string;
|
|
72
|
+
} | {
|
|
73
|
+
role: "assistant";
|
|
74
|
+
text: string;
|
|
75
|
+
toolCalls?: Array<{
|
|
76
|
+
id: string;
|
|
77
|
+
name: string;
|
|
78
|
+
arguments: string;
|
|
79
|
+
}>;
|
|
80
|
+
} | {
|
|
81
|
+
role: "tool";
|
|
82
|
+
toolCallId: string;
|
|
83
|
+
name: string;
|
|
84
|
+
text: string;
|
|
85
|
+
};
|
|
86
|
+
/**
|
|
87
|
+
* A response's tokens, over every pass of the model (a tool call's too).
|
|
88
|
+
* `cachedTokens` is there only where the framework counts it right: for a
|
|
89
|
+
* session without tools.
|
|
90
|
+
*/
|
|
91
|
+
export type NativeUsage = {
|
|
92
|
+
inputTokens: number;
|
|
93
|
+
cachedTokens?: number;
|
|
94
|
+
outputTokens: number;
|
|
95
|
+
};
|
|
96
|
+
/** What a session sends while it responds, for the `turn` of the `respond` call. */
|
|
97
|
+
export type NativeEvent = {
|
|
98
|
+
sessionId: string;
|
|
99
|
+
turn: number;
|
|
100
|
+
} & ({
|
|
101
|
+
type: "delta";
|
|
102
|
+
text: string;
|
|
103
|
+
} | {
|
|
104
|
+
type: "toolCalls";
|
|
105
|
+
calls: Array<{
|
|
106
|
+
id: string;
|
|
107
|
+
name: string;
|
|
108
|
+
arguments: string;
|
|
109
|
+
}>;
|
|
110
|
+
}
|
|
111
|
+
/** `asText`: a schema's answer written as text (permissive guardrails' fallback), not held to the schema. */
|
|
112
|
+
| {
|
|
113
|
+
type: "done";
|
|
114
|
+
text: string;
|
|
115
|
+
usage?: NativeUsage;
|
|
116
|
+
asText?: boolean;
|
|
117
|
+
} | {
|
|
118
|
+
type: "error";
|
|
119
|
+
code: string;
|
|
120
|
+
message: string;
|
|
121
|
+
});
|
|
122
|
+
/**
|
|
123
|
+
* Apple's guardrails for the model: "default", or "permissiveContentTransformations",
|
|
124
|
+
* which lets through text the default ones can block out of context (a card game's
|
|
125
|
+
* "Searing Blood" or "Play with Fire").
|
|
126
|
+
*/
|
|
127
|
+
export type AfmGuardrails = "default" | "permissiveContentTransformations";
|
|
128
|
+
export interface AfmNative {
|
|
129
|
+
availability(options?: {
|
|
130
|
+
guardrails?: AfmGuardrails;
|
|
131
|
+
}): Promise<NativeAvailability>;
|
|
132
|
+
createSession(options: {
|
|
133
|
+
sessionId: string;
|
|
134
|
+
instructions?: string;
|
|
135
|
+
tools: Array<NativeTool>;
|
|
136
|
+
messages: Array<NativeMessage>;
|
|
137
|
+
guardrails?: AfmGuardrails;
|
|
138
|
+
}): Promise<void>;
|
|
139
|
+
prewarm(options: {
|
|
140
|
+
sessionId: string;
|
|
141
|
+
}): Promise<void>;
|
|
142
|
+
/** Prompts a session; the response comes as events. A `schema` holds the answer to it (JSON in `done`). */
|
|
143
|
+
respond(options: {
|
|
144
|
+
sessionId: string;
|
|
145
|
+
turn: number;
|
|
146
|
+
prompt: string;
|
|
147
|
+
options?: {
|
|
148
|
+
temperature?: number;
|
|
149
|
+
maximumResponseTokens?: number;
|
|
150
|
+
/** Whether the model may call the session's tools, must, or mustn't (iOS/macOS 27). Left out, the framework decides. */
|
|
151
|
+
toolCallingMode?: "allowed" | "required" | "disallowed";
|
|
152
|
+
};
|
|
153
|
+
schema?: NativeGenerationSchema;
|
|
154
|
+
}): Promise<void>;
|
|
155
|
+
/** A tool's result: the session's waiting call returns it, and the response goes on. */
|
|
156
|
+
submitToolResult(options: {
|
|
157
|
+
sessionId: string;
|
|
158
|
+
callId: string;
|
|
159
|
+
output: string;
|
|
160
|
+
}): Promise<void>;
|
|
161
|
+
/** Stops a session's response. */
|
|
162
|
+
cancel(options: {
|
|
163
|
+
sessionId: string;
|
|
164
|
+
}): Promise<void>;
|
|
165
|
+
deleteSession(options: {
|
|
166
|
+
sessionId: string;
|
|
167
|
+
}): Promise<void>;
|
|
168
|
+
addListener(eventName: "event", listener: (event: NativeEvent) => void): Promise<PluginListenerHandle>;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* The Capacitor plugin (iOS), loading Capacitor on its first call. Elsewhere
|
|
172
|
+
* its calls reject: Capacitor has no web version of it.
|
|
173
|
+
*/
|
|
174
|
+
export declare const Afm: AfmNative;
|
|
175
|
+
//# sourceMappingURL=native.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"native.d.ts","sourceRoot":"","sources":["../src/native.ts"],"names":[],"mappings":"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"}
|
package/dist/native.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
let loading;
|
|
2
|
+
/**
|
|
3
|
+
* The plugin, loading Capacitor on first use: pages elsewhere never load it.
|
|
4
|
+
* It's wrapped: a Capacitor plugin answers every property, `then` too, so a
|
|
5
|
+
* promise of the plugin itself would wait on it forever.
|
|
6
|
+
*/
|
|
7
|
+
const capacitor = () => (loading ??= import("@capacitor/core").then(({ registerPlugin }) => ({ plugin: registerPlugin("Afm") })));
|
|
8
|
+
/**
|
|
9
|
+
* The Capacitor plugin (iOS), loading Capacitor on its first call. Elsewhere
|
|
10
|
+
* its calls reject: Capacitor has no web version of it.
|
|
11
|
+
*/
|
|
12
|
+
export const Afm = {
|
|
13
|
+
availability: async (options) => (await capacitor()).plugin.availability(options),
|
|
14
|
+
createSession: async (options) => (await capacitor()).plugin.createSession(options),
|
|
15
|
+
prewarm: async (options) => (await capacitor()).plugin.prewarm(options),
|
|
16
|
+
respond: async (options) => (await capacitor()).plugin.respond(options),
|
|
17
|
+
submitToolResult: async (options) => (await capacitor()).plugin.submitToolResult(options),
|
|
18
|
+
cancel: async (options) => (await capacitor()).plugin.cancel(options),
|
|
19
|
+
deleteSession: async (options) => (await capacitor()).plugin.deleteSession(options),
|
|
20
|
+
addListener: async (name, listener) => (await capacitor()).plugin.addListener(name, listener)
|
|
21
|
+
};
|
package/dist/schema.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSON Schema (what `chat()` gives adapters for tools and structured output)
|
|
3
|
+
* read into the framework's own schema (`NativeSchema`), which the model's
|
|
4
|
+
* output is held to: a tool's arguments always fit its parameters.
|
|
5
|
+
*
|
|
6
|
+
* Properties keep their order, which the model writes them in. Each object is
|
|
7
|
+
* named by its path, so two objects never share a name. A property that may be
|
|
8
|
+
* null is optional. `$ref`s to `$defs` (or `definitions`) become references to
|
|
9
|
+
* named definitions; one to a plain value is written in place.
|
|
10
|
+
*/
|
|
11
|
+
import type { NativeGenerationSchema } from "./native.js";
|
|
12
|
+
/**
|
|
13
|
+
* The framework's schema for a JSON Schema, named `name` (a tool's name, or
|
|
14
|
+
* the structured answer's).
|
|
15
|
+
*/
|
|
16
|
+
export declare function toNativeSchema(schema: unknown, name: string): NativeGenerationSchema;
|
|
17
|
+
//# sourceMappingURL=schema.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,KAAK,EAAE,sBAAsB,EAAgB,MAAM,aAAa,CAAA;AA2BvE;;;GAGG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,sBAAsB,CA+GpF"}
|