synthux-mcp 2.1.0 → 2.1.1
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 +7 -1
- package/apple-fm-bridge.swift +277 -0
- package/dist/apple-fm-bridge.swift +277 -0
- package/dist/cli.js +600 -12
- package/package.json +2 -1
- package/server.js +377 -12
package/README.md
CHANGED
|
@@ -8,7 +8,13 @@ Connect desktop AI coding agents (**Google Antigravity**, **Claude Desktop / Cla
|
|
|
8
8
|
- **Live Chrome Extension Bridge:** Connects to the synthux MV3 Chrome Extension over local WebSocket (`ws://127.0.0.1:17483/ws`).
|
|
9
9
|
- **Two AI Evaluation Modes:**
|
|
10
10
|
1. **Agent-Brain Mode (Passthrough):** Your connected MCP Agent (e.g., Antigravity Gemini 4 Argon, Claude, Codex) uses `synthux_prepare_external_audit` to scan the live DOM, run `axe-core` WCAG 2.2 AA checks, and get Sectoral Rule Pack prompts — evaluates them with its own intelligence — and calls `synthux_submit_external_audit` to render live page heatmaps, annotated screenshots, and Enhanced v2.0 Markdown/JSON reports.
|
|
11
|
-
2. **Extension AI Engine Mode:** Trigger `synthux_run_ux_audit` to run the audit using the extension's configured **Local LLM (Ollama: `gemma4`, `qwen3.8`, `deepseek-r1`)** or **Cloud BYOK API (`Gemini`, `OpenAI`, `Claude`)**.
|
|
11
|
+
2. **Extension AI Engine Mode:** Trigger `synthux_run_ux_audit` to run the audit using the extension's configured **Local LLM (Ollama: `gemma4`, `qwen3.8`, `deepseek-r1`)**, ** Apple Intelligence (macOS 26/27 `FoundationModels`)**, **Chrome Built-in AI (`window.LanguageModel`)**, or **Cloud BYOK API (`Gemini`, `OpenAI`, `Claude`)**.
|
|
12
|
+
- **Native macOS 26/27 Apple Intelligence Bridge (`mcp/apple-fm-bridge.swift`):**
|
|
13
|
+
- Automatically compiles and caches a native Swift bridge to Apple's `FoundationModels.framework` (`SystemLanguageModel.default` & `PrivateCloudComputeLanguageModel` on macOS 27+).
|
|
14
|
+
- Exposes local HTTP endpoints on `http://127.0.0.1:17483`:
|
|
15
|
+
- `GET /apple-fm/status` & `POST /apple-fm/generate` (Native synthux Apple Intelligence provider)
|
|
16
|
+
- `GET /api/tags`, `POST /api/show`, `POST /api/generate` (Ollama-compatible endpoints)
|
|
17
|
+
- `GET /v1/models`, `POST /v1/chat/completions` (OpenAI-compatible endpoints)
|
|
12
18
|
- **Privacy & Security Hardened:**
|
|
13
19
|
- Strictly bound to `127.0.0.1` loopback (never exposed to LAN/`0.0.0.0`).
|
|
14
20
|
- Strict Origin Verification blocks all `http://` and `https://` web page origins from accessing `/ws` or `/mcp` (CSRF / WebSocket hijacking protection).
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
#!/usr/bin/env swift
|
|
2
|
+
/**
|
|
3
|
+
* synthux — Apple Foundation Models (macOS 26 / macOS 27+) Local Bridge
|
|
4
|
+
*
|
|
5
|
+
* Interfaces directly with Apple's native `FoundationModels` framework:
|
|
6
|
+
* - `SystemLanguageModel.default` (On-Device Apple Silicon Neural Engine model)
|
|
7
|
+
* - `SystemLanguageModel(useCase: .contentTagging)` (Fast structured tagging)
|
|
8
|
+
* - `PrivateCloudComputeLanguageModel()` (macOS 27+ Apple Private Cloud Compute)
|
|
9
|
+
*
|
|
10
|
+
* Commands:
|
|
11
|
+
* 1. `synthux-apple-fm status`
|
|
12
|
+
* Outputs JSON with availability, macOS version, context window size, and models.
|
|
13
|
+
* 2. `synthux-apple-fm generate`
|
|
14
|
+
* Reads JSON payload from stdin:
|
|
15
|
+
* { "model": "apple-on-device", "prompt": "...", "system": "...", "temperature": 0.3, "maxTokens": 2048 }
|
|
16
|
+
* Outputs JSON response to stdout:
|
|
17
|
+
* { "success": true, "model": "apple-on-device", "response": "{...}", "inputTokens": 0, "outputTokens": 0 }
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import Foundation
|
|
21
|
+
#if canImport(FoundationModels)
|
|
22
|
+
import FoundationModels
|
|
23
|
+
#endif
|
|
24
|
+
|
|
25
|
+
struct BridgeRequest: Decodable {
|
|
26
|
+
let model: String?
|
|
27
|
+
let prompt: String
|
|
28
|
+
let system: String?
|
|
29
|
+
let temperature: Double?
|
|
30
|
+
let maxTokens: Int?
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
func jsonOut(_ dict: [String: Any]) {
|
|
34
|
+
if let data = try? JSONSerialization.data(withJSONObject: dict, options: []),
|
|
35
|
+
let str = String(data: data, encoding: .utf8) {
|
|
36
|
+
FileHandle.standardOutput.write((str + "\n").data(using: .utf8)!)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
func cleanJsonResponse(_ text: String) -> String {
|
|
41
|
+
var trimmed = text.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
42
|
+
if trimmed.hasPrefix("```json") {
|
|
43
|
+
trimmed = String(trimmed.dropFirst(7))
|
|
44
|
+
} else if trimmed.hasPrefix("```") {
|
|
45
|
+
trimmed = String(trimmed.dropFirst(3))
|
|
46
|
+
}
|
|
47
|
+
if trimmed.hasSuffix("```") {
|
|
48
|
+
trimmed = String(trimmed.dropLast(3))
|
|
49
|
+
}
|
|
50
|
+
return trimmed.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
func osVersionString() -> String {
|
|
54
|
+
let v = ProcessInfo.processInfo.operatingSystemVersion
|
|
55
|
+
return "macOS \(v.majorVersion).\(v.minorVersion).\(v.patchVersion)"
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
struct SynthuxAppleFMBridge {
|
|
59
|
+
static func run() async {
|
|
60
|
+
let args = CommandLine.arguments
|
|
61
|
+
let command = args.count > 1 ? args[1] : "status"
|
|
62
|
+
|
|
63
|
+
#if canImport(FoundationModels)
|
|
64
|
+
if #available(macOS 26.0, *) {
|
|
65
|
+
if command == "status" {
|
|
66
|
+
await handleStatus()
|
|
67
|
+
return
|
|
68
|
+
} else if command == "generate" {
|
|
69
|
+
await handleGenerate()
|
|
70
|
+
return
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
#endif
|
|
74
|
+
|
|
75
|
+
jsonOut([
|
|
76
|
+
"available": false,
|
|
77
|
+
"status": "unsupported_os",
|
|
78
|
+
"reason": "Apple FoundationModels framework requires macOS 26.0 or newer (macOS 27 recommended).",
|
|
79
|
+
"osVersion": osVersionString(),
|
|
80
|
+
"models": []
|
|
81
|
+
])
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
#if canImport(FoundationModels)
|
|
85
|
+
@available(macOS 26.0, *)
|
|
86
|
+
static func handleStatus() async {
|
|
87
|
+
let defaultModel = SystemLanguageModel.default
|
|
88
|
+
var statusStr = "connected"
|
|
89
|
+
var reasonStr: String? = nil
|
|
90
|
+
var isAvailable = false
|
|
91
|
+
|
|
92
|
+
switch defaultModel.availability {
|
|
93
|
+
case .available:
|
|
94
|
+
isAvailable = true
|
|
95
|
+
statusStr = "connected"
|
|
96
|
+
case .unavailable(let reason):
|
|
97
|
+
isAvailable = false
|
|
98
|
+
switch reason {
|
|
99
|
+
case .deviceNotEligible:
|
|
100
|
+
statusStr = "device_not_eligible"
|
|
101
|
+
reasonStr = "This Mac is not eligible for Apple Intelligence (Apple Silicon M1 or newer required)."
|
|
102
|
+
case .appleIntelligenceNotEnabled:
|
|
103
|
+
statusStr = "apple_intelligence_disabled"
|
|
104
|
+
reasonStr = "Apple Intelligence is disabled. Enable it in System Settings → Apple Intelligence & Siri."
|
|
105
|
+
case .modelNotReady:
|
|
106
|
+
statusStr = "model_not_ready"
|
|
107
|
+
reasonStr = "Apple Foundation Model is still preparing or downloading in macOS System Settings."
|
|
108
|
+
@unknown default:
|
|
109
|
+
statusStr = "unavailable"
|
|
110
|
+
reasonStr = "Apple Foundation Model is currently unavailable."
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
var contextSize = 4096
|
|
115
|
+
var variantName = "Apple On-Device Foundation Model"
|
|
116
|
+
var pccAvailable = false
|
|
117
|
+
|
|
118
|
+
if #available(macOS 27.0, *) {
|
|
119
|
+
if defaultModel.contextSize > 0 {
|
|
120
|
+
contextSize = defaultModel.contextSize
|
|
121
|
+
}
|
|
122
|
+
variantName = defaultModel.variant.displayName
|
|
123
|
+
let pccModel = PrivateCloudComputeLanguageModel()
|
|
124
|
+
pccAvailable = pccModel.isAvailable
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
var models: [[String: Any]] = [
|
|
128
|
+
[
|
|
129
|
+
"id": "apple-on-device",
|
|
130
|
+
"name": "Apple Foundation Model (On-Device ANE)",
|
|
131
|
+
"size": 0
|
|
132
|
+
],
|
|
133
|
+
[
|
|
134
|
+
"id": "apple-content-tagging",
|
|
135
|
+
"name": "Apple Foundation Model (Fast Tagging)",
|
|
136
|
+
"size": 0
|
|
137
|
+
]
|
|
138
|
+
]
|
|
139
|
+
|
|
140
|
+
if #available(macOS 27.0, *) {
|
|
141
|
+
models.insert([
|
|
142
|
+
"id": "apple-pcc",
|
|
143
|
+
"name": "Apple Private Cloud Compute (macOS 27)",
|
|
144
|
+
"size": 0
|
|
145
|
+
], at: 1)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
jsonOut([
|
|
149
|
+
"available": isAvailable,
|
|
150
|
+
"status": statusStr,
|
|
151
|
+
"reason": reasonStr as Any,
|
|
152
|
+
"osVersion": osVersionString(),
|
|
153
|
+
"contextSize": contextSize,
|
|
154
|
+
"variant": variantName,
|
|
155
|
+
"pccAvailable": pccAvailable,
|
|
156
|
+
"models": models
|
|
157
|
+
])
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
@available(macOS 26.0, *)
|
|
161
|
+
static func handleGenerate() async {
|
|
162
|
+
let inputData = FileHandle.standardInput.readDataToEndOfFile()
|
|
163
|
+
guard !inputData.isEmpty,
|
|
164
|
+
let req = try? JSONDecoder().decode(BridgeRequest.self, from: inputData) else {
|
|
165
|
+
jsonOut([
|
|
166
|
+
"success": false,
|
|
167
|
+
"error": "Invalid JSON input payload for Apple Foundation Model bridge."
|
|
168
|
+
])
|
|
169
|
+
return
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
let startTime = Date()
|
|
173
|
+
let requestedModel = req.model ?? "apple-on-device"
|
|
174
|
+
let temperature = req.temperature ?? 0.3
|
|
175
|
+
let maxTokens = req.maxTokens ?? 2048
|
|
176
|
+
let options = GenerationOptions(
|
|
177
|
+
temperature: temperature,
|
|
178
|
+
maximumResponseTokens: maxTokens
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
// Trim prompt safely if it exceeds on-device context budget (~12,000 chars ≈ 3,000 tokens)
|
|
182
|
+
let maxPromptChars = requestedModel == "apple-pcc" ? 28000 : 12000
|
|
183
|
+
let safePrompt: String
|
|
184
|
+
if req.prompt.count > maxPromptChars {
|
|
185
|
+
safePrompt = String(req.prompt.prefix(maxPromptChars)) + "\n\n[DOM truncated to fit on-device context window. Respond ONLY with valid JSON.]"
|
|
186
|
+
} else {
|
|
187
|
+
safePrompt = req.prompt
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
do {
|
|
191
|
+
var responseText = ""
|
|
192
|
+
var inputTokens = max(1, safePrompt.count / 4)
|
|
193
|
+
var outputTokens = 0
|
|
194
|
+
|
|
195
|
+
if #available(macOS 27.0, *), requestedModel == "apple-pcc" {
|
|
196
|
+
let pccModel = PrivateCloudComputeLanguageModel()
|
|
197
|
+
if pccModel.isAvailable {
|
|
198
|
+
let session = LanguageModelSession(
|
|
199
|
+
model: pccModel,
|
|
200
|
+
instructions: req.system
|
|
201
|
+
)
|
|
202
|
+
let res = try await session.respond(to: safePrompt, options: options)
|
|
203
|
+
responseText = cleanJsonResponse(res.content)
|
|
204
|
+
inputTokens = res.usage.input.totalTokenCount
|
|
205
|
+
outputTokens = res.usage.output.totalTokenCount
|
|
206
|
+
} else {
|
|
207
|
+
// Fallback to on-device model if PCC is not ready
|
|
208
|
+
let onDevice = SystemLanguageModel(
|
|
209
|
+
useCase: .general,
|
|
210
|
+
guardrails: .permissiveContentTransformations
|
|
211
|
+
)
|
|
212
|
+
let session = LanguageModelSession(
|
|
213
|
+
model: onDevice,
|
|
214
|
+
instructions: req.system
|
|
215
|
+
)
|
|
216
|
+
let res = try await session.respond(to: safePrompt, options: options)
|
|
217
|
+
responseText = cleanJsonResponse(res.content)
|
|
218
|
+
inputTokens = res.usage.input.totalTokenCount
|
|
219
|
+
outputTokens = res.usage.output.totalTokenCount
|
|
220
|
+
}
|
|
221
|
+
} else {
|
|
222
|
+
let useCase: SystemLanguageModel.UseCase = (requestedModel == "apple-content-tagging")
|
|
223
|
+
? .contentTagging
|
|
224
|
+
: .general
|
|
225
|
+
let onDevice = SystemLanguageModel(
|
|
226
|
+
useCase: useCase,
|
|
227
|
+
guardrails: .permissiveContentTransformations
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
guard onDevice.isAvailable else {
|
|
231
|
+
jsonOut([
|
|
232
|
+
"success": false,
|
|
233
|
+
"error": "Apple Foundation Model is not available on this Mac (\(onDevice.availability)). Make sure Apple Intelligence is enabled in System Settings."
|
|
234
|
+
])
|
|
235
|
+
return
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
let session = LanguageModelSession(
|
|
239
|
+
model: onDevice,
|
|
240
|
+
instructions: req.system
|
|
241
|
+
)
|
|
242
|
+
let res = try await session.respond(to: safePrompt, options: options)
|
|
243
|
+
responseText = cleanJsonResponse(res.content)
|
|
244
|
+
if #available(macOS 27.0, *) {
|
|
245
|
+
inputTokens = res.usage.input.totalTokenCount
|
|
246
|
+
outputTokens = res.usage.output.totalTokenCount
|
|
247
|
+
} else {
|
|
248
|
+
outputTokens = max(1, responseText.count / 4)
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
let elapsedNs = Int64(Date().timeIntervalSince(startTime) * 1_000_000_000)
|
|
253
|
+
|
|
254
|
+
jsonOut([
|
|
255
|
+
"success": true,
|
|
256
|
+
"model": requestedModel,
|
|
257
|
+
"response": responseText,
|
|
258
|
+
"inputTokens": inputTokens,
|
|
259
|
+
"outputTokens": outputTokens,
|
|
260
|
+
"totalDuration": elapsedNs
|
|
261
|
+
])
|
|
262
|
+
} catch {
|
|
263
|
+
jsonOut([
|
|
264
|
+
"success": false,
|
|
265
|
+
"error": "Apple Foundation Model error: \(error.localizedDescription)"
|
|
266
|
+
])
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
#endif
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
Task {
|
|
273
|
+
await SynthuxAppleFMBridge.run()
|
|
274
|
+
exit(0)
|
|
275
|
+
}
|
|
276
|
+
RunLoop.main.run()
|
|
277
|
+
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
#!/usr/bin/env swift
|
|
2
|
+
/**
|
|
3
|
+
* synthux — Apple Foundation Models (macOS 26 / macOS 27+) Local Bridge
|
|
4
|
+
*
|
|
5
|
+
* Interfaces directly with Apple's native `FoundationModels` framework:
|
|
6
|
+
* - `SystemLanguageModel.default` (On-Device Apple Silicon Neural Engine model)
|
|
7
|
+
* - `SystemLanguageModel(useCase: .contentTagging)` (Fast structured tagging)
|
|
8
|
+
* - `PrivateCloudComputeLanguageModel()` (macOS 27+ Apple Private Cloud Compute)
|
|
9
|
+
*
|
|
10
|
+
* Commands:
|
|
11
|
+
* 1. `synthux-apple-fm status`
|
|
12
|
+
* Outputs JSON with availability, macOS version, context window size, and models.
|
|
13
|
+
* 2. `synthux-apple-fm generate`
|
|
14
|
+
* Reads JSON payload from stdin:
|
|
15
|
+
* { "model": "apple-on-device", "prompt": "...", "system": "...", "temperature": 0.3, "maxTokens": 2048 }
|
|
16
|
+
* Outputs JSON response to stdout:
|
|
17
|
+
* { "success": true, "model": "apple-on-device", "response": "{...}", "inputTokens": 0, "outputTokens": 0 }
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import Foundation
|
|
21
|
+
#if canImport(FoundationModels)
|
|
22
|
+
import FoundationModels
|
|
23
|
+
#endif
|
|
24
|
+
|
|
25
|
+
struct BridgeRequest: Decodable {
|
|
26
|
+
let model: String?
|
|
27
|
+
let prompt: String
|
|
28
|
+
let system: String?
|
|
29
|
+
let temperature: Double?
|
|
30
|
+
let maxTokens: Int?
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
func jsonOut(_ dict: [String: Any]) {
|
|
34
|
+
if let data = try? JSONSerialization.data(withJSONObject: dict, options: []),
|
|
35
|
+
let str = String(data: data, encoding: .utf8) {
|
|
36
|
+
FileHandle.standardOutput.write((str + "\n").data(using: .utf8)!)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
func cleanJsonResponse(_ text: String) -> String {
|
|
41
|
+
var trimmed = text.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
42
|
+
if trimmed.hasPrefix("```json") {
|
|
43
|
+
trimmed = String(trimmed.dropFirst(7))
|
|
44
|
+
} else if trimmed.hasPrefix("```") {
|
|
45
|
+
trimmed = String(trimmed.dropFirst(3))
|
|
46
|
+
}
|
|
47
|
+
if trimmed.hasSuffix("```") {
|
|
48
|
+
trimmed = String(trimmed.dropLast(3))
|
|
49
|
+
}
|
|
50
|
+
return trimmed.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
func osVersionString() -> String {
|
|
54
|
+
let v = ProcessInfo.processInfo.operatingSystemVersion
|
|
55
|
+
return "macOS \(v.majorVersion).\(v.minorVersion).\(v.patchVersion)"
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
struct SynthuxAppleFMBridge {
|
|
59
|
+
static func run() async {
|
|
60
|
+
let args = CommandLine.arguments
|
|
61
|
+
let command = args.count > 1 ? args[1] : "status"
|
|
62
|
+
|
|
63
|
+
#if canImport(FoundationModels)
|
|
64
|
+
if #available(macOS 26.0, *) {
|
|
65
|
+
if command == "status" {
|
|
66
|
+
await handleStatus()
|
|
67
|
+
return
|
|
68
|
+
} else if command == "generate" {
|
|
69
|
+
await handleGenerate()
|
|
70
|
+
return
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
#endif
|
|
74
|
+
|
|
75
|
+
jsonOut([
|
|
76
|
+
"available": false,
|
|
77
|
+
"status": "unsupported_os",
|
|
78
|
+
"reason": "Apple FoundationModels framework requires macOS 26.0 or newer (macOS 27 recommended).",
|
|
79
|
+
"osVersion": osVersionString(),
|
|
80
|
+
"models": []
|
|
81
|
+
])
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
#if canImport(FoundationModels)
|
|
85
|
+
@available(macOS 26.0, *)
|
|
86
|
+
static func handleStatus() async {
|
|
87
|
+
let defaultModel = SystemLanguageModel.default
|
|
88
|
+
var statusStr = "connected"
|
|
89
|
+
var reasonStr: String? = nil
|
|
90
|
+
var isAvailable = false
|
|
91
|
+
|
|
92
|
+
switch defaultModel.availability {
|
|
93
|
+
case .available:
|
|
94
|
+
isAvailable = true
|
|
95
|
+
statusStr = "connected"
|
|
96
|
+
case .unavailable(let reason):
|
|
97
|
+
isAvailable = false
|
|
98
|
+
switch reason {
|
|
99
|
+
case .deviceNotEligible:
|
|
100
|
+
statusStr = "device_not_eligible"
|
|
101
|
+
reasonStr = "This Mac is not eligible for Apple Intelligence (Apple Silicon M1 or newer required)."
|
|
102
|
+
case .appleIntelligenceNotEnabled:
|
|
103
|
+
statusStr = "apple_intelligence_disabled"
|
|
104
|
+
reasonStr = "Apple Intelligence is disabled. Enable it in System Settings → Apple Intelligence & Siri."
|
|
105
|
+
case .modelNotReady:
|
|
106
|
+
statusStr = "model_not_ready"
|
|
107
|
+
reasonStr = "Apple Foundation Model is still preparing or downloading in macOS System Settings."
|
|
108
|
+
@unknown default:
|
|
109
|
+
statusStr = "unavailable"
|
|
110
|
+
reasonStr = "Apple Foundation Model is currently unavailable."
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
var contextSize = 4096
|
|
115
|
+
var variantName = "Apple On-Device Foundation Model"
|
|
116
|
+
var pccAvailable = false
|
|
117
|
+
|
|
118
|
+
if #available(macOS 27.0, *) {
|
|
119
|
+
if defaultModel.contextSize > 0 {
|
|
120
|
+
contextSize = defaultModel.contextSize
|
|
121
|
+
}
|
|
122
|
+
variantName = defaultModel.variant.displayName
|
|
123
|
+
let pccModel = PrivateCloudComputeLanguageModel()
|
|
124
|
+
pccAvailable = pccModel.isAvailable
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
var models: [[String: Any]] = [
|
|
128
|
+
[
|
|
129
|
+
"id": "apple-on-device",
|
|
130
|
+
"name": "Apple Foundation Model (On-Device ANE)",
|
|
131
|
+
"size": 0
|
|
132
|
+
],
|
|
133
|
+
[
|
|
134
|
+
"id": "apple-content-tagging",
|
|
135
|
+
"name": "Apple Foundation Model (Fast Tagging)",
|
|
136
|
+
"size": 0
|
|
137
|
+
]
|
|
138
|
+
]
|
|
139
|
+
|
|
140
|
+
if #available(macOS 27.0, *) {
|
|
141
|
+
models.insert([
|
|
142
|
+
"id": "apple-pcc",
|
|
143
|
+
"name": "Apple Private Cloud Compute (macOS 27)",
|
|
144
|
+
"size": 0
|
|
145
|
+
], at: 1)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
jsonOut([
|
|
149
|
+
"available": isAvailable,
|
|
150
|
+
"status": statusStr,
|
|
151
|
+
"reason": reasonStr as Any,
|
|
152
|
+
"osVersion": osVersionString(),
|
|
153
|
+
"contextSize": contextSize,
|
|
154
|
+
"variant": variantName,
|
|
155
|
+
"pccAvailable": pccAvailable,
|
|
156
|
+
"models": models
|
|
157
|
+
])
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
@available(macOS 26.0, *)
|
|
161
|
+
static func handleGenerate() async {
|
|
162
|
+
let inputData = FileHandle.standardInput.readDataToEndOfFile()
|
|
163
|
+
guard !inputData.isEmpty,
|
|
164
|
+
let req = try? JSONDecoder().decode(BridgeRequest.self, from: inputData) else {
|
|
165
|
+
jsonOut([
|
|
166
|
+
"success": false,
|
|
167
|
+
"error": "Invalid JSON input payload for Apple Foundation Model bridge."
|
|
168
|
+
])
|
|
169
|
+
return
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
let startTime = Date()
|
|
173
|
+
let requestedModel = req.model ?? "apple-on-device"
|
|
174
|
+
let temperature = req.temperature ?? 0.3
|
|
175
|
+
let maxTokens = req.maxTokens ?? 2048
|
|
176
|
+
let options = GenerationOptions(
|
|
177
|
+
temperature: temperature,
|
|
178
|
+
maximumResponseTokens: maxTokens
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
// Trim prompt safely if it exceeds on-device context budget (~12,000 chars ≈ 3,000 tokens)
|
|
182
|
+
let maxPromptChars = requestedModel == "apple-pcc" ? 28000 : 12000
|
|
183
|
+
let safePrompt: String
|
|
184
|
+
if req.prompt.count > maxPromptChars {
|
|
185
|
+
safePrompt = String(req.prompt.prefix(maxPromptChars)) + "\n\n[DOM truncated to fit on-device context window. Respond ONLY with valid JSON.]"
|
|
186
|
+
} else {
|
|
187
|
+
safePrompt = req.prompt
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
do {
|
|
191
|
+
var responseText = ""
|
|
192
|
+
var inputTokens = max(1, safePrompt.count / 4)
|
|
193
|
+
var outputTokens = 0
|
|
194
|
+
|
|
195
|
+
if #available(macOS 27.0, *), requestedModel == "apple-pcc" {
|
|
196
|
+
let pccModel = PrivateCloudComputeLanguageModel()
|
|
197
|
+
if pccModel.isAvailable {
|
|
198
|
+
let session = LanguageModelSession(
|
|
199
|
+
model: pccModel,
|
|
200
|
+
instructions: req.system
|
|
201
|
+
)
|
|
202
|
+
let res = try await session.respond(to: safePrompt, options: options)
|
|
203
|
+
responseText = cleanJsonResponse(res.content)
|
|
204
|
+
inputTokens = res.usage.input.totalTokenCount
|
|
205
|
+
outputTokens = res.usage.output.totalTokenCount
|
|
206
|
+
} else {
|
|
207
|
+
// Fallback to on-device model if PCC is not ready
|
|
208
|
+
let onDevice = SystemLanguageModel(
|
|
209
|
+
useCase: .general,
|
|
210
|
+
guardrails: .permissiveContentTransformations
|
|
211
|
+
)
|
|
212
|
+
let session = LanguageModelSession(
|
|
213
|
+
model: onDevice,
|
|
214
|
+
instructions: req.system
|
|
215
|
+
)
|
|
216
|
+
let res = try await session.respond(to: safePrompt, options: options)
|
|
217
|
+
responseText = cleanJsonResponse(res.content)
|
|
218
|
+
inputTokens = res.usage.input.totalTokenCount
|
|
219
|
+
outputTokens = res.usage.output.totalTokenCount
|
|
220
|
+
}
|
|
221
|
+
} else {
|
|
222
|
+
let useCase: SystemLanguageModel.UseCase = (requestedModel == "apple-content-tagging")
|
|
223
|
+
? .contentTagging
|
|
224
|
+
: .general
|
|
225
|
+
let onDevice = SystemLanguageModel(
|
|
226
|
+
useCase: useCase,
|
|
227
|
+
guardrails: .permissiveContentTransformations
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
guard onDevice.isAvailable else {
|
|
231
|
+
jsonOut([
|
|
232
|
+
"success": false,
|
|
233
|
+
"error": "Apple Foundation Model is not available on this Mac (\(onDevice.availability)). Make sure Apple Intelligence is enabled in System Settings."
|
|
234
|
+
])
|
|
235
|
+
return
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
let session = LanguageModelSession(
|
|
239
|
+
model: onDevice,
|
|
240
|
+
instructions: req.system
|
|
241
|
+
)
|
|
242
|
+
let res = try await session.respond(to: safePrompt, options: options)
|
|
243
|
+
responseText = cleanJsonResponse(res.content)
|
|
244
|
+
if #available(macOS 27.0, *) {
|
|
245
|
+
inputTokens = res.usage.input.totalTokenCount
|
|
246
|
+
outputTokens = res.usage.output.totalTokenCount
|
|
247
|
+
} else {
|
|
248
|
+
outputTokens = max(1, responseText.count / 4)
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
let elapsedNs = Int64(Date().timeIntervalSince(startTime) * 1_000_000_000)
|
|
253
|
+
|
|
254
|
+
jsonOut([
|
|
255
|
+
"success": true,
|
|
256
|
+
"model": requestedModel,
|
|
257
|
+
"response": responseText,
|
|
258
|
+
"inputTokens": inputTokens,
|
|
259
|
+
"outputTokens": outputTokens,
|
|
260
|
+
"totalDuration": elapsedNs
|
|
261
|
+
])
|
|
262
|
+
} catch {
|
|
263
|
+
jsonOut([
|
|
264
|
+
"success": false,
|
|
265
|
+
"error": "Apple Foundation Model error: \(error.localizedDescription)"
|
|
266
|
+
])
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
#endif
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
Task {
|
|
273
|
+
await SynthuxAppleFMBridge.run()
|
|
274
|
+
exit(0)
|
|
275
|
+
}
|
|
276
|
+
RunLoop.main.run()
|
|
277
|
+
|