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/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import {
2
+ AbortError,
2
3
  AppleLLM,
3
4
  AppleLLMError,
4
5
  CLOUD_CONTEXT_TOKENS,
@@ -8,14 +9,21 @@ import {
8
9
  ContextLengthError,
9
10
  Conversation,
10
11
  DEFAULT_MAX_TOKENS,
12
+ DEFAULT_MAX_TOOL_CALLS,
11
13
  DEFAULT_TEMPERATURE,
12
14
  DeviceClient,
15
+ ModelBusyError,
13
16
  ModelUnavailableError,
14
17
  QuotaError,
15
18
  RefusalError,
19
+ ReplayBook,
20
+ ResultStream,
16
21
  SchemaRejectedError,
22
+ SchemaValidationError,
17
23
  SetupRequiredError,
18
24
  TimeoutError,
25
+ ToolExecutionError,
26
+ UnsupportedError,
19
27
  assertTools,
20
28
  cacheDir,
21
29
  captureScreenshot,
@@ -27,18 +35,26 @@ import {
27
35
  hostTarget,
28
36
  installCloudShortcut,
29
37
  isAppleSiliconMac,
38
+ isStandardSchema,
39
+ normalizeTools,
30
40
  parseImageFlag,
31
41
  parseLlmJson,
32
42
  probe,
33
43
  probeCloud,
34
44
  probeDevice,
45
+ resolveSchema,
46
+ restoreNulls,
35
47
  shortcutDefinition,
48
+ splitMessages,
36
49
  stripCodeFences,
37
50
  targetTripleFrom,
38
51
  toAppleSchema,
52
+ tool,
53
+ toolOutputText,
39
54
  withDocuments
40
- } from "./chunk-FQTRQ3KP.js";
55
+ } from "./chunk-GM325EMJ.js";
41
56
  export {
57
+ AbortError,
42
58
  AppleLLM,
43
59
  AppleLLMError,
44
60
  CLOUD_CONTEXT_TOKENS,
@@ -48,14 +64,21 @@ export {
48
64
  ContextLengthError,
49
65
  Conversation,
50
66
  DEFAULT_MAX_TOKENS,
67
+ DEFAULT_MAX_TOOL_CALLS,
51
68
  DEFAULT_TEMPERATURE,
52
69
  DeviceClient,
70
+ ModelBusyError,
53
71
  ModelUnavailableError,
54
72
  QuotaError,
55
73
  RefusalError,
74
+ ReplayBook,
75
+ ResultStream,
56
76
  SchemaRejectedError,
77
+ SchemaValidationError,
57
78
  SetupRequiredError,
58
79
  TimeoutError,
80
+ ToolExecutionError,
81
+ UnsupportedError,
59
82
  assertTools,
60
83
  cacheDir,
61
84
  captureScreenshot,
@@ -67,14 +90,21 @@ export {
67
90
  hostTarget,
68
91
  installCloudShortcut,
69
92
  isAppleSiliconMac,
93
+ isStandardSchema,
94
+ normalizeTools,
70
95
  parseImageFlag,
71
96
  parseLlmJson,
72
97
  probe,
73
98
  probeCloud,
74
99
  probeDevice,
100
+ resolveSchema,
101
+ restoreNulls,
75
102
  shortcutDefinition,
103
+ splitMessages,
76
104
  stripCodeFences,
77
105
  targetTripleFrom,
78
106
  toAppleSchema,
107
+ tool,
108
+ toolOutputText,
79
109
  withDocuments
80
110
  };
@@ -0,0 +1,361 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return 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; }
2
+
3
+ var _chunkOQATUZWFcjs = require('./chunk-OQATUZWF.cjs');
4
+
5
+
6
+
7
+
8
+
9
+
10
+
11
+
12
+
13
+
14
+
15
+
16
+
17
+
18
+
19
+
20
+ var _chunkNRDZIP5Gcjs = require('./chunk-NRDZIP5G.cjs');
21
+
22
+ // src/server.ts
23
+ var _http = require('http'); var _http2 = _interopRequireDefault(_http);
24
+ var _crypto = require('crypto');
25
+ var DEFAULT_PORT = 11436;
26
+ var MODELS = {
27
+ "apple-on-device": "device",
28
+ "apple-private-cloud": "cloud"
29
+ };
30
+ function errorResponse(error) {
31
+ const message = error instanceof Error ? error.message : String(error);
32
+ const make = (status, type, code) => ({
33
+ status,
34
+ body: { error: { message, type, code: _nullishCoalesce(code, () => ( null)), param: null } }
35
+ });
36
+ if (error instanceof _chunkNRDZIP5Gcjs.ContextLengthError) return make(400, "invalid_request_error", "context_length_exceeded");
37
+ if (error instanceof _chunkNRDZIP5Gcjs.SchemaRejectedError || error instanceof _chunkNRDZIP5Gcjs.SchemaValidationError) {
38
+ return make(400, "invalid_request_error", "invalid_schema");
39
+ }
40
+ if (error instanceof _chunkNRDZIP5Gcjs.UnsupportedError) return make(400, "invalid_request_error", "unsupported");
41
+ if (error instanceof _chunkNRDZIP5Gcjs.RefusalError) return make(400, "invalid_request_error", "content_filter");
42
+ if (error instanceof _chunkNRDZIP5Gcjs.QuotaError) return make(429, "rate_limit_error", "quota_exceeded");
43
+ if (error instanceof _chunkNRDZIP5Gcjs.TimeoutError) return make(504, "timeout_error");
44
+ if (error instanceof _chunkNRDZIP5Gcjs.AbortError) return make(499, "request_cancelled");
45
+ if (error instanceof _chunkNRDZIP5Gcjs.ModelBusyError) return make(503, "service_unavailable", "model_busy");
46
+ if (error instanceof _chunkNRDZIP5Gcjs.ModelUnavailableError || error instanceof _chunkNRDZIP5Gcjs.SetupRequiredError) {
47
+ return make(503, "service_unavailable", "model_unavailable");
48
+ }
49
+ if (error instanceof HttpError) return make(error.status, "invalid_request_error");
50
+ if (error instanceof _chunkNRDZIP5Gcjs.AppleLLMError) return make(500, "server_error");
51
+ return make(500, "server_error");
52
+ }
53
+ var HttpError = class extends Error {
54
+ constructor(status, message) {
55
+ super(message);
56
+ this.status = status;
57
+ }
58
+
59
+ };
60
+ function textOf(content) {
61
+ if (typeof content === "string") return content;
62
+ if (content === null || content === void 0) return "";
63
+ if (Array.isArray(content)) {
64
+ return content.filter((p) => p.type === "text" || p.type === "input_text").map((p) => _nullishCoalesce(p.text, () => ( ""))).join("\n");
65
+ }
66
+ return String(content);
67
+ }
68
+ async function toMessages(raw, images, signal) {
69
+ if (!Array.isArray(raw) || raw.length === 0) throw new HttpError(400, "`messages` must be a non-empty array.");
70
+ const out = [];
71
+ for (const message of raw) {
72
+ switch (message.role) {
73
+ case "system":
74
+ case "developer":
75
+ out.push({ role: "system", content: textOf(message.content) });
76
+ break;
77
+ case "user": {
78
+ const paths = [];
79
+ if (Array.isArray(message.content)) {
80
+ for (const part of message.content) {
81
+ if (part.type === "image_url" && part.image_url !== void 0) {
82
+ const url = typeof part.image_url === "string" ? part.image_url : part.image_url.url;
83
+ paths.push(await images.add(url, void 0, signal));
84
+ } else if (part.type !== "text" && part.type !== "input_text") {
85
+ throw new HttpError(400, `Content part "${part.type}" is not supported.`);
86
+ }
87
+ }
88
+ }
89
+ out.push({ role: "user", content: textOf(message.content), ...paths.length > 0 ? { images: paths } : {} });
90
+ break;
91
+ }
92
+ case "assistant":
93
+ out.push({
94
+ role: "assistant",
95
+ content: textOf(message.content),
96
+ ...message.tool_calls !== void 0 && message.tool_calls.length > 0 ? {
97
+ toolCalls: message.tool_calls.map((call) => ({
98
+ id: call.id,
99
+ name: call.function.name,
100
+ arguments: parseArguments(call.function.arguments)
101
+ }))
102
+ } : {}
103
+ });
104
+ break;
105
+ case "tool":
106
+ out.push({ role: "tool", toolCallId: String(_nullishCoalesce(message.tool_call_id, () => ( ""))), name: message.name, content: textOf(message.content) });
107
+ break;
108
+ default:
109
+ throw new HttpError(400, `Message role "${message.role}" is not supported.`);
110
+ }
111
+ }
112
+ return out;
113
+ }
114
+ function parseArguments(text) {
115
+ try {
116
+ return JSON.parse(text);
117
+ } catch (e) {
118
+ return text;
119
+ }
120
+ }
121
+ function openAIToolCalls(calls) {
122
+ return calls.filter((call) => call.output === void 0).map((call, index) => ({
123
+ index,
124
+ id: call.id,
125
+ type: "function",
126
+ function: { name: call.name, arguments: JSON.stringify(_nullishCoalesce(call.arguments, () => ( {}))) }
127
+ }));
128
+ }
129
+ function finishReason(result) {
130
+ return result.finishReason === "tool-calls" ? "tool_calls" : result.finishReason;
131
+ }
132
+ function usageOf(result) {
133
+ if (result.usage === void 0) return void 0;
134
+ return {
135
+ prompt_tokens: result.usage.inputTokens,
136
+ completion_tokens: result.usage.outputTokens,
137
+ total_tokens: result.usage.totalTokens
138
+ };
139
+ }
140
+ async function readBody(req, limit = 32 * 1024 * 1024) {
141
+ const chunks = [];
142
+ let size = 0;
143
+ for await (const chunk of req) {
144
+ size += chunk.length;
145
+ if (size > limit) throw new HttpError(413, "Request body too large.");
146
+ chunks.push(chunk);
147
+ }
148
+ const text = Buffer.concat(chunks).toString("utf8");
149
+ if (text === "") return {};
150
+ try {
151
+ return JSON.parse(text);
152
+ } catch (e2) {
153
+ throw new HttpError(400, "Request body is not valid JSON.");
154
+ }
155
+ }
156
+ function createServer(options = {}) {
157
+ const { port: _port, host: _host, tier: defaultTier = "device", apiKey, cors, log, ...llmOptions } = options;
158
+ const clients = /* @__PURE__ */ new Map();
159
+ const clientFor = (tier) => {
160
+ let client = clients.get(tier);
161
+ if (client === void 0) {
162
+ client = new (0, _chunkNRDZIP5Gcjs.AppleLLM)({ ...llmOptions, tier });
163
+ clients.set(tier, client);
164
+ }
165
+ return client;
166
+ };
167
+ const server = _http2.default.createServer((req, res) => {
168
+ const started = Date.now();
169
+ const controller = new AbortController();
170
+ res.on("close", () => {
171
+ if (!res.writableFinished) controller.abort();
172
+ });
173
+ const send = (status, body) => {
174
+ if (res.headersSent) return;
175
+ res.writeHead(status, { "content-type": "application/json", ...corsHeaders() });
176
+ res.end(JSON.stringify(body));
177
+ };
178
+ const corsHeaders = () => cors === void 0 ? {} : {
179
+ "access-control-allow-origin": cors,
180
+ "access-control-allow-headers": "authorization, content-type",
181
+ "access-control-allow-methods": "GET, POST, OPTIONS"
182
+ };
183
+ const route = async () => {
184
+ const url = new URL(_nullishCoalesce(req.url, () => ( "/")), "http://localhost");
185
+ const pathname = url.pathname.replace(/\/+$/, "") || "/";
186
+ if (req.method === "OPTIONS") {
187
+ res.writeHead(204, corsHeaders());
188
+ res.end();
189
+ return void 0;
190
+ }
191
+ if (apiKey !== void 0 && req.headers.authorization !== `Bearer ${apiKey}`) {
192
+ send(401, { error: { message: "Invalid API key.", type: "invalid_request_error", code: "invalid_api_key", param: null } });
193
+ return void 0;
194
+ }
195
+ if (req.method === "GET" && (pathname === "/health" || pathname === "/v1/health")) {
196
+ const state = await _chunkNRDZIP5Gcjs.probe.call(void 0, );
197
+ send(state.device.available || state.cloud.available ? 200 : 503, {
198
+ status: state.device.available || state.cloud.available ? "ok" : "unavailable",
199
+ device: { available: state.device.available, variant: state.device.variant, contextSize: state.device.contextSize, reason: state.device.reason },
200
+ cloud: { available: state.cloud.available, quota: _optionalChain([state, 'access', _ => _.cloud, 'access', _2 => _2.quota, 'optionalAccess', _3 => _3.status]), reason: state.cloud.reason }
201
+ });
202
+ return void 0;
203
+ }
204
+ if (req.method === "GET" && pathname === "/v1/models") {
205
+ const created = Math.floor(Date.now() / 1e3);
206
+ send(200, {
207
+ object: "list",
208
+ data: Object.keys(MODELS).map((id) => ({ id, object: "model", created, owned_by: "apple" }))
209
+ });
210
+ return void 0;
211
+ }
212
+ if (req.method === "POST" && pathname === "/v1/chat/completions") {
213
+ const body = await readBody(req);
214
+ return chatCompletions(body);
215
+ }
216
+ send(404, { error: { message: `No route for ${req.method} ${pathname}.`, type: "invalid_request_error", code: "not_found", param: null } });
217
+ return void 0;
218
+ };
219
+ const chatCompletions = async (body) => {
220
+ const requested = _nullishCoalesce(body.model, () => ( ""));
221
+ const tier = requested in MODELS ? MODELS[requested] : defaultTier;
222
+ const model = requested in MODELS ? requested : tier === "cloud" ? "apple-private-cloud" : "apple-on-device";
223
+ if (body.n !== void 0 && body.n !== 1) throw new HttpError(400, "Only n=1 is supported.");
224
+ const images = new (0, _chunkOQATUZWFcjs.TempImages)();
225
+ try {
226
+ const messages = await toMessages(body.messages, images, controller.signal);
227
+ const functions = [];
228
+ if (body.tool_choice !== "none") {
229
+ for (const def of _nullishCoalesce(body.tools, () => ( []))) {
230
+ if (def.type !== "function" || def.function === void 0) continue;
231
+ functions.push(_chunkNRDZIP5Gcjs.tool.call(void 0, { name: def.function.name, description: def.function.description, parameters: def.function.parameters }));
232
+ }
233
+ }
234
+ let schema;
235
+ if (_optionalChain([body, 'access', _4 => _4.response_format, 'optionalAccess', _5 => _5.type]) === "json_schema" && _optionalChain([body, 'access', _6 => _6.response_format, 'access', _7 => _7.json_schema, 'optionalAccess', _8 => _8.schema]) !== void 0) {
236
+ schema = body.response_format.json_schema.schema;
237
+ } else if (_optionalChain([body, 'access', _9 => _9.response_format, 'optionalAccess', _10 => _10.type]) === "json_object") {
238
+ messages.unshift({ role: "system", content: "Reply with a single JSON object and nothing else." });
239
+ }
240
+ let sampling;
241
+ if (body.top_p !== void 0) sampling = { mode: "threshold", p: body.top_p, seed: body.seed };
242
+ else if (body.seed !== void 0) sampling = { mode: "topK", k: 50, seed: body.seed };
243
+ const call = {
244
+ temperature: body.temperature,
245
+ maxTokens: _nullishCoalesce(body.max_completion_tokens, () => ( body.max_tokens)),
246
+ sampling,
247
+ tools: functions.length > 0 ? functions : void 0,
248
+ signal: controller.signal
249
+ };
250
+ const client = clientFor(tier);
251
+ const id = `chatcmpl-${_crypto.randomUUID.call(void 0, ).replace(/-/g, "").slice(0, 24)}`;
252
+ const created = Math.floor(Date.now() / 1e3);
253
+ if (body.stream === true) {
254
+ res.writeHead(200, {
255
+ "content-type": "text/event-stream",
256
+ "cache-control": "no-cache",
257
+ connection: "keep-alive",
258
+ ...corsHeaders()
259
+ });
260
+ const event = (payload) => {
261
+ if (!res.destroyed) res.write(`data: ${typeof payload === "string" ? payload : JSON.stringify(payload)}
262
+
263
+ `);
264
+ };
265
+ const chunk = (delta, finish = null) => {
266
+ event({ id, object: "chat.completion.chunk", created, model, choices: [{ index: 0, delta, finish_reason: finish, logprobs: null }] });
267
+ };
268
+ chunk({ role: "assistant", content: "" });
269
+ try {
270
+ let result2;
271
+ if (schema === void 0) {
272
+ const stream = client.stream(messages, call);
273
+ for await (const delta of stream) chunk({ content: delta });
274
+ result2 = await stream.result;
275
+ } else {
276
+ result2 = await client.generate(messages, { ...call, schema });
277
+ if (result2.finishReason !== "tool-calls") chunk({ content: result2.text });
278
+ }
279
+ const calls2 = openAIToolCalls(result2.toolCalls);
280
+ if (calls2.length > 0 && result2.finishReason === "tool-calls") chunk({ tool_calls: calls2 });
281
+ chunk({}, finishReason(result2));
282
+ if (_optionalChain([body, 'access', _11 => _11.stream_options, 'optionalAccess', _12 => _12.include_usage]) === true) {
283
+ event({ id, object: "chat.completion.chunk", created, model, choices: [], usage: _nullishCoalesce(usageOf(result2), () => ( null)) });
284
+ }
285
+ } catch (error) {
286
+ event(errorResponse(error).body);
287
+ if (!res.destroyed) res.end("data: [DONE]\n\n");
288
+ return `${model} stream failed: ${error instanceof Error ? error.message.split("\n")[0] : String(error)}`;
289
+ }
290
+ if (!res.destroyed) res.end("data: [DONE]\n\n");
291
+ return `${model} stream`;
292
+ }
293
+ const result = schema === void 0 ? await client.generate(messages, call) : await client.generate(messages, { ...call, schema });
294
+ const calls = result.finishReason === "tool-calls" ? openAIToolCalls(result.toolCalls) : [];
295
+ send(200, {
296
+ id,
297
+ object: "chat.completion",
298
+ created,
299
+ model,
300
+ choices: [
301
+ {
302
+ index: 0,
303
+ message: {
304
+ role: "assistant",
305
+ content: result.finishReason === "tool-calls" ? null : result.text,
306
+ ...calls.length > 0 ? { tool_calls: calls.map(({ index: _i, ...rest }) => rest) } : {},
307
+ refusal: null
308
+ },
309
+ finish_reason: finishReason(result),
310
+ logprobs: null
311
+ }
312
+ ],
313
+ usage: _nullishCoalesce(usageOf(result), () => ( { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 }))
314
+ });
315
+ return model;
316
+ } finally {
317
+ await images.dispose();
318
+ }
319
+ };
320
+ route().then((what) => {
321
+ if (what !== void 0) _optionalChain([log, 'optionalCall', _13 => _13(`${req.method} ${req.url} ${what} ${res.statusCode} ${Date.now() - started}ms`)]);
322
+ }).catch((error) => {
323
+ const { status, body } = errorResponse(error);
324
+ if (error instanceof _chunkNRDZIP5Gcjs.ModelBusyError && !res.headersSent) res.setHeader("retry-after", "2");
325
+ send(status, body);
326
+ _optionalChain([log, 'optionalCall', _14 => _14(`${req.method} ${req.url} ${status} ${Date.now() - started}ms ${error instanceof Error ? error.message.split("\n")[0] : ""}`)]);
327
+ });
328
+ });
329
+ server.on("close", () => {
330
+ for (const client of clients.values()) client.close();
331
+ });
332
+ return server;
333
+ }
334
+ async function serve(options = {}) {
335
+ const server = createServer(options);
336
+ const port = _nullishCoalesce(options.port, () => ( DEFAULT_PORT));
337
+ const host = _nullishCoalesce(options.host, () => ( "127.0.0.1"));
338
+ await new Promise((resolve, reject) => {
339
+ server.once("error", reject);
340
+ server.listen(port, host, () => {
341
+ server.off("error", reject);
342
+ resolve();
343
+ });
344
+ });
345
+ const address = server.address();
346
+ const bound = typeof address === "object" && address !== null ? address.port : port;
347
+ return {
348
+ server,
349
+ url: `http://${host === "0.0.0.0" ? "127.0.0.1" : host}:${bound}/v1`,
350
+ close: () => new Promise((resolve) => {
351
+ server.close(() => resolve());
352
+ _optionalChain([server, 'access', _15 => _15.closeAllConnections, 'optionalCall', _16 => _16()]);
353
+ })
354
+ };
355
+ }
356
+
357
+
358
+
359
+
360
+
361
+ exports.DEFAULT_PORT = DEFAULT_PORT; exports.MODELS = MODELS; exports.createServer = createServer; exports.serve = serve;
@@ -0,0 +1,51 @@
1
+ import http from 'node:http';
2
+ import { A as AppleLLMOptions } from './client-CXewzZTj.cjs';
3
+ import 'node:child_process';
4
+
5
+ /**
6
+ * An OpenAI-compatible HTTP server for Apple's models.
7
+ *
8
+ * apple-llm serve # http://127.0.0.1:11436/v1
9
+ *
10
+ * Point any OpenAI client at it — the official SDKs, LangChain, LlamaIndex,
11
+ * editor plugins, Open WebUI — with any API key, and it talks to the on-device
12
+ * model. Implements `POST /v1/chat/completions` (streaming, tools,
13
+ * `response_format` with JSON Schema, images), `GET /v1/models` and
14
+ * `GET /health`.
15
+ *
16
+ * Safe defaults, because a local model server is reachable by more than you
17
+ * might think: it binds to 127.0.0.1 only; CORS is off, so a web page you
18
+ * happen to visit cannot use your model; an API key can be required; and the
19
+ * default tier is the device, so nothing leaves the machine unless a client
20
+ * asks for the `apple-private-cloud` model by name.
21
+ */
22
+
23
+ declare const DEFAULT_PORT = 11436;
24
+ /** Model ids a client can ask for. Anything else gets the server's default tier. */
25
+ declare const MODELS: {
26
+ readonly 'apple-on-device': "device";
27
+ readonly 'apple-private-cloud': "cloud";
28
+ };
29
+ interface ServerOptions extends Omit<AppleLLMOptions, 'tier'> {
30
+ port?: number;
31
+ /** Interface to bind. Default 127.0.0.1; use 0.0.0.0 only on a network you trust. */
32
+ host?: string;
33
+ /** Tier for requests that do not name an Apple model. Default `device`. */
34
+ tier?: 'device' | 'cloud' | 'auto';
35
+ /** Require `Authorization: Bearer <apiKey>`. */
36
+ apiKey?: string;
37
+ /** `Access-Control-Allow-Origin` value, for browser clients. Off by default. */
38
+ cors?: string;
39
+ /** One line per request. Default: none. */
40
+ log?: (line: string) => void;
41
+ }
42
+ /** Build the server without listening. `serve()` is the one-liner. */
43
+ declare function createServer(options?: ServerOptions): http.Server;
44
+ /** Start listening. Resolves once the port is bound. */
45
+ declare function serve(options?: ServerOptions): Promise<{
46
+ server: http.Server;
47
+ url: string;
48
+ close: () => Promise<void>;
49
+ }>;
50
+
51
+ export { DEFAULT_PORT, MODELS, type ServerOptions, createServer, serve };
@@ -0,0 +1,51 @@
1
+ import http from 'node:http';
2
+ import { A as AppleLLMOptions } from './client-CXewzZTj.js';
3
+ import 'node:child_process';
4
+
5
+ /**
6
+ * An OpenAI-compatible HTTP server for Apple's models.
7
+ *
8
+ * apple-llm serve # http://127.0.0.1:11436/v1
9
+ *
10
+ * Point any OpenAI client at it — the official SDKs, LangChain, LlamaIndex,
11
+ * editor plugins, Open WebUI — with any API key, and it talks to the on-device
12
+ * model. Implements `POST /v1/chat/completions` (streaming, tools,
13
+ * `response_format` with JSON Schema, images), `GET /v1/models` and
14
+ * `GET /health`.
15
+ *
16
+ * Safe defaults, because a local model server is reachable by more than you
17
+ * might think: it binds to 127.0.0.1 only; CORS is off, so a web page you
18
+ * happen to visit cannot use your model; an API key can be required; and the
19
+ * default tier is the device, so nothing leaves the machine unless a client
20
+ * asks for the `apple-private-cloud` model by name.
21
+ */
22
+
23
+ declare const DEFAULT_PORT = 11436;
24
+ /** Model ids a client can ask for. Anything else gets the server's default tier. */
25
+ declare const MODELS: {
26
+ readonly 'apple-on-device': "device";
27
+ readonly 'apple-private-cloud': "cloud";
28
+ };
29
+ interface ServerOptions extends Omit<AppleLLMOptions, 'tier'> {
30
+ port?: number;
31
+ /** Interface to bind. Default 127.0.0.1; use 0.0.0.0 only on a network you trust. */
32
+ host?: string;
33
+ /** Tier for requests that do not name an Apple model. Default `device`. */
34
+ tier?: 'device' | 'cloud' | 'auto';
35
+ /** Require `Authorization: Bearer <apiKey>`. */
36
+ apiKey?: string;
37
+ /** `Access-Control-Allow-Origin` value, for browser clients. Off by default. */
38
+ cors?: string;
39
+ /** One line per request. Default: none. */
40
+ log?: (line: string) => void;
41
+ }
42
+ /** Build the server without listening. `serve()` is the one-liner. */
43
+ declare function createServer(options?: ServerOptions): http.Server;
44
+ /** Start listening. Resolves once the port is bound. */
45
+ declare function serve(options?: ServerOptions): Promise<{
46
+ server: http.Server;
47
+ url: string;
48
+ close: () => Promise<void>;
49
+ }>;
50
+
51
+ export { DEFAULT_PORT, MODELS, type ServerOptions, createServer, serve };