apple-llm 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/CHANGELOG.md +94 -0
- package/README.md +251 -112
- package/dist/ai-sdk.cjs +281 -0
- package/dist/ai-sdk.d.cts +54 -0
- package/dist/ai-sdk.d.ts +54 -0
- package/dist/ai-sdk.js +281 -0
- package/dist/chunk-GM325EMJ.js +2584 -0
- package/dist/chunk-NRDZIP5G.cjs +2588 -0
- package/dist/chunk-OQATUZWF.cjs +81 -0
- package/dist/chunk-ZB4RDEPW.js +81 -0
- package/dist/cli.js +3163 -24
- package/dist/client-CXewzZTj.d.cts +1160 -0
- package/dist/client-CXewzZTj.d.ts +1160 -0
- package/dist/index.cjs +110 -1667
- package/dist/index.d.cts +84 -627
- package/dist/index.d.ts +84 -627
- package/dist/index.js +31 -1
- package/dist/server.cjs +361 -0
- package/dist/server.d.cts +51 -0
- package/dist/server.d.ts +51 -0
- package/dist/server.js +361 -0
- package/package.json +66 -14
- package/swift/helper.swift +748 -255
- package/dist/chunk-FQTRQ3KP.js +0 -1590
- package/dist/cli.cjs +0 -2027
|
@@ -0,0 +1,2588 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { newObj[key] = obj[key]; } } } newObj.default = obj; return newObj; } } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } async function _asyncNullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return await rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } async function _asyncOptionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = await fn(value); } else if (op === 'call' || op === 'optionalCall') { value = await fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _class; var _class2; var _class3; var _class4; var _class5; var _class6; var _class7; var _class8; var _class9; var _class10; var _class11; var _class12; var _class13; var _class14; var _class15; var _class16; var _class17;// node_modules/tsup/assets/cjs_shims.js
|
|
2
|
+
var getImportMetaUrl = () => typeof document === "undefined" ? new URL(`file:${__filename}`).href : document.currentScript && document.currentScript.tagName.toUpperCase() === "SCRIPT" ? document.currentScript.src : new URL("main.js", document.baseURI).href;
|
|
3
|
+
var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
|
|
4
|
+
|
|
5
|
+
// src/errors.ts
|
|
6
|
+
var AppleLLMError = (_class = class extends Error {
|
|
7
|
+
constructor(message, tier, options) {
|
|
8
|
+
super(message, _optionalChain([options, 'optionalAccess', _ => _.cause]) === void 0 ? void 0 : { cause: options.cause });_class.prototype.__init.call(this);;
|
|
9
|
+
this.tier = tier;
|
|
10
|
+
this.name = new.target.name;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
__init() {this.code = "APPLE_LLM_ERROR"}
|
|
14
|
+
}, _class);
|
|
15
|
+
var ModelUnavailableError = (_class2 = class extends AppleLLMError {
|
|
16
|
+
constructor(message, reason, tier, options) {
|
|
17
|
+
super(message, tier, options);_class2.prototype.__init2.call(this);;
|
|
18
|
+
this.reason = reason;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
__init2() {this.code = "MODEL_UNAVAILABLE"}
|
|
22
|
+
}, _class2);
|
|
23
|
+
var SchemaRejectedError = (_class3 = class extends AppleLLMError {constructor(...args2) { super(...args2); _class3.prototype.__init3.call(this); }
|
|
24
|
+
__init3() {this.code = "SCHEMA_REJECTED"}
|
|
25
|
+
}, _class3);
|
|
26
|
+
var SchemaValidationError = (_class4 = class extends AppleLLMError {
|
|
27
|
+
constructor(message, issues, text, tier) {
|
|
28
|
+
super(message, tier);_class4.prototype.__init4.call(this);;
|
|
29
|
+
this.issues = issues;
|
|
30
|
+
this.text = text;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
__init4() {this.code = "SCHEMA_VALIDATION"}
|
|
35
|
+
}, _class4);
|
|
36
|
+
var ContextLengthError = (_class5 = class extends AppleLLMError {
|
|
37
|
+
constructor(message, tier, contextSize, tokenCount) {
|
|
38
|
+
super(message, tier);_class5.prototype.__init5.call(this);;
|
|
39
|
+
this.contextSize = contextSize;
|
|
40
|
+
this.tokenCount = tokenCount;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
__init5() {this.code = "CONTEXT_LENGTH"}
|
|
45
|
+
}, _class5);
|
|
46
|
+
var QuotaError = (_class6 = class extends AppleLLMError {
|
|
47
|
+
constructor(message, tier, resetDate) {
|
|
48
|
+
super(message, tier);_class6.prototype.__init6.call(this);;
|
|
49
|
+
this.resetDate = resetDate;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
__init6() {this.code = "QUOTA"}
|
|
53
|
+
}, _class6);
|
|
54
|
+
var TimeoutError = (_class7 = class extends AppleLLMError {constructor(...args3) { super(...args3); _class7.prototype.__init7.call(this); }
|
|
55
|
+
__init7() {this.code = "TIMEOUT"}
|
|
56
|
+
}, _class7);
|
|
57
|
+
var AbortError = (_class8 = class extends AppleLLMError {constructor(...args4) { super(...args4); _class8.prototype.__init8.call(this); }
|
|
58
|
+
__init8() {this.code = "ABORTED"}
|
|
59
|
+
}, _class8);
|
|
60
|
+
var SetupRequiredError = (_class9 = class extends AppleLLMError {
|
|
61
|
+
constructor(message, step, tier) {
|
|
62
|
+
super(message, tier);_class9.prototype.__init9.call(this);;
|
|
63
|
+
this.step = step;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
__init9() {this.code = "SETUP_REQUIRED"}
|
|
67
|
+
}, _class9);
|
|
68
|
+
var RefusalError = (_class10 = class extends AppleLLMError {constructor(...args5) { super(...args5); _class10.prototype.__init10.call(this); }
|
|
69
|
+
__init10() {this.code = "REFUSAL"}
|
|
70
|
+
}, _class10);
|
|
71
|
+
var ToolExecutionError = (_class11 = class extends AppleLLMError {
|
|
72
|
+
constructor(message, toolName, options) {
|
|
73
|
+
super(message, "device", options);_class11.prototype.__init11.call(this);;
|
|
74
|
+
this.toolName = toolName;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
__init11() {this.code = "TOOL_EXECUTION"}
|
|
78
|
+
}, _class11);
|
|
79
|
+
var ModelBusyError = (_class12 = class extends AppleLLMError {constructor(...args6) { super(...args6); _class12.prototype.__init12.call(this); }
|
|
80
|
+
__init12() {this.code = "MODEL_BUSY"}
|
|
81
|
+
}, _class12);
|
|
82
|
+
var UnsupportedError = (_class13 = class extends AppleLLMError {constructor(...args7) { super(...args7); _class13.prototype.__init13.call(this); }
|
|
83
|
+
__init13() {this.code = "UNSUPPORTED"}
|
|
84
|
+
}, _class13);
|
|
85
|
+
function unavailableMessage(reason) {
|
|
86
|
+
const base = "Apple\u2019s on-device model is not available";
|
|
87
|
+
if (_optionalChain([reason, 'optionalAccess', _2 => _2.includes, 'call', _3 => _3("appleIntelligenceNotEnabled")])) {
|
|
88
|
+
return `${base}: Apple Intelligence is turned off.
|
|
89
|
+
Enable it in System Settings \u203A Apple Intelligence & Siri, then try again.`;
|
|
90
|
+
}
|
|
91
|
+
if (_optionalChain([reason, 'optionalAccess', _4 => _4.includes, 'call', _5 => _5("modelNotReady")])) {
|
|
92
|
+
return `${base}: the model is still downloading.
|
|
93
|
+
Leave the Mac online and plugged in for a few minutes, then try again.`;
|
|
94
|
+
}
|
|
95
|
+
if (_optionalChain([reason, 'optionalAccess', _6 => _6.includes, 'call', _7 => _7("deviceNotEligible")])) {
|
|
96
|
+
return `${base}: this Mac is not eligible for Apple Intelligence.`;
|
|
97
|
+
}
|
|
98
|
+
if (_optionalChain([reason, 'optionalAccess', _8 => _8.includes, 'call', _9 => _9("unsupportedPlatform")])) {
|
|
99
|
+
return `${base}: this platform is not supported (macOS on Apple Silicon only).`;
|
|
100
|
+
}
|
|
101
|
+
if (_optionalChain([reason, 'optionalAccess', _10 => _10.includes, 'call', _11 => _11("unsupportedOSVersion")])) {
|
|
102
|
+
return `${base}: needs macOS 26 or later.`;
|
|
103
|
+
}
|
|
104
|
+
if (_optionalChain([reason, 'optionalAccess', _12 => _12.includes, 'call', _13 => _13("noSwiftCompiler")])) {
|
|
105
|
+
return `${base}: no Swift compiler found.
|
|
106
|
+
Install the Xcode command line tools with \`xcode-select --install\`, then try again.`;
|
|
107
|
+
}
|
|
108
|
+
return `${base}${reason ? `: ${reason}` : "."}`;
|
|
109
|
+
}
|
|
110
|
+
function toUnavailableReason(reason) {
|
|
111
|
+
if (_optionalChain([reason, 'optionalAccess', _14 => _14.includes, 'call', _15 => _15("appleIntelligenceNotEnabled")])) return "appleIntelligenceNotEnabled";
|
|
112
|
+
if (_optionalChain([reason, 'optionalAccess', _16 => _16.includes, 'call', _17 => _17("modelNotReady")])) return "modelNotReady";
|
|
113
|
+
if (_optionalChain([reason, 'optionalAccess', _18 => _18.includes, 'call', _19 => _19("deviceNotEligible")])) return "deviceNotEligible";
|
|
114
|
+
if (_optionalChain([reason, 'optionalAccess', _20 => _20.includes, 'call', _21 => _21("unsupportedPlatform")])) return "unsupportedPlatform";
|
|
115
|
+
if (_optionalChain([reason, 'optionalAccess', _22 => _22.includes, 'call', _23 => _23("unsupportedOSVersion")])) return "unsupportedOSVersion";
|
|
116
|
+
if (_optionalChain([reason, 'optionalAccess', _24 => _24.includes, 'call', _25 => _25("noSwiftCompiler")])) return "noSwiftCompiler";
|
|
117
|
+
return "unknown";
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// src/json-recovery.ts
|
|
121
|
+
function stripCodeFences(text) {
|
|
122
|
+
const trimmed = text.trim();
|
|
123
|
+
const fenced = /```(?:json)?\s*\n?([\s\S]*?)```/i.exec(trimmed);
|
|
124
|
+
if (_optionalChain([fenced, 'optionalAccess', _26 => _26[1]]) !== void 0) return fenced[1].trim();
|
|
125
|
+
if (trimmed.startsWith("```")) {
|
|
126
|
+
return trimmed.replace(/^```(?:json)?\s*/i, "").replace(/```\s*$/, "").trim();
|
|
127
|
+
}
|
|
128
|
+
return trimmed;
|
|
129
|
+
}
|
|
130
|
+
function extractJsonSpan(text) {
|
|
131
|
+
for (let start = 0; start < text.length; start += 1) {
|
|
132
|
+
const open = text[start];
|
|
133
|
+
if (open !== "{" && open !== "[") continue;
|
|
134
|
+
const close = open === "{" ? "}" : "]";
|
|
135
|
+
let depth = 0;
|
|
136
|
+
let inString = false;
|
|
137
|
+
let escaped = false;
|
|
138
|
+
for (let i = start; i < text.length; i += 1) {
|
|
139
|
+
const ch = text[i];
|
|
140
|
+
if (escaped) {
|
|
141
|
+
escaped = false;
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
if (ch === "\\") {
|
|
145
|
+
if (inString) escaped = true;
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
if (ch === '"') {
|
|
149
|
+
inString = !inString;
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
if (inString) continue;
|
|
153
|
+
if (ch === open) depth += 1;
|
|
154
|
+
else if (ch === close) {
|
|
155
|
+
depth -= 1;
|
|
156
|
+
if (depth === 0) return text.slice(start, i + 1);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
function parseLlmJson(raw) {
|
|
163
|
+
const text = stripCodeFences(raw);
|
|
164
|
+
try {
|
|
165
|
+
return JSON.parse(text);
|
|
166
|
+
} catch (e) {
|
|
167
|
+
}
|
|
168
|
+
let offset = 0;
|
|
169
|
+
while (offset < text.length) {
|
|
170
|
+
const span = extractJsonSpan(text.slice(offset));
|
|
171
|
+
if (span === null) break;
|
|
172
|
+
try {
|
|
173
|
+
return JSON.parse(span);
|
|
174
|
+
} catch (e2) {
|
|
175
|
+
}
|
|
176
|
+
const idx = text.indexOf(span, offset);
|
|
177
|
+
offset = (idx === -1 ? offset : idx) + 1;
|
|
178
|
+
}
|
|
179
|
+
throw new Error(`Model returned invalid JSON: ${raw.slice(0, 200)}`);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// src/target.ts
|
|
183
|
+
var _child_process = require('child_process');
|
|
184
|
+
var _util = require('util');
|
|
185
|
+
var execFileAsync = _util.promisify.call(void 0, _child_process.execFile);
|
|
186
|
+
function isAppleSiliconMac() {
|
|
187
|
+
return process.platform === "darwin" && process.arch === "arm64";
|
|
188
|
+
}
|
|
189
|
+
function targetTripleFrom(versionOutput, arch = process.arch) {
|
|
190
|
+
const [major, minor = "0"] = versionOutput.trim().split(".");
|
|
191
|
+
if (!major || !/^\d+$/.test(major)) return null;
|
|
192
|
+
if (minor !== void 0 && !/^\d+$/.test(minor)) return null;
|
|
193
|
+
const cpu = arch === "x64" || arch === "x86_64" ? "x86_64" : "arm64";
|
|
194
|
+
return `${cpu}-apple-macos${major}.${minor}`;
|
|
195
|
+
}
|
|
196
|
+
async function macosMajor() {
|
|
197
|
+
try {
|
|
198
|
+
const { stdout } = await execFileAsync("sw_vers", ["-productVersion"]);
|
|
199
|
+
const major = Number.parseInt(_nullishCoalesce(stdout.trim().split(".")[0], () => ( "")), 10);
|
|
200
|
+
return Number.isFinite(major) ? major : null;
|
|
201
|
+
} catch (e3) {
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
async function hostTarget() {
|
|
206
|
+
for (const [cmd, args] of [
|
|
207
|
+
["xcrun", ["--show-sdk-version"]],
|
|
208
|
+
["sw_vers", ["-productVersion"]]
|
|
209
|
+
]) {
|
|
210
|
+
try {
|
|
211
|
+
const { stdout } = await execFileAsync(cmd, args);
|
|
212
|
+
const triple = targetTripleFrom(stdout);
|
|
213
|
+
if (triple !== null) return triple;
|
|
214
|
+
} catch (e4) {
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return null;
|
|
218
|
+
}
|
|
219
|
+
async function findSwiftc() {
|
|
220
|
+
try {
|
|
221
|
+
await execFileAsync("xcrun", ["-f", "swiftc"]);
|
|
222
|
+
return { command: "xcrun", prefixArgs: ["swiftc"] };
|
|
223
|
+
} catch (e5) {
|
|
224
|
+
}
|
|
225
|
+
const { stat: stat3 } = await Promise.resolve().then(() => _interopRequireWildcard(require("fs/promises")));
|
|
226
|
+
try {
|
|
227
|
+
await stat3("/usr/bin/swiftc");
|
|
228
|
+
return { command: "/usr/bin/swiftc", prefixArgs: [] };
|
|
229
|
+
} catch (e6) {
|
|
230
|
+
return null;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// src/cloud.ts
|
|
235
|
+
|
|
236
|
+
var _promises = require('fs/promises');
|
|
237
|
+
var _os = require('os'); var _os2 = _interopRequireDefault(_os);
|
|
238
|
+
var _path = require('path'); var _path2 = _interopRequireDefault(_path);
|
|
239
|
+
|
|
240
|
+
var execFileAsync2 = _util.promisify.call(void 0, _child_process.execFile);
|
|
241
|
+
var CLOUD_SHORTCUT_NAME = "Apple LLM Cloud";
|
|
242
|
+
var CLOUD_SHORTCUT_NAME_WEB = "Apple LLM Cloud Web";
|
|
243
|
+
var CALL_TIMEOUT_MS = 12e4;
|
|
244
|
+
var CLOUD_CONTEXT_TOKENS = 32768;
|
|
245
|
+
var IMPORT_TIMEOUT_MS = 9e4;
|
|
246
|
+
var IMPORT_POLL_MS = 2e3;
|
|
247
|
+
function shortcutDefinition(webSearch = false) {
|
|
248
|
+
const parameters = {
|
|
249
|
+
// Stable UUID: re-running setup re-imports the same action rather than
|
|
250
|
+
// accumulating variants.
|
|
251
|
+
UUID: webSearch ? "B47F1A90-2D5E-4C83-A6F1-9E0C7B34D215" : "3827CFFE-3A65-4456-BFA0-49EF061ACEE3",
|
|
252
|
+
// Plain text out. "Automatic" reshapes the result to suit whatever action
|
|
253
|
+
// comes next, and nothing comes next here.
|
|
254
|
+
WFGenerativeResultType: "Text",
|
|
255
|
+
// The shortcut's own input, spliced in as a variable. U+FFFC is the
|
|
256
|
+
// object-replacement character marking the attachment's position.
|
|
257
|
+
WFLLMPrompt: {
|
|
258
|
+
Value: {
|
|
259
|
+
string: "\uFFFC",
|
|
260
|
+
attachmentsByRange: { "{0, 1}": { Type: "ExtensionInput" } }
|
|
261
|
+
},
|
|
262
|
+
WFSerializationType: "WFTextTokenString"
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
if (webSearch) parameters.WFAllowWebSearch = true;
|
|
266
|
+
return {
|
|
267
|
+
WFWorkflowMinimumClientVersionString: "900",
|
|
268
|
+
WFWorkflowMinimumClientVersion: 900,
|
|
269
|
+
WFWorkflowClientVersion: "5037.0.17",
|
|
270
|
+
WFWorkflowIcon: {
|
|
271
|
+
WFWorkflowIconStartColor: 431817727,
|
|
272
|
+
WFWorkflowIconGlyphNumber: 61440
|
|
273
|
+
},
|
|
274
|
+
WFWorkflowOutputContentItemClasses: [],
|
|
275
|
+
WFWorkflowHasOutputFallback: false,
|
|
276
|
+
WFWorkflowActions: [
|
|
277
|
+
{
|
|
278
|
+
WFWorkflowActionIdentifier: "is.workflow.actions.askllm",
|
|
279
|
+
WFWorkflowActionParameters: parameters
|
|
280
|
+
}
|
|
281
|
+
],
|
|
282
|
+
WFWorkflowInputContentItemClasses: ["WFStringContentItem", "WFRichTextContentItem"],
|
|
283
|
+
WFWorkflowImportQuestions: [],
|
|
284
|
+
WFQuickActionSurfaces: [],
|
|
285
|
+
WFWorkflowTypes: ["WFWorkflowTypeShowInSearch"],
|
|
286
|
+
WFWorkflowHasShortcutInputVariables: true
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
async function listShortcuts() {
|
|
290
|
+
try {
|
|
291
|
+
const { stdout } = await execFileAsync2("shortcuts", ["list"], { timeout: 2e4 });
|
|
292
|
+
return stdout.split("\n").map((l) => l.trim()).filter((l) => l !== "");
|
|
293
|
+
} catch (e7) {
|
|
294
|
+
return [];
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
async function installCount(name = CLOUD_SHORTCUT_NAME) {
|
|
298
|
+
return (await listShortcuts()).filter((l) => l === name).length;
|
|
299
|
+
}
|
|
300
|
+
function cloudSetupHint() {
|
|
301
|
+
return "The cloud tier needs a one-time shortcut installed.\nRun: apple-llm setup-cloud\nIt generates and signs the shortcut on this machine \u2014 no account, no API key, nothing uploaded.";
|
|
302
|
+
}
|
|
303
|
+
function duplicateMessage(count, name) {
|
|
304
|
+
return `${count} shortcuts are named "${name}"; \`shortcuts run\` cannot tell them apart and fails with "Couldn't find shortcut".
|
|
305
|
+
Delete the duplicates in the Shortcuts app, then try again.`;
|
|
306
|
+
}
|
|
307
|
+
async function installCloudShortcut(onProgress, options = {}) {
|
|
308
|
+
const name = options.webSearch === true ? CLOUD_SHORTCUT_NAME_WEB : CLOUD_SHORTCUT_NAME;
|
|
309
|
+
const existing = await installCount(name);
|
|
310
|
+
if (existing > 1) throw new AppleLLMError(duplicateMessage(existing, name), "cloud");
|
|
311
|
+
if (existing === 1 && options.force !== true) {
|
|
312
|
+
_optionalChain([onProgress, 'optionalCall', _27 => _27({ status: `"${name}" is already installed` })]);
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
const dir = await _promises.mkdtemp.call(void 0, _path2.default.join(_os2.default.tmpdir(), "apple-llm-shortcut-"));
|
|
316
|
+
try {
|
|
317
|
+
const jsonPath = _path2.default.join(dir, "definition.json");
|
|
318
|
+
const unsignedPath = _path2.default.join(dir, "unsigned.shortcut");
|
|
319
|
+
const signedPath = _path2.default.join(dir, `${name}.shortcut`);
|
|
320
|
+
await _promises.writeFile.call(void 0, jsonPath, JSON.stringify(shortcutDefinition(options.webSearch)), "utf8");
|
|
321
|
+
await execFileAsync2("plutil", ["-convert", "binary1", "-o", unsignedPath, jsonPath]);
|
|
322
|
+
_optionalChain([onProgress, 'optionalCall', _28 => _28({ status: "signing the shortcut locally" })]);
|
|
323
|
+
await execFileAsync2("shortcuts", ["sign", "-m", "anyone", "-i", unsignedPath, "-o", signedPath]);
|
|
324
|
+
_optionalChain([onProgress, 'optionalCall', _29 => _29({ status: `installing "${name}" (Shortcuts imports asynchronously)` })]);
|
|
325
|
+
await execFileAsync2("open", [signedPath]);
|
|
326
|
+
const deadline = Date.now() + IMPORT_TIMEOUT_MS;
|
|
327
|
+
for (; ; ) {
|
|
328
|
+
if (await installCount(name) >= 1) break;
|
|
329
|
+
if (Date.now() > deadline) {
|
|
330
|
+
throw new SetupRequiredError(
|
|
331
|
+
`Shortcuts did not import "${name}" within ${IMPORT_TIMEOUT_MS / 1e3}s.
|
|
332
|
+
If a confirmation panel is open in the Shortcuts app, accept it and run setup again.`,
|
|
333
|
+
"setup-cloud",
|
|
334
|
+
"cloud"
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
|
+
await new Promise((resolve) => setTimeout(resolve, IMPORT_POLL_MS));
|
|
338
|
+
}
|
|
339
|
+
_optionalChain([onProgress, 'optionalCall', _30 => _30({ status: `"${name}" installed` })]);
|
|
340
|
+
} finally {
|
|
341
|
+
await _promises.rm.call(void 0, dir, { recursive: true, force: true });
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
async function probeCloud() {
|
|
345
|
+
if (process.platform !== "darwin") {
|
|
346
|
+
return { available: false, installed: false, reason: `unsupportedPlatform: this machine reports "${process.platform}"` };
|
|
347
|
+
}
|
|
348
|
+
if (!isAppleSiliconMac()) {
|
|
349
|
+
return { available: false, installed: false, reason: "deviceNotEligible: Apple Intelligence needs Apple Silicon" };
|
|
350
|
+
}
|
|
351
|
+
try {
|
|
352
|
+
await execFileAsync2("shortcuts", ["list"], { timeout: 2e4 });
|
|
353
|
+
} catch (e8) {
|
|
354
|
+
return { available: false, installed: false, reason: "this system does not provide /usr/bin/shortcuts" };
|
|
355
|
+
}
|
|
356
|
+
const count = await installCount();
|
|
357
|
+
if (count === 0) {
|
|
358
|
+
return { available: false, installed: false, reason: cloudSetupHint() };
|
|
359
|
+
}
|
|
360
|
+
if (count > 1) {
|
|
361
|
+
return { available: false, installed: true, reason: duplicateMessage(count, CLOUD_SHORTCUT_NAME) };
|
|
362
|
+
}
|
|
363
|
+
return { available: true, installed: true, contextSize: CLOUD_CONTEXT_TOKENS };
|
|
364
|
+
}
|
|
365
|
+
var CloudClient = (_class14 = class {constructor() { _class14.prototype.__init14.call(this); }
|
|
366
|
+
__init14() {this.ready = false}
|
|
367
|
+
/** Last known quota, set by `probe()` so a call can fail fast. */
|
|
368
|
+
|
|
369
|
+
/** What the framework says the server model can do, when it could be read. */
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Tell the client what the framework reported about the quota.
|
|
373
|
+
*
|
|
374
|
+
* Worth doing because a `shortcuts run` against an exhausted quota costs a
|
|
375
|
+
* full round trip to find out; this turns that into an immediate typed error.
|
|
376
|
+
*/
|
|
377
|
+
setQuota(quota) {
|
|
378
|
+
this.quota = quota;
|
|
379
|
+
this.model = quota;
|
|
380
|
+
}
|
|
381
|
+
/** Whether the server model is known to read images. False when it could not be read (macOS 26). */
|
|
382
|
+
get supportsImages() {
|
|
383
|
+
return _optionalChain([this, 'access', _31 => _31.model, 'optionalAccess', _32 => _32.capabilities, 'optionalAccess', _33 => _33.vision]) === true;
|
|
384
|
+
}
|
|
385
|
+
assertQuota() {
|
|
386
|
+
if (_optionalChain([this, 'access', _34 => _34.quota, 'optionalAccess', _35 => _35.status]) !== "limitReached") return;
|
|
387
|
+
throw new QuotaError(
|
|
388
|
+
"Apple Private Cloud Compute quota is exhausted (reported by the framework before the call was made).\nFall back to the on-device tier, or try again later.",
|
|
389
|
+
"cloud",
|
|
390
|
+
this.quota.resetDate === void 0 ? void 0 : new Date(this.quota.resetDate)
|
|
391
|
+
);
|
|
392
|
+
}
|
|
393
|
+
get label() {
|
|
394
|
+
return "apple private cloud compute";
|
|
395
|
+
}
|
|
396
|
+
get contextSize() {
|
|
397
|
+
return _nullishCoalesce(_optionalChain([this, 'access', _36 => _36.model, 'optionalAccess', _37 => _37.contextSize]), () => ( CLOUD_CONTEXT_TOKENS));
|
|
398
|
+
}
|
|
399
|
+
async ensureReady(onProgress) {
|
|
400
|
+
if (this.ready) return;
|
|
401
|
+
const probe2 = await probeCloud();
|
|
402
|
+
if (!probe2.available) {
|
|
403
|
+
if (probe2.installed !== true) {
|
|
404
|
+
throw new SetupRequiredError(_nullishCoalesce(probe2.reason, () => ( cloudSetupHint())), "setup-cloud", "cloud");
|
|
405
|
+
}
|
|
406
|
+
throw new AppleLLMError(_nullishCoalesce(probe2.reason, () => ( "the cloud tier is unavailable")), "cloud");
|
|
407
|
+
}
|
|
408
|
+
this.ready = true;
|
|
409
|
+
_optionalChain([onProgress, 'optionalCall', _38 => _38({ status: `apple private cloud compute ready (${CLOUD_CONTEXT_TOKENS} token context)` })]);
|
|
410
|
+
}
|
|
411
|
+
async text(request) {
|
|
412
|
+
await this.ensureReady();
|
|
413
|
+
this.assertQuota();
|
|
414
|
+
const name = request.webSearch === true ? CLOUD_SHORTCUT_NAME_WEB : CLOUD_SHORTCUT_NAME;
|
|
415
|
+
if (request.webSearch === true && await installCount(name) !== 1) {
|
|
416
|
+
throw new SetupRequiredError(
|
|
417
|
+
`webSearch needs the "${name}" shortcut.
|
|
418
|
+
Run: apple-llm setup-cloud --web-search`,
|
|
419
|
+
"setup-cloud --web-search",
|
|
420
|
+
"cloud"
|
|
421
|
+
);
|
|
422
|
+
}
|
|
423
|
+
const images = await imageInputs(request.images);
|
|
424
|
+
let prompt = request.system ? `${request.system}
|
|
425
|
+
|
|
426
|
+
${request.prompt}` : request.prompt;
|
|
427
|
+
const labels = images.map((image, i) => image.label === void 0 ? void 0 : `${i + 1}: ${image.label}`);
|
|
428
|
+
if (labels.some((label) => label !== void 0)) {
|
|
429
|
+
prompt = `${prompt}
|
|
430
|
+
|
|
431
|
+
Attached images, in order \u2014 ${labels.map((l, i) => _nullishCoalesce(l, () => ( `${i + 1}`))).join(", ")}.`;
|
|
432
|
+
}
|
|
433
|
+
const dir = await _promises.mkdtemp.call(void 0, _path2.default.join(_os2.default.tmpdir(), "apple-llm-cloud-"));
|
|
434
|
+
try {
|
|
435
|
+
const inPath = _path2.default.join(dir, "prompt.txt");
|
|
436
|
+
const outPath = _path2.default.join(dir, "reply.txt");
|
|
437
|
+
await _promises.writeFile.call(void 0, inPath, prompt, "utf8");
|
|
438
|
+
const inputs = [inPath, ...images.map((image) => image.path)].flatMap((p) => ["-i", p]);
|
|
439
|
+
try {
|
|
440
|
+
await execFileAsync2("shortcuts", ["run", name, ...inputs, "-o", outPath], {
|
|
441
|
+
timeout: CALL_TIMEOUT_MS,
|
|
442
|
+
killSignal: "SIGKILL",
|
|
443
|
+
signal: request.signal
|
|
444
|
+
});
|
|
445
|
+
} catch (error) {
|
|
446
|
+
if (_optionalChain([request, 'access', _39 => _39.signal, 'optionalAccess', _40 => _40.aborted]) === true) {
|
|
447
|
+
throw new AbortError("The request was aborted.", "cloud", { cause: request.signal.reason });
|
|
448
|
+
}
|
|
449
|
+
throw describeRunFailure(error, name);
|
|
450
|
+
}
|
|
451
|
+
let reply;
|
|
452
|
+
try {
|
|
453
|
+
reply = await _promises.readFile.call(void 0, outPath, "utf8");
|
|
454
|
+
} catch (e9) {
|
|
455
|
+
throw new AppleLLMError("Apple cloud generation produced no output.", "cloud");
|
|
456
|
+
}
|
|
457
|
+
if (reply.trim() === "") {
|
|
458
|
+
throw new AppleLLMError("Apple cloud generation returned an empty reply.", "cloud");
|
|
459
|
+
}
|
|
460
|
+
return reply;
|
|
461
|
+
} finally {
|
|
462
|
+
await _promises.rm.call(void 0, dir, { recursive: true, force: true });
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
/**
|
|
466
|
+
* There is no constrained decoding on this tier, so the schema is spelled out
|
|
467
|
+
* in the prompt and the reply is mined for JSON. The shape is a request here,
|
|
468
|
+
* not a guarantee — unlike on device.
|
|
469
|
+
*/
|
|
470
|
+
async json(request) {
|
|
471
|
+
const instruction = `Reply with a single JSON value matching this JSON Schema. Output only the JSON, with no commentary and no code fence.
|
|
472
|
+
|
|
473
|
+
${JSON.stringify(request.schema, null, 2)}`;
|
|
474
|
+
const system = request.system ? `${request.system}
|
|
475
|
+
|
|
476
|
+
${instruction}` : instruction;
|
|
477
|
+
return parseLlmJson(await this.text({ ...request, system }));
|
|
478
|
+
}
|
|
479
|
+
// eslint-disable-next-line class-methods-use-this
|
|
480
|
+
close() {
|
|
481
|
+
}
|
|
482
|
+
}, _class14);
|
|
483
|
+
async function imageInputs(images) {
|
|
484
|
+
const out = [];
|
|
485
|
+
for (const image of _nullishCoalesce(images, () => ( []))) {
|
|
486
|
+
const entry = typeof image === "string" ? { path: image } : image;
|
|
487
|
+
const absolute = _path2.default.resolve(entry.path);
|
|
488
|
+
try {
|
|
489
|
+
await _promises.stat.call(void 0, absolute);
|
|
490
|
+
} catch (e10) {
|
|
491
|
+
throw new AppleLLMError(`Image not found: ${entry.path}`, "cloud");
|
|
492
|
+
}
|
|
493
|
+
out.push({ path: absolute, ...entry.label === void 0 ? {} : { label: entry.label } });
|
|
494
|
+
}
|
|
495
|
+
return out;
|
|
496
|
+
}
|
|
497
|
+
function describeRunFailure(error, name = CLOUD_SHORTCUT_NAME) {
|
|
498
|
+
const err = error;
|
|
499
|
+
if (err.killed === true) {
|
|
500
|
+
return new TimeoutError(
|
|
501
|
+
`Apple cloud generation got no response within ${CALL_TIMEOUT_MS / 1e3}s. Open "${name}" in the Shortcuts app and check that Request is bound to the Shortcut Input variable \u2014 an unbound Request makes the action wait for a human.`,
|
|
502
|
+
"cloud"
|
|
503
|
+
);
|
|
504
|
+
}
|
|
505
|
+
const stderr = _nullishCoalesce(_optionalChain([err, 'access', _41 => _41.stderr, 'optionalAccess', _42 => _42.trim, 'call', _43 => _43()]), () => ( ""));
|
|
506
|
+
if (/maximum allowed length/i.test(stderr)) {
|
|
507
|
+
return new ContextLengthError(
|
|
508
|
+
`The prompt exceeded the cloud model's ~${CLOUD_CONTEXT_TOKENS}-token context window.`,
|
|
509
|
+
"cloud"
|
|
510
|
+
);
|
|
511
|
+
}
|
|
512
|
+
if (/QuotaLimitReached|quota/i.test(stderr)) {
|
|
513
|
+
const match = /(\d{4}-\d{2}-\d{2}[T ][\d:]+)/.exec(stderr);
|
|
514
|
+
const resetDate = match ? new Date(match[1].replace(" ", "T")) : void 0;
|
|
515
|
+
return new QuotaError(
|
|
516
|
+
`Apple Private Cloud Compute quota reached.${resetDate ? ` Resets ${resetDate.toISOString()}.` : ""}
|
|
517
|
+
Fall back to the on-device tier, or try again later.`,
|
|
518
|
+
"cloud",
|
|
519
|
+
resetDate !== void 0 && !Number.isNaN(resetDate.getTime()) ? resetDate : void 0
|
|
520
|
+
);
|
|
521
|
+
}
|
|
522
|
+
if (/Couldn.t find shortcut/i.test(stderr)) {
|
|
523
|
+
return new SetupRequiredError(
|
|
524
|
+
`Shortcuts could not find "${name}".
|
|
525
|
+
${cloudSetupHint()}`,
|
|
526
|
+
"setup-cloud",
|
|
527
|
+
"cloud"
|
|
528
|
+
);
|
|
529
|
+
}
|
|
530
|
+
return new AppleLLMError(
|
|
531
|
+
`Apple cloud generation failed: ${stderr !== "" ? stderr : _nullishCoalesce(err.message, () => ( String(error)))}`,
|
|
532
|
+
"cloud"
|
|
533
|
+
);
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
// src/compile.ts
|
|
537
|
+
|
|
538
|
+
|
|
539
|
+
var _crypto = require('crypto');
|
|
540
|
+
|
|
541
|
+
|
|
542
|
+
var _url = require('url');
|
|
543
|
+
|
|
544
|
+
var execFileAsync3 = _util.promisify.call(void 0, _child_process.execFile);
|
|
545
|
+
function cacheDir() {
|
|
546
|
+
return _path2.default.join(_os2.default.homedir(), "Library", "Caches", "apple-llm", "bin");
|
|
547
|
+
}
|
|
548
|
+
function fingerprint(source, triple) {
|
|
549
|
+
return _crypto.createHash.call(void 0, "sha256").update(`${source}
|
|
550
|
+
${triple}`, "utf8").digest("hex").slice(0, 12);
|
|
551
|
+
}
|
|
552
|
+
var cachedSource;
|
|
553
|
+
async function helperSource() {
|
|
554
|
+
if (cachedSource !== void 0) return cachedSource;
|
|
555
|
+
const here = _path2.default.dirname(_url.fileURLToPath.call(void 0, importMetaUrl));
|
|
556
|
+
const candidates = [
|
|
557
|
+
_path2.default.join(here, "..", "swift", "helper.swift"),
|
|
558
|
+
_path2.default.join(here, "swift", "helper.swift")
|
|
559
|
+
];
|
|
560
|
+
for (const candidate of candidates) {
|
|
561
|
+
try {
|
|
562
|
+
cachedSource = await _promises.readFile.call(void 0, candidate, "utf8");
|
|
563
|
+
return cachedSource;
|
|
564
|
+
} catch (e11) {
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
throw new Error(
|
|
568
|
+
`apple-llm could not find its embedded helper.swift (looked in ${candidates.join(", ")}).`
|
|
569
|
+
);
|
|
570
|
+
}
|
|
571
|
+
async function fileExists(p) {
|
|
572
|
+
try {
|
|
573
|
+
await _promises.stat.call(void 0, p);
|
|
574
|
+
return true;
|
|
575
|
+
} catch (e12) {
|
|
576
|
+
return false;
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
var inFlight;
|
|
580
|
+
var cachedKey;
|
|
581
|
+
async function ensureBinary(onProgress, options = {}) {
|
|
582
|
+
if (options.force === true) {
|
|
583
|
+
inFlight = void 0;
|
|
584
|
+
cachedKey = void 0;
|
|
585
|
+
} else if (inFlight !== void 0 && cachedKey !== void 0) {
|
|
586
|
+
const pending = inFlight;
|
|
587
|
+
try {
|
|
588
|
+
const source = await helperSource();
|
|
589
|
+
const triple = await _asyncNullishCoalesce(await hostTarget(), async () => ( process.arch));
|
|
590
|
+
if (fingerprint(source, triple) === cachedKey) return pending;
|
|
591
|
+
inFlight = void 0;
|
|
592
|
+
cachedKey = void 0;
|
|
593
|
+
} catch (e13) {
|
|
594
|
+
return pending;
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
if (inFlight === void 0) {
|
|
598
|
+
try {
|
|
599
|
+
const source = await helperSource();
|
|
600
|
+
const triple = await _asyncNullishCoalesce(await hostTarget(), async () => ( process.arch));
|
|
601
|
+
cachedKey = fingerprint(source, triple);
|
|
602
|
+
} catch (e14) {
|
|
603
|
+
}
|
|
604
|
+
inFlight = build(onProgress).catch((err) => {
|
|
605
|
+
inFlight = void 0;
|
|
606
|
+
cachedKey = void 0;
|
|
607
|
+
throw err;
|
|
608
|
+
});
|
|
609
|
+
}
|
|
610
|
+
return inFlight;
|
|
611
|
+
}
|
|
612
|
+
async function build(onProgress) {
|
|
613
|
+
const source = await helperSource();
|
|
614
|
+
const triple = await _asyncNullishCoalesce(await hostTarget(), async () => ( process.arch));
|
|
615
|
+
const dir = cacheDir();
|
|
616
|
+
const target = _path2.default.join(dir, `fm-helper-${fingerprint(source, triple)}`);
|
|
617
|
+
if (await fileExists(target)) return target;
|
|
618
|
+
const swiftc = await findSwiftc();
|
|
619
|
+
if (swiftc === null) {
|
|
620
|
+
throw new ModelUnavailableError(
|
|
621
|
+
"apple-llm needs a Swift compiler to build its on-device helper, but none was found.\nInstall the Xcode command line tools with `xcode-select --install`, then try again.",
|
|
622
|
+
"noSwiftCompiler",
|
|
623
|
+
"device"
|
|
624
|
+
);
|
|
625
|
+
}
|
|
626
|
+
_optionalChain([onProgress, 'optionalCall', _44 => _44({ status: "building the Apple on-device helper (one time, a few seconds)" })]);
|
|
627
|
+
await _promises.mkdir.call(void 0, dir, { recursive: true });
|
|
628
|
+
const unique = `${process.pid}.${Math.random().toString(36).slice(2)}`;
|
|
629
|
+
const sourcePath = `${target}.${unique}.swift`;
|
|
630
|
+
const staging = `${target}.${unique}.tmp`;
|
|
631
|
+
const { writeFile: writeFile2 } = await Promise.resolve().then(() => _interopRequireWildcard(require("fs/promises")));
|
|
632
|
+
await writeFile2(sourcePath, source, "utf8");
|
|
633
|
+
try {
|
|
634
|
+
const targetArgs = triple.includes("-apple-") ? ["-target", triple] : [];
|
|
635
|
+
await execFileAsync3(swiftc.command, [
|
|
636
|
+
...swiftc.prefixArgs,
|
|
637
|
+
...targetArgs,
|
|
638
|
+
"-parse-as-library",
|
|
639
|
+
"-O",
|
|
640
|
+
sourcePath,
|
|
641
|
+
"-o",
|
|
642
|
+
staging
|
|
643
|
+
]);
|
|
644
|
+
await _promises.chmod.call(void 0, staging, 493);
|
|
645
|
+
await _promises.rename.call(void 0, staging, target);
|
|
646
|
+
} catch (err) {
|
|
647
|
+
await _promises.rm.call(void 0, staging, { force: true });
|
|
648
|
+
if (await fileExists(target)) return target;
|
|
649
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
650
|
+
throw new ModelUnavailableError(
|
|
651
|
+
`Failed to build the Apple on-device helper with ${swiftc.command}.
|
|
652
|
+
${reason.slice(0, 400)}
|
|
653
|
+
This usually means the macOS SDK predates the Foundation Models framework (macOS 26+).`,
|
|
654
|
+
"unsupportedOSVersion",
|
|
655
|
+
"device"
|
|
656
|
+
);
|
|
657
|
+
} finally {
|
|
658
|
+
await _promises.rm.call(void 0, sourcePath, { force: true });
|
|
659
|
+
}
|
|
660
|
+
_optionalChain([onProgress, 'optionalCall', _45 => _45({ status: "helper built" })]);
|
|
661
|
+
return target;
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
// src/messages.ts
|
|
665
|
+
function splitMessages(messages) {
|
|
666
|
+
const systems = [];
|
|
667
|
+
const rest = [];
|
|
668
|
+
for (const message of messages) {
|
|
669
|
+
if (message.role === "system") systems.push(message.content);
|
|
670
|
+
else rest.push(message);
|
|
671
|
+
}
|
|
672
|
+
let lastUser = -1;
|
|
673
|
+
for (let i = rest.length - 1; i >= 0; i -= 1) {
|
|
674
|
+
if (rest[i].role === "user") {
|
|
675
|
+
lastUser = i;
|
|
676
|
+
break;
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
if (lastUser === -1) {
|
|
680
|
+
throw new AppleLLMError("A message list needs at least one user message.");
|
|
681
|
+
}
|
|
682
|
+
const tail = rest.slice(lastUser + 1);
|
|
683
|
+
const final = tail[tail.length - 1];
|
|
684
|
+
if (final !== void 0 && final.role !== "tool") {
|
|
685
|
+
throw new AppleLLMError(
|
|
686
|
+
"The last message must be from the user, or a tool result answering the assistant\u2019s tool call."
|
|
687
|
+
);
|
|
688
|
+
}
|
|
689
|
+
const callNames = /* @__PURE__ */ new Map();
|
|
690
|
+
for (const message of rest) {
|
|
691
|
+
if (message.role === "assistant") for (const call of _nullishCoalesce(message.toolCalls, () => ( []))) callNames.set(call.id, call);
|
|
692
|
+
}
|
|
693
|
+
const history = [];
|
|
694
|
+
for (const message of rest.slice(0, lastUser)) {
|
|
695
|
+
const entry = toHistory(message, callNames);
|
|
696
|
+
if (entry !== void 0) history.push(entry);
|
|
697
|
+
}
|
|
698
|
+
const replay = [];
|
|
699
|
+
for (const message of tail) {
|
|
700
|
+
if (message.role !== "tool") continue;
|
|
701
|
+
const call = callNames.get(message.toolCallId);
|
|
702
|
+
replay.push({
|
|
703
|
+
name: _nullishCoalesce(_nullishCoalesce(message.name, () => ( _optionalChain([call, 'optionalAccess', _46 => _46.name]))), () => ( "")),
|
|
704
|
+
arguments: _nullishCoalesce(_optionalChain([call, 'optionalAccess', _47 => _47.arguments]), () => ( {})),
|
|
705
|
+
output: message.content
|
|
706
|
+
});
|
|
707
|
+
}
|
|
708
|
+
const last = rest[lastUser];
|
|
709
|
+
return {
|
|
710
|
+
system: systems.length > 0 ? systems.join("\n\n") : void 0,
|
|
711
|
+
history,
|
|
712
|
+
prompt: last.content,
|
|
713
|
+
images: _nullishCoalesce(last.images, () => ( [])),
|
|
714
|
+
replay
|
|
715
|
+
};
|
|
716
|
+
}
|
|
717
|
+
function toHistory(message, calls) {
|
|
718
|
+
switch (message.role) {
|
|
719
|
+
case "user": {
|
|
720
|
+
const images = _nullishCoalesce(_optionalChain([message, 'access', _48 => _48.images, 'optionalAccess', _49 => _49.length]), () => ( 0));
|
|
721
|
+
const note = images === 0 ? "" : ` [${images === 1 ? "an image was" : `${images} images were`} attached here]`;
|
|
722
|
+
return { role: "user", content: `${message.content}${note}` };
|
|
723
|
+
}
|
|
724
|
+
case "assistant":
|
|
725
|
+
return {
|
|
726
|
+
role: "assistant",
|
|
727
|
+
content: _nullishCoalesce(message.content, () => ( "")),
|
|
728
|
+
...message.toolCalls !== void 0 && message.toolCalls.length > 0 ? { toolCalls: message.toolCalls } : {}
|
|
729
|
+
};
|
|
730
|
+
case "tool":
|
|
731
|
+
return {
|
|
732
|
+
role: "tool",
|
|
733
|
+
toolCallId: message.toolCallId,
|
|
734
|
+
name: _nullishCoalesce(_nullishCoalesce(message.name, () => ( _optionalChain([calls, 'access', _50 => _50.get, 'call', _51 => _51(message.toolCallId), 'optionalAccess', _52 => _52.name]))), () => ( "tool")),
|
|
735
|
+
content: message.content
|
|
736
|
+
};
|
|
737
|
+
default:
|
|
738
|
+
return void 0;
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
function renderHistory(history) {
|
|
742
|
+
return history.map((entry) => {
|
|
743
|
+
if (entry.role === "user") return `User: ${entry.content}`;
|
|
744
|
+
if (entry.role === "tool") return `Tool ${entry.name} returned: ${entry.content}`;
|
|
745
|
+
const calls = (_nullishCoalesce(entry.toolCalls, () => ( []))).map((c) => `[called ${c.name}(${JSON.stringify(c.arguments)})]`);
|
|
746
|
+
return `Assistant: ${[entry.content, ...calls].filter((s) => s !== "").join(" ")}`;
|
|
747
|
+
}).join("\n");
|
|
748
|
+
}
|
|
749
|
+
function canonicalJson(value) {
|
|
750
|
+
if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
|
|
751
|
+
if (value !== null && typeof value === "object") {
|
|
752
|
+
const keys = Object.keys(value).sort();
|
|
753
|
+
return `{${keys.map((k) => `${JSON.stringify(k)}:${canonicalJson(value[k])}`).join(",")}}`;
|
|
754
|
+
}
|
|
755
|
+
return _nullishCoalesce(JSON.stringify(value), () => ( "null"));
|
|
756
|
+
}
|
|
757
|
+
var ReplayBook = class {
|
|
758
|
+
|
|
759
|
+
constructor(results) {
|
|
760
|
+
this.unused = [...results];
|
|
761
|
+
}
|
|
762
|
+
take(name, args) {
|
|
763
|
+
const key = canonicalJson(args);
|
|
764
|
+
let index = this.unused.findIndex((r) => r.name === name && canonicalJson(r.arguments) === key);
|
|
765
|
+
if (index < 0) index = this.unused.findIndex((r) => r.name === name);
|
|
766
|
+
if (index < 0) return void 0;
|
|
767
|
+
const [hit] = this.unused.splice(index, 1);
|
|
768
|
+
return hit.output;
|
|
769
|
+
}
|
|
770
|
+
};
|
|
771
|
+
|
|
772
|
+
// src/schema.ts
|
|
773
|
+
var SCHEMA_MAP_KEYS = ["properties", "$defs", "definitions"];
|
|
774
|
+
var SCHEMA_LIST_KEYS = ["anyOf", "oneOf", "allOf"];
|
|
775
|
+
var SCHEMA_KEYS = ["items", "not", "additionalItems"];
|
|
776
|
+
function isPlainObject(value) {
|
|
777
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
778
|
+
}
|
|
779
|
+
function sanitize(name, counter) {
|
|
780
|
+
const cleaned = name.replace(/[^A-Za-z0-9_]/g, "");
|
|
781
|
+
if (cleaned.length === 0 || /^[0-9]/.test(cleaned)) {
|
|
782
|
+
counter.n += 1;
|
|
783
|
+
return `Schema${counter.n}`;
|
|
784
|
+
}
|
|
785
|
+
return cleaned;
|
|
786
|
+
}
|
|
787
|
+
function titleFrom(name, ctx) {
|
|
788
|
+
const base = sanitize(name, ctx.counter);
|
|
789
|
+
if (!ctx.used.has(base)) {
|
|
790
|
+
ctx.used.add(base);
|
|
791
|
+
return base;
|
|
792
|
+
}
|
|
793
|
+
for (let i = 2; ; i += 1) {
|
|
794
|
+
const candidate = `${base}${i}`;
|
|
795
|
+
if (!ctx.used.has(candidate)) {
|
|
796
|
+
ctx.used.add(candidate);
|
|
797
|
+
return candidate;
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
function memberType(value) {
|
|
802
|
+
if (typeof value === "number") return Number.isInteger(value) ? "integer" : "number";
|
|
803
|
+
if (typeof value === "boolean") return "boolean";
|
|
804
|
+
return "string";
|
|
805
|
+
}
|
|
806
|
+
function rewriteRef(ref, ctx) {
|
|
807
|
+
const slash = ref.lastIndexOf("/");
|
|
808
|
+
if (slash === -1) return ref;
|
|
809
|
+
const name = ref.slice(slash + 1);
|
|
810
|
+
const title = ctx.defTitles.get(name);
|
|
811
|
+
return title === void 0 || title === name ? ref : `${ref.slice(0, slash + 1)}${title}`;
|
|
812
|
+
}
|
|
813
|
+
function convertNode(node, name, ctx) {
|
|
814
|
+
const out = {};
|
|
815
|
+
let nullable = false;
|
|
816
|
+
const converted = /* @__PURE__ */ new Map();
|
|
817
|
+
const properties = node.properties;
|
|
818
|
+
if (isPlainObject(properties)) {
|
|
819
|
+
for (const [key, value] of Object.entries(properties)) {
|
|
820
|
+
converted.set(
|
|
821
|
+
key,
|
|
822
|
+
isPlainObject(value) ? convertNode(value, key, ctx) : { schema: value, nullable: false }
|
|
823
|
+
);
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
for (const [key, value] of Object.entries(node)) {
|
|
827
|
+
if (key === "type" || key === "enum" || key === "required" || key === "properties") continue;
|
|
828
|
+
if (key === "$ref" && typeof value === "string") {
|
|
829
|
+
out.$ref = rewriteRef(value, ctx);
|
|
830
|
+
} else if (SCHEMA_MAP_KEYS.includes(key) && isPlainObject(value)) {
|
|
831
|
+
const isDefs = key === "$defs" || key === "definitions";
|
|
832
|
+
const mapped = {};
|
|
833
|
+
for (const [childName, childValue] of Object.entries(value)) {
|
|
834
|
+
if (!isPlainObject(childValue)) {
|
|
835
|
+
mapped[childName] = childValue;
|
|
836
|
+
continue;
|
|
837
|
+
}
|
|
838
|
+
const child = convertNode(childValue, childName, ctx).schema;
|
|
839
|
+
const reserved = isDefs ? ctx.defTitles.get(childName) : void 0;
|
|
840
|
+
if (reserved !== void 0) child.title = reserved;
|
|
841
|
+
mapped[childName] = child;
|
|
842
|
+
}
|
|
843
|
+
out[key] = mapped;
|
|
844
|
+
} else if (SCHEMA_LIST_KEYS.includes(key) && Array.isArray(value)) {
|
|
845
|
+
if (key === "allOf") {
|
|
846
|
+
const allOfConverted = value.map(
|
|
847
|
+
(entry) => isPlainObject(entry) ? convertNode(entry, name, ctx).schema : entry
|
|
848
|
+
);
|
|
849
|
+
if (allOfConverted.length === 1 && isPlainObject(allOfConverted[0])) {
|
|
850
|
+
for (const [k, v] of Object.entries(allOfConverted[0])) {
|
|
851
|
+
if (out[k] === void 0) out[k] = v;
|
|
852
|
+
}
|
|
853
|
+
} else {
|
|
854
|
+
out.allOf = allOfConverted;
|
|
855
|
+
}
|
|
856
|
+
} else {
|
|
857
|
+
const branches = value.filter((entry) => !(isPlainObject(entry) && entry.type === "null"));
|
|
858
|
+
if (branches.length !== value.length) nullable = true;
|
|
859
|
+
const convertedBranches = branches.map(
|
|
860
|
+
(entry) => isPlainObject(entry) ? convertNode(entry, name, ctx) : { schema: entry, nullable: false }
|
|
861
|
+
);
|
|
862
|
+
if (convertedBranches.some((b) => b.nullable)) nullable = true;
|
|
863
|
+
if (convertedBranches.length === 1 && isPlainObject(convertedBranches[0].schema)) {
|
|
864
|
+
for (const [k, v] of Object.entries(convertedBranches[0].schema)) {
|
|
865
|
+
if (out[k] === void 0) out[k] = v;
|
|
866
|
+
}
|
|
867
|
+
} else if (convertedBranches.length > 0) {
|
|
868
|
+
out.anyOf = convertedBranches.map((b) => b.schema);
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
} else if (SCHEMA_KEYS.includes(key) && isPlainObject(value)) {
|
|
872
|
+
out[key] = convertNode(value, name, ctx).schema;
|
|
873
|
+
} else {
|
|
874
|
+
out[key] = value;
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
const rawType = node.type;
|
|
878
|
+
if (Array.isArray(rawType)) {
|
|
879
|
+
const nonNull = rawType.filter((t) => t !== "null");
|
|
880
|
+
if (nonNull.length !== rawType.length) nullable = true;
|
|
881
|
+
if (nonNull.length > 1) {
|
|
882
|
+
out.anyOf = nonNull.map((t) => ({ type: t }));
|
|
883
|
+
} else {
|
|
884
|
+
out.type = _nullishCoalesce(nonNull[0], () => ( "string"));
|
|
885
|
+
}
|
|
886
|
+
} else if (rawType !== void 0) {
|
|
887
|
+
out.type = rawType;
|
|
888
|
+
}
|
|
889
|
+
const rawEnum = node.enum;
|
|
890
|
+
if (Array.isArray(rawEnum) && rawEnum.length > 0) {
|
|
891
|
+
const members = rawEnum.filter((v) => v !== null);
|
|
892
|
+
if (members.length !== rawEnum.length) nullable = true;
|
|
893
|
+
if (members.length > 0) {
|
|
894
|
+
delete out.type;
|
|
895
|
+
if (members.every((v) => typeof v === "string")) {
|
|
896
|
+
out.anyOf = members.map((value) => ({ type: "string", const: value }));
|
|
897
|
+
} else {
|
|
898
|
+
const types = [...new Set(members.map(memberType))];
|
|
899
|
+
if (types.length === 1) out.type = types[0];
|
|
900
|
+
else out.anyOf = types.map((t) => ({ type: t }));
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
const merged = converted.size === 0 && isPlainObject(out.properties);
|
|
905
|
+
const isObject2 = !merged && (out.type === "object" || converted.size > 0);
|
|
906
|
+
if (isObject2) {
|
|
907
|
+
const props = {};
|
|
908
|
+
for (const [key, child] of converted) props[key] = child.schema;
|
|
909
|
+
out.properties = props;
|
|
910
|
+
const originalRequired = Array.isArray(node.required) ? node.required : [];
|
|
911
|
+
out.required = originalRequired.filter((key) => !_optionalChain([converted, 'access', _53 => _53.get, 'call', _54 => _54(key), 'optionalAccess', _55 => _55.nullable]));
|
|
912
|
+
const keys = Object.keys(props);
|
|
913
|
+
const declaredOrder = Array.isArray(node["x-order"]) ? node["x-order"].filter((k) => typeof k === "string" && keys.includes(k)) : [];
|
|
914
|
+
out["x-order"] = [...declaredOrder, ...keys.filter((k) => !declaredOrder.includes(k))];
|
|
915
|
+
if (typeof out.title === "string") ctx.used.add(out.title);
|
|
916
|
+
else out.title = titleFrom(name, ctx);
|
|
917
|
+
if (out.additionalProperties === void 0) out.additionalProperties = false;
|
|
918
|
+
} else if (Array.isArray(node.required)) {
|
|
919
|
+
out.required = node.required;
|
|
920
|
+
}
|
|
921
|
+
if ((Array.isArray(out.anyOf) || Array.isArray(out.allOf)) && typeof out.title !== "string") {
|
|
922
|
+
out.title = titleFrom(name, ctx);
|
|
923
|
+
}
|
|
924
|
+
if (out.type === "string" && out.anyOf === void 0 && out.allOf === void 0)
|
|
925
|
+
delete out.title;
|
|
926
|
+
delete out.pattern;
|
|
927
|
+
return { schema: out, nullable };
|
|
928
|
+
}
|
|
929
|
+
function reserveDefTitles(schema, ctx) {
|
|
930
|
+
for (const key of ["$defs", "definitions"]) {
|
|
931
|
+
const defs = schema[key];
|
|
932
|
+
if (!isPlainObject(defs)) continue;
|
|
933
|
+
for (const [name, value] of Object.entries(defs)) {
|
|
934
|
+
const declared = isPlainObject(value) && typeof value.title === "string" ? value.title : null;
|
|
935
|
+
const title = _nullishCoalesce(declared, () => ( sanitize(name, ctx.counter)));
|
|
936
|
+
ctx.defTitles.set(name, title);
|
|
937
|
+
ctx.used.add(title);
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
function toAppleSchema(schema, rootName = "Response") {
|
|
942
|
+
const ctx = { counter: { n: 0 }, defTitles: /* @__PURE__ */ new Map(), used: /* @__PURE__ */ new Set() };
|
|
943
|
+
reserveDefTitles(schema, ctx);
|
|
944
|
+
const { $schema: _draft, ...rest } = schema;
|
|
945
|
+
return convertNode(rest, rootName, ctx).schema;
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
// src/standard-schema.ts
|
|
949
|
+
function isStandardSchema(value) {
|
|
950
|
+
if (value === null || typeof value !== "object" && typeof value !== "function") return false;
|
|
951
|
+
const props = value["~standard"];
|
|
952
|
+
return props !== null && typeof props === "object" && typeof props.validate === "function";
|
|
953
|
+
}
|
|
954
|
+
function resolveSchema(schema) {
|
|
955
|
+
if (!isStandardSchema(schema)) return { json: schema };
|
|
956
|
+
const props = schema["~standard"];
|
|
957
|
+
const describe = props.jsonSchema;
|
|
958
|
+
if (describe === void 0 || typeof describe.input !== "function") {
|
|
959
|
+
throw new UnsupportedError(
|
|
960
|
+
`This ${props.vendor} schema cannot describe itself as JSON Schema, which Apple's decoder needs.
|
|
961
|
+
` + (props.vendor === "valibot" ? "Wrap it: `toStandardJsonSchema(schema)` from @valibot/to-json-schema." : "Use a version that implements Standard JSON Schema (zod >= 4.2, arktype >= 2.1), or pass a JSON Schema.")
|
|
962
|
+
);
|
|
963
|
+
}
|
|
964
|
+
let json;
|
|
965
|
+
try {
|
|
966
|
+
json = describe.input({ target: "draft-2020-12" });
|
|
967
|
+
} catch (e15) {
|
|
968
|
+
json = describe.input({ target: "draft-07" });
|
|
969
|
+
}
|
|
970
|
+
return { json, validate: async (value) => props.validate(value) };
|
|
971
|
+
}
|
|
972
|
+
function formatPath(path3) {
|
|
973
|
+
if (path3 === void 0 || path3.length === 0) return "";
|
|
974
|
+
return path3.map((segment) => typeof segment === "object" && segment !== null ? segment.key : segment).map((key) => String(key)).join(".");
|
|
975
|
+
}
|
|
976
|
+
function describeIssues(issues) {
|
|
977
|
+
return issues.slice(0, 8).map((issue) => {
|
|
978
|
+
const at = formatPath(issue.path);
|
|
979
|
+
return at === "" ? issue.message : `${at}: ${issue.message}`;
|
|
980
|
+
}).join("; ");
|
|
981
|
+
}
|
|
982
|
+
async function validateWith(resolved, value, text, tier) {
|
|
983
|
+
if (resolved.validate === void 0) {
|
|
984
|
+
const issues = checkPatterns(value, resolved.json);
|
|
985
|
+
if (issues.length === 0) return value;
|
|
986
|
+
throw new SchemaValidationError(
|
|
987
|
+
`The model's reply did not satisfy the schema: ${describeIssues(issues)}`,
|
|
988
|
+
issues.map((issue) => ({ message: issue.message, path: issue.path })),
|
|
989
|
+
text,
|
|
990
|
+
tier
|
|
991
|
+
);
|
|
992
|
+
}
|
|
993
|
+
const result = await resolved.validate(value);
|
|
994
|
+
if (result.issues !== void 0) {
|
|
995
|
+
throw new SchemaValidationError(
|
|
996
|
+
`The model's reply did not satisfy the schema: ${describeIssues(result.issues)}`,
|
|
997
|
+
result.issues.map((issue) => ({ message: issue.message, path: issue.path })),
|
|
998
|
+
text,
|
|
999
|
+
tier
|
|
1000
|
+
);
|
|
1001
|
+
}
|
|
1002
|
+
return result.value;
|
|
1003
|
+
}
|
|
1004
|
+
function isObject(value) {
|
|
1005
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
1006
|
+
}
|
|
1007
|
+
function allowsNull(schema) {
|
|
1008
|
+
if (!isObject(schema)) return false;
|
|
1009
|
+
if (schema.type === "null" || schema.nullable === true) return true;
|
|
1010
|
+
if (Array.isArray(schema.type) && schema.type.includes("null")) return true;
|
|
1011
|
+
if (Array.isArray(schema.enum) && schema.enum.includes(null)) return true;
|
|
1012
|
+
for (const key of ["anyOf", "oneOf"]) {
|
|
1013
|
+
const branches = schema[key];
|
|
1014
|
+
if (Array.isArray(branches) && branches.some(allowsNull)) return true;
|
|
1015
|
+
}
|
|
1016
|
+
return false;
|
|
1017
|
+
}
|
|
1018
|
+
function restoreNulls(value, schema, root = schema, depth = 0) {
|
|
1019
|
+
if (depth > 32 || !isObject(schema)) return value;
|
|
1020
|
+
const ref = schema.$ref;
|
|
1021
|
+
if (typeof ref === "string") {
|
|
1022
|
+
const target = resolveRef(ref, root);
|
|
1023
|
+
return target === void 0 ? value : restoreNulls(value, target, root, depth + 1);
|
|
1024
|
+
}
|
|
1025
|
+
for (const key of ["anyOf", "oneOf"]) {
|
|
1026
|
+
const branches = schema[key];
|
|
1027
|
+
if (Array.isArray(branches)) {
|
|
1028
|
+
const nonNull = branches.filter((b) => !(isObject(b) && b.type === "null"));
|
|
1029
|
+
if (nonNull.length === 1 && isObject(nonNull[0])) {
|
|
1030
|
+
return restoreNulls(value, nonNull[0], root, depth + 1);
|
|
1031
|
+
}
|
|
1032
|
+
return value;
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
if (Array.isArray(schema.allOf) && schema.allOf.length === 1 && isObject(schema.allOf[0])) {
|
|
1036
|
+
return restoreNulls(value, schema.allOf[0], root, depth + 1);
|
|
1037
|
+
}
|
|
1038
|
+
if (Array.isArray(value)) {
|
|
1039
|
+
return isObject(schema.items) ? value.map((item) => restoreNulls(item, schema.items, root, depth + 1)) : value;
|
|
1040
|
+
}
|
|
1041
|
+
if (!isObject(value) || !isObject(schema.properties)) return value;
|
|
1042
|
+
const properties = schema.properties;
|
|
1043
|
+
const required = Array.isArray(schema.required) ? schema.required : [];
|
|
1044
|
+
const out = { ...value };
|
|
1045
|
+
for (const [key, child] of Object.entries(properties)) {
|
|
1046
|
+
if (out[key] === void 0) {
|
|
1047
|
+
if (required.includes(key) && allowsNull(child)) out[key] = null;
|
|
1048
|
+
continue;
|
|
1049
|
+
}
|
|
1050
|
+
if (isObject(child)) out[key] = restoreNulls(out[key], child, root, depth + 1);
|
|
1051
|
+
}
|
|
1052
|
+
return out;
|
|
1053
|
+
}
|
|
1054
|
+
function resolveRef(ref, root) {
|
|
1055
|
+
const match = /^#\/(\$defs|definitions)\/(.+)$/.exec(ref);
|
|
1056
|
+
if (match === null) return ref === "#" ? root : void 0;
|
|
1057
|
+
const defs = root[match[1]];
|
|
1058
|
+
const target = isObject(defs) ? defs[decodeURIComponent(match[2])] : void 0;
|
|
1059
|
+
return isObject(target) ? target : void 0;
|
|
1060
|
+
}
|
|
1061
|
+
function checkPatterns(value, schema, root = schema, path3 = [], depth = 0) {
|
|
1062
|
+
if (depth > 32 || !isObject(schema)) return [];
|
|
1063
|
+
if (typeof schema.$ref === "string") {
|
|
1064
|
+
const target = resolveRef(schema.$ref, root);
|
|
1065
|
+
return target === void 0 ? [] : checkPatterns(value, target, root, path3, depth + 1);
|
|
1066
|
+
}
|
|
1067
|
+
if (Array.isArray(schema.allOf) && schema.allOf.length === 1 && isObject(schema.allOf[0])) {
|
|
1068
|
+
return checkPatterns(value, schema.allOf[0], root, path3, depth + 1);
|
|
1069
|
+
}
|
|
1070
|
+
for (const key of ["anyOf", "oneOf"]) {
|
|
1071
|
+
const branches = schema[key];
|
|
1072
|
+
if (!Array.isArray(branches)) continue;
|
|
1073
|
+
const nonNull = branches.filter((b) => !(isObject(b) && b.type === "null"));
|
|
1074
|
+
return nonNull.length === 1 && isObject(nonNull[0]) && value !== null ? checkPatterns(value, nonNull[0], root, path3, depth + 1) : [];
|
|
1075
|
+
}
|
|
1076
|
+
if (typeof value === "string" && typeof schema.pattern === "string") {
|
|
1077
|
+
let regex;
|
|
1078
|
+
try {
|
|
1079
|
+
regex = new RegExp(schema.pattern, "u");
|
|
1080
|
+
} catch (e16) {
|
|
1081
|
+
regex = void 0;
|
|
1082
|
+
}
|
|
1083
|
+
return regex === void 0 || regex.test(value) ? [] : [{ message: `must match the pattern ${schema.pattern}`, path: path3 }];
|
|
1084
|
+
}
|
|
1085
|
+
if (Array.isArray(value) && isObject(schema.items)) {
|
|
1086
|
+
return value.flatMap((item, i) => checkPatterns(item, schema.items, root, [...path3, i], depth + 1));
|
|
1087
|
+
}
|
|
1088
|
+
if (isObject(value) && isObject(schema.properties)) {
|
|
1089
|
+
return Object.entries(schema.properties).flatMap(
|
|
1090
|
+
([key, child]) => value[key] === void 0 || !isObject(child) ? [] : checkPatterns(value[key], child, root, [...path3, key], depth + 1)
|
|
1091
|
+
);
|
|
1092
|
+
}
|
|
1093
|
+
return [];
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
// src/tools.ts
|
|
1097
|
+
var BUILT_IN_TOOLS = /* @__PURE__ */ new Set(["ocr", "barcode", "spotlight"]);
|
|
1098
|
+
function tool(definition) {
|
|
1099
|
+
return definition;
|
|
1100
|
+
}
|
|
1101
|
+
var TOOL_NAME = /^[A-Za-z_][A-Za-z0-9_-]{0,63}$/;
|
|
1102
|
+
var EMPTY_PARAMETERS = { type: "object", properties: {}, required: [] };
|
|
1103
|
+
function normalizeTools(tools) {
|
|
1104
|
+
const out = { builtIn: [], functions: [] };
|
|
1105
|
+
if (tools === void 0) return out;
|
|
1106
|
+
const entries = Array.isArray(tools) ? [...tools] : Object.entries(tools).map(([name, def]) => ({ ...def, name: _nullishCoalesce(def.name, () => ( name)) }));
|
|
1107
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1108
|
+
for (const entry of entries) {
|
|
1109
|
+
if (typeof entry === "string") {
|
|
1110
|
+
if (!BUILT_IN_TOOLS.has(entry)) {
|
|
1111
|
+
throw new AppleLLMError(`Unknown tool "${entry}" (want ${[...BUILT_IN_TOOLS].join(", ")}).`, "device");
|
|
1112
|
+
}
|
|
1113
|
+
if (!out.builtIn.includes(entry)) out.builtIn.push(entry);
|
|
1114
|
+
continue;
|
|
1115
|
+
}
|
|
1116
|
+
if (entry === null || typeof entry !== "object" || typeof entry.name !== "string") {
|
|
1117
|
+
throw new AppleLLMError("A function tool needs a name.", "device");
|
|
1118
|
+
}
|
|
1119
|
+
if (!TOOL_NAME.test(entry.name)) {
|
|
1120
|
+
throw new AppleLLMError(
|
|
1121
|
+
`Tool name "${entry.name}" is invalid: use letters, digits, "_" or "-", starting with a letter or "_".`,
|
|
1122
|
+
"device"
|
|
1123
|
+
);
|
|
1124
|
+
}
|
|
1125
|
+
if (seen.has(entry.name) || BUILT_IN_TOOLS.has(entry.name)) {
|
|
1126
|
+
throw new AppleLLMError(`Tool name "${entry.name}" is used twice.`, "device");
|
|
1127
|
+
}
|
|
1128
|
+
seen.add(entry.name);
|
|
1129
|
+
const resolved = entry.parameters === void 0 ? void 0 : resolveSchema(entry.parameters);
|
|
1130
|
+
out.functions.push({ ...entry, resolved, json: _nullishCoalesce(_optionalChain([resolved, 'optionalAccess', _56 => _56.json]), () => ( EMPTY_PARAMETERS)) });
|
|
1131
|
+
}
|
|
1132
|
+
return out;
|
|
1133
|
+
}
|
|
1134
|
+
function toolOutputText(value) {
|
|
1135
|
+
if (typeof value === "string") return value;
|
|
1136
|
+
if (value === void 0) return "";
|
|
1137
|
+
try {
|
|
1138
|
+
return JSON.stringify(value);
|
|
1139
|
+
} catch (e17) {
|
|
1140
|
+
return String(value);
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
// src/device.ts
|
|
1145
|
+
|
|
1146
|
+
|
|
1147
|
+
|
|
1148
|
+
|
|
1149
|
+
// src/protocol.ts
|
|
1150
|
+
|
|
1151
|
+
var defaultSpawner = (binary, args) => _child_process.spawn.call(void 0, binary, args, { stdio: ["pipe", "pipe", "pipe"] });
|
|
1152
|
+
function abortErrorFrom(signal) {
|
|
1153
|
+
const reason = signal.reason;
|
|
1154
|
+
if (reason instanceof AppleLLMError) return reason;
|
|
1155
|
+
if (reason instanceof Error && reason.name === "TimeoutError") {
|
|
1156
|
+
return new TimeoutError(`Apple generation timed out: ${reason.message}`, "device", { cause: reason });
|
|
1157
|
+
}
|
|
1158
|
+
return new AbortError("The request was aborted.", void 0, { cause: reason });
|
|
1159
|
+
}
|
|
1160
|
+
var HelperServer = (_class15 = class {
|
|
1161
|
+
constructor(binary, options = {}) {;_class15.prototype.__init15.call(this);_class15.prototype.__init16.call(this);_class15.prototype.__init17.call(this);_class15.prototype.__init18.call(this);_class15.prototype.__init19.call(this);
|
|
1162
|
+
this.binary = binary;
|
|
1163
|
+
this.timeoutMs = _nullishCoalesce(options.timeoutMs, () => ( 12e4));
|
|
1164
|
+
this.spawner = _nullishCoalesce(options.spawner, () => ( defaultSpawner));
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
|
|
1168
|
+
__init15() {this.buffer = ""}
|
|
1169
|
+
__init16() {this.queue = []}
|
|
1170
|
+
/** Serialises callers so one request's reply cannot be handed to another. */
|
|
1171
|
+
__init17() {this.chain = Promise.resolve()}
|
|
1172
|
+
__init18() {this.inFlight = 0}
|
|
1173
|
+
/** The helper's recent stderr, quoted when it dies. Always drained: an unread pipe blocks the writer. */
|
|
1174
|
+
__init19() {this.stderrTail = ""}
|
|
1175
|
+
|
|
1176
|
+
|
|
1177
|
+
start() {
|
|
1178
|
+
if (this.child !== void 0 && this.child.exitCode === null && !this.child.killed) {
|
|
1179
|
+
return this.child;
|
|
1180
|
+
}
|
|
1181
|
+
const child = this.spawner(this.binary, ["--serve"]);
|
|
1182
|
+
this.child = child;
|
|
1183
|
+
this.buffer = "";
|
|
1184
|
+
this.stderrTail = "";
|
|
1185
|
+
_optionalChain([child, 'access', _57 => _57.stdout, 'optionalAccess', _58 => _58.on, 'call', _59 => _59("data", (chunk) => {
|
|
1186
|
+
this.buffer += chunk.toString();
|
|
1187
|
+
for (; ; ) {
|
|
1188
|
+
const newline = this.buffer.indexOf("\n");
|
|
1189
|
+
if (newline === -1) break;
|
|
1190
|
+
const line = this.buffer.slice(0, newline).trim();
|
|
1191
|
+
this.buffer = this.buffer.slice(newline + 1);
|
|
1192
|
+
if (line !== "") this.dispatch(line);
|
|
1193
|
+
}
|
|
1194
|
+
})]);
|
|
1195
|
+
_optionalChain([child, 'access', _60 => _60.stderr, 'optionalAccess', _61 => _61.on, 'call', _62 => _62("data", (chunk) => {
|
|
1196
|
+
this.stderrTail = (this.stderrTail + chunk.toString()).slice(-2e3);
|
|
1197
|
+
})]);
|
|
1198
|
+
const fail = (err) => {
|
|
1199
|
+
if (this.child === child) this.child = void 0;
|
|
1200
|
+
const pending = this.queue;
|
|
1201
|
+
this.queue = [];
|
|
1202
|
+
for (const p of pending) {
|
|
1203
|
+
clearTimeout(p.timer);
|
|
1204
|
+
p.finish(void 0, err);
|
|
1205
|
+
}
|
|
1206
|
+
};
|
|
1207
|
+
child.on("error", (err) => fail(err instanceof Error ? err : new Error(String(err))));
|
|
1208
|
+
child.on("close", (code) => {
|
|
1209
|
+
const tail = this.stderrTail.trim();
|
|
1210
|
+
fail(new AppleLLMError(`Apple helper exited with code ${code}${tail ? `: ${tail.slice(-400)}` : ""}`, "device"));
|
|
1211
|
+
});
|
|
1212
|
+
_optionalChain([child, 'access', _63 => _63.stdin, 'optionalAccess', _64 => _64.on, 'call', _65 => _65("error", () => {
|
|
1213
|
+
})]);
|
|
1214
|
+
const killer = () => {
|
|
1215
|
+
if (child.exitCode === null) child.kill();
|
|
1216
|
+
};
|
|
1217
|
+
process.once("exit", killer);
|
|
1218
|
+
child.once("close", () => process.removeListener("exit", killer));
|
|
1219
|
+
this.setRef(this.inFlight > 0);
|
|
1220
|
+
return child;
|
|
1221
|
+
}
|
|
1222
|
+
/** Keep the event loop alive only while a request is in flight. */
|
|
1223
|
+
setRef(on) {
|
|
1224
|
+
const child = this.child;
|
|
1225
|
+
if (child === void 0) return;
|
|
1226
|
+
const handles = [child, child.stdin, child.stdout, child.stderr];
|
|
1227
|
+
for (const handle of handles) {
|
|
1228
|
+
if (on) _optionalChain([handle, 'optionalAccess', _66 => _66.ref, 'optionalCall', _67 => _67()]);
|
|
1229
|
+
else _optionalChain([handle, 'optionalAccess', _68 => _68.unref, 'optionalCall', _69 => _69()]);
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
/** Route one line: an event to the head's hooks, anything else completes the head. */
|
|
1233
|
+
dispatch(line) {
|
|
1234
|
+
const head = this.queue[0];
|
|
1235
|
+
if (head === void 0) return;
|
|
1236
|
+
let parsed;
|
|
1237
|
+
try {
|
|
1238
|
+
const value = JSON.parse(line);
|
|
1239
|
+
if (value !== null && typeof value === "object" && !Array.isArray(value)) {
|
|
1240
|
+
parsed = value;
|
|
1241
|
+
}
|
|
1242
|
+
} catch (e18) {
|
|
1243
|
+
}
|
|
1244
|
+
if (_optionalChain([parsed, 'optionalAccess', _70 => _70.done]) === false) {
|
|
1245
|
+
this.arm(head);
|
|
1246
|
+
if (head.abandoned) {
|
|
1247
|
+
const call = parsed.toolCall;
|
|
1248
|
+
if (typeof _optionalChain([call, 'optionalAccess', _71 => _71.id]) === "string") this.write({ op: "toolResult", callId: call.id, stop: true });
|
|
1249
|
+
return;
|
|
1250
|
+
}
|
|
1251
|
+
try {
|
|
1252
|
+
_optionalChain([head, 'access', _72 => _72.hooks, 'access', _73 => _73.onEvent, 'optionalCall', _74 => _74(parsed, (control) => this.write(control))]);
|
|
1253
|
+
} catch (e19) {
|
|
1254
|
+
}
|
|
1255
|
+
return;
|
|
1256
|
+
}
|
|
1257
|
+
this.queue.shift();
|
|
1258
|
+
clearTimeout(head.timer);
|
|
1259
|
+
head.finish(line);
|
|
1260
|
+
}
|
|
1261
|
+
/**
|
|
1262
|
+
* (Re)start the wedge timer. It measures silence, not total time: every
|
|
1263
|
+
* event resets it, so a long stream is fine while a helper that stops
|
|
1264
|
+
* talking is not.
|
|
1265
|
+
*/
|
|
1266
|
+
arm(entry) {
|
|
1267
|
+
clearTimeout(entry.timer);
|
|
1268
|
+
entry.timer = setTimeout(() => this.timeOut(entry), this.timeoutMs);
|
|
1269
|
+
}
|
|
1270
|
+
/**
|
|
1271
|
+
* Pause the wedge timer while the client itself is busy — a function tool
|
|
1272
|
+
* running in JS. The helper is legitimately silent then: it is waiting on us.
|
|
1273
|
+
*/
|
|
1274
|
+
pause() {
|
|
1275
|
+
const head = this.queue[0];
|
|
1276
|
+
if (head !== void 0) clearTimeout(head.timer);
|
|
1277
|
+
}
|
|
1278
|
+
/** Resume the wedge timer paused by `pause()`. */
|
|
1279
|
+
resume() {
|
|
1280
|
+
const head = this.queue[0];
|
|
1281
|
+
if (head !== void 0) this.arm(head);
|
|
1282
|
+
}
|
|
1283
|
+
timeOut(entry) {
|
|
1284
|
+
const index = this.queue.indexOf(entry);
|
|
1285
|
+
if (index < 0) return;
|
|
1286
|
+
this.queue.splice(index, 1);
|
|
1287
|
+
this.stop();
|
|
1288
|
+
entry.finish(
|
|
1289
|
+
void 0,
|
|
1290
|
+
new TimeoutError(`Apple on-device generation timed out after ${this.timeoutMs / 1e3}s of silence.`, "device")
|
|
1291
|
+
);
|
|
1292
|
+
}
|
|
1293
|
+
/** Write a control line (cancel, toolResult) for the request in flight. */
|
|
1294
|
+
write(control) {
|
|
1295
|
+
_optionalChain([this, 'access', _75 => _75.child, 'optionalAccess', _76 => _76.stdin, 'optionalAccess', _77 => _77.write, 'call', _78 => _78(`${JSON.stringify(control)}
|
|
1296
|
+
`)]);
|
|
1297
|
+
}
|
|
1298
|
+
/**
|
|
1299
|
+
* One request. Resolves with its final line; events go to `hooks.onEvent`.
|
|
1300
|
+
* Serialised against every other request through one chain.
|
|
1301
|
+
*/
|
|
1302
|
+
request(payload, hooks = {}) {
|
|
1303
|
+
const { signal } = hooks;
|
|
1304
|
+
if (_optionalChain([signal, 'optionalAccess', _79 => _79.aborted]) === true) return Promise.reject(abortErrorFrom(signal));
|
|
1305
|
+
let entry;
|
|
1306
|
+
let rejectCaller = () => void 0;
|
|
1307
|
+
const caller = new Promise((resolve, reject) => {
|
|
1308
|
+
rejectCaller = reject;
|
|
1309
|
+
const run = () => {
|
|
1310
|
+
if (_optionalChain([signal, 'optionalAccess', _80 => _80.aborted]) === true) {
|
|
1311
|
+
reject(abortErrorFrom(signal));
|
|
1312
|
+
return Promise.resolve();
|
|
1313
|
+
}
|
|
1314
|
+
const child = this.start();
|
|
1315
|
+
this.inFlight += 1;
|
|
1316
|
+
this.setRef(true);
|
|
1317
|
+
return new Promise((settle) => {
|
|
1318
|
+
const pending = {
|
|
1319
|
+
hooks,
|
|
1320
|
+
abandoned: false,
|
|
1321
|
+
finish: (line, err) => {
|
|
1322
|
+
this.inFlight -= 1;
|
|
1323
|
+
if (this.inFlight === 0) this.setRef(false);
|
|
1324
|
+
settle();
|
|
1325
|
+
if (pending.abandoned) return;
|
|
1326
|
+
if (err !== void 0) reject(err);
|
|
1327
|
+
else resolve(_nullishCoalesce(line, () => ( "")));
|
|
1328
|
+
}
|
|
1329
|
+
};
|
|
1330
|
+
entry = pending;
|
|
1331
|
+
this.arm(pending);
|
|
1332
|
+
this.queue.push(pending);
|
|
1333
|
+
_optionalChain([child, 'access', _81 => _81.stdin, 'optionalAccess', _82 => _82.write, 'call', _83 => _83(`${payload.replace(/\n/g, " ")}
|
|
1334
|
+
`)]);
|
|
1335
|
+
});
|
|
1336
|
+
};
|
|
1337
|
+
const slot = this.chain.then(run, run);
|
|
1338
|
+
this.chain = slot.catch(() => void 0);
|
|
1339
|
+
});
|
|
1340
|
+
if (signal !== void 0) {
|
|
1341
|
+
const onAbort = () => {
|
|
1342
|
+
if (entry !== void 0 && this.queue.includes(entry)) {
|
|
1343
|
+
entry.abandoned = true;
|
|
1344
|
+
if (hooks.id !== void 0) this.write({ op: "cancel", id: hooks.id });
|
|
1345
|
+
}
|
|
1346
|
+
rejectCaller(abortErrorFrom(signal));
|
|
1347
|
+
};
|
|
1348
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
1349
|
+
const detach = () => signal.removeEventListener("abort", onAbort);
|
|
1350
|
+
caller.then(detach, detach);
|
|
1351
|
+
}
|
|
1352
|
+
return caller;
|
|
1353
|
+
}
|
|
1354
|
+
/** Version-1 shape: one request, one line. */
|
|
1355
|
+
send(payload, hooks = {}) {
|
|
1356
|
+
return this.request(payload, hooks);
|
|
1357
|
+
}
|
|
1358
|
+
/**
|
|
1359
|
+
* Streaming request, kept for callers of the version-1 API: deltas go to
|
|
1360
|
+
* `onDelta` and the promise resolves with the final line.
|
|
1361
|
+
*/
|
|
1362
|
+
stream(payload, onDelta, hooks = {}) {
|
|
1363
|
+
return this.request(payload, {
|
|
1364
|
+
...hooks,
|
|
1365
|
+
onEvent: (event, write) => {
|
|
1366
|
+
if (typeof event.delta === "string") onDelta(event.delta);
|
|
1367
|
+
_optionalChain([hooks, 'access', _84 => _84.onEvent, 'optionalCall', _85 => _85(event, write)]);
|
|
1368
|
+
}
|
|
1369
|
+
});
|
|
1370
|
+
}
|
|
1371
|
+
stop() {
|
|
1372
|
+
const child = this.child;
|
|
1373
|
+
this.child = void 0;
|
|
1374
|
+
if (child !== void 0 && child.exitCode === null) {
|
|
1375
|
+
_optionalChain([child, 'access', _86 => _86.stdin, 'optionalAccess', _87 => _87.end, 'call', _88 => _88()]);
|
|
1376
|
+
child.kill();
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
}, _class15);
|
|
1380
|
+
|
|
1381
|
+
// src/device.ts
|
|
1382
|
+
var execFileAsync4 = _util.promisify.call(void 0, _child_process.execFile);
|
|
1383
|
+
var DEFAULT_TEMPERATURE = 0.4;
|
|
1384
|
+
var DEFAULT_MAX_TOKENS = 2048;
|
|
1385
|
+
var DEFAULT_MAX_TOOL_CALLS = 8;
|
|
1386
|
+
var CALL_TIMEOUT_MS2 = 12e4;
|
|
1387
|
+
var BUSY_RETRY_DELAYS_MS = [250, 1e3, 2500];
|
|
1388
|
+
function assertTools(tools) {
|
|
1389
|
+
if (tools === void 0) return;
|
|
1390
|
+
for (const tool2 of tools) {
|
|
1391
|
+
if (!BUILT_IN_TOOLS.has(tool2)) {
|
|
1392
|
+
throw new AppleLLMError(
|
|
1393
|
+
`Unknown tool "${tool2}" (want ${[...BUILT_IN_TOOLS].join(", ")}).`,
|
|
1394
|
+
"device"
|
|
1395
|
+
);
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1399
|
+
async function probeDevice(onProgress) {
|
|
1400
|
+
if (process.platform !== "darwin") {
|
|
1401
|
+
return { available: false, reason: `unsupportedPlatform: this machine reports "${process.platform}"` };
|
|
1402
|
+
}
|
|
1403
|
+
if (!isAppleSiliconMac()) {
|
|
1404
|
+
return { available: false, reason: "deviceNotEligible: Apple Intelligence needs Apple Silicon" };
|
|
1405
|
+
}
|
|
1406
|
+
const major = await macosMajor();
|
|
1407
|
+
if (major !== null && major < 26) {
|
|
1408
|
+
return { available: false, reason: `unsupportedOSVersion: needs macOS 26 or later, this is ${major}` };
|
|
1409
|
+
}
|
|
1410
|
+
let binary;
|
|
1411
|
+
try {
|
|
1412
|
+
binary = await ensureBinary(onProgress);
|
|
1413
|
+
} catch (err) {
|
|
1414
|
+
return { available: false, reason: err instanceof Error ? err.message : String(err) };
|
|
1415
|
+
}
|
|
1416
|
+
try {
|
|
1417
|
+
const { stdout } = await execFileAsync4(binary, ["--probe"], { timeout: 2e4 });
|
|
1418
|
+
return JSON.parse(stdout.trim());
|
|
1419
|
+
} catch (err) {
|
|
1420
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
1421
|
+
return { available: false, reason: `the helper could not be run: ${reason.slice(0, 300)}` };
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
1424
|
+
function raiseFor(response, raw, toolFailure) {
|
|
1425
|
+
const detail = _nullishCoalesce(response.error, () => ( raw.slice(0, 200)));
|
|
1426
|
+
switch (response.kind) {
|
|
1427
|
+
case "availability":
|
|
1428
|
+
throw new ModelUnavailableError(
|
|
1429
|
+
unavailableMessage(detail),
|
|
1430
|
+
toUnavailableReason(detail),
|
|
1431
|
+
"device"
|
|
1432
|
+
);
|
|
1433
|
+
case "schema":
|
|
1434
|
+
throw new SchemaRejectedError(
|
|
1435
|
+
`Apple rejected the response schema: ${detail}
|
|
1436
|
+
Every object needs a title, an x-order and additionalProperties; enums must be anyOf/const; unions cannot include null.`,
|
|
1437
|
+
"device"
|
|
1438
|
+
);
|
|
1439
|
+
case "context":
|
|
1440
|
+
throw new ContextLengthError(
|
|
1441
|
+
`The prompt exceeded the on-device context window: ${detail}`,
|
|
1442
|
+
"device",
|
|
1443
|
+
response.contextSize,
|
|
1444
|
+
response.tokenCount
|
|
1445
|
+
);
|
|
1446
|
+
case "quota": {
|
|
1447
|
+
const parsed = response.resetDate === void 0 ? void 0 : new Date(response.resetDate);
|
|
1448
|
+
const resetDate = parsed !== void 0 && !Number.isNaN(parsed.getTime()) ? parsed : void 0;
|
|
1449
|
+
throw new QuotaError(
|
|
1450
|
+
`Apple rate limited the on-device model: ${detail}`,
|
|
1451
|
+
"device",
|
|
1452
|
+
resetDate
|
|
1453
|
+
);
|
|
1454
|
+
}
|
|
1455
|
+
case "guardrail":
|
|
1456
|
+
throw new RefusalError(`The on-device model declined to answer: ${detail}`, "device");
|
|
1457
|
+
case "timeout":
|
|
1458
|
+
throw new TimeoutError(`Apple on-device generation timed out: ${detail}`, "device");
|
|
1459
|
+
case "cancelled":
|
|
1460
|
+
throw new AbortError("The request was cancelled.", "device");
|
|
1461
|
+
case "tool": {
|
|
1462
|
+
const name = _nullishCoalesce(_nullishCoalesce(_optionalChain([toolFailure, 'optionalAccess', _89 => _89.name]), () => ( response.tool)), () => ( "unknown"));
|
|
1463
|
+
const cause = _optionalChain([toolFailure, 'optionalAccess', _90 => _90.error]);
|
|
1464
|
+
const why = cause instanceof Error ? cause.message : cause === void 0 ? detail : String(cause);
|
|
1465
|
+
throw new ToolExecutionError(`Tool "${name}" failed: ${why}`, name, { cause });
|
|
1466
|
+
}
|
|
1467
|
+
case "unsupported":
|
|
1468
|
+
throw new UnsupportedError(`Apple on-device generation failed: ${detail}`, "device");
|
|
1469
|
+
case "busy":
|
|
1470
|
+
throw new ModelBusyError(
|
|
1471
|
+
"Apple\u2019s on-device model is busy serving other processes and turned the request away (ModelManagerError 1042), even after retrying. Try again shortly.",
|
|
1472
|
+
"device"
|
|
1473
|
+
);
|
|
1474
|
+
default:
|
|
1475
|
+
throw new AppleLLMError(`Apple on-device generation failed: ${detail}`, "device");
|
|
1476
|
+
}
|
|
1477
|
+
}
|
|
1478
|
+
function usageFrom(raw) {
|
|
1479
|
+
if (raw === void 0 || typeof raw.inputTokens !== "number" || typeof raw.outputTokens !== "number") {
|
|
1480
|
+
return void 0;
|
|
1481
|
+
}
|
|
1482
|
+
return {
|
|
1483
|
+
inputTokens: raw.inputTokens,
|
|
1484
|
+
outputTokens: raw.outputTokens,
|
|
1485
|
+
totalTokens: raw.inputTokens + raw.outputTokens,
|
|
1486
|
+
...typeof raw.cachedInputTokens === "number" ? { cachedInputTokens: raw.cachedInputTokens } : {}
|
|
1487
|
+
};
|
|
1488
|
+
}
|
|
1489
|
+
function finishFrom(raw) {
|
|
1490
|
+
if (raw === "length") return "length";
|
|
1491
|
+
if (raw === "toolCalls") return "tool-calls";
|
|
1492
|
+
return "stop";
|
|
1493
|
+
}
|
|
1494
|
+
var DeviceClient = class {
|
|
1495
|
+
constructor(options = {}) {
|
|
1496
|
+
this.options = options;
|
|
1497
|
+
this.server = options.server;
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
|
|
1501
|
+
|
|
1502
|
+
|
|
1503
|
+
|
|
1504
|
+
get label() {
|
|
1505
|
+
const variant = _optionalChain([this, 'access', _91 => _91.probeResult, 'optionalAccess', _92 => _92.variant]);
|
|
1506
|
+
return variant ? `apple on-device (${variant})` : "apple on-device";
|
|
1507
|
+
}
|
|
1508
|
+
get contextSize() {
|
|
1509
|
+
return _optionalChain([this, 'access', _93 => _93.probeResult, 'optionalAccess', _94 => _94.contextSize]);
|
|
1510
|
+
}
|
|
1511
|
+
/** Probe and build once; concurrent first calls share the one attempt. */
|
|
1512
|
+
async ensureReady(onProgress) {
|
|
1513
|
+
if (_optionalChain([this, 'access', _95 => _95.probeResult, 'optionalAccess', _96 => _96.available]) === true && this.binary !== void 0) return;
|
|
1514
|
+
this.readying ??= (async () => {
|
|
1515
|
+
const probe2 = await probeDevice(onProgress);
|
|
1516
|
+
this.probeResult = probe2;
|
|
1517
|
+
if (!probe2.available) {
|
|
1518
|
+
throw new ModelUnavailableError(
|
|
1519
|
+
unavailableMessage(probe2.reason),
|
|
1520
|
+
toUnavailableReason(probe2.reason),
|
|
1521
|
+
"device"
|
|
1522
|
+
);
|
|
1523
|
+
}
|
|
1524
|
+
this.binary = await ensureBinary(onProgress);
|
|
1525
|
+
_optionalChain([onProgress, 'optionalCall', _97 => _97({
|
|
1526
|
+
status: `apple on-device model ready${probe2.variant ? ` (${probe2.variant}, ${probe2.contextSize} token context)` : ""}`
|
|
1527
|
+
})]);
|
|
1528
|
+
})().finally(() => {
|
|
1529
|
+
this.readying = void 0;
|
|
1530
|
+
});
|
|
1531
|
+
return this.readying;
|
|
1532
|
+
}
|
|
1533
|
+
getProbe() {
|
|
1534
|
+
return this.probeResult;
|
|
1535
|
+
}
|
|
1536
|
+
async helper() {
|
|
1537
|
+
if (this.options.server !== void 0) return this.options.server;
|
|
1538
|
+
await this.ensureReady();
|
|
1539
|
+
const binary = await _asyncNullishCoalesce(this.binary, async () => ( await ensureBinary()));
|
|
1540
|
+
this.server ??= new HelperServer(binary, { timeoutMs: CALL_TIMEOUT_MS2 });
|
|
1541
|
+
return this.server;
|
|
1542
|
+
}
|
|
1543
|
+
/** Send one single-line envelope and return the parsed reply, raising a typed error on failure. */
|
|
1544
|
+
async exchange(envelope, signal) {
|
|
1545
|
+
const server = await this.helper();
|
|
1546
|
+
const id = _crypto.randomUUID.call(void 0, );
|
|
1547
|
+
const raw = await server.request(JSON.stringify({ ...envelope, id }), { signal, id });
|
|
1548
|
+
const response = parse(raw);
|
|
1549
|
+
if (response.ok !== true) raiseFor(response, raw);
|
|
1550
|
+
return response;
|
|
1551
|
+
}
|
|
1552
|
+
/** The request envelope, shared by every generation path. */
|
|
1553
|
+
async envelopeFor(request, op) {
|
|
1554
|
+
assertTools(request.tools);
|
|
1555
|
+
return {
|
|
1556
|
+
op,
|
|
1557
|
+
instructions: _nullishCoalesce(request.system, () => ( "")),
|
|
1558
|
+
prompt: await withDocuments(request.prompt, request.documents),
|
|
1559
|
+
schema: _nullishCoalesce(request.schema, () => ( null)),
|
|
1560
|
+
temperature: _nullishCoalesce(request.temperature, () => ( DEFAULT_TEMPERATURE)),
|
|
1561
|
+
maxTokens: _nullishCoalesce(request.maxTokens, () => ( DEFAULT_MAX_TOKENS)),
|
|
1562
|
+
includeSchemaInPrompt: request.includeSchemaInPrompt,
|
|
1563
|
+
reuseSession: _nullishCoalesce(request.reuseSession, () => ( false)),
|
|
1564
|
+
sessionId: request.sessionId,
|
|
1565
|
+
history: request.history !== void 0 && request.history.length > 0 ? request.history : void 0,
|
|
1566
|
+
trimHistory: request.trimHistory,
|
|
1567
|
+
tools: request.tools !== void 0 && request.tools.length > 0 ? request.tools : void 0,
|
|
1568
|
+
functions: request.functions !== void 0 && request.functions.length > 0 ? request.functions.map((fn) => ({
|
|
1569
|
+
name: fn.name,
|
|
1570
|
+
description: _nullishCoalesce(fn.description, () => ( "")),
|
|
1571
|
+
parameters: toAppleSchema(fn.json, `${fn.name}Arguments`)
|
|
1572
|
+
})) : void 0,
|
|
1573
|
+
images: request.images,
|
|
1574
|
+
useCase: _nullishCoalesce(request.useCase, () => ( this.options.useCase)),
|
|
1575
|
+
guardrails: _nullishCoalesce(request.guardrails, () => ( this.options.guardrails)),
|
|
1576
|
+
sampling: request.sampling
|
|
1577
|
+
};
|
|
1578
|
+
}
|
|
1579
|
+
/**
|
|
1580
|
+
* One generation, with every event handled: text deltas, partial objects,
|
|
1581
|
+
* and function tool calls, which run here and answer the helper.
|
|
1582
|
+
*/
|
|
1583
|
+
async run(request, op = "generate", hooks = {}) {
|
|
1584
|
+
const envelope = await this.envelopeFor(request, op);
|
|
1585
|
+
const server = await this.helper();
|
|
1586
|
+
const functions = new Map((_nullishCoalesce(request.functions, () => ( []))).map((fn) => [fn.name, fn]));
|
|
1587
|
+
const maxToolCalls = _nullishCoalesce(hooks.maxToolCalls, () => ( DEFAULT_MAX_TOOL_CALLS));
|
|
1588
|
+
const calls = [];
|
|
1589
|
+
let toolFailure;
|
|
1590
|
+
const answerToolCall = async (call, write) => {
|
|
1591
|
+
const reply = (fields) => write({ op: "toolResult", callId: call.id, ...fields });
|
|
1592
|
+
const fn = functions.get(call.name);
|
|
1593
|
+
if (fn === void 0) {
|
|
1594
|
+
reply({ output: `There is no tool named "${call.name}".`, isError: true });
|
|
1595
|
+
return;
|
|
1596
|
+
}
|
|
1597
|
+
if (calls.length > maxToolCalls) {
|
|
1598
|
+
call.output = "Tool call limit reached. Answer with the information you already have.";
|
|
1599
|
+
reply({ output: call.output });
|
|
1600
|
+
return;
|
|
1601
|
+
}
|
|
1602
|
+
const replayed = _optionalChain([hooks, 'access', _98 => _98.replay, 'optionalAccess', _99 => _99.take, 'call', _100 => _100(call.name, call.arguments)]);
|
|
1603
|
+
if (replayed !== void 0) {
|
|
1604
|
+
call.output = replayed;
|
|
1605
|
+
reply({ output: replayed });
|
|
1606
|
+
return;
|
|
1607
|
+
}
|
|
1608
|
+
if (fn.execute === void 0) {
|
|
1609
|
+
reply({ stop: true });
|
|
1610
|
+
return;
|
|
1611
|
+
}
|
|
1612
|
+
let args = restoreNulls(call.arguments, fn.json);
|
|
1613
|
+
if (_optionalChain([fn, 'access', _101 => _101.resolved, 'optionalAccess', _102 => _102.validate]) !== void 0) {
|
|
1614
|
+
const checked = await fn.resolved.validate(args);
|
|
1615
|
+
if (checked.issues !== void 0) {
|
|
1616
|
+
call.output = `Invalid arguments: ${describeIssues(checked.issues)}`;
|
|
1617
|
+
reply({ output: call.output });
|
|
1618
|
+
return;
|
|
1619
|
+
}
|
|
1620
|
+
args = checked.value;
|
|
1621
|
+
}
|
|
1622
|
+
server.pause();
|
|
1623
|
+
try {
|
|
1624
|
+
const result = await fn.execute(args, { toolCallId: call.id, signal: hooks.signal });
|
|
1625
|
+
call.output = toolOutputText(result);
|
|
1626
|
+
reply({ output: call.output });
|
|
1627
|
+
} catch (error) {
|
|
1628
|
+
toolFailure = { name: call.name, error };
|
|
1629
|
+
reply({ output: error instanceof Error ? error.message : String(error), isError: true });
|
|
1630
|
+
} finally {
|
|
1631
|
+
server.resume();
|
|
1632
|
+
}
|
|
1633
|
+
};
|
|
1634
|
+
let delivered = false;
|
|
1635
|
+
const attempt = (id = _crypto.randomUUID.call(void 0, )) => server.request(JSON.stringify({ ...envelope, id }), {
|
|
1636
|
+
id,
|
|
1637
|
+
signal: hooks.signal,
|
|
1638
|
+
onEvent: (event, write) => {
|
|
1639
|
+
delivered = true;
|
|
1640
|
+
if (typeof event.delta === "string") {
|
|
1641
|
+
if (event.delta !== "") _optionalChain([hooks, 'access', _103 => _103.onDelta, 'optionalCall', _104 => _104(event.delta)]);
|
|
1642
|
+
} else if (typeof event.partial === "string") {
|
|
1643
|
+
_optionalChain([hooks, 'access', _105 => _105.onPartial, 'optionalCall', _106 => _106(event.partial)]);
|
|
1644
|
+
} else if (event.toolCall !== null && typeof event.toolCall === "object") {
|
|
1645
|
+
const toolCall = event.toolCall;
|
|
1646
|
+
const call = {
|
|
1647
|
+
id: String(_nullishCoalesce(toolCall.id, () => ( _crypto.randomUUID.call(void 0, )))),
|
|
1648
|
+
name: String(_nullishCoalesce(toolCall.name, () => ( ""))),
|
|
1649
|
+
arguments: _nullishCoalesce(toolCall.arguments, () => ( {}))
|
|
1650
|
+
};
|
|
1651
|
+
calls.push(call);
|
|
1652
|
+
try {
|
|
1653
|
+
_optionalChain([hooks, 'access', _107 => _107.onToolCall, 'optionalCall', _108 => _108(call)]);
|
|
1654
|
+
} catch (e20) {
|
|
1655
|
+
}
|
|
1656
|
+
void answerToolCall(call, write).catch((error) => {
|
|
1657
|
+
toolFailure ??= { name: call.name, error };
|
|
1658
|
+
write({ op: "toolResult", callId: call.id, output: String(error), isError: true });
|
|
1659
|
+
});
|
|
1660
|
+
}
|
|
1661
|
+
}
|
|
1662
|
+
});
|
|
1663
|
+
let raw = await attempt();
|
|
1664
|
+
let response = parse(raw);
|
|
1665
|
+
for (const delay of BUSY_RETRY_DELAYS_MS) {
|
|
1666
|
+
if (response.ok === true || response.kind !== "busy" || delivered) break;
|
|
1667
|
+
await sleep(delay, hooks.signal);
|
|
1668
|
+
raw = await attempt();
|
|
1669
|
+
response = parse(raw);
|
|
1670
|
+
}
|
|
1671
|
+
if (response.ok !== true) raiseFor(response, raw, toolFailure);
|
|
1672
|
+
if (typeof response.content !== "string") {
|
|
1673
|
+
throw new AppleLLMError("Apple helper returned no content.", "device");
|
|
1674
|
+
}
|
|
1675
|
+
const builtIn = (_nullishCoalesce(response.toolCalls, () => ( []))).filter((c) => typeof c.name === "string" && !functions.has(c.name)).map((c) => ({
|
|
1676
|
+
id: String(_nullishCoalesce(c.id, () => ( ""))),
|
|
1677
|
+
name: String(c.name),
|
|
1678
|
+
arguments: _nullishCoalesce(c.arguments, () => ( {})),
|
|
1679
|
+
...typeof c.output === "string" ? { output: c.output } : {}
|
|
1680
|
+
}));
|
|
1681
|
+
return {
|
|
1682
|
+
content: response.content,
|
|
1683
|
+
finishReason: finishFrom(response.finishReason),
|
|
1684
|
+
usage: usageFrom(response.usage),
|
|
1685
|
+
toolCalls: [...calls, ...builtIn],
|
|
1686
|
+
trimmedTurns: typeof response.trimmedTurns === "number" ? response.trimmedTurns : 0
|
|
1687
|
+
};
|
|
1688
|
+
}
|
|
1689
|
+
/** One request. Returns the helper's `content` string, unparsed. */
|
|
1690
|
+
async complete(request, hooks = {}) {
|
|
1691
|
+
return (await this.run(request, "generate", hooks)).content;
|
|
1692
|
+
}
|
|
1693
|
+
/**
|
|
1694
|
+
* Streaming text. Deltas arrive via onDelta as the model generates; the
|
|
1695
|
+
* promise resolves with the full text.
|
|
1696
|
+
*/
|
|
1697
|
+
async stream(prompt, options = {}) {
|
|
1698
|
+
const { onDelta, signal, ...rest } = options;
|
|
1699
|
+
return (await this.run({ ...rest, prompt, schema: null }, "stream", { onDelta, signal })).content;
|
|
1700
|
+
}
|
|
1701
|
+
/**
|
|
1702
|
+
* Conversation history for a named session: the mirrored turns the helper
|
|
1703
|
+
* persisted, oldest first. Survives helper restarts, and the next call
|
|
1704
|
+
* rebuilds the native transcript from it.
|
|
1705
|
+
*/
|
|
1706
|
+
async history(sessionId) {
|
|
1707
|
+
const response = await this.exchange({ op: "history", sessionId, instructions: "" });
|
|
1708
|
+
return {
|
|
1709
|
+
instructions: typeof response.instructions === "string" ? response.instructions : "",
|
|
1710
|
+
history: Array.isArray(response.history) ? response.history : []
|
|
1711
|
+
};
|
|
1712
|
+
}
|
|
1713
|
+
/** Drop a named session (and its persisted history), or all of them. */
|
|
1714
|
+
async resetSession(sessionId) {
|
|
1715
|
+
await this.exchange({ op: "reset", sessionId: _nullishCoalesce(sessionId, () => ( "")), instructions: "" });
|
|
1716
|
+
}
|
|
1717
|
+
/**
|
|
1718
|
+
* How many tokens a request costs, before sending it.
|
|
1719
|
+
*
|
|
1720
|
+
* The point is to turn a ContextLengthError into an arithmetic check: compare
|
|
1721
|
+
* against `contextSize` and trim, rather than discovering the ceiling by
|
|
1722
|
+
* hitting it. Counts everything that shares the window: instructions,
|
|
1723
|
+
* history, tools, schema, images and inlined documents.
|
|
1724
|
+
*/
|
|
1725
|
+
async countTokens(prompt, options = {}) {
|
|
1726
|
+
const envelope = await this.envelopeFor(
|
|
1727
|
+
{
|
|
1728
|
+
prompt,
|
|
1729
|
+
system: options.system,
|
|
1730
|
+
images: options.images,
|
|
1731
|
+
tools: options.tools,
|
|
1732
|
+
documents: options.documents,
|
|
1733
|
+
history: options.history,
|
|
1734
|
+
schema: options.schema === void 0 ? null : toAppleSchema(options.schema),
|
|
1735
|
+
functions: options.functions
|
|
1736
|
+
},
|
|
1737
|
+
"generate"
|
|
1738
|
+
);
|
|
1739
|
+
const response = await this.exchange({ ...envelope, op: "countTokens" }, options.signal);
|
|
1740
|
+
if (typeof response.tokens !== "number" || typeof response.contextSize !== "number") {
|
|
1741
|
+
throw new AppleLLMError("Apple helper returned an unreadable token count.", "device");
|
|
1742
|
+
}
|
|
1743
|
+
return { tokens: response.tokens, contextSize: response.contextSize };
|
|
1744
|
+
}
|
|
1745
|
+
/**
|
|
1746
|
+
* Load the model assets now so the first real call does not pay for it.
|
|
1747
|
+
*
|
|
1748
|
+
* Cheap and idempotent, but do not expect much on a warm machine: with the
|
|
1749
|
+
* assets already resident this measured 0.31s against 0.36s for an
|
|
1750
|
+
* unprewarmed first call — inside the noise. The win is on a genuinely cold
|
|
1751
|
+
* system, where the very first call to the framework here took 7.8s. Worth
|
|
1752
|
+
* calling at startup when you know a request is coming; not worth building
|
|
1753
|
+
* around.
|
|
1754
|
+
*/
|
|
1755
|
+
async prewarm(system) {
|
|
1756
|
+
await this.exchange({ op: "prewarm", instructions: _nullishCoalesce(system, () => ( "")) });
|
|
1757
|
+
}
|
|
1758
|
+
async text(prompt, options = {}) {
|
|
1759
|
+
return this.complete({ ...options, prompt, schema: null });
|
|
1760
|
+
}
|
|
1761
|
+
async json(prompt, options) {
|
|
1762
|
+
const content = await this.complete({ ...options, prompt, schema: toAppleSchema(options.schema) });
|
|
1763
|
+
try {
|
|
1764
|
+
return restoreNulls(JSON.parse(content), options.schema);
|
|
1765
|
+
} catch (e21) {
|
|
1766
|
+
throw new AppleLLMError(
|
|
1767
|
+
`Apple on-device model returned invalid JSON: ${content.slice(0, 200)}`,
|
|
1768
|
+
"device"
|
|
1769
|
+
);
|
|
1770
|
+
}
|
|
1771
|
+
}
|
|
1772
|
+
/** Shut the helper process down. Safe to call more than once. */
|
|
1773
|
+
close() {
|
|
1774
|
+
_optionalChain([this, 'access', _109 => _109.server, 'optionalAccess', _110 => _110.stop, 'call', _111 => _111()]);
|
|
1775
|
+
this.server = void 0;
|
|
1776
|
+
}
|
|
1777
|
+
};
|
|
1778
|
+
function sleep(ms, signal) {
|
|
1779
|
+
return new Promise((resolve, reject) => {
|
|
1780
|
+
if (_optionalChain([signal, 'optionalAccess', _112 => _112.aborted]) === true) {
|
|
1781
|
+
reject(abortErrorFrom(signal));
|
|
1782
|
+
return;
|
|
1783
|
+
}
|
|
1784
|
+
const timer = setTimeout(() => {
|
|
1785
|
+
_optionalChain([signal, 'optionalAccess', _113 => _113.removeEventListener, 'call', _114 => _114("abort", onAbort)]);
|
|
1786
|
+
resolve();
|
|
1787
|
+
}, ms);
|
|
1788
|
+
const onAbort = () => {
|
|
1789
|
+
clearTimeout(timer);
|
|
1790
|
+
reject(abortErrorFrom(signal));
|
|
1791
|
+
};
|
|
1792
|
+
_optionalChain([signal, 'optionalAccess', _115 => _115.addEventListener, 'call', _116 => _116("abort", onAbort, { once: true })]);
|
|
1793
|
+
});
|
|
1794
|
+
}
|
|
1795
|
+
function parse(raw) {
|
|
1796
|
+
try {
|
|
1797
|
+
return JSON.parse(raw);
|
|
1798
|
+
} catch (e22) {
|
|
1799
|
+
throw new AppleLLMError(`Apple helper returned an unreadable envelope: ${raw.slice(0, 200)}`, "device");
|
|
1800
|
+
}
|
|
1801
|
+
}
|
|
1802
|
+
async function withDocuments(prompt, documents) {
|
|
1803
|
+
if (documents === void 0 || documents.length === 0) return prompt;
|
|
1804
|
+
const { readFile: readFile3, stat: stat3 } = await Promise.resolve().then(() => _interopRequireWildcard(require("fs/promises")));
|
|
1805
|
+
const parts = [prompt];
|
|
1806
|
+
for (const doc of documents) {
|
|
1807
|
+
let size = 0;
|
|
1808
|
+
try {
|
|
1809
|
+
size = (await stat3(doc)).size;
|
|
1810
|
+
} catch (e23) {
|
|
1811
|
+
throw new AppleLLMError(`Document not found: ${doc}`);
|
|
1812
|
+
}
|
|
1813
|
+
if (size > 512e3) {
|
|
1814
|
+
throw new AppleLLMError(`Document too large to inline (>${512e3} bytes): ${doc}`);
|
|
1815
|
+
}
|
|
1816
|
+
let text;
|
|
1817
|
+
try {
|
|
1818
|
+
text = await readFile3(doc, "utf8");
|
|
1819
|
+
} catch (e24) {
|
|
1820
|
+
throw new AppleLLMError(`Document is not readable text: ${doc}`);
|
|
1821
|
+
}
|
|
1822
|
+
if (text.includes("\uFFFD")) {
|
|
1823
|
+
throw new AppleLLMError(`Document is not readable text: ${doc}`);
|
|
1824
|
+
}
|
|
1825
|
+
parts.push(`
|
|
1826
|
+
|
|
1827
|
+
--- Document: ${doc} ---
|
|
1828
|
+
${text}`);
|
|
1829
|
+
}
|
|
1830
|
+
return parts.join("");
|
|
1831
|
+
}
|
|
1832
|
+
function parseImageFlag(value) {
|
|
1833
|
+
const sep = value.lastIndexOf("::");
|
|
1834
|
+
if (sep > 0) {
|
|
1835
|
+
const path3 = value.slice(0, sep);
|
|
1836
|
+
const label = value.slice(sep + 2).trim();
|
|
1837
|
+
if (path3 !== "" && label !== "") return { path: path3, label };
|
|
1838
|
+
}
|
|
1839
|
+
return value;
|
|
1840
|
+
}
|
|
1841
|
+
|
|
1842
|
+
// src/stream.ts
|
|
1843
|
+
var Channel = (_class16 = class {constructor() { _class16.prototype.__init20.call(this);_class16.prototype.__init21.call(this); }
|
|
1844
|
+
__init20() {this.items = []}
|
|
1845
|
+
|
|
1846
|
+
__init21() {this.closed = false}
|
|
1847
|
+
push(value) {
|
|
1848
|
+
this.offer({ value });
|
|
1849
|
+
}
|
|
1850
|
+
end() {
|
|
1851
|
+
this.offer({ done: true });
|
|
1852
|
+
}
|
|
1853
|
+
fail(error) {
|
|
1854
|
+
this.offer({ error });
|
|
1855
|
+
}
|
|
1856
|
+
offer(item) {
|
|
1857
|
+
if (this.closed) return;
|
|
1858
|
+
if (!("value" in item)) this.closed = true;
|
|
1859
|
+
const waiter = this.waiter;
|
|
1860
|
+
if (waiter !== void 0) {
|
|
1861
|
+
this.waiter = void 0;
|
|
1862
|
+
waiter(item);
|
|
1863
|
+
} else {
|
|
1864
|
+
this.items.push(item);
|
|
1865
|
+
}
|
|
1866
|
+
}
|
|
1867
|
+
next() {
|
|
1868
|
+
const item = this.items.shift();
|
|
1869
|
+
if (item !== void 0) return Promise.resolve(item);
|
|
1870
|
+
return new Promise((resolve) => {
|
|
1871
|
+
this.waiter = resolve;
|
|
1872
|
+
});
|
|
1873
|
+
}
|
|
1874
|
+
}, _class16);
|
|
1875
|
+
var ResultStream = (_class17 = class {
|
|
1876
|
+
/** Everything about the finished generation: text, usage, tool calls, finish reason. */
|
|
1877
|
+
|
|
1878
|
+
__init22() {this.channel = new Channel()}
|
|
1879
|
+
__init23() {this.controller = new AbortController()}
|
|
1880
|
+
|
|
1881
|
+
__init24() {this.iterated = false}
|
|
1882
|
+
__init25() {this.finished = false}
|
|
1883
|
+
constructor(run, pick) {;_class17.prototype.__init22.call(this);_class17.prototype.__init23.call(this);_class17.prototype.__init24.call(this);_class17.prototype.__init25.call(this);
|
|
1884
|
+
this.result = run((chunk) => this.channel.push(chunk), this.controller.signal).then(
|
|
1885
|
+
(result) => {
|
|
1886
|
+
this.finished = true;
|
|
1887
|
+
this.channel.end();
|
|
1888
|
+
return result;
|
|
1889
|
+
},
|
|
1890
|
+
(error) => {
|
|
1891
|
+
this.finished = true;
|
|
1892
|
+
this.channel.fail(error);
|
|
1893
|
+
throw error;
|
|
1894
|
+
}
|
|
1895
|
+
);
|
|
1896
|
+
this.final = this.result.then(pick);
|
|
1897
|
+
this.result.catch(() => void 0);
|
|
1898
|
+
this.final.catch(() => void 0);
|
|
1899
|
+
}
|
|
1900
|
+
/** Stop generating. The promise and the iterator both reject with an AbortError. */
|
|
1901
|
+
abort(reason) {
|
|
1902
|
+
this.controller.abort(reason);
|
|
1903
|
+
}
|
|
1904
|
+
then(onfulfilled, onrejected) {
|
|
1905
|
+
return this.final.then(onfulfilled, onrejected);
|
|
1906
|
+
}
|
|
1907
|
+
catch(onrejected) {
|
|
1908
|
+
return this.final.catch(onrejected);
|
|
1909
|
+
}
|
|
1910
|
+
finally(onfinally) {
|
|
1911
|
+
return this.final.finally(onfinally);
|
|
1912
|
+
}
|
|
1913
|
+
[Symbol.asyncIterator]() {
|
|
1914
|
+
if (this.iterated) throw new Error("A stream can only be iterated once.");
|
|
1915
|
+
this.iterated = true;
|
|
1916
|
+
return {
|
|
1917
|
+
next: async () => {
|
|
1918
|
+
const item = await this.channel.next();
|
|
1919
|
+
if ("value" in item) return { value: item.value, done: false };
|
|
1920
|
+
if ("error" in item) throw item.error;
|
|
1921
|
+
return { value: void 0, done: true };
|
|
1922
|
+
},
|
|
1923
|
+
return: async () => {
|
|
1924
|
+
if (!this.finished) this.abort();
|
|
1925
|
+
return { value: void 0, done: true };
|
|
1926
|
+
}
|
|
1927
|
+
};
|
|
1928
|
+
}
|
|
1929
|
+
}, _class17);
|
|
1930
|
+
|
|
1931
|
+
// src/client.ts
|
|
1932
|
+
async function probe(onProgress) {
|
|
1933
|
+
const [device, cloud] = await Promise.all([probeDevice(onProgress), probeCloud()]);
|
|
1934
|
+
if (device.cloud !== void 0) {
|
|
1935
|
+
const { capabilities, contextSize, ...quota } = device.cloud;
|
|
1936
|
+
cloud.quota = quota;
|
|
1937
|
+
if (capabilities !== void 0) cloud.capabilities = capabilities;
|
|
1938
|
+
if (contextSize !== void 0 && cloud.contextSize !== void 0) cloud.contextSize = contextSize;
|
|
1939
|
+
}
|
|
1940
|
+
return { device, cloud };
|
|
1941
|
+
}
|
|
1942
|
+
function anySignal(signals) {
|
|
1943
|
+
const list = signals.filter((s) => s !== void 0);
|
|
1944
|
+
if (list.length <= 1) return list[0];
|
|
1945
|
+
const any = AbortSignal.any;
|
|
1946
|
+
if (typeof any === "function") return any(list);
|
|
1947
|
+
const controller = new AbortController();
|
|
1948
|
+
for (const signal of list) {
|
|
1949
|
+
if (signal.aborted) {
|
|
1950
|
+
controller.abort(signal.reason);
|
|
1951
|
+
break;
|
|
1952
|
+
}
|
|
1953
|
+
signal.addEventListener("abort", () => controller.abort(signal.reason), { once: true });
|
|
1954
|
+
}
|
|
1955
|
+
return controller.signal;
|
|
1956
|
+
}
|
|
1957
|
+
var AppleLLM = class {
|
|
1958
|
+
|
|
1959
|
+
|
|
1960
|
+
/** Which tier `auto` settled on, once resolved. */
|
|
1961
|
+
|
|
1962
|
+
|
|
1963
|
+
|
|
1964
|
+
constructor(options = {}) {
|
|
1965
|
+
this.options = options;
|
|
1966
|
+
}
|
|
1967
|
+
get tier() {
|
|
1968
|
+
return _nullishCoalesce(this.options.tier, () => ( "auto"));
|
|
1969
|
+
}
|
|
1970
|
+
/** Human-readable name of the tier in use, for logs. */
|
|
1971
|
+
get label() {
|
|
1972
|
+
if (this.resolved === "cloud") return _nullishCoalesce(_optionalChain([this, 'access', _117 => _117.cloud, 'optionalAccess', _118 => _118.label]), () => ( "apple private cloud compute"));
|
|
1973
|
+
if (this.resolved === "device") return _nullishCoalesce(_optionalChain([this, 'access', _119 => _119.device, 'optionalAccess', _120 => _120.label]), () => ( "apple on-device"));
|
|
1974
|
+
return `apple (${this.tier})`;
|
|
1975
|
+
}
|
|
1976
|
+
/** Context window of the resolved tier, in tokens. */
|
|
1977
|
+
get contextSize() {
|
|
1978
|
+
if (this.resolved === "cloud") return _nullishCoalesce(_optionalChain([this, 'access', _121 => _121.cloud, 'optionalAccess', _122 => _122.contextSize]), () => ( CLOUD_CONTEXT_TOKENS));
|
|
1979
|
+
return _optionalChain([this, 'access', _123 => _123.device, 'optionalAccess', _124 => _124.contextSize]);
|
|
1980
|
+
}
|
|
1981
|
+
/**
|
|
1982
|
+
* Resolve the tier and do any one-time setup. Called automatically, but
|
|
1983
|
+
* exposed so a caller can pay the compile cost up front with a progress bar.
|
|
1984
|
+
* Concurrent first calls share one attempt.
|
|
1985
|
+
*/
|
|
1986
|
+
async ensureReady(onProgress) {
|
|
1987
|
+
if (this.resolved !== void 0) return;
|
|
1988
|
+
this.readying ??= this.resolve(_nullishCoalesce(onProgress, () => ( this.options.onProgress))).finally(() => {
|
|
1989
|
+
this.readying = void 0;
|
|
1990
|
+
});
|
|
1991
|
+
return this.readying;
|
|
1992
|
+
}
|
|
1993
|
+
newDevice() {
|
|
1994
|
+
return new DeviceClient({ useCase: this.options.useCase, guardrails: this.options.guardrails });
|
|
1995
|
+
}
|
|
1996
|
+
async readyCloud(progress) {
|
|
1997
|
+
this.cloud ??= new CloudClient();
|
|
1998
|
+
await this.cloud.ensureReady(progress);
|
|
1999
|
+
this.cloud.setQuota(await _asyncOptionalChain([(await probeDevice().catch(() => void 0)), 'optionalAccess', async _125 => _125.cloud]));
|
|
2000
|
+
this.resolved = "cloud";
|
|
2001
|
+
}
|
|
2002
|
+
async resolve(progress) {
|
|
2003
|
+
if (this.tier === "device") {
|
|
2004
|
+
this.device ??= this.newDevice();
|
|
2005
|
+
await this.device.ensureReady(progress);
|
|
2006
|
+
this.resolved = "device";
|
|
2007
|
+
return;
|
|
2008
|
+
}
|
|
2009
|
+
if (this.tier === "cloud") {
|
|
2010
|
+
await this.readyCloud(progress);
|
|
2011
|
+
return;
|
|
2012
|
+
}
|
|
2013
|
+
this.device ??= this.newDevice();
|
|
2014
|
+
try {
|
|
2015
|
+
await this.device.ensureReady(progress);
|
|
2016
|
+
this.resolved = "device";
|
|
2017
|
+
} catch (deviceError) {
|
|
2018
|
+
try {
|
|
2019
|
+
await this.readyCloud(progress);
|
|
2020
|
+
} catch (cloudError) {
|
|
2021
|
+
const deviceReason = deviceError instanceof Error ? deviceError.message : String(deviceError);
|
|
2022
|
+
const cloudReason = cloudError instanceof Error ? cloudError.message : String(cloudError);
|
|
2023
|
+
throw new ModelUnavailableError(
|
|
2024
|
+
`No Apple model is available.
|
|
2025
|
+
|
|
2026
|
+
On-device: ${deviceReason}
|
|
2027
|
+
|
|
2028
|
+
Cloud: ${cloudReason}`,
|
|
2029
|
+
deviceError instanceof ModelUnavailableError ? deviceError.reason : "unknown"
|
|
2030
|
+
);
|
|
2031
|
+
}
|
|
2032
|
+
}
|
|
2033
|
+
}
|
|
2034
|
+
/** Free text. */
|
|
2035
|
+
async text(input, options = {}) {
|
|
2036
|
+
return (await this.execute(input, options, {})).text;
|
|
2037
|
+
}
|
|
2038
|
+
async generate(input, options = {}) {
|
|
2039
|
+
const { schema, ...rest } = options;
|
|
2040
|
+
return this.execute(input, rest, { schema: schema === void 0 ? void 0 : resolveSchema(schema) });
|
|
2041
|
+
}
|
|
2042
|
+
/**
|
|
2043
|
+
* Ask for JSON. On device the *shape* is guaranteed by constrained decoding;
|
|
2044
|
+
* a Standard Schema's own validation (refinements, transforms) then runs on
|
|
2045
|
+
* top, and the result is typed by it. On cloud the shape is requested in the
|
|
2046
|
+
* prompt and recovered from the reply. Either way, a reply that fails
|
|
2047
|
+
* validation is retried once with the problems pointed out, then raised as a
|
|
2048
|
+
* SchemaValidationError.
|
|
2049
|
+
*/
|
|
2050
|
+
async json(input, options) {
|
|
2051
|
+
const { schema, ...rest } = options;
|
|
2052
|
+
const result = await this.execute(input, rest, { schema: resolveSchema(schema) });
|
|
2053
|
+
return result.object;
|
|
2054
|
+
}
|
|
2055
|
+
/**
|
|
2056
|
+
* Streaming text. Iterate it for deltas, or await it for the whole reply:
|
|
2057
|
+
*
|
|
2058
|
+
* for await (const delta of llm.stream(prompt)) process.stdout.write(delta);
|
|
2059
|
+
* const text = await llm.stream(prompt, { onDelta });
|
|
2060
|
+
*
|
|
2061
|
+
* On the cloud tier the reply arrives as one chunk: Shortcuts is not
|
|
2062
|
+
* incremental.
|
|
2063
|
+
*/
|
|
2064
|
+
stream(input, options = {}) {
|
|
2065
|
+
const { onDelta, ...rest } = options;
|
|
2066
|
+
return new ResultStream(
|
|
2067
|
+
(emit, signal) => this.execute(input, rest, {
|
|
2068
|
+
streaming: true,
|
|
2069
|
+
signal,
|
|
2070
|
+
onDelta: (delta) => {
|
|
2071
|
+
_optionalChain([onDelta, 'optionalCall', _126 => _126(delta)]);
|
|
2072
|
+
emit(delta);
|
|
2073
|
+
}
|
|
2074
|
+
}),
|
|
2075
|
+
(result) => result.text
|
|
2076
|
+
);
|
|
2077
|
+
}
|
|
2078
|
+
/**
|
|
2079
|
+
* Streaming JSON: partial objects as the model fills them in — render a form
|
|
2080
|
+
* or a card while it is still being written — and the final object, parsed
|
|
2081
|
+
* and validated, as the awaited value.
|
|
2082
|
+
*
|
|
2083
|
+
* for await (const partial of llm.streamJson(prompt, { schema })) render(partial);
|
|
2084
|
+
*/
|
|
2085
|
+
streamJson(input, options) {
|
|
2086
|
+
const { schema, ...rest } = options;
|
|
2087
|
+
const resolved = resolveSchema(schema);
|
|
2088
|
+
return new ResultStream(
|
|
2089
|
+
(emit, signal) => this.execute(input, rest, {
|
|
2090
|
+
schema: resolved,
|
|
2091
|
+
streaming: true,
|
|
2092
|
+
signal,
|
|
2093
|
+
onPartial: (json) => {
|
|
2094
|
+
try {
|
|
2095
|
+
emit(JSON.parse(json));
|
|
2096
|
+
} catch (e25) {
|
|
2097
|
+
}
|
|
2098
|
+
}
|
|
2099
|
+
}),
|
|
2100
|
+
(result) => result.object
|
|
2101
|
+
);
|
|
2102
|
+
}
|
|
2103
|
+
/** The one path every generation takes. */
|
|
2104
|
+
async execute(input, options, mode) {
|
|
2105
|
+
const started = Date.now();
|
|
2106
|
+
await this.ensureReady();
|
|
2107
|
+
let prompt;
|
|
2108
|
+
let history = [];
|
|
2109
|
+
let replay;
|
|
2110
|
+
let system = _nullishCoalesce(options.system, () => ( this.options.system));
|
|
2111
|
+
let trimHistory;
|
|
2112
|
+
let images = options.images;
|
|
2113
|
+
if (typeof input === "string") {
|
|
2114
|
+
prompt = input;
|
|
2115
|
+
} else {
|
|
2116
|
+
if (options.sessionId !== void 0) {
|
|
2117
|
+
throw new AppleLLMError(
|
|
2118
|
+
"Pass either a message list or a sessionId, not both: a named session keeps its own history."
|
|
2119
|
+
);
|
|
2120
|
+
}
|
|
2121
|
+
const split = splitMessages(input);
|
|
2122
|
+
prompt = split.prompt;
|
|
2123
|
+
history = split.history;
|
|
2124
|
+
replay = split.replay.length > 0 ? new ReplayBook(split.replay) : void 0;
|
|
2125
|
+
if (split.system !== void 0) system = system === void 0 ? split.system : `${system}
|
|
2126
|
+
|
|
2127
|
+
${split.system}`;
|
|
2128
|
+
trimHistory = _nullishCoalesce(options.trimHistory, () => ( true));
|
|
2129
|
+
if (split.images.length > 0) images = [..._nullishCoalesce(images, () => ( [])), ...split.images];
|
|
2130
|
+
}
|
|
2131
|
+
const tools = normalizeTools(options.tools);
|
|
2132
|
+
const timeoutMs = _nullishCoalesce(options.timeoutMs, () => ( this.options.timeoutMs));
|
|
2133
|
+
const signal = anySignal([
|
|
2134
|
+
options.signal,
|
|
2135
|
+
mode.signal,
|
|
2136
|
+
timeoutMs === void 0 ? void 0 : AbortSignal.timeout(timeoutMs)
|
|
2137
|
+
]);
|
|
2138
|
+
if (_optionalChain([signal, 'optionalAccess', _127 => _127.aborted]) === true) throw abortErrorFrom(signal);
|
|
2139
|
+
const attempt = async (feedback) => {
|
|
2140
|
+
const promptText = feedback === void 0 ? prompt : `${prompt}
|
|
2141
|
+
|
|
2142
|
+
${feedback}`;
|
|
2143
|
+
let text;
|
|
2144
|
+
let finishReason = "stop";
|
|
2145
|
+
let usage;
|
|
2146
|
+
let toolCalls = [];
|
|
2147
|
+
let trimmedTurns = 0;
|
|
2148
|
+
if (this.resolved === "cloud") {
|
|
2149
|
+
this.assertCloudCompatible({ ...options, images }, tools.builtIn.length + tools.functions.length > 0);
|
|
2150
|
+
let cloudPrompt = await withDocuments(promptText, options.documents);
|
|
2151
|
+
if (history.length > 0) {
|
|
2152
|
+
cloudPrompt = `Conversation so far:
|
|
2153
|
+
${renderHistory(history)}
|
|
2154
|
+
|
|
2155
|
+
New message from the user:
|
|
2156
|
+
${cloudPrompt}`;
|
|
2157
|
+
}
|
|
2158
|
+
let cloudSystem = system;
|
|
2159
|
+
if (mode.schema !== void 0) {
|
|
2160
|
+
const instruction = "Reply with a single JSON value matching this JSON Schema. Output only the JSON, with no commentary and no code fence.\n\n" + JSON.stringify(mode.schema.json, null, 2);
|
|
2161
|
+
cloudSystem = cloudSystem ? `${cloudSystem}
|
|
2162
|
+
|
|
2163
|
+
${instruction}` : instruction;
|
|
2164
|
+
}
|
|
2165
|
+
text = await this.cloud.text({
|
|
2166
|
+
system: cloudSystem,
|
|
2167
|
+
prompt: cloudPrompt,
|
|
2168
|
+
webSearch: options.webSearch,
|
|
2169
|
+
signal,
|
|
2170
|
+
images: images !== void 0 && images.length > 0 ? images : void 0
|
|
2171
|
+
});
|
|
2172
|
+
if (mode.schema === void 0) _optionalChain([mode, 'access', _128 => _128.onDelta, 'optionalCall', _129 => _129(text)]);
|
|
2173
|
+
} else {
|
|
2174
|
+
const outcome = await this.device.run(
|
|
2175
|
+
{
|
|
2176
|
+
system,
|
|
2177
|
+
prompt: promptText,
|
|
2178
|
+
schema: mode.schema === void 0 ? null : toAppleSchema(mode.schema.json),
|
|
2179
|
+
temperature: _nullishCoalesce(options.temperature, () => ( this.options.temperature)),
|
|
2180
|
+
maxTokens: _nullishCoalesce(options.maxTokens, () => ( this.options.maxTokens)),
|
|
2181
|
+
images,
|
|
2182
|
+
documents: options.documents,
|
|
2183
|
+
sessionId: options.sessionId,
|
|
2184
|
+
history: history.length > 0 ? history : void 0,
|
|
2185
|
+
trimHistory,
|
|
2186
|
+
tools: tools.builtIn.length > 0 ? tools.builtIn : void 0,
|
|
2187
|
+
functions: tools.functions.length > 0 ? tools.functions : void 0,
|
|
2188
|
+
useCase: _nullishCoalesce(options.useCase, () => ( this.options.useCase)),
|
|
2189
|
+
guardrails: _nullishCoalesce(options.guardrails, () => ( this.options.guardrails)),
|
|
2190
|
+
sampling: _nullishCoalesce(options.sampling, () => ( this.options.sampling))
|
|
2191
|
+
},
|
|
2192
|
+
mode.streaming === true ? "stream" : "generate",
|
|
2193
|
+
{
|
|
2194
|
+
signal,
|
|
2195
|
+
onDelta: mode.onDelta,
|
|
2196
|
+
onPartial: mode.onPartial,
|
|
2197
|
+
replay,
|
|
2198
|
+
maxToolCalls: options.maxToolCalls
|
|
2199
|
+
}
|
|
2200
|
+
);
|
|
2201
|
+
text = outcome.content;
|
|
2202
|
+
finishReason = outcome.finishReason;
|
|
2203
|
+
usage = outcome.usage;
|
|
2204
|
+
toolCalls = outcome.toolCalls;
|
|
2205
|
+
trimmedTurns = outcome.trimmedTurns;
|
|
2206
|
+
}
|
|
2207
|
+
let object;
|
|
2208
|
+
if (mode.schema !== void 0 && finishReason !== "tool-calls") {
|
|
2209
|
+
object = await this.parseObject(text, mode.schema);
|
|
2210
|
+
if (this.resolved === "cloud") _optionalChain([mode, 'access', _130 => _130.onPartial, 'optionalCall', _131 => _131(JSON.stringify(object))]);
|
|
2211
|
+
}
|
|
2212
|
+
return {
|
|
2213
|
+
text,
|
|
2214
|
+
object,
|
|
2215
|
+
finishReason,
|
|
2216
|
+
usage,
|
|
2217
|
+
toolCalls,
|
|
2218
|
+
trimmedTurns,
|
|
2219
|
+
tier: this.resolved === "cloud" ? "cloud" : "device",
|
|
2220
|
+
durationMs: Date.now() - started,
|
|
2221
|
+
message: {
|
|
2222
|
+
role: "assistant",
|
|
2223
|
+
content: finishReason === "tool-calls" ? null : text,
|
|
2224
|
+
...toolCalls.length > 0 ? { toolCalls: toolCalls.map(({ id, name, arguments: args }) => ({ id, name, arguments: args })) } : {}
|
|
2225
|
+
}
|
|
2226
|
+
};
|
|
2227
|
+
};
|
|
2228
|
+
try {
|
|
2229
|
+
return await attempt();
|
|
2230
|
+
} catch (error) {
|
|
2231
|
+
if (!(error instanceof SchemaValidationError) || mode.streaming === true) throw error;
|
|
2232
|
+
return attempt(
|
|
2233
|
+
`Your previous answer was rejected: ${error.message.replace(/^The model's reply did not satisfy the schema: /, "")}. Answer again, fixing those problems.`
|
|
2234
|
+
);
|
|
2235
|
+
}
|
|
2236
|
+
}
|
|
2237
|
+
/** Parse, restore nulls, validate. Throws SchemaValidationError for anything the schema rejects. */
|
|
2238
|
+
async parseObject(text, schema) {
|
|
2239
|
+
const tier = this.resolved === "cloud" ? "cloud" : "device";
|
|
2240
|
+
let value;
|
|
2241
|
+
try {
|
|
2242
|
+
value = tier === "cloud" ? parseLlmJson(text) : JSON.parse(text);
|
|
2243
|
+
} catch (e26) {
|
|
2244
|
+
throw new SchemaValidationError("The model did not reply with valid JSON.", [{ message: "invalid JSON" }], text, tier);
|
|
2245
|
+
}
|
|
2246
|
+
return validateWith(schema, restoreNulls(value, schema.json), text, tier);
|
|
2247
|
+
}
|
|
2248
|
+
assertCloudCompatible(options, hasTools) {
|
|
2249
|
+
const refuse = (what) => {
|
|
2250
|
+
throw new UnsupportedError(`${what} needs the on-device tier.`, "cloud");
|
|
2251
|
+
};
|
|
2252
|
+
if (options.images !== void 0 && options.images.length > 0 && _optionalChain([this, 'access', _132 => _132.cloud, 'optionalAccess', _133 => _133.supportsImages]) !== true) {
|
|
2253
|
+
throw new UnsupportedError(
|
|
2254
|
+
"images on the cloud tier need a server model that reads them \u2014 macOS 27 (Golden Gate) or later, where the helper can confirm it. Use the on-device tier for images here.",
|
|
2255
|
+
"cloud"
|
|
2256
|
+
);
|
|
2257
|
+
}
|
|
2258
|
+
if (options.sessionId !== void 0) refuse("sessionId");
|
|
2259
|
+
if (hasTools) refuse("tools");
|
|
2260
|
+
}
|
|
2261
|
+
/** Conversation history for a named session (device tier only). */
|
|
2262
|
+
async history(sessionId) {
|
|
2263
|
+
await this.ensureReady();
|
|
2264
|
+
if (this.resolved !== "device") {
|
|
2265
|
+
throw new UnsupportedError("history needs the on-device tier.", "cloud");
|
|
2266
|
+
}
|
|
2267
|
+
return this.device.history(sessionId);
|
|
2268
|
+
}
|
|
2269
|
+
/** Drop a named session, or all sessions when omitted (device tier only). */
|
|
2270
|
+
async resetSession(sessionId) {
|
|
2271
|
+
await this.ensureReady();
|
|
2272
|
+
if (this.resolved === "device") await this.device.resetSession(sessionId);
|
|
2273
|
+
}
|
|
2274
|
+
/**
|
|
2275
|
+
* A named conversation: calls sharing one native transcript, like one thread
|
|
2276
|
+
* in the Siri app. History persists across helper restarts, and the
|
|
2277
|
+
* transcript is rebuilt from it — trimmed to fit when it outgrows the window.
|
|
2278
|
+
*/
|
|
2279
|
+
conversation(sessionId, options = {}) {
|
|
2280
|
+
return new Conversation(this, sessionId, options);
|
|
2281
|
+
}
|
|
2282
|
+
/**
|
|
2283
|
+
* Write with Siri, anywhere you type: drafting, rewriting and feedback
|
|
2284
|
+
* built on the permissive-content-transformation guardrails. Device tier
|
|
2285
|
+
* only — these are transformation tasks the default guardrails refuse.
|
|
2286
|
+
*/
|
|
2287
|
+
async rewrite(text, options = {}) {
|
|
2288
|
+
const { instruction, ...rest } = options;
|
|
2289
|
+
return this.text(`Rewrite the following text. ${_nullishCoalesce(instruction, () => ( "Keep the meaning, improve clarity."))}
|
|
2290
|
+
|
|
2291
|
+
---
|
|
2292
|
+
${text}`, {
|
|
2293
|
+
...rest,
|
|
2294
|
+
guardrails: _nullishCoalesce(rest.guardrails, () => ( "permissive")),
|
|
2295
|
+
system: _nullishCoalesce(rest.system, () => ( "You rewrite text. Reply with only the rewritten text, no commentary."))
|
|
2296
|
+
});
|
|
2297
|
+
}
|
|
2298
|
+
async proofread(text, options = {}) {
|
|
2299
|
+
return this.text(`Fix spelling, grammar and punctuation in the following text. Preserve the meaning and tone.
|
|
2300
|
+
|
|
2301
|
+
---
|
|
2302
|
+
${text}`, {
|
|
2303
|
+
...options,
|
|
2304
|
+
guardrails: _nullishCoalesce(options.guardrails, () => ( "permissive")),
|
|
2305
|
+
system: _nullishCoalesce(options.system, () => ( "You proofread text. Reply with only the corrected text, no commentary."))
|
|
2306
|
+
});
|
|
2307
|
+
}
|
|
2308
|
+
/**
|
|
2309
|
+
* Summarise text of any length. Text that does not fit the context window
|
|
2310
|
+
* is summarised in parts, then the parts are summarised together — so a
|
|
2311
|
+
* long report works on an 8k-token model instead of failing.
|
|
2312
|
+
*/
|
|
2313
|
+
async summarize(text, options = {}) {
|
|
2314
|
+
const { length, ...rest } = options;
|
|
2315
|
+
const once = (body, what, size) => this.text(`Summarize ${what} in ${size}.
|
|
2316
|
+
|
|
2317
|
+
---
|
|
2318
|
+
${body}`, {
|
|
2319
|
+
...rest,
|
|
2320
|
+
guardrails: _nullishCoalesce(rest.guardrails, () => ( "permissive")),
|
|
2321
|
+
system: _nullishCoalesce(rest.system, () => ( "You summarize text tersely."))
|
|
2322
|
+
});
|
|
2323
|
+
let current = text;
|
|
2324
|
+
for (let round = 0; round < 4; round += 1) {
|
|
2325
|
+
const chunks = await this.chunksFor(current, rest.maxTokens);
|
|
2326
|
+
if (chunks.length <= 1) break;
|
|
2327
|
+
const parts = [];
|
|
2328
|
+
for (const chunk of chunks) {
|
|
2329
|
+
parts.push(await once(chunk, "this part of a longer document", "a few sentences, keeping names, numbers and decisions"));
|
|
2330
|
+
}
|
|
2331
|
+
current = parts.join("\n\n");
|
|
2332
|
+
}
|
|
2333
|
+
return once(current, current === text ? "the following text" : "these notes on a longer document", _nullishCoalesce(length, () => ( "one short paragraph")));
|
|
2334
|
+
}
|
|
2335
|
+
/** Split text into pieces that each fit one call, on paragraph boundaries where possible. */
|
|
2336
|
+
async chunksFor(text, maxTokens) {
|
|
2337
|
+
await this.ensureReady();
|
|
2338
|
+
const window = _nullishCoalesce(this.contextSize, () => ( 4096));
|
|
2339
|
+
const budget = Math.max(512, window - (_nullishCoalesce(_nullishCoalesce(maxTokens, () => ( this.options.maxTokens)), () => ( 1024))) - 256);
|
|
2340
|
+
let tokens;
|
|
2341
|
+
if (this.resolved === "device") {
|
|
2342
|
+
try {
|
|
2343
|
+
tokens = (await this.device.countTokens(text)).tokens;
|
|
2344
|
+
} catch (e27) {
|
|
2345
|
+
tokens = Math.ceil(text.length / 3);
|
|
2346
|
+
}
|
|
2347
|
+
} else {
|
|
2348
|
+
tokens = Math.ceil(text.length / 3);
|
|
2349
|
+
}
|
|
2350
|
+
if (tokens <= budget) return [text];
|
|
2351
|
+
const maxChars = Math.floor(text.length / tokens * budget * 0.85);
|
|
2352
|
+
const chunks = [];
|
|
2353
|
+
let current = "";
|
|
2354
|
+
for (const paragraph of text.split(/\n\s*\n/)) {
|
|
2355
|
+
const pieces = paragraph.length > maxChars ? _nullishCoalesce(paragraph.match(new RegExp(`[\\s\\S]{1,${maxChars}}`, "g")), () => ( [])) : [paragraph];
|
|
2356
|
+
for (const piece of pieces) {
|
|
2357
|
+
if (current.length + piece.length + 2 > maxChars && current !== "") {
|
|
2358
|
+
chunks.push(current);
|
|
2359
|
+
current = "";
|
|
2360
|
+
}
|
|
2361
|
+
current = current === "" ? piece : `${current}
|
|
2362
|
+
|
|
2363
|
+
${piece}`;
|
|
2364
|
+
}
|
|
2365
|
+
}
|
|
2366
|
+
if (current !== "") chunks.push(current);
|
|
2367
|
+
return chunks;
|
|
2368
|
+
}
|
|
2369
|
+
async draft(topic, options = {}) {
|
|
2370
|
+
const { kind, ...rest } = options;
|
|
2371
|
+
return this.text(`Write a ${_nullishCoalesce(kind, () => ( "short draft"))} about the following topic.
|
|
2372
|
+
|
|
2373
|
+
---
|
|
2374
|
+
${topic}`, {
|
|
2375
|
+
...rest,
|
|
2376
|
+
system: _nullishCoalesce(rest.system, () => ( "You are a helpful writing assistant."))
|
|
2377
|
+
});
|
|
2378
|
+
}
|
|
2379
|
+
async tone(text, tone, options = {}) {
|
|
2380
|
+
return this.text(`Rewrite the following text to sound more ${tone}.
|
|
2381
|
+
|
|
2382
|
+
---
|
|
2383
|
+
${text}`, {
|
|
2384
|
+
...options,
|
|
2385
|
+
guardrails: _nullishCoalesce(options.guardrails, () => ( "permissive")),
|
|
2386
|
+
system: _nullishCoalesce(options.system, () => ( "You rewrite text. Reply with only the rewritten text, no commentary."))
|
|
2387
|
+
});
|
|
2388
|
+
}
|
|
2389
|
+
/**
|
|
2390
|
+
* Ask about what's on screen: captures a screenshot (interactive selection
|
|
2391
|
+
* by default, like Cmd+Shift+Space Visual Intelligence) and asks the model
|
|
2392
|
+
* about it with vision. Device tier, macOS 27+.
|
|
2393
|
+
*/
|
|
2394
|
+
async askScreen(question, options = {}) {
|
|
2395
|
+
await this.ensureReady();
|
|
2396
|
+
if (this.resolved !== "device") {
|
|
2397
|
+
throw new UnsupportedError("askScreen needs the on-device tier.", "cloud");
|
|
2398
|
+
}
|
|
2399
|
+
const { mode, ...rest } = options;
|
|
2400
|
+
const shot = await captureScreenshot(_nullishCoalesce(mode, () => ( "interactive")));
|
|
2401
|
+
try {
|
|
2402
|
+
return await this.text(question, { ...rest, images: [..._nullishCoalesce(rest.images, () => ( [])), shot] });
|
|
2403
|
+
} finally {
|
|
2404
|
+
const { rm: rm3 } = await Promise.resolve().then(() => _interopRequireWildcard(require("fs/promises")));
|
|
2405
|
+
await rm3(shot, { force: true }).catch(() => void 0);
|
|
2406
|
+
}
|
|
2407
|
+
}
|
|
2408
|
+
/**
|
|
2409
|
+
* `json()` in the `(system, user, schema)` shape many LLM clients use, for
|
|
2410
|
+
* dropping this in behind an existing interface.
|
|
2411
|
+
*/
|
|
2412
|
+
async completeJson(system, user, schema) {
|
|
2413
|
+
return this.json(user, { system, schema });
|
|
2414
|
+
}
|
|
2415
|
+
/**
|
|
2416
|
+
* How many tokens a request costs, before sending it. Device tier only.
|
|
2417
|
+
*
|
|
2418
|
+
* Turns a ContextLengthError into arithmetic: compare against `contextSize`
|
|
2419
|
+
* and trim, rather than finding the ceiling by hitting it. Counts
|
|
2420
|
+
* everything that shares the window — instructions, message history, tools,
|
|
2421
|
+
* schema, images and documents.
|
|
2422
|
+
*/
|
|
2423
|
+
async countTokens(input, options = {}) {
|
|
2424
|
+
await this.ensureReady();
|
|
2425
|
+
if (this.resolved !== "device") {
|
|
2426
|
+
throw new UnsupportedError("countTokens needs the on-device tier.", "cloud");
|
|
2427
|
+
}
|
|
2428
|
+
let prompt;
|
|
2429
|
+
let history;
|
|
2430
|
+
let system = _nullishCoalesce(options.system, () => ( this.options.system));
|
|
2431
|
+
if (typeof input === "string") {
|
|
2432
|
+
prompt = input;
|
|
2433
|
+
} else {
|
|
2434
|
+
const split = splitMessages(input);
|
|
2435
|
+
prompt = split.prompt;
|
|
2436
|
+
history = split.history;
|
|
2437
|
+
if (split.system !== void 0) system = system === void 0 ? split.system : `${system}
|
|
2438
|
+
|
|
2439
|
+
${split.system}`;
|
|
2440
|
+
}
|
|
2441
|
+
const tools = normalizeTools(options.tools);
|
|
2442
|
+
return this.device.countTokens(prompt, {
|
|
2443
|
+
system,
|
|
2444
|
+
images: options.images,
|
|
2445
|
+
documents: options.documents,
|
|
2446
|
+
history,
|
|
2447
|
+
tools: tools.builtIn.length > 0 ? tools.builtIn : void 0,
|
|
2448
|
+
functions: tools.functions.length > 0 ? tools.functions : void 0,
|
|
2449
|
+
schema: options.schema === void 0 ? void 0 : resolveSchema(options.schema).json,
|
|
2450
|
+
signal: options.signal
|
|
2451
|
+
});
|
|
2452
|
+
}
|
|
2453
|
+
/**
|
|
2454
|
+
* Load the model assets now so the first real call does not pay for it.
|
|
2455
|
+
* Device tier only; a no-op elsewhere. See `DeviceClient.prewarm` for what it
|
|
2456
|
+
* is actually worth (little, on a warm machine).
|
|
2457
|
+
*/
|
|
2458
|
+
async prewarm(system) {
|
|
2459
|
+
await this.ensureReady();
|
|
2460
|
+
if (this.resolved === "device") await this.device.prewarm(_nullishCoalesce(system, () => ( this.options.system)));
|
|
2461
|
+
}
|
|
2462
|
+
/** Release the long-lived helper process. Safe to call more than once. */
|
|
2463
|
+
close() {
|
|
2464
|
+
_optionalChain([this, 'access', _134 => _134.device, 'optionalAccess', _135 => _135.close, 'call', _136 => _136()]);
|
|
2465
|
+
_optionalChain([this, 'access', _137 => _137.cloud, 'optionalAccess', _138 => _138.close, 'call', _139 => _139()]);
|
|
2466
|
+
}
|
|
2467
|
+
/** `using llm = new AppleLLM()` closes it at the end of the block. */
|
|
2468
|
+
[Symbol.dispose]() {
|
|
2469
|
+
this.close();
|
|
2470
|
+
}
|
|
2471
|
+
/** `await using llm = new AppleLLM()` closes it at the end of the block. */
|
|
2472
|
+
async [Symbol.asyncDispose]() {
|
|
2473
|
+
this.close();
|
|
2474
|
+
}
|
|
2475
|
+
};
|
|
2476
|
+
var Conversation = class {
|
|
2477
|
+
constructor(llm, sessionId, defaults = {}) {
|
|
2478
|
+
this.llm = llm;
|
|
2479
|
+
this.sessionId = sessionId;
|
|
2480
|
+
this.defaults = defaults;
|
|
2481
|
+
}
|
|
2482
|
+
|
|
2483
|
+
|
|
2484
|
+
|
|
2485
|
+
with(options) {
|
|
2486
|
+
return { ...options, system: _nullishCoalesce(options.system, () => ( this.defaults.system)), sessionId: this.sessionId };
|
|
2487
|
+
}
|
|
2488
|
+
async text(prompt, options = {}) {
|
|
2489
|
+
return this.llm.text(prompt, this.with(options));
|
|
2490
|
+
}
|
|
2491
|
+
stream(prompt, options = {}) {
|
|
2492
|
+
return this.llm.stream(prompt, this.with(options));
|
|
2493
|
+
}
|
|
2494
|
+
async generate(prompt, options = {}) {
|
|
2495
|
+
return this.llm.generate(prompt, this.with(options));
|
|
2496
|
+
}
|
|
2497
|
+
async json(prompt, options) {
|
|
2498
|
+
return this.llm.json(prompt, this.with(options));
|
|
2499
|
+
}
|
|
2500
|
+
async history() {
|
|
2501
|
+
return this.llm.history(this.sessionId);
|
|
2502
|
+
}
|
|
2503
|
+
async reset() {
|
|
2504
|
+
await this.llm.resetSession(this.sessionId);
|
|
2505
|
+
}
|
|
2506
|
+
};
|
|
2507
|
+
async function captureScreenshot(mode = "interactive") {
|
|
2508
|
+
const { execFile: execFile5 } = await Promise.resolve().then(() => _interopRequireWildcard(require("child_process")));
|
|
2509
|
+
const { promisify: promisify5 } = await Promise.resolve().then(() => _interopRequireWildcard(require("util")));
|
|
2510
|
+
const { mkdtemp: mkdtemp2, stat: stat3 } = await Promise.resolve().then(() => _interopRequireWildcard(require("fs/promises")));
|
|
2511
|
+
const os3 = await Promise.resolve().then(() => _interopRequireWildcard(require("os")));
|
|
2512
|
+
const path3 = await Promise.resolve().then(() => _interopRequireWildcard(require("path")));
|
|
2513
|
+
const execFileAsync5 = promisify5(execFile5);
|
|
2514
|
+
const dir = await mkdtemp2(path3.join(os3.tmpdir(), "apple-llm-screen-"));
|
|
2515
|
+
const out = path3.join(dir, "screen.png");
|
|
2516
|
+
const args = mode === "interactive" ? ["-i", "-x", out] : mode === "window" ? ["-w", "-x", out] : ["-x", out];
|
|
2517
|
+
try {
|
|
2518
|
+
await execFileAsync5("screencapture", args, { timeout: 12e4 });
|
|
2519
|
+
} catch (err) {
|
|
2520
|
+
throw new AppleLLMError(
|
|
2521
|
+
`Could not capture a screenshot (screencapture failed): ${err instanceof Error ? err.message : String(err)}`,
|
|
2522
|
+
"device"
|
|
2523
|
+
);
|
|
2524
|
+
}
|
|
2525
|
+
try {
|
|
2526
|
+
const st = await stat3(out);
|
|
2527
|
+
if (st.size === 0) throw new Error("empty capture");
|
|
2528
|
+
} catch (e28) {
|
|
2529
|
+
throw new AppleLLMError("Screenshot capture was cancelled or produced no image.", "device");
|
|
2530
|
+
}
|
|
2531
|
+
return out;
|
|
2532
|
+
}
|
|
2533
|
+
|
|
2534
|
+
|
|
2535
|
+
|
|
2536
|
+
|
|
2537
|
+
|
|
2538
|
+
|
|
2539
|
+
|
|
2540
|
+
|
|
2541
|
+
|
|
2542
|
+
|
|
2543
|
+
|
|
2544
|
+
|
|
2545
|
+
|
|
2546
|
+
|
|
2547
|
+
|
|
2548
|
+
|
|
2549
|
+
|
|
2550
|
+
|
|
2551
|
+
|
|
2552
|
+
|
|
2553
|
+
|
|
2554
|
+
|
|
2555
|
+
|
|
2556
|
+
|
|
2557
|
+
|
|
2558
|
+
|
|
2559
|
+
|
|
2560
|
+
|
|
2561
|
+
|
|
2562
|
+
|
|
2563
|
+
|
|
2564
|
+
|
|
2565
|
+
|
|
2566
|
+
|
|
2567
|
+
|
|
2568
|
+
|
|
2569
|
+
|
|
2570
|
+
|
|
2571
|
+
|
|
2572
|
+
|
|
2573
|
+
|
|
2574
|
+
|
|
2575
|
+
|
|
2576
|
+
|
|
2577
|
+
|
|
2578
|
+
|
|
2579
|
+
|
|
2580
|
+
|
|
2581
|
+
|
|
2582
|
+
|
|
2583
|
+
|
|
2584
|
+
|
|
2585
|
+
|
|
2586
|
+
|
|
2587
|
+
|
|
2588
|
+
exports.AppleLLMError = AppleLLMError; exports.ModelUnavailableError = ModelUnavailableError; exports.SchemaRejectedError = SchemaRejectedError; exports.SchemaValidationError = SchemaValidationError; exports.ContextLengthError = ContextLengthError; exports.QuotaError = QuotaError; exports.TimeoutError = TimeoutError; exports.AbortError = AbortError; exports.SetupRequiredError = SetupRequiredError; exports.RefusalError = RefusalError; exports.ToolExecutionError = ToolExecutionError; exports.ModelBusyError = ModelBusyError; exports.UnsupportedError = UnsupportedError; exports.stripCodeFences = stripCodeFences; exports.extractJsonSpan = extractJsonSpan; exports.parseLlmJson = parseLlmJson; exports.isAppleSiliconMac = isAppleSiliconMac; exports.targetTripleFrom = targetTripleFrom; exports.hostTarget = hostTarget; exports.CLOUD_SHORTCUT_NAME = CLOUD_SHORTCUT_NAME; exports.CLOUD_SHORTCUT_NAME_WEB = CLOUD_SHORTCUT_NAME_WEB; exports.CLOUD_CONTEXT_TOKENS = CLOUD_CONTEXT_TOKENS; exports.shortcutDefinition = shortcutDefinition; exports.cloudSetupHint = cloudSetupHint; exports.installCloudShortcut = installCloudShortcut; exports.probeCloud = probeCloud; exports.CloudClient = CloudClient; exports.cacheDir = cacheDir; exports.fingerprint = fingerprint; exports.helperSource = helperSource; exports.ensureBinary = ensureBinary; exports.splitMessages = splitMessages; exports.ReplayBook = ReplayBook; exports.toAppleSchema = toAppleSchema; exports.isStandardSchema = isStandardSchema; exports.resolveSchema = resolveSchema; exports.restoreNulls = restoreNulls; exports.tool = tool; exports.normalizeTools = normalizeTools; exports.toolOutputText = toolOutputText; exports.DEFAULT_TEMPERATURE = DEFAULT_TEMPERATURE; exports.DEFAULT_MAX_TOKENS = DEFAULT_MAX_TOKENS; exports.DEFAULT_MAX_TOOL_CALLS = DEFAULT_MAX_TOOL_CALLS; exports.assertTools = assertTools; exports.probeDevice = probeDevice; exports.DeviceClient = DeviceClient; exports.withDocuments = withDocuments; exports.parseImageFlag = parseImageFlag; exports.ResultStream = ResultStream; exports.probe = probe; exports.AppleLLM = AppleLLM; exports.Conversation = Conversation; exports.captureScreenshot = captureScreenshot;
|