@agentick/apple 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +290 -0
- package/dist/.tsbuildinfo.build +1 -0
- package/dist/apple-model.d.ts +23 -0
- package/dist/apple-model.d.ts.map +1 -0
- package/dist/apple-model.jsx +23 -0
- package/dist/apple-model.jsx.map +1 -0
- package/dist/apple.d.ts +43 -0
- package/dist/apple.d.ts.map +1 -0
- package/dist/apple.js +296 -0
- package/dist/apple.js.map +1 -0
- package/dist/index.d.ts +37 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +36 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +87 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +12 -0
- package/dist/types.js.map +1 -0
- package/inference.swift +444 -0
- package/package.json +50 -0
- package/src/index.ts +36 -0
package/inference.swift
ADDED
|
@@ -0,0 +1,444 @@
|
|
|
1
|
+
/// Apple Foundation Models Bridge
|
|
2
|
+
///
|
|
3
|
+
/// A CLI executable that wraps Apple's on-device Foundation Models framework
|
|
4
|
+
/// with a JSON wire protocol compatible with agentick's adapter interface.
|
|
5
|
+
///
|
|
6
|
+
/// Wire protocol:
|
|
7
|
+
/// Input: JSON on stdin (subset of ModelInput)
|
|
8
|
+
/// Output: JSON on stdout (ModelOutput for non-streaming, NDJSON AdapterDeltas for streaming)
|
|
9
|
+
///
|
|
10
|
+
/// Build:
|
|
11
|
+
/// swiftc -parse-as-library -framework FoundationModels inference.swift -o apple-fm-bridge
|
|
12
|
+
///
|
|
13
|
+
/// Usage:
|
|
14
|
+
/// echo '{"messages":[{"role":"user","content":"Hello"}],"stream":false}' | ./apple-fm-bridge
|
|
15
|
+
|
|
16
|
+
import Foundation
|
|
17
|
+
import FoundationModels
|
|
18
|
+
|
|
19
|
+
// ============================================================================
|
|
20
|
+
// Wire Protocol Types (aligned with @agentick/shared)
|
|
21
|
+
// ============================================================================
|
|
22
|
+
|
|
23
|
+
// --- Input ---
|
|
24
|
+
|
|
25
|
+
struct BridgeInput: Decodable {
|
|
26
|
+
let messages: [WireMessage]
|
|
27
|
+
let system: String?
|
|
28
|
+
let temperature: Double?
|
|
29
|
+
let maxTokens: Int?
|
|
30
|
+
let stream: Bool?
|
|
31
|
+
let responseFormat: ResponseFormat?
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
struct ResponseFormat: Decodable {
|
|
35
|
+
let type: String
|
|
36
|
+
let schema: JsonSchema?
|
|
37
|
+
let name: String?
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
struct JsonSchema: Decodable {
|
|
41
|
+
let type: String
|
|
42
|
+
let description: String?
|
|
43
|
+
let properties: [String: SchemaProperty]?
|
|
44
|
+
let items: SchemaProperty? // For array schemas
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Use indirect enum to handle recursion
|
|
48
|
+
indirect enum SchemaProperty: Decodable {
|
|
49
|
+
case leaf(type: String, description: String?)
|
|
50
|
+
case object(type: String, description: String?, properties: [String: SchemaProperty])
|
|
51
|
+
case array(type: String, description: String?, items: SchemaProperty)
|
|
52
|
+
|
|
53
|
+
enum CodingKeys: String, CodingKey {
|
|
54
|
+
case type, description, properties, items
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
init(from decoder: Decoder) throws {
|
|
58
|
+
let container = try decoder.container(keyedBy: CodingKeys.self)
|
|
59
|
+
let type = try container.decode(String.self, forKey: .type)
|
|
60
|
+
let description = try container.decodeIfPresent(String.self, forKey: .description)
|
|
61
|
+
|
|
62
|
+
if type == "object" {
|
|
63
|
+
let properties = try container.decodeIfPresent([String: SchemaProperty].self, forKey: .properties) ?? [:]
|
|
64
|
+
self = .object(type: type, description: description, properties: properties)
|
|
65
|
+
} else if type == "array" {
|
|
66
|
+
let items = try container.decode(SchemaProperty.self, forKey: .items)
|
|
67
|
+
self = .array(type: type, description: description, items: items)
|
|
68
|
+
} else {
|
|
69
|
+
self = .leaf(type: type, description: description)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
var propertyType: String {
|
|
74
|
+
switch self {
|
|
75
|
+
case .leaf(let type, _): return type
|
|
76
|
+
case .object(let type, _, _): return type
|
|
77
|
+
case .array(let type, _, _): return type
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
var propertyDescription: String? {
|
|
82
|
+
switch self {
|
|
83
|
+
case .leaf(_, let desc): return desc
|
|
84
|
+
case .object(_, let desc, _): return desc
|
|
85
|
+
case .array(_, let desc, _): return desc
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
struct WireMessage: Decodable {
|
|
91
|
+
let role: String
|
|
92
|
+
let content: MessageContent
|
|
93
|
+
|
|
94
|
+
enum MessageContent: Decodable {
|
|
95
|
+
case text(String)
|
|
96
|
+
case blocks([ContentBlock])
|
|
97
|
+
|
|
98
|
+
init(from decoder: Decoder) throws {
|
|
99
|
+
let container = try decoder.singleValueContainer()
|
|
100
|
+
if let str = try? container.decode(String.self) {
|
|
101
|
+
self = .text(str)
|
|
102
|
+
} else {
|
|
103
|
+
self = .blocks(try container.decode([ContentBlock].self))
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
var text: String {
|
|
108
|
+
switch self {
|
|
109
|
+
case .text(let str): return str
|
|
110
|
+
case .blocks(let blocks):
|
|
111
|
+
return blocks
|
|
112
|
+
.filter { $0.type == "text" }
|
|
113
|
+
.compactMap { $0.text }
|
|
114
|
+
.joined(separator: "\n")
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
struct ContentBlock: Decodable {
|
|
120
|
+
let type: String
|
|
121
|
+
let text: String?
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// --- Output (non-streaming, matches ModelOutput) ---
|
|
126
|
+
|
|
127
|
+
struct BridgeOutput: Encodable {
|
|
128
|
+
let model: String
|
|
129
|
+
let createdAt: String
|
|
130
|
+
let message: OutputMessage
|
|
131
|
+
let stopReason: String
|
|
132
|
+
let usage: WireUsage
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
struct OutputMessage: Encodable {
|
|
136
|
+
let role: String
|
|
137
|
+
let content: [OutputContentBlock]
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
struct OutputContentBlock: Encodable {
|
|
141
|
+
let type: String
|
|
142
|
+
let text: String
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
struct WireUsage: Encodable {
|
|
146
|
+
let inputTokens: Int
|
|
147
|
+
let outputTokens: Int
|
|
148
|
+
let totalTokens: Int
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// --- Output (streaming, matches AdapterDelta) ---
|
|
152
|
+
|
|
153
|
+
struct TextDelta: Encodable {
|
|
154
|
+
let type = "text"
|
|
155
|
+
let delta: String
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
struct MessageEnd: Encodable {
|
|
159
|
+
let type = "message_end"
|
|
160
|
+
let stopReason: String
|
|
161
|
+
let usage: WireUsage
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
struct ErrorOutput: Encodable {
|
|
165
|
+
let type = "error"
|
|
166
|
+
let error: String
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// ============================================================================
|
|
170
|
+
// Bridge
|
|
171
|
+
// ============================================================================
|
|
172
|
+
|
|
173
|
+
@main
|
|
174
|
+
struct AppleFoundationBridge {
|
|
175
|
+
static let modelId = "apple-foundation-3b"
|
|
176
|
+
|
|
177
|
+
static func main() async {
|
|
178
|
+
let encoder = JSONEncoder()
|
|
179
|
+
encoder.outputFormatting = [.sortedKeys]
|
|
180
|
+
|
|
181
|
+
do {
|
|
182
|
+
try await run(encoder: encoder)
|
|
183
|
+
} catch {
|
|
184
|
+
writeJSON(ErrorOutput(error: String(describing: error)), encoder: encoder)
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
static func run(encoder: JSONEncoder) async throws {
|
|
189
|
+
let inputData = FileHandle.standardInput.readDataToEndOfFile()
|
|
190
|
+
guard !inputData.isEmpty else {
|
|
191
|
+
writeJSON(ErrorOutput(error: "No input on stdin"), encoder: encoder)
|
|
192
|
+
return
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
let input = try JSONDecoder().decode(BridgeInput.self, from: inputData)
|
|
196
|
+
|
|
197
|
+
// Check availability
|
|
198
|
+
let model = SystemLanguageModel.default
|
|
199
|
+
guard model.availability == .available else {
|
|
200
|
+
writeJSON(
|
|
201
|
+
ErrorOutput(error: "Model not available: \(model.availability)"),
|
|
202
|
+
encoder: encoder
|
|
203
|
+
)
|
|
204
|
+
return
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Extract system prompt: explicit field takes precedence, then system-role messages
|
|
208
|
+
let systemPrompt = input.system ?? input.messages
|
|
209
|
+
.filter { $0.role == "system" }
|
|
210
|
+
.map { $0.content.text }
|
|
211
|
+
.joined(separator: "\n")
|
|
212
|
+
|
|
213
|
+
// Build conversation prompt from non-system messages
|
|
214
|
+
let prompt = buildPrompt(from: input.messages.filter { $0.role != "system" })
|
|
215
|
+
|
|
216
|
+
guard !prompt.isEmpty else {
|
|
217
|
+
writeJSON(ErrorOutput(error: "No user messages provided"), encoder: encoder)
|
|
218
|
+
return
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
let session = systemPrompt.isEmpty
|
|
222
|
+
? LanguageModelSession()
|
|
223
|
+
: LanguageModelSession(instructions: systemPrompt)
|
|
224
|
+
|
|
225
|
+
// Check if structured output is requested
|
|
226
|
+
if let responseFormat = input.responseFormat, responseFormat.type == "json_schema" {
|
|
227
|
+
if input.stream == true {
|
|
228
|
+
writeJSON(ErrorOutput(error: "Streaming not supported with json_schema response format"), encoder: encoder)
|
|
229
|
+
return
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
guard let schema = responseFormat.schema else {
|
|
233
|
+
writeJSON(ErrorOutput(error: "json_schema requires schema field"), encoder: encoder)
|
|
234
|
+
return
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
try await generateStructuredResponse(
|
|
238
|
+
session: session,
|
|
239
|
+
prompt: prompt,
|
|
240
|
+
schema: schema,
|
|
241
|
+
encoder: encoder
|
|
242
|
+
)
|
|
243
|
+
} else if input.stream == true {
|
|
244
|
+
try await streamResponse(session: session, prompt: prompt, encoder: encoder)
|
|
245
|
+
} else {
|
|
246
|
+
try await generateResponse(session: session, prompt: prompt, encoder: encoder)
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// MARK: - Non-streaming
|
|
251
|
+
|
|
252
|
+
static func generateResponse(
|
|
253
|
+
session: LanguageModelSession,
|
|
254
|
+
prompt: String,
|
|
255
|
+
encoder: JSONEncoder
|
|
256
|
+
) async throws {
|
|
257
|
+
let response = try await session.respond(to: prompt)
|
|
258
|
+
let now = ISO8601DateFormatter().string(from: Date())
|
|
259
|
+
|
|
260
|
+
let output = BridgeOutput(
|
|
261
|
+
model: modelId,
|
|
262
|
+
createdAt: now,
|
|
263
|
+
message: OutputMessage(
|
|
264
|
+
role: "assistant",
|
|
265
|
+
content: [OutputContentBlock(type: "text", text: response.content)]
|
|
266
|
+
),
|
|
267
|
+
stopReason: "stop",
|
|
268
|
+
usage: WireUsage(inputTokens: 0, outputTokens: 0, totalTokens: 0)
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
writeJSON(output, encoder: encoder)
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// MARK: - Streaming
|
|
275
|
+
|
|
276
|
+
static func streamResponse(
|
|
277
|
+
session: LanguageModelSession,
|
|
278
|
+
prompt: String,
|
|
279
|
+
encoder: JSONEncoder
|
|
280
|
+
) async throws {
|
|
281
|
+
let stream = session.streamResponse(to: prompt)
|
|
282
|
+
var emitted = 0
|
|
283
|
+
|
|
284
|
+
for try await partial in stream {
|
|
285
|
+
let content = partial.content
|
|
286
|
+
if content.count > emitted {
|
|
287
|
+
let startIdx = content.index(content.startIndex, offsetBy: emitted)
|
|
288
|
+
let newText = String(content[startIdx...])
|
|
289
|
+
writeLine(TextDelta(delta: newText), encoder: encoder)
|
|
290
|
+
emitted = content.count
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
writeLine(
|
|
295
|
+
MessageEnd(
|
|
296
|
+
stopReason: "stop",
|
|
297
|
+
usage: WireUsage(inputTokens: 0, outputTokens: 0, totalTokens: 0)
|
|
298
|
+
),
|
|
299
|
+
encoder: encoder
|
|
300
|
+
)
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// MARK: - Structured Output
|
|
304
|
+
|
|
305
|
+
static func generateStructuredResponse(
|
|
306
|
+
session: LanguageModelSession,
|
|
307
|
+
prompt: String,
|
|
308
|
+
schema: JsonSchema,
|
|
309
|
+
encoder: JSONEncoder
|
|
310
|
+
) async throws {
|
|
311
|
+
// Convert JSON Schema to DynamicGenerationSchema
|
|
312
|
+
let dynamicSchema = try buildDynamicSchema(from: schema)
|
|
313
|
+
let generationSchema = try GenerationSchema(root: dynamicSchema, dependencies: [])
|
|
314
|
+
|
|
315
|
+
// Generate with schema
|
|
316
|
+
let response = try await session.respond(to: prompt, schema: generationSchema)
|
|
317
|
+
let now = ISO8601DateFormatter().string(from: Date())
|
|
318
|
+
|
|
319
|
+
// Get JSON string directly from GeneratedContent
|
|
320
|
+
let jsonContent = response.content.jsonString
|
|
321
|
+
|
|
322
|
+
let output = BridgeOutput(
|
|
323
|
+
model: modelId,
|
|
324
|
+
createdAt: now,
|
|
325
|
+
message: OutputMessage(
|
|
326
|
+
role: "assistant",
|
|
327
|
+
content: [OutputContentBlock(type: "text", text: jsonContent)]
|
|
328
|
+
),
|
|
329
|
+
stopReason: "stop",
|
|
330
|
+
usage: WireUsage(inputTokens: 0, outputTokens: 0, totalTokens: 0)
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
writeJSON(output, encoder: encoder)
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
static func buildDynamicSchema(from jsonSchema: JsonSchema) throws -> DynamicGenerationSchema {
|
|
337
|
+
guard jsonSchema.type == "object" else {
|
|
338
|
+
throw NSError(domain: "AppleFMBridge", code: 1, userInfo: [
|
|
339
|
+
NSLocalizedDescriptionKey: "Only object schemas are supported at root level"
|
|
340
|
+
])
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
guard let properties = jsonSchema.properties else {
|
|
344
|
+
throw NSError(domain: "AppleFMBridge", code: 2, userInfo: [
|
|
345
|
+
NSLocalizedDescriptionKey: "Object schema must have properties"
|
|
346
|
+
])
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
let dynamicProperties = try properties.map { (key, prop) -> DynamicGenerationSchema.Property in
|
|
350
|
+
DynamicGenerationSchema.Property(
|
|
351
|
+
name: key,
|
|
352
|
+
description: prop.propertyDescription,
|
|
353
|
+
schema: try buildPropertySchema(from: prop)
|
|
354
|
+
)
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
return DynamicGenerationSchema(
|
|
358
|
+
name: jsonSchema.description ?? "response",
|
|
359
|
+
description: jsonSchema.description,
|
|
360
|
+
properties: dynamicProperties
|
|
361
|
+
)
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
static func buildPropertySchema(from prop: SchemaProperty) throws -> DynamicGenerationSchema {
|
|
365
|
+
switch prop {
|
|
366
|
+
case .leaf(let type, _):
|
|
367
|
+
switch type {
|
|
368
|
+
case "string":
|
|
369
|
+
return DynamicGenerationSchema(type: String.self)
|
|
370
|
+
case "integer":
|
|
371
|
+
return DynamicGenerationSchema(type: Int.self)
|
|
372
|
+
case "number":
|
|
373
|
+
return DynamicGenerationSchema(type: Double.self)
|
|
374
|
+
case "boolean":
|
|
375
|
+
return DynamicGenerationSchema(type: Bool.self)
|
|
376
|
+
default:
|
|
377
|
+
throw NSError(domain: "AppleFMBridge", code: 5, userInfo: [
|
|
378
|
+
NSLocalizedDescriptionKey: "Unsupported property type: \(type)"
|
|
379
|
+
])
|
|
380
|
+
}
|
|
381
|
+
case .object(_, let description, let nestedProps):
|
|
382
|
+
let dynamicProperties = try nestedProps.map { (key, nestedProp) -> DynamicGenerationSchema.Property in
|
|
383
|
+
DynamicGenerationSchema.Property(
|
|
384
|
+
name: key,
|
|
385
|
+
description: nestedProp.propertyDescription,
|
|
386
|
+
schema: try buildPropertySchema(from: nestedProp)
|
|
387
|
+
)
|
|
388
|
+
}
|
|
389
|
+
return DynamicGenerationSchema(
|
|
390
|
+
name: description ?? "nested",
|
|
391
|
+
description: description,
|
|
392
|
+
properties: dynamicProperties
|
|
393
|
+
)
|
|
394
|
+
case .array(_, _, _):
|
|
395
|
+
// Arrays are not properly supported in DynamicGenerationSchema
|
|
396
|
+
// The API exists but the correct pattern isn't documented
|
|
397
|
+
// For now, recommend using comma-separated strings or numbered properties
|
|
398
|
+
throw NSError(domain: "AppleFMBridge", code: 6, userInfo: [
|
|
399
|
+
NSLocalizedDescriptionKey: "Array types are not supported. Use comma-separated strings or numbered object properties instead."
|
|
400
|
+
])
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// MARK: - Prompt Construction
|
|
405
|
+
|
|
406
|
+
/// Flatten conversation messages into a single prompt string.
|
|
407
|
+
/// Foundation Models doesn't have a native multi-turn message API,
|
|
408
|
+
/// so we format the conversation as structured text.
|
|
409
|
+
static func buildPrompt(from messages: [WireMessage]) -> String {
|
|
410
|
+
if messages.count == 1 {
|
|
411
|
+
return messages[0].content.text
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
var parts: [String] = []
|
|
415
|
+
for msg in messages {
|
|
416
|
+
let text = msg.content.text
|
|
417
|
+
switch msg.role {
|
|
418
|
+
case "user":
|
|
419
|
+
parts.append("User: \(text)")
|
|
420
|
+
case "assistant":
|
|
421
|
+
parts.append("Assistant: \(text)")
|
|
422
|
+
case "tool_result":
|
|
423
|
+
parts.append("Tool Result: \(text)")
|
|
424
|
+
default:
|
|
425
|
+
parts.append("\(msg.role): \(text)")
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
return parts.joined(separator: "\n\n")
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// MARK: - Output Helpers
|
|
432
|
+
|
|
433
|
+
static func writeJSON<T: Encodable>(_ value: T, encoder: JSONEncoder) {
|
|
434
|
+
guard let data = try? encoder.encode(value) else { return }
|
|
435
|
+
FileHandle.standardOutput.write(data)
|
|
436
|
+
FileHandle.standardOutput.write("\n".data(using: .utf8)!)
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
static func writeLine<T: Encodable>(_ value: T, encoder: JSONEncoder) {
|
|
440
|
+
guard let data = try? encoder.encode(value) else { return }
|
|
441
|
+
FileHandle.standardOutput.write(data)
|
|
442
|
+
FileHandle.standardOutput.write("\n".data(using: .utf8)!)
|
|
443
|
+
}
|
|
444
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@agentick/apple",
|
|
3
|
+
"version": "0.7.0",
|
|
4
|
+
"description": "Apple Foundation Models adapter for Agentick — on-device inference via macOS 26+",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"adapter",
|
|
7
|
+
"agent",
|
|
8
|
+
"ai",
|
|
9
|
+
"apple",
|
|
10
|
+
"foundation-models",
|
|
11
|
+
"on-device"
|
|
12
|
+
],
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"author": "Ryan Lindgren",
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "git+https://github.com/agenticklabs/agentick.git",
|
|
18
|
+
"directory": "packages/adapters/apple"
|
|
19
|
+
},
|
|
20
|
+
"bin": {
|
|
21
|
+
"apple-fm-bridge": "bin/apple-fm-bridge"
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"dist",
|
|
25
|
+
"inference.swift"
|
|
26
|
+
],
|
|
27
|
+
"os": [
|
|
28
|
+
"darwin"
|
|
29
|
+
],
|
|
30
|
+
"type": "module",
|
|
31
|
+
"main": "src/index.ts",
|
|
32
|
+
"publishConfig": {
|
|
33
|
+
"access": "public",
|
|
34
|
+
"main": "./dist/index.js",
|
|
35
|
+
"types": "./dist/index.d.ts"
|
|
36
|
+
},
|
|
37
|
+
"scripts": {
|
|
38
|
+
"postinstall": "node scripts/compile-bridge.mjs",
|
|
39
|
+
"build": "tsc -p tsconfig.build.json",
|
|
40
|
+
"test": "echo \"Tests run from workspace root\"",
|
|
41
|
+
"typecheck": "tsc -p tsconfig.build.json --noEmit",
|
|
42
|
+
"clean": "rm -rf dist bin tsconfig.build.tsbuildinfo",
|
|
43
|
+
"prepublishOnly": "pnpm build",
|
|
44
|
+
"dev": "tsc --watch"
|
|
45
|
+
},
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"@agentick/core": "workspace:*",
|
|
48
|
+
"@agentick/shared": "workspace:*"
|
|
49
|
+
}
|
|
50
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* # Agentick Apple Foundation Models Adapter
|
|
3
|
+
*
|
|
4
|
+
* On-device inference via Apple's Foundation Models framework (macOS 26+).
|
|
5
|
+
* Uses a compiled Swift bridge executable for communication.
|
|
6
|
+
*
|
|
7
|
+
* ## Features
|
|
8
|
+
*
|
|
9
|
+
* - **On-Device** — ~3B parameter model running locally on Apple Silicon
|
|
10
|
+
* - **Streaming** — Full streaming support via NDJSON
|
|
11
|
+
* - **Vision** — Multimodal input (text + images)
|
|
12
|
+
* - **Private** — All inference on-device, no network required
|
|
13
|
+
* - **Free** — No API keys or usage costs
|
|
14
|
+
*
|
|
15
|
+
* ## Prerequisites
|
|
16
|
+
*
|
|
17
|
+
* 1. macOS 26+ (Tahoe) with Apple Intelligence enabled
|
|
18
|
+
* 2. Compile the Swift bridge:
|
|
19
|
+
* ```bash
|
|
20
|
+
* swiftc -parse-as-library -framework FoundationModels inference.swift -o apple-fm-bridge
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* ## Quick Start
|
|
24
|
+
*
|
|
25
|
+
* ```typescript
|
|
26
|
+
* import { apple } from '@agentick/apple';
|
|
27
|
+
*
|
|
28
|
+
* const model = apple({ bridgePath: './apple-fm-bridge' });
|
|
29
|
+
* const app = createApp(Agent, { model });
|
|
30
|
+
* ```
|
|
31
|
+
*
|
|
32
|
+
* @module @agentick/apple
|
|
33
|
+
*/
|
|
34
|
+
export * from "./apple-model";
|
|
35
|
+
export * from "./apple";
|
|
36
|
+
export type { AppleAdapterConfig, BridgeInput, BridgeOutput, BridgeChunk } from "./types";
|