Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
141 commits
Select commit Hold shift + click to select a range
62ca999
Harden LLM command decoding
IchenDEV Jun 30, 2026
93b17cb
Handle case-insensitive LLM command fields
IchenDEV Jun 30, 2026
6f36634
Preserve preset intents with LLM metadata
IchenDEV Jun 30, 2026
8f1f967
Parse nested LLM command JSON candidates
IchenDEV Jun 30, 2026
9044d5a
Normalize LLM confidence percentages
IchenDEV Jun 30, 2026
241b6aa
Decode structured LLM replacement payloads
IchenDEV Jun 30, 2026
5206bdb
Prefer later valid LLM command candidates
IchenDEV Jun 30, 2026
856b4b3
Decode structured LLM action payloads
IchenDEV Jun 30, 2026
e7e35ac
Extract LLM JSON candidates from string fields
IchenDEV Jun 30, 2026
c89fb1a
Accept top-level LLM action aliases
IchenDEV Jun 30, 2026
5d9dfe0
Accept top-level LLM confidence aliases
IchenDEV Jun 30, 2026
3ddf6e6
Centralize LLM resolution field aliases
IchenDEV Jun 30, 2026
0d7595b
Extract structured LLM final text output
IchenDEV Jun 30, 2026
bd59b76
Parse remote LLM content blocks
IchenDEV Jun 30, 2026
8a8b796
Strip scaffolded LLM final sections
IchenDEV Jun 30, 2026
7edabaf
Recognize localized LLM final scaffolds
IchenDEV Jun 30, 2026
c3e36ff
Parse structured local ASR transcripts
IchenDEV Jun 30, 2026
120cf69
Harden LLM final text contracts
IchenDEV Jun 30, 2026
06e76ec
Extend LLM output contracts to commands
IchenDEV Jun 30, 2026
c127e73
Accept more LLM resolution aliases
IchenDEV Jun 30, 2026
2d2689a
Parse typed LLM final text blocks
IchenDEV Jun 30, 2026
e830b70
Add focused text context to voice prompts
IchenDEV Jun 30, 2026
f5c32a7
Escape focused context in voice prompts
IchenDEV Jun 30, 2026
f950ae3
Escape external voice prompt context
IchenDEV Jun 30, 2026
f8f2b63
Tolerate sparse OpenAI response choices
IchenDEV Jun 30, 2026
ea50d1d
Parse Responses output text blocks
IchenDEV Jun 30, 2026
7d259a5
Parse top-level LLM output arrays
IchenDEV Jun 30, 2026
b397486
Extract embedded LLM output arrays
IchenDEV Jun 30, 2026
d05b5c0
Parse OpenAI Responses text output
IchenDEV Jun 30, 2026
ae6d442
Harden LLM scaffold tag cleanup
IchenDEV Jun 30, 2026
84039bd
Fence selection edit memory context
IchenDEV Jun 30, 2026
ed5087e
Parse top-level LLM text block arrays
IchenDEV Jun 30, 2026
75be106
Prefer final LLM edit commands
IchenDEV Jun 30, 2026
3e213f5
Avoid copied example edit commands
IchenDEV Jun 30, 2026
902bb84
Parse remote tool call response text
IchenDEV Jun 30, 2026
8bdfb29
Parse decoded remote tool arguments
IchenDEV Jun 30, 2026
03ddbed
Parse Anthropic tool use outputs
IchenDEV Jun 30, 2026
e2ed216
Parse OpenAI parsed response payloads
IchenDEV Jun 30, 2026
c4d4173
Parse structured content object payloads
IchenDEV Jun 30, 2026
28dbcdd
Parse tool payload field variants
IchenDEV Jun 30, 2026
1a384ac
Extract final text from response wrappers
IchenDEV Jun 30, 2026
268e324
Extract final text from content wrappers
IchenDEV Jun 30, 2026
a25bdf9
Parse chunked local ASR transcripts
IchenDEV Jun 30, 2026
41e7035
Parse top-level ASR segment arrays
IchenDEV Jun 30, 2026
dc5edc2
Parse ASR alternative transcripts
IchenDEV Jun 30, 2026
e5f3c10
Parse ASR n-best transcripts
IchenDEV Jun 30, 2026
db8f044
Parse ASR channel transcripts
IchenDEV Jun 30, 2026
81615e0
Accept more local ASR transcript fields
IchenDEV Jun 30, 2026
4ecb00a
Choose confident ASR alternatives
IchenDEV Jun 30, 2026
cc9dfaa
Parse word-level ASR transcripts
IchenDEV Jun 30, 2026
9cd0b55
Decode nested final text payloads
IchenDEV Jun 30, 2026
b9baf72
Extract tool-wrapped final text
IchenDEV Jun 30, 2026
21652d6
Decode richer replacement payloads
IchenDEV Jun 30, 2026
0a14013
Decode richer edit intent payloads
IchenDEV Jun 30, 2026
71fbf8c
Decode top-level edit intent aliases
IchenDEV Jun 30, 2026
01c4225
Decode nested confidence aliases
IchenDEV Jun 30, 2026
8421cfe
Decode parsed response block payloads
IchenDEV Jun 30, 2026
992f403
Decode parsed tool argument payloads
IchenDEV Jun 30, 2026
f61af65
Prefer structured response payloads
IchenDEV Jun 30, 2026
99949f7
Prefer structured ASR transcript candidates
IchenDEV Jun 30, 2026
f16eb75
Prefer Anthropic tool payloads
IchenDEV Jun 30, 2026
1e77b15
Prefer OpenAI response payloads
IchenDEV Jun 30, 2026
33839bf
Ignore irrelevant LLM tool payloads
IchenDEV Jun 30, 2026
e52261d
Improve streaming transcript merging
IchenDEV Jun 30, 2026
0986f88
Fence personal prompt context
IchenDEV Jun 30, 2026
371e9fc
Avoid stale streaming transcripts
IchenDEV Jun 30, 2026
e2e294f
Tighten streaming overlap merging
IchenDEV Jun 30, 2026
f5a7c34
Parse structured JSON LLM blocks
IchenDEV Jun 30, 2026
40ee7a9
Ignore LLM value metadata
IchenDEV Jun 30, 2026
82baa82
Parse typed final text blocks
IchenDEV Jun 30, 2026
a2746d1
Normalize LLM block type names
IchenDEV Jun 30, 2026
df59621
Read LLM block keys case-insensitively
IchenDEV Jun 30, 2026
2680a74
Read LLM response envelope keys case-insensitively
IchenDEV Jun 30, 2026
5cdc957
Ignore non-output LLM content objects
IchenDEV Jun 30, 2026
367fad5
Skip non-object OpenAI choices
IchenDEV Jun 30, 2026
a1e2784
Accept non-array Anthropic content
IchenDEV Jun 30, 2026
ec5efd3
Prefer message tool calls in LLM responses
IchenDEV Jun 30, 2026
785e469
Parse common ASR transcript aliases
IchenDEV Jun 30, 2026
126dc86
Collapse repeated ASR transcripts
IchenDEV Jun 30, 2026
b6116ca
Parse token-level ASR transcripts
IchenDEV Jun 30, 2026
8476472
Improve ASR token joining
IchenDEV Jun 30, 2026
30adaf7
Join ASR connector tokens
IchenDEV Jun 30, 2026
5c1a25b
Normalize ASR tokenizer pieces
IchenDEV Jun 30, 2026
965a5bf
Prefer final ASR transcripts
IchenDEV Jun 30, 2026
d0ecfe3
Prefer final ASR array events
IchenDEV Jun 30, 2026
bbcdfb2
Parse ASR wrapper event outputs
IchenDEV Jun 30, 2026
abf981d
Handle ASR tokenizer status variants
IchenDEV Jun 30, 2026
cc4b3c6
Constrain command screen context facts
IchenDEV Jun 30, 2026
807aeb2
Keep edit context targets distinct
IchenDEV Jun 30, 2026
ce19432
Handle OpenAI delta response payloads
IchenDEV Jun 30, 2026
5c04f66
Parse OpenAI event stream responses
IchenDEV Jun 30, 2026
f3c4a4f
Parse Anthropic event stream responses
IchenDEV Jun 30, 2026
d5a893f
Parse OpenAI Responses event streams
IchenDEV Jun 30, 2026
1a106b2
Parse JSONL local ASR transcripts
IchenDEV Jun 30, 2026
bd4e6c7
Continue streaming word fragments
IchenDEV Jun 30, 2026
092aa65
Parse streamed OpenAI content blocks
IchenDEV Jun 30, 2026
d5c8815
Parse streamed tool argument objects
IchenDEV Jun 30, 2026
d3c3d06
Parse completed Responses stream output
IchenDEV Jun 30, 2026
49f78a4
Join final ASR transcript segments
IchenDEV Jun 30, 2026
bcb4342
Parse NDJSON LLM event streams
IchenDEV Jun 30, 2026
18c32ea
Join final ASR result alternatives
IchenDEV Jun 30, 2026
aaf2ae9
Join Japanese ASR token pieces
IchenDEV Jun 30, 2026
7ebe401
Skip ASR tokenizer control tokens
IchenDEV Jun 30, 2026
929e8fb
Keep JSONL transcripts before terminal events
IchenDEV Jun 30, 2026
94f0817
Share no-space script joining
IchenDEV Jun 30, 2026
66ebcf8
Parse speech-final channel transcripts
IchenDEV Jun 30, 2026
ca213da
Recognize ASR endpoint finality aliases
IchenDEV Jun 30, 2026
df21233
Parse compound ASR finality statuses
IchenDEV Jun 30, 2026
7309aa7
Parse ASR result wrapper aliases
IchenDEV Jun 30, 2026
dc2d44d
Parse endpoint ASR finality statuses
IchenDEV Jun 30, 2026
4acd3ba
Parse ASR confidence envelopes
IchenDEV Jun 30, 2026
fc540b1
Parse Responses content-part done events
IchenDEV Jun 30, 2026
67c5d88
Parse final text value envelopes
IchenDEV Jun 30, 2026
8c0653d
Parse wrapped replacement payloads
IchenDEV Jun 30, 2026
288d0a9
Parse singular streaming tool calls
IchenDEV Jun 30, 2026
4c41e2a
Parse Anthropic stream field aliases
IchenDEV Jun 30, 2026
5342088
Parse typed OpenAI stream deltas
IchenDEV Jun 30, 2026
55407e8
Preserve SSE event names for LLM streams
IchenDEV Jun 30, 2026
2c5c640
Parse Anthropic stream index aliases
IchenDEV Jun 30, 2026
6e76ada
Parse serialized ASR payload wrappers
IchenDEV Jun 30, 2026
7919efb
Skip ASR runner log envelopes
IchenDEV Jun 30, 2026
023913e
Share ASR transcript signal detection
IchenDEV Jun 30, 2026
a1142e8
Refine ASR transcript signal detection
IchenDEV Jun 30, 2026
88f4279
Parse LLM confidence percent aliases
IchenDEV Jun 30, 2026
90e9feb
Parse LLM command type aliases
IchenDEV Jun 30, 2026
33a3769
Parse top-level replacement aliases
IchenDEV Jun 30, 2026
b7fb302
Parse LLM intent goal aliases
IchenDEV Jun 30, 2026
e55d986
Decode quoted JSON command payloads
IchenDEV Jun 30, 2026
9b48945
Parse ASR candidate aliases
IchenDEV Jun 30, 2026
5718c19
Parse ASR confidence aliases
IchenDEV Jun 30, 2026
fcff840
Parse ASR typed value elements
IchenDEV Jun 30, 2026
39a6a7c
Parse ASR stable transcript wrappers
IchenDEV Jun 30, 2026
485787c
Parse LLM action target pairs
IchenDEV Jun 30, 2026
c25d8f6
Decode structured LLM edit targets
IchenDEV Jun 30, 2026
996ef62
Decode LLM target flags
IchenDEV Jun 30, 2026
fe1e0a3
Decode LLM action flags
IchenDEV Jun 30, 2026
7717b82
Decode nested LLM action targets
IchenDEV Jun 30, 2026
e4508ef
Decode LLM action parameter targets
IchenDEV Jun 30, 2026
3d9fa78
Decode LLM intent type presets
IchenDEV Jun 30, 2026
4b12362
Treat LLM certainty as metadata
IchenDEV Jun 30, 2026
a1b518f
Treat LLM justification as metadata
IchenDEV Jul 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Sources/App/VoicePipeline+EditCommands.swift
Original file line numberDiff line numberDiff line change
Expand Up@@ -233,7 +233,8 @@ extension VoicePipeline {

let context = InputContext.capture(
targetApp: targetApp,
screenContext: selectedText,
screenContext: "",
selectedTextOverride: selectedText,
outputMode: .command,
inputLanguage: settings.inputLanguage,
source: .menuBar
Expand Down
2 changes: 1 addition & 1 deletion Sources/App/VoicePipeline+RewriteLast.swift
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,7 +19,7 @@ extension VoicePipeline {

let context = InputContext.capture(
targetApp: targetApp,
screenContext: insertedText,
screenContext: "",
outputMode: .command,
inputLanguage: settings.inputLanguage,
source: .menuBar
Expand Down
17 changes: 2 additions & 15 deletions Sources/LLM/RemoteLLMClient.swift
Original file line numberDiff line numberDiff line change
Expand Up@@ -85,14 +85,7 @@ actor RemoteLLMClient {
let (data, response) = try await URLSession.shared.data(for: request)
try validateHTTP(response, data: data)

guard let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
let choices = json["choices"] as? [[String: Any]],
let first = choices.first,
let message = first["message"] as? [String: Any],
let content = message["content"] as? String else {
throw RemoteLLMError.invalidResponse
}
return content.trimmingCharacters(in: .whitespacesAndNewlines)
return try RemoteLLMResponseText.openAI(from: data)
}

// MARK: - Anthropic Messages format
Expand DownExpand Up@@ -133,13 +126,7 @@ actor RemoteLLMClient {
let (data, response) = try await URLSession.shared.data(for: request)
try validateHTTP(response, data: data)

guard let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
let contentArray = json["content"] as? [[String: Any]],
let first = contentArray.first(where: { ($0["type"] as? String) == "text" }),
let text = first["text"] as? String else {
throw RemoteLLMError.invalidResponse
}
return text.trimmingCharacters(in: .whitespacesAndNewlines)
return try RemoteLLMResponseText.anthropic(from: data)
}

// MARK: - Shared
Expand Down
81 changes: 81 additions & 0 deletions Sources/LLM/RemoteLLMEventPayloads.swift
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
import Foundation

enum RemoteLLMEventPayloads {
static func values(in text: String) -> [String] {
let normalized = text
.replacingOccurrences(of: "\r\n", with: "\n")
.replacingOccurrences(of: "\r", with: "\n")

let ssePayloads = sseValues(in: normalized)
if !ssePayloads.isEmpty { return ssePayloads }

return normalized
.split(separator: "\n", omittingEmptySubsequences: false)
.map { String($0).trimmingCharacters(in: .whitespacesAndNewlines) }
.filter { $0.hasPrefix("{") || $0.hasPrefix("[") }
}
}

private extension RemoteLLMEventPayloads {
static func sseValues(in text: String) -> [String] {
var payloads: [String] = []
var dataLines: [String] = []
var eventName: String?

func flush() {
guard !dataLines.isEmpty else { return }
let payload = dataLines.joined(separator: "\n").trimmingCharacters(in: .whitespacesAndNewlines)
payloads.append(payloadWithEventType(payload, eventName: eventName))
dataLines.removeAll()
eventName = nil
}

for line in text.split(separator: "\n", omittingEmptySubsequences: false) {
if line.isEmpty {
flush()
continue
}
let rawLine = String(line)
if rawLine.localizedCaseInsensitiveComparePrefix("event:") {
eventName = String(rawLine.dropFirst(6)).trimmingCharacters(in: .whitespacesAndNewlines)
continue
}
guard rawLine.localizedCaseInsensitiveComparePrefix("data:") else { continue }
dataLines.append(String(rawLine.dropFirst(5)).trimmingCharacters(in: .whitespaces))
}
flush()
return payloads.filter { !$0.isEmpty }
}

static func payloadWithEventType(_ payload: String, eventName: String?) -> String {
guard let eventName,
!eventName.isEmpty,
let data = payload.data(using: .utf8),
var object = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
object.value(forCaseInsensitiveKey: "type") == nil else {
return payload
}
object["type"] = eventName
guard JSONSerialization.isValidJSONObject(object),
let typedData = try? JSONSerialization.data(withJSONObject: object),
let typedPayload = String(data: typedData, encoding: .utf8) else {
return payload
}
return typedPayload
}
}

private extension String {
func localizedCaseInsensitiveComparePrefix(_ prefix: String) -> Bool {
range(of: prefix, options: [.anchored, .caseInsensitive]) != nil
}
}

private extension Dictionary where Key == String {
func value(forCaseInsensitiveKey key: String) -> Value? {
if let value = self[key] {
return value
}
return first { $0.key.localizedCaseInsensitiveCompare(key) == .orderedSame }?.value
}
}
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
141 commits
Select commit Hold shift + click to select a range
62ca999
Harden LLM command decoding
IchenDEV Jun 30, 2026
93b17cb
Handle case-insensitive LLM command fields
IchenDEV Jun 30, 2026
6f36634
Preserve preset intents with LLM metadata
IchenDEV Jun 30, 2026
8f1f967
Parse nested LLM command JSON candidates
IchenDEV Jun 30, 2026
9044d5a
Normalize LLM confidence percentages
IchenDEV Jun 30, 2026
241b6aa
Decode structured LLM replacement payloads
IchenDEV Jun 30, 2026
5206bdb
Prefer later valid LLM command candidates
IchenDEV Jun 30, 2026
856b4b3
Decode structured LLM action payloads
IchenDEV Jun 30, 2026
e7e35ac
Extract LLM JSON candidates from string fields
IchenDEV Jun 30, 2026
c89fb1a
Accept top-level LLM action aliases
IchenDEV Jun 30, 2026
5d9dfe0
Accept top-level LLM confidence aliases
IchenDEV Jun 30, 2026
3ddf6e6
Centralize LLM resolution field aliases
IchenDEV Jun 30, 2026
0d7595b
Extract structured LLM final text output
IchenDEV Jun 30, 2026
bd59b76
Parse remote LLM content blocks
IchenDEV Jun 30, 2026
8a8b796
Strip scaffolded LLM final sections
IchenDEV Jun 30, 2026
7edabaf
Recognize localized LLM final scaffolds
IchenDEV Jun 30, 2026
c3e36ff
Parse structured local ASR transcripts
IchenDEV Jun 30, 2026
120cf69
Harden LLM final text contracts
IchenDEV Jun 30, 2026
06e76ec
Extend LLM output contracts to commands
IchenDEV Jun 30, 2026
c127e73
Accept more LLM resolution aliases
IchenDEV Jun 30, 2026
2d2689a
Parse typed LLM final text blocks
IchenDEV Jun 30, 2026
e830b70
Add focused text context to voice prompts
IchenDEV Jun 30, 2026
f5c32a7
Escape focused context in voice prompts
IchenDEV Jun 30, 2026
f950ae3
Escape external voice prompt context
IchenDEV Jun 30, 2026
f8f2b63
Tolerate sparse OpenAI response choices
IchenDEV Jun 30, 2026
ea50d1d
Parse Responses output text blocks
IchenDEV Jun 30, 2026
7d259a5
Parse top-level LLM output arrays
IchenDEV Jun 30, 2026
b397486
Extract embedded LLM output arrays
IchenDEV Jun 30, 2026
d05b5c0
Parse OpenAI Responses text output
IchenDEV Jun 30, 2026
ae6d442
Harden LLM scaffold tag cleanup
IchenDEV Jun 30, 2026
84039bd
Fence selection edit memory context
IchenDEV Jun 30, 2026
ed5087e
Parse top-level LLM text block arrays
IchenDEV Jun 30, 2026
75be106
Prefer final LLM edit commands
IchenDEV Jun 30, 2026
3e213f5
Avoid copied example edit commands
IchenDEV Jun 30, 2026
902bb84
Parse remote tool call response text
IchenDEV Jun 30, 2026
8bdfb29
Parse decoded remote tool arguments
IchenDEV Jun 30, 2026
03ddbed
Parse Anthropic tool use outputs
IchenDEV Jun 30, 2026
e2ed216
Parse OpenAI parsed response payloads
IchenDEV Jun 30, 2026
c4d4173
Parse structured content object payloads
IchenDEV Jun 30, 2026
28dbcdd
Parse tool payload field variants
IchenDEV Jun 30, 2026
1a384ac
Extract final text from response wrappers
IchenDEV Jun 30, 2026
268e324
Extract final text from content wrappers
IchenDEV Jun 30, 2026
a25bdf9
Parse chunked local ASR transcripts
IchenDEV Jun 30, 2026
41e7035
Parse top-level ASR segment arrays
IchenDEV Jun 30, 2026
dc5edc2
Parse ASR alternative transcripts
IchenDEV Jun 30, 2026
e5f3c10
Parse ASR n-best transcripts
IchenDEV Jun 30, 2026
db8f044
Parse ASR channel transcripts
IchenDEV Jun 30, 2026
81615e0
Accept more local ASR transcript fields
IchenDEV Jun 30, 2026
4ecb00a
Choose confident ASR alternatives
IchenDEV Jun 30, 2026
cc9dfaa
Parse word-level ASR transcripts
IchenDEV Jun 30, 2026
9cd0b55
Decode nested final text payloads
IchenDEV Jun 30, 2026
b9baf72
Extract tool-wrapped final text
IchenDEV Jun 30, 2026
21652d6
Decode richer replacement payloads
IchenDEV Jun 30, 2026
0a14013
Decode richer edit intent payloads
IchenDEV Jun 30, 2026
71fbf8c
Decode top-level edit intent aliases
IchenDEV Jun 30, 2026
01c4225
Decode nested confidence aliases
IchenDEV Jun 30, 2026
8421cfe
Decode parsed response block payloads
IchenDEV Jun 30, 2026
992f403
Decode parsed tool argument payloads
IchenDEV Jun 30, 2026
f61af65
Prefer structured response payloads
IchenDEV Jun 30, 2026
99949f7
Prefer structured ASR transcript candidates
IchenDEV Jun 30, 2026
f16eb75
Prefer Anthropic tool payloads
IchenDEV Jun 30, 2026
1e77b15
Prefer OpenAI response payloads
IchenDEV Jun 30, 2026
33839bf
Ignore irrelevant LLM tool payloads
IchenDEV Jun 30, 2026
e52261d
Improve streaming transcript merging
IchenDEV Jun 30, 2026
0986f88
Fence personal prompt context
IchenDEV Jun 30, 2026
371e9fc
Avoid stale streaming transcripts
IchenDEV Jun 30, 2026
e2e294f
Tighten streaming overlap merging
IchenDEV Jun 30, 2026
f5a7c34
Parse structured JSON LLM blocks
IchenDEV Jun 30, 2026
40ee7a9
Ignore LLM value metadata
IchenDEV Jun 30, 2026
82baa82
Parse typed final text blocks
IchenDEV Jun 30, 2026
a2746d1
Normalize LLM block type names
IchenDEV Jun 30, 2026
df59621
Read LLM block keys case-insensitively
IchenDEV Jun 30, 2026
2680a74
Read LLM response envelope keys case-insensitively
IchenDEV Jun 30, 2026
5cdc957
Ignore non-output LLM content objects
IchenDEV Jun 30, 2026
367fad5
Skip non-object OpenAI choices
IchenDEV Jun 30, 2026
a1e2784
Accept non-array Anthropic content
IchenDEV Jun 30, 2026
ec5efd3
Prefer message tool calls in LLM responses
IchenDEV Jun 30, 2026
785e469
Parse common ASR transcript aliases
IchenDEV Jun 30, 2026
126dc86
Collapse repeated ASR transcripts
IchenDEV Jun 30, 2026
b6116ca
Parse token-level ASR transcripts
IchenDEV Jun 30, 2026
8476472
Improve ASR token joining
IchenDEV Jun 30, 2026
30adaf7
Join ASR connector tokens
IchenDEV Jun 30, 2026
5c1a25b
Normalize ASR tokenizer pieces
IchenDEV Jun 30, 2026
965a5bf
Prefer final ASR transcripts
IchenDEV Jun 30, 2026
d0ecfe3
Prefer final ASR array events
IchenDEV Jun 30, 2026
bbcdfb2
Parse ASR wrapper event outputs
IchenDEV Jun 30, 2026
abf981d
Handle ASR tokenizer status variants
IchenDEV Jun 30, 2026
cc4b3c6
Constrain command screen context facts
IchenDEV Jun 30, 2026
807aeb2
Keep edit context targets distinct
IchenDEV Jun 30, 2026
ce19432
Handle OpenAI delta response payloads
IchenDEV Jun 30, 2026
5c04f66
Parse OpenAI event stream responses
IchenDEV Jun 30, 2026
f3c4a4f
Parse Anthropic event stream responses
IchenDEV Jun 30, 2026
d5a893f
Parse OpenAI Responses event streams
IchenDEV Jun 30, 2026
1a106b2
Parse JSONL local ASR transcripts
IchenDEV Jun 30, 2026
bd4e6c7
Continue streaming word fragments
IchenDEV Jun 30, 2026
092aa65
Parse streamed OpenAI content blocks
IchenDEV Jun 30, 2026
d5c8815
Parse streamed tool argument objects
IchenDEV Jun 30, 2026
d3c3d06
Parse completed Responses stream output
IchenDEV Jun 30, 2026
49f78a4
Join final ASR transcript segments
IchenDEV Jun 30, 2026
bcb4342
Parse NDJSON LLM event streams
IchenDEV Jun 30, 2026
18c32ea
Join final ASR result alternatives
IchenDEV Jun 30, 2026
aaf2ae9
Join Japanese ASR token pieces
IchenDEV Jun 30, 2026
7ebe401
Skip ASR tokenizer control tokens
IchenDEV Jun 30, 2026
929e8fb
Keep JSONL transcripts before terminal events
IchenDEV Jun 30, 2026
94f0817
Share no-space script joining
IchenDEV Jun 30, 2026
66ebcf8
Parse speech-final channel transcripts
IchenDEV Jun 30, 2026
ca213da
Recognize ASR endpoint finality aliases
IchenDEV Jun 30, 2026
df21233
Parse compound ASR finality statuses
IchenDEV Jun 30, 2026
7309aa7
Parse ASR result wrapper aliases
IchenDEV Jun 30, 2026
dc2d44d
Parse endpoint ASR finality statuses
IchenDEV Jun 30, 2026
4acd3ba
Parse ASR confidence envelopes
IchenDEV Jun 30, 2026
fc540b1
Parse Responses content-part done events
IchenDEV Jun 30, 2026
67c5d88
Parse final text value envelopes
IchenDEV Jun 30, 2026
8c0653d
Parse wrapped replacement payloads
IchenDEV Jun 30, 2026
288d0a9
Parse singular streaming tool calls
IchenDEV Jun 30, 2026
4c41e2a
Parse Anthropic stream field aliases
IchenDEV Jun 30, 2026
5342088
Parse typed OpenAI stream deltas
IchenDEV Jun 30, 2026
55407e8
Preserve SSE event names for LLM streams
IchenDEV Jun 30, 2026
2c5c640
Parse Anthropic stream index aliases
IchenDEV Jun 30, 2026
6e76ada
Parse serialized ASR payload wrappers
IchenDEV Jun 30, 2026
7919efb
Skip ASR runner log envelopes
IchenDEV Jun 30, 2026
023913e
Share ASR transcript signal detection
IchenDEV Jun 30, 2026
a1142e8
Refine ASR transcript signal detection
IchenDEV Jun 30, 2026
88f4279
Parse LLM confidence percent aliases
IchenDEV Jun 30, 2026
90e9feb
Parse LLM command type aliases
IchenDEV Jun 30, 2026
33a3769
Parse top-level replacement aliases
IchenDEV Jun 30, 2026
b7fb302
Parse LLM intent goal aliases
IchenDEV Jun 30, 2026
e55d986
Decode quoted JSON command payloads
IchenDEV Jun 30, 2026
9b48945
Parse ASR candidate aliases
IchenDEV Jun 30, 2026
5718c19
Parse ASR confidence aliases
IchenDEV Jun 30, 2026
fcff840
Parse ASR typed value elements
IchenDEV Jun 30, 2026
39a6a7c
Parse ASR stable transcript wrappers
IchenDEV Jun 30, 2026
485787c
Parse LLM action target pairs
IchenDEV Jun 30, 2026
c25d8f6
Decode structured LLM edit targets
IchenDEV Jun 30, 2026
996ef62
Decode LLM target flags
IchenDEV Jun 30, 2026
fe1e0a3
Decode LLM action flags
IchenDEV Jun 30, 2026
7717b82
Decode nested LLM action targets
IchenDEV Jun 30, 2026
e4508ef
Decode LLM action parameter targets
IchenDEV Jun 30, 2026
3d9fa78
Decode LLM intent type presets
IchenDEV Jun 30, 2026
4b12362
Treat LLM certainty as metadata
IchenDEV Jun 30, 2026
a1b518f
Treat LLM justification as metadata
IchenDEV Jul 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Sources/App/VoicePipeline+EditCommands.swift
Original file line numberDiff line numberDiff line change
Expand Up@@ -233,7 +233,8 @@ extension VoicePipeline {

let context = InputContext.capture(
targetApp: targetApp,
screenContext: selectedText,
screenContext: "",
selectedTextOverride: selectedText,
outputMode: .command,
inputLanguage: settings.inputLanguage,
source: .menuBar
Expand Down
2 changes: 1 addition & 1 deletion Sources/App/VoicePipeline+RewriteLast.swift
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,7 +19,7 @@ extension VoicePipeline {

let context = InputContext.capture(
targetApp: targetApp,
screenContext: insertedText,
screenContext: "",
outputMode: .command,
inputLanguage: settings.inputLanguage,
source: .menuBar
Expand Down
17 changes: 2 additions & 15 deletions Sources/LLM/RemoteLLMClient.swift
Original file line numberDiff line numberDiff line change
Expand Up@@ -85,14 +85,7 @@ actor RemoteLLMClient {
let (data, response) = try await URLSession.shared.data(for: request)
try validateHTTP(response, data: data)

guard let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
let choices = json["choices"] as? [[String: Any]],
let first = choices.first,
let message = first["message"] as? [String: Any],
let content = message["content"] as? String else {
throw RemoteLLMError.invalidResponse
}
return content.trimmingCharacters(in: .whitespacesAndNewlines)
return try RemoteLLMResponseText.openAI(from: data)
}

// MARK: - Anthropic Messages format
Expand DownExpand Up@@ -133,13 +126,7 @@ actor RemoteLLMClient {
let (data, response) = try await URLSession.shared.data(for: request)
try validateHTTP(response, data: data)

guard let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
let contentArray = json["content"] as? [[String: Any]],
let first = contentArray.first(where: { ($0["type"] as? String) == "text" }),
let text = first["text"] as? String else {
throw RemoteLLMError.invalidResponse
}
return text.trimmingCharacters(in: .whitespacesAndNewlines)
return try RemoteLLMResponseText.anthropic(from: data)
}

// MARK: - Shared
Expand Down
81 changes: 81 additions & 0 deletions Sources/LLM/RemoteLLMEventPayloads.swift
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
import Foundation

enum RemoteLLMEventPayloads {
static func values(in text: String) -> [String] {
let normalized = text
.replacingOccurrences(of: "\r\n", with: "\n")
.replacingOccurrences(of: "\r", with: "\n")

let ssePayloads = sseValues(in: normalized)
if !ssePayloads.isEmpty { return ssePayloads }

return normalized
.split(separator: "\n", omittingEmptySubsequences: false)
.map { String($0).trimmingCharacters(in: .whitespacesAndNewlines) }
.filter { $0.hasPrefix("{") || $0.hasPrefix("[") }
}
}

private extension RemoteLLMEventPayloads {
static func sseValues(in text: String) -> [String] {
var payloads: [String] = []
var dataLines: [String] = []
var eventName: String?

func flush() {
guard !dataLines.isEmpty else { return }
let payload = dataLines.joined(separator: "\n").trimmingCharacters(in: .whitespacesAndNewlines)
payloads.append(payloadWithEventType(payload, eventName: eventName))
dataLines.removeAll()
eventName = nil
}

for line in text.split(separator: "\n", omittingEmptySubsequences: false) {
if line.isEmpty {
flush()
continue
}
let rawLine = String(line)
if rawLine.localizedCaseInsensitiveComparePrefix("event:") {
eventName = String(rawLine.dropFirst(6)).trimmingCharacters(in: .whitespacesAndNewlines)
continue
}
guard rawLine.localizedCaseInsensitiveComparePrefix("data:") else { continue }
dataLines.append(String(rawLine.dropFirst(5)).trimmingCharacters(in: .whitespaces))
}
flush()
return payloads.filter { !$0.isEmpty }
}

static func payloadWithEventType(_ payload: String, eventName: String?) -> String {
guard let eventName,
!eventName.isEmpty,
let data = payload.data(using: .utf8),
var object = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
object.value(forCaseInsensitiveKey: "type") == nil else {
return payload
}
object["type"] = eventName
guard JSONSerialization.isValidJSONObject(object),
let typedData = try? JSONSerialization.data(withJSONObject: object),
let typedPayload = String(data: typedData, encoding: .utf8) else {
return payload
}
return typedPayload
}
}

private extension String {
func localizedCaseInsensitiveComparePrefix(_ prefix: String) -> Bool {
range(of: prefix, options: [.anchored, .caseInsensitive]) != nil
}
}

private extension Dictionary where Key == String {
func value(forCaseInsensitiveKey key: String) -> Value? {
if let value = self[key] {
return value
}
return first { $0.key.localizedCaseInsensitiveCompare(key) == .orderedSame }?.value
}
}
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
141 commits
Select commit Hold shift + click to select a range
62ca999
Harden LLM command decoding
IchenDEV Jun 30, 2026
93b17cb
Handle case-insensitive LLM command fields
IchenDEV Jun 30, 2026
6f36634
Preserve preset intents with LLM metadata
IchenDEV Jun 30, 2026
8f1f967
Parse nested LLM command JSON candidates
IchenDEV Jun 30, 2026
9044d5a
Normalize LLM confidence percentages
IchenDEV Jun 30, 2026
241b6aa
Decode structured LLM replacement payloads
IchenDEV Jun 30, 2026
5206bdb
Prefer later valid LLM command candidates
IchenDEV Jun 30, 2026
856b4b3
Decode structured LLM action payloads
IchenDEV Jun 30, 2026
e7e35ac
Extract LLM JSON candidates from string fields
IchenDEV Jun 30, 2026
c89fb1a
Accept top-level LLM action aliases
IchenDEV Jun 30, 2026
5d9dfe0
Accept top-level LLM confidence aliases
IchenDEV Jun 30, 2026
3ddf6e6
Centralize LLM resolution field aliases
IchenDEV Jun 30, 2026
0d7595b
Extract structured LLM final text output
IchenDEV Jun 30, 2026
bd59b76
Parse remote LLM content blocks
IchenDEV Jun 30, 2026
8a8b796
Strip scaffolded LLM final sections
IchenDEV Jun 30, 2026
7edabaf
Recognize localized LLM final scaffolds
IchenDEV Jun 30, 2026
c3e36ff
Parse structured local ASR transcripts
IchenDEV Jun 30, 2026
120cf69
Harden LLM final text contracts
IchenDEV Jun 30, 2026
06e76ec
Extend LLM output contracts to commands
IchenDEV Jun 30, 2026
c127e73
Accept more LLM resolution aliases
IchenDEV Jun 30, 2026
2d2689a
Parse typed LLM final text blocks
IchenDEV Jun 30, 2026
e830b70
Add focused text context to voice prompts
IchenDEV Jun 30, 2026
f5c32a7
Escape focused context in voice prompts
IchenDEV Jun 30, 2026
f950ae3
Escape external voice prompt context
IchenDEV Jun 30, 2026
f8f2b63
Tolerate sparse OpenAI response choices
IchenDEV Jun 30, 2026
ea50d1d
Parse Responses output text blocks
IchenDEV Jun 30, 2026
7d259a5
Parse top-level LLM output arrays
IchenDEV Jun 30, 2026
b397486
Extract embedded LLM output arrays
IchenDEV Jun 30, 2026
d05b5c0
Parse OpenAI Responses text output
IchenDEV Jun 30, 2026
ae6d442
Harden LLM scaffold tag cleanup
IchenDEV Jun 30, 2026
84039bd
Fence selection edit memory context
IchenDEV Jun 30, 2026
ed5087e
Parse top-level LLM text block arrays
IchenDEV Jun 30, 2026
75be106
Prefer final LLM edit commands
IchenDEV Jun 30, 2026
3e213f5
Avoid copied example edit commands
IchenDEV Jun 30, 2026
902bb84
Parse remote tool call response text
IchenDEV Jun 30, 2026
8bdfb29
Parse decoded remote tool arguments
IchenDEV Jun 30, 2026
03ddbed
Parse Anthropic tool use outputs
IchenDEV Jun 30, 2026
e2ed216
Parse OpenAI parsed response payloads
IchenDEV Jun 30, 2026
c4d4173
Parse structured content object payloads
IchenDEV Jun 30, 2026
28dbcdd
Parse tool payload field variants
IchenDEV Jun 30, 2026
1a384ac
Extract final text from response wrappers
IchenDEV Jun 30, 2026
268e324
Extract final text from content wrappers
IchenDEV Jun 30, 2026
a25bdf9
Parse chunked local ASR transcripts
IchenDEV Jun 30, 2026
41e7035
Parse top-level ASR segment arrays
IchenDEV Jun 30, 2026
dc5edc2
Parse ASR alternative transcripts
IchenDEV Jun 30, 2026
e5f3c10
Parse ASR n-best transcripts
IchenDEV Jun 30, 2026
db8f044
Parse ASR channel transcripts
IchenDEV Jun 30, 2026
81615e0
Accept more local ASR transcript fields
IchenDEV Jun 30, 2026
4ecb00a
Choose confident ASR alternatives
IchenDEV Jun 30, 2026
cc9dfaa
Parse word-level ASR transcripts
IchenDEV Jun 30, 2026
9cd0b55
Decode nested final text payloads
IchenDEV Jun 30, 2026
b9baf72
Extract tool-wrapped final text
IchenDEV Jun 30, 2026
21652d6
Decode richer replacement payloads
IchenDEV Jun 30, 2026
0a14013
Decode richer edit intent payloads
IchenDEV Jun 30, 2026
71fbf8c
Decode top-level edit intent aliases
IchenDEV Jun 30, 2026
01c4225
Decode nested confidence aliases
IchenDEV Jun 30, 2026
8421cfe
Decode parsed response block payloads
IchenDEV Jun 30, 2026
992f403
Decode parsed tool argument payloads
IchenDEV Jun 30, 2026
f61af65
Prefer structured response payloads
IchenDEV Jun 30, 2026
99949f7
Prefer structured ASR transcript candidates
IchenDEV Jun 30, 2026
f16eb75
Prefer Anthropic tool payloads
IchenDEV Jun 30, 2026
1e77b15
Prefer OpenAI response payloads
IchenDEV Jun 30, 2026
33839bf
Ignore irrelevant LLM tool payloads
IchenDEV Jun 30, 2026
e52261d
Improve streaming transcript merging
IchenDEV Jun 30, 2026
0986f88
Fence personal prompt context
IchenDEV Jun 30, 2026
371e9fc
Avoid stale streaming transcripts
IchenDEV Jun 30, 2026
e2e294f
Tighten streaming overlap merging
IchenDEV Jun 30, 2026
f5a7c34
Parse structured JSON LLM blocks
IchenDEV Jun 30, 2026
40ee7a9
Ignore LLM value metadata
IchenDEV Jun 30, 2026
82baa82
Parse typed final text blocks
IchenDEV Jun 30, 2026
a2746d1
Normalize LLM block type names
IchenDEV Jun 30, 2026
df59621
Read LLM block keys case-insensitively
IchenDEV Jun 30, 2026
2680a74
Read LLM response envelope keys case-insensitively
IchenDEV Jun 30, 2026
5cdc957
Ignore non-output LLM content objects
IchenDEV Jun 30, 2026
367fad5
Skip non-object OpenAI choices
IchenDEV Jun 30, 2026
a1e2784
Accept non-array Anthropic content
IchenDEV Jun 30, 2026
ec5efd3
Prefer message tool calls in LLM responses
IchenDEV Jun 30, 2026
785e469
Parse common ASR transcript aliases
IchenDEV Jun 30, 2026
126dc86
Collapse repeated ASR transcripts
IchenDEV Jun 30, 2026
b6116ca
Parse token-level ASR transcripts
IchenDEV Jun 30, 2026
8476472
Improve ASR token joining
IchenDEV Jun 30, 2026
30adaf7
Join ASR connector tokens
IchenDEV Jun 30, 2026
5c1a25b
Normalize ASR tokenizer pieces
IchenDEV Jun 30, 2026
965a5bf
Prefer final ASR transcripts
IchenDEV Jun 30, 2026
d0ecfe3
Prefer final ASR array events
IchenDEV Jun 30, 2026
bbcdfb2
Parse ASR wrapper event outputs
IchenDEV Jun 30, 2026
abf981d
Handle ASR tokenizer status variants
IchenDEV Jun 30, 2026
cc4b3c6
Constrain command screen context facts
IchenDEV Jun 30, 2026
807aeb2
Keep edit context targets distinct
IchenDEV Jun 30, 2026
ce19432
Handle OpenAI delta response payloads
IchenDEV Jun 30, 2026
5c04f66
Parse OpenAI event stream responses
IchenDEV Jun 30, 2026
f3c4a4f
Parse Anthropic event stream responses
IchenDEV Jun 30, 2026
d5a893f
Parse OpenAI Responses event streams
IchenDEV Jun 30, 2026
1a106b2
Parse JSONL local ASR transcripts
IchenDEV Jun 30, 2026
bd4e6c7
Continue streaming word fragments
IchenDEV Jun 30, 2026
092aa65
Parse streamed OpenAI content blocks
IchenDEV Jun 30, 2026
d5c8815
Parse streamed tool argument objects
IchenDEV Jun 30, 2026
d3c3d06
Parse completed Responses stream output
IchenDEV Jun 30, 2026
49f78a4
Join final ASR transcript segments
IchenDEV Jun 30, 2026
bcb4342
Parse NDJSON LLM event streams
IchenDEV Jun 30, 2026
18c32ea
Join final ASR result alternatives
IchenDEV Jun 30, 2026
aaf2ae9
Join Japanese ASR token pieces
IchenDEV Jun 30, 2026
7ebe401
Skip ASR tokenizer control tokens
IchenDEV Jun 30, 2026
929e8fb
Keep JSONL transcripts before terminal events
IchenDEV Jun 30, 2026
94f0817
Share no-space script joining
IchenDEV Jun 30, 2026
66ebcf8
Parse speech-final channel transcripts
IchenDEV Jun 30, 2026
ca213da
Recognize ASR endpoint finality aliases
IchenDEV Jun 30, 2026
df21233
Parse compound ASR finality statuses
IchenDEV Jun 30, 2026
7309aa7
Parse ASR result wrapper aliases
IchenDEV Jun 30, 2026
dc2d44d
Parse endpoint ASR finality statuses
IchenDEV Jun 30, 2026
4acd3ba
Parse ASR confidence envelopes
IchenDEV Jun 30, 2026
fc540b1
Parse Responses content-part done events
IchenDEV Jun 30, 2026
67c5d88
Parse final text value envelopes
IchenDEV Jun 30, 2026
8c0653d
Parse wrapped replacement payloads
IchenDEV Jun 30, 2026
288d0a9
Parse singular streaming tool calls
IchenDEV Jun 30, 2026
4c41e2a
Parse Anthropic stream field aliases
IchenDEV Jun 30, 2026
5342088
Parse typed OpenAI stream deltas
IchenDEV Jun 30, 2026
55407e8
Preserve SSE event names for LLM streams
IchenDEV Jun 30, 2026
2c5c640
Parse Anthropic stream index aliases
IchenDEV Jun 30, 2026
6e76ada
Parse serialized ASR payload wrappers
IchenDEV Jun 30, 2026
7919efb
Skip ASR runner log envelopes
IchenDEV Jun 30, 2026
023913e
Share ASR transcript signal detection
IchenDEV Jun 30, 2026
a1142e8
Refine ASR transcript signal detection
IchenDEV Jun 30, 2026
88f4279
Parse LLM confidence percent aliases
IchenDEV Jun 30, 2026
90e9feb
Parse LLM command type aliases
IchenDEV Jun 30, 2026
33a3769
Parse top-level replacement aliases
IchenDEV Jun 30, 2026
b7fb302
Parse LLM intent goal aliases
IchenDEV Jun 30, 2026
e55d986
Decode quoted JSON command payloads
IchenDEV Jun 30, 2026
9b48945
Parse ASR candidate aliases
IchenDEV Jun 30, 2026
5718c19
Parse ASR confidence aliases
IchenDEV Jun 30, 2026
fcff840
Parse ASR typed value elements
IchenDEV Jun 30, 2026
39a6a7c
Parse ASR stable transcript wrappers
IchenDEV Jun 30, 2026
485787c
Parse LLM action target pairs
IchenDEV Jun 30, 2026
c25d8f6
Decode structured LLM edit targets
IchenDEV Jun 30, 2026
996ef62
Decode LLM target flags
IchenDEV Jun 30, 2026
fe1e0a3
Decode LLM action flags
IchenDEV Jun 30, 2026
7717b82
Decode nested LLM action targets
IchenDEV Jun 30, 2026
e4508ef
Decode LLM action parameter targets
IchenDEV Jun 30, 2026
3d9fa78
Decode LLM intent type presets
IchenDEV Jun 30, 2026
4b12362
Treat LLM certainty as metadata
IchenDEV Jun 30, 2026
a1b518f
Treat LLM justification as metadata
IchenDEV Jul 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Sources/App/VoicePipeline+EditCommands.swift
Original file line numberDiff line numberDiff line change
Expand Up@@ -233,7 +233,8 @@ extension VoicePipeline {

let context = InputContext.capture(
targetApp: targetApp,
screenContext: selectedText,
screenContext: "",
selectedTextOverride: selectedText,
outputMode: .command,
inputLanguage: settings.inputLanguage,
source: .menuBar
Expand Down
2 changes: 1 addition & 1 deletion Sources/App/VoicePipeline+RewriteLast.swift
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,7 +19,7 @@ extension VoicePipeline {

let context = InputContext.capture(
targetApp: targetApp,
screenContext: insertedText,
screenContext: "",
outputMode: .command,
inputLanguage: settings.inputLanguage,
source: .menuBar
Expand Down
17 changes: 2 additions & 15 deletions Sources/LLM/RemoteLLMClient.swift
Original file line numberDiff line numberDiff line change
Expand Up@@ -85,14 +85,7 @@ actor RemoteLLMClient {
let (data, response) = try await URLSession.shared.data(for: request)
try validateHTTP(response, data: data)

guard let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
let choices = json["choices"] as? [[String: Any]],
let first = choices.first,
let message = first["message"] as? [String: Any],
let content = message["content"] as? String else {
throw RemoteLLMError.invalidResponse
}
return content.trimmingCharacters(in: .whitespacesAndNewlines)
return try RemoteLLMResponseText.openAI(from: data)
}

// MARK: - Anthropic Messages format
Expand DownExpand Up@@ -133,13 +126,7 @@ actor RemoteLLMClient {
let (data, response) = try await URLSession.shared.data(for: request)
try validateHTTP(response, data: data)

guard let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
let contentArray = json["content"] as? [[String: Any]],
let first = contentArray.first(where: { ($0["type"] as? String) == "text" }),
let text = first["text"] as? String else {
throw RemoteLLMError.invalidResponse
}
return text.trimmingCharacters(in: .whitespacesAndNewlines)
return try RemoteLLMResponseText.anthropic(from: data)
}

// MARK: - Shared
Expand Down
81 changes: 81 additions & 0 deletions Sources/LLM/RemoteLLMEventPayloads.swift
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
import Foundation

enum RemoteLLMEventPayloads {
static func values(in text: String) -> [String] {
let normalized = text
.replacingOccurrences(of: "\r\n", with: "\n")
.replacingOccurrences(of: "\r", with: "\n")

let ssePayloads = sseValues(in: normalized)
if !ssePayloads.isEmpty { return ssePayloads }

return normalized
.split(separator: "\n", omittingEmptySubsequences: false)
.map { String($0).trimmingCharacters(in: .whitespacesAndNewlines) }
.filter { $0.hasPrefix("{") || $0.hasPrefix("[") }
}
}

private extension RemoteLLMEventPayloads {
static func sseValues(in text: String) -> [String] {
var payloads: [String] = []
var dataLines: [String] = []
var eventName: String?

func flush() {
guard !dataLines.isEmpty else { return }
let payload = dataLines.joined(separator: "\n").trimmingCharacters(in: .whitespacesAndNewlines)
payloads.append(payloadWithEventType(payload, eventName: eventName))
dataLines.removeAll()
eventName = nil
}

for line in text.split(separator: "\n", omittingEmptySubsequences: false) {
if line.isEmpty {
flush()
continue
}
let rawLine = String(line)
if rawLine.localizedCaseInsensitiveComparePrefix("event:") {
eventName = String(rawLine.dropFirst(6)).trimmingCharacters(in: .whitespacesAndNewlines)
continue
}
guard rawLine.localizedCaseInsensitiveComparePrefix("data:") else { continue }
dataLines.append(String(rawLine.dropFirst(5)).trimmingCharacters(in: .whitespaces))
}
flush()
return payloads.filter { !$0.isEmpty }
}

static func payloadWithEventType(_ payload: String, eventName: String?) -> String {
guard let eventName,
!eventName.isEmpty,
let data = payload.data(using: .utf8),
var object = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
object.value(forCaseInsensitiveKey: "type") == nil else {
return payload
}
object["type"] = eventName
guard JSONSerialization.isValidJSONObject(object),
let typedData = try? JSONSerialization.data(withJSONObject: object),
let typedPayload = String(data: typedData, encoding: .utf8) else {
return payload
}
return typedPayload
}
}

private extension String {
func localizedCaseInsensitiveComparePrefix(_ prefix: String) -> Bool {
range(of: prefix, options: [.anchored, .caseInsensitive]) != nil
}
}

private extension Dictionary where Key == String {
func value(forCaseInsensitiveKey key: String) -> Value? {
if let value = self[key] {
return value
}
return first { $0.key.localizedCaseInsensitiveCompare(key) == .orderedSame }?.value
}
}
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
141 commits
Select commit Hold shift + click to select a range
62ca999
Harden LLM command decoding
IchenDEV Jun 30, 2026
93b17cb
Handle case-insensitive LLM command fields
IchenDEV Jun 30, 2026
6f36634
Preserve preset intents with LLM metadata
IchenDEV Jun 30, 2026
8f1f967
Parse nested LLM command JSON candidates
IchenDEV Jun 30, 2026
9044d5a
Normalize LLM confidence percentages
IchenDEV Jun 30, 2026
241b6aa
Decode structured LLM replacement payloads
IchenDEV Jun 30, 2026
5206bdb
Prefer later valid LLM command candidates
IchenDEV Jun 30, 2026
856b4b3
Decode structured LLM action payloads
IchenDEV Jun 30, 2026
e7e35ac
Extract LLM JSON candidates from string fields
IchenDEV Jun 30, 2026
c89fb1a
Accept top-level LLM action aliases
IchenDEV Jun 30, 2026
5d9dfe0
Accept top-level LLM confidence aliases
IchenDEV Jun 30, 2026
3ddf6e6
Centralize LLM resolution field aliases
IchenDEV Jun 30, 2026
0d7595b
Extract structured LLM final text output
IchenDEV Jun 30, 2026
bd59b76
Parse remote LLM content blocks
IchenDEV Jun 30, 2026
8a8b796
Strip scaffolded LLM final sections
IchenDEV Jun 30, 2026
7edabaf
Recognize localized LLM final scaffolds
IchenDEV Jun 30, 2026
c3e36ff
Parse structured local ASR transcripts
IchenDEV Jun 30, 2026
120cf69
Harden LLM final text contracts
IchenDEV Jun 30, 2026
06e76ec
Extend LLM output contracts to commands
IchenDEV Jun 30, 2026
c127e73
Accept more LLM resolution aliases
IchenDEV Jun 30, 2026
2d2689a
Parse typed LLM final text blocks
IchenDEV Jun 30, 2026
e830b70
Add focused text context to voice prompts
IchenDEV Jun 30, 2026
f5c32a7
Escape focused context in voice prompts
IchenDEV Jun 30, 2026
f950ae3
Escape external voice prompt context
IchenDEV Jun 30, 2026
f8f2b63
Tolerate sparse OpenAI response choices
IchenDEV Jun 30, 2026
ea50d1d
Parse Responses output text blocks
IchenDEV Jun 30, 2026
7d259a5
Parse top-level LLM output arrays
IchenDEV Jun 30, 2026
b397486
Extract embedded LLM output arrays
IchenDEV Jun 30, 2026
d05b5c0
Parse OpenAI Responses text output
IchenDEV Jun 30, 2026
ae6d442
Harden LLM scaffold tag cleanup
IchenDEV Jun 30, 2026
84039bd
Fence selection edit memory context
IchenDEV Jun 30, 2026
ed5087e
Parse top-level LLM text block arrays
IchenDEV Jun 30, 2026
75be106
Prefer final LLM edit commands
IchenDEV Jun 30, 2026
3e213f5
Avoid copied example edit commands
IchenDEV Jun 30, 2026
902bb84
Parse remote tool call response text
IchenDEV Jun 30, 2026
8bdfb29
Parse decoded remote tool arguments
IchenDEV Jun 30, 2026
03ddbed
Parse Anthropic tool use outputs
IchenDEV Jun 30, 2026
e2ed216
Parse OpenAI parsed response payloads
IchenDEV Jun 30, 2026
c4d4173
Parse structured content object payloads
IchenDEV Jun 30, 2026
28dbcdd
Parse tool payload field variants
IchenDEV Jun 30, 2026
1a384ac
Extract final text from response wrappers
IchenDEV Jun 30, 2026
268e324
Extract final text from content wrappers
IchenDEV Jun 30, 2026
a25bdf9
Parse chunked local ASR transcripts
IchenDEV Jun 30, 2026
41e7035
Parse top-level ASR segment arrays
IchenDEV Jun 30, 2026
dc5edc2
Parse ASR alternative transcripts
IchenDEV Jun 30, 2026
e5f3c10
Parse ASR n-best transcripts
IchenDEV Jun 30, 2026
db8f044
Parse ASR channel transcripts
IchenDEV Jun 30, 2026
81615e0
Accept more local ASR transcript fields
IchenDEV Jun 30, 2026
4ecb00a
Choose confident ASR alternatives
IchenDEV Jun 30, 2026
cc9dfaa
Parse word-level ASR transcripts
IchenDEV Jun 30, 2026
9cd0b55
Decode nested final text payloads
IchenDEV Jun 30, 2026
b9baf72
Extract tool-wrapped final text
IchenDEV Jun 30, 2026
21652d6
Decode richer replacement payloads
IchenDEV Jun 30, 2026
0a14013
Decode richer edit intent payloads
IchenDEV Jun 30, 2026
71fbf8c
Decode top-level edit intent aliases
IchenDEV Jun 30, 2026
01c4225
Decode nested confidence aliases
IchenDEV Jun 30, 2026
8421cfe
Decode parsed response block payloads
IchenDEV Jun 30, 2026
992f403
Decode parsed tool argument payloads
IchenDEV Jun 30, 2026
f61af65
Prefer structured response payloads
IchenDEV Jun 30, 2026
99949f7
Prefer structured ASR transcript candidates
IchenDEV Jun 30, 2026
f16eb75
Prefer Anthropic tool payloads
IchenDEV Jun 30, 2026
1e77b15
Prefer OpenAI response payloads
IchenDEV Jun 30, 2026
33839bf
Ignore irrelevant LLM tool payloads
IchenDEV Jun 30, 2026
e52261d
Improve streaming transcript merging
IchenDEV Jun 30, 2026
0986f88
Fence personal prompt context
IchenDEV Jun 30, 2026
371e9fc
Avoid stale streaming transcripts
IchenDEV Jun 30, 2026
e2e294f
Tighten streaming overlap merging
IchenDEV Jun 30, 2026
f5a7c34
Parse structured JSON LLM blocks
IchenDEV Jun 30, 2026
40ee7a9
Ignore LLM value metadata
IchenDEV Jun 30, 2026
82baa82
Parse typed final text blocks
IchenDEV Jun 30, 2026
a2746d1
Normalize LLM block type names
IchenDEV Jun 30, 2026
df59621
Read LLM block keys case-insensitively
IchenDEV Jun 30, 2026
2680a74
Read LLM response envelope keys case-insensitively
IchenDEV Jun 30, 2026
5cdc957
Ignore non-output LLM content objects
IchenDEV Jun 30, 2026
367fad5
Skip non-object OpenAI choices
IchenDEV Jun 30, 2026
a1e2784
Accept non-array Anthropic content
IchenDEV Jun 30, 2026
ec5efd3
Prefer message tool calls in LLM responses
IchenDEV Jun 30, 2026
785e469
Parse common ASR transcript aliases
IchenDEV Jun 30, 2026
126dc86
Collapse repeated ASR transcripts
IchenDEV Jun 30, 2026
b6116ca
Parse token-level ASR transcripts
IchenDEV Jun 30, 2026
8476472
Improve ASR token joining
IchenDEV Jun 30, 2026
30adaf7
Join ASR connector tokens
IchenDEV Jun 30, 2026
5c1a25b
Normalize ASR tokenizer pieces
IchenDEV Jun 30, 2026
965a5bf
Prefer final ASR transcripts
IchenDEV Jun 30, 2026
d0ecfe3
Prefer final ASR array events
IchenDEV Jun 30, 2026
bbcdfb2
Parse ASR wrapper event outputs
IchenDEV Jun 30, 2026
abf981d
Handle ASR tokenizer status variants
IchenDEV Jun 30, 2026
cc4b3c6
Constrain command screen context facts
IchenDEV Jun 30, 2026
807aeb2
Keep edit context targets distinct
IchenDEV Jun 30, 2026
ce19432
Handle OpenAI delta response payloads
IchenDEV Jun 30, 2026
5c04f66
Parse OpenAI event stream responses
IchenDEV Jun 30, 2026
f3c4a4f
Parse Anthropic event stream responses
IchenDEV Jun 30, 2026
d5a893f
Parse OpenAI Responses event streams
IchenDEV Jun 30, 2026
1a106b2
Parse JSONL local ASR transcripts
IchenDEV Jun 30, 2026
bd4e6c7
Continue streaming word fragments
IchenDEV Jun 30, 2026
092aa65
Parse streamed OpenAI content blocks
IchenDEV Jun 30, 2026
d5c8815
Parse streamed tool argument objects
IchenDEV Jun 30, 2026
d3c3d06
Parse completed Responses stream output
IchenDEV Jun 30, 2026
49f78a4
Join final ASR transcript segments
IchenDEV Jun 30, 2026
bcb4342
Parse NDJSON LLM event streams
IchenDEV Jun 30, 2026
18c32ea
Join final ASR result alternatives
IchenDEV Jun 30, 2026
aaf2ae9
Join Japanese ASR token pieces
IchenDEV Jun 30, 2026
7ebe401
Skip ASR tokenizer control tokens
IchenDEV Jun 30, 2026
929e8fb
Keep JSONL transcripts before terminal events
IchenDEV Jun 30, 2026
94f0817
Share no-space script joining
IchenDEV Jun 30, 2026
66ebcf8
Parse speech-final channel transcripts
IchenDEV Jun 30, 2026
ca213da
Recognize ASR endpoint finality aliases
IchenDEV Jun 30, 2026
df21233
Parse compound ASR finality statuses
IchenDEV Jun 30, 2026
7309aa7
Parse ASR result wrapper aliases
IchenDEV Jun 30, 2026
dc2d44d
Parse endpoint ASR finality statuses
IchenDEV Jun 30, 2026
4acd3ba
Parse ASR confidence envelopes
IchenDEV Jun 30, 2026
fc540b1
Parse Responses content-part done events
IchenDEV Jun 30, 2026
67c5d88
Parse final text value envelopes
IchenDEV Jun 30, 2026
8c0653d
Parse wrapped replacement payloads
IchenDEV Jun 30, 2026
288d0a9
Parse singular streaming tool calls
IchenDEV Jun 30, 2026
4c41e2a
Parse Anthropic stream field aliases
IchenDEV Jun 30, 2026
5342088
Parse typed OpenAI stream deltas
IchenDEV Jun 30, 2026
55407e8
Preserve SSE event names for LLM streams
IchenDEV Jun 30, 2026
2c5c640
Parse Anthropic stream index aliases
IchenDEV Jun 30, 2026
6e76ada
Parse serialized ASR payload wrappers
IchenDEV Jun 30, 2026
7919efb
Skip ASR runner log envelopes
IchenDEV Jun 30, 2026
023913e
Share ASR transcript signal detection
IchenDEV Jun 30, 2026
a1142e8
Refine ASR transcript signal detection
IchenDEV Jun 30, 2026
88f4279
Parse LLM confidence percent aliases
IchenDEV Jun 30, 2026
90e9feb
Parse LLM command type aliases
IchenDEV Jun 30, 2026
33a3769
Parse top-level replacement aliases
IchenDEV Jun 30, 2026
b7fb302
Parse LLM intent goal aliases
IchenDEV Jun 30, 2026
e55d986
Decode quoted JSON command payloads
IchenDEV Jun 30, 2026
9b48945
Parse ASR candidate aliases
IchenDEV Jun 30, 2026
5718c19
Parse ASR confidence aliases
IchenDEV Jun 30, 2026
fcff840
Parse ASR typed value elements
IchenDEV Jun 30, 2026
39a6a7c
Parse ASR stable transcript wrappers
IchenDEV Jun 30, 2026
485787c
Parse LLM action target pairs
IchenDEV Jun 30, 2026
c25d8f6
Decode structured LLM edit targets
IchenDEV Jun 30, 2026
996ef62
Decode LLM target flags
IchenDEV Jun 30, 2026
fe1e0a3
Decode LLM action flags
IchenDEV Jun 30, 2026
7717b82
Decode nested LLM action targets
IchenDEV Jun 30, 2026
e4508ef
Decode LLM action parameter targets
IchenDEV Jun 30, 2026
3d9fa78
Decode LLM intent type presets
IchenDEV Jun 30, 2026
4b12362
Treat LLM certainty as metadata
IchenDEV Jun 30, 2026
a1b518f
Treat LLM justification as metadata
IchenDEV Jul 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Sources/App/VoicePipeline+EditCommands.swift
Original file line numberDiff line numberDiff line change
Expand Up@@ -233,7 +233,8 @@ extension VoicePipeline {

let context = InputContext.capture(
targetApp: targetApp,
screenContext: selectedText,
screenContext: "",
selectedTextOverride: selectedText,
outputMode: .command,
inputLanguage: settings.inputLanguage,
source: .menuBar
Expand Down
2 changes: 1 addition & 1 deletion Sources/App/VoicePipeline+RewriteLast.swift
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,7 +19,7 @@ extension VoicePipeline {

let context = InputContext.capture(
targetApp: targetApp,
screenContext: insertedText,
screenContext: "",
outputMode: .command,
inputLanguage: settings.inputLanguage,
source: .menuBar
Expand Down
17 changes: 2 additions & 15 deletions Sources/LLM/RemoteLLMClient.swift
Original file line numberDiff line numberDiff line change
Expand Up@@ -85,14 +85,7 @@ actor RemoteLLMClient {
let (data, response) = try await URLSession.shared.data(for: request)
try validateHTTP(response, data: data)

guard let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
let choices = json["choices"] as? [[String: Any]],
let first = choices.first,
let message = first["message"] as? [String: Any],
let content = message["content"] as? String else {
throw RemoteLLMError.invalidResponse
}
return content.trimmingCharacters(in: .whitespacesAndNewlines)
return try RemoteLLMResponseText.openAI(from: data)
}

// MARK: - Anthropic Messages format
Expand DownExpand Up@@ -133,13 +126,7 @@ actor RemoteLLMClient {
let (data, response) = try await URLSession.shared.data(for: request)
try validateHTTP(response, data: data)

guard let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
let contentArray = json["content"] as? [[String: Any]],
let first = contentArray.first(where: { ($0["type"] as? String) == "text" }),
let text = first["text"] as? String else {
throw RemoteLLMError.invalidResponse
}
return text.trimmingCharacters(in: .whitespacesAndNewlines)
return try RemoteLLMResponseText.anthropic(from: data)
}

// MARK: - Shared
Expand Down
81 changes: 81 additions & 0 deletions Sources/LLM/RemoteLLMEventPayloads.swift
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
import Foundation

enum RemoteLLMEventPayloads {
static func values(in text: String) -> [String] {
let normalized = text
.replacingOccurrences(of: "\r\n", with: "\n")
.replacingOccurrences(of: "\r", with: "\n")

let ssePayloads = sseValues(in: normalized)
if !ssePayloads.isEmpty { return ssePayloads }

return normalized
.split(separator: "\n", omittingEmptySubsequences: false)
.map { String($0).trimmingCharacters(in: .whitespacesAndNewlines) }
.filter { $0.hasPrefix("{") || $0.hasPrefix("[") }
}
}

private extension RemoteLLMEventPayloads {
static func sseValues(in text: String) -> [String] {
var payloads: [String] = []
var dataLines: [String] = []
var eventName: String?

func flush() {
guard !dataLines.isEmpty else { return }
let payload = dataLines.joined(separator: "\n").trimmingCharacters(in: .whitespacesAndNewlines)
payloads.append(payloadWithEventType(payload, eventName: eventName))
dataLines.removeAll()
eventName = nil
}

for line in text.split(separator: "\n", omittingEmptySubsequences: false) {
if line.isEmpty {
flush()
continue
}
let rawLine = String(line)
if rawLine.localizedCaseInsensitiveComparePrefix("event:") {
eventName = String(rawLine.dropFirst(6)).trimmingCharacters(in: .whitespacesAndNewlines)
continue
}
guard rawLine.localizedCaseInsensitiveComparePrefix("data:") else { continue }
dataLines.append(String(rawLine.dropFirst(5)).trimmingCharacters(in: .whitespaces))
}
flush()
return payloads.filter { !$0.isEmpty }
}

static func payloadWithEventType(_ payload: String, eventName: String?) -> String {
guard let eventName,
!eventName.isEmpty,
let data = payload.data(using: .utf8),
var object = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
object.value(forCaseInsensitiveKey: "type") == nil else {
return payload
}
object["type"] = eventName
guard JSONSerialization.isValidJSONObject(object),
let typedData = try? JSONSerialization.data(withJSONObject: object),
let typedPayload = String(data: typedData, encoding: .utf8) else {
return payload
}
return typedPayload
}
}

private extension String {
func localizedCaseInsensitiveComparePrefix(_ prefix: String) -> Bool {
range(of: prefix, options: [.anchored, .caseInsensitive]) != nil
}
}

private extension Dictionary where Key == String {
func value(forCaseInsensitiveKey key: String) -> Value? {
if let value = self[key] {
return value
}
return first { $0.key.localizedCaseInsensitiveCompare(key) == .orderedSame }?.value
}
}
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
141 commits
Select commit Hold shift + click to select a range
62ca999
Harden LLM command decoding
IchenDEV Jun 30, 2026
93b17cb
Handle case-insensitive LLM command fields
IchenDEV Jun 30, 2026
6f36634
Preserve preset intents with LLM metadata
IchenDEV Jun 30, 2026
8f1f967
Parse nested LLM command JSON candidates
IchenDEV Jun 30, 2026
9044d5a
Normalize LLM confidence percentages
IchenDEV Jun 30, 2026
241b6aa
Decode structured LLM replacement payloads
IchenDEV Jun 30, 2026
5206bdb
Prefer later valid LLM command candidates
IchenDEV Jun 30, 2026
856b4b3
Decode structured LLM action payloads
IchenDEV Jun 30, 2026
e7e35ac
Extract LLM JSON candidates from string fields
IchenDEV Jun 30, 2026
c89fb1a
Accept top-level LLM action aliases
IchenDEV Jun 30, 2026
5d9dfe0
Accept top-level LLM confidence aliases
IchenDEV Jun 30, 2026
3ddf6e6
Centralize LLM resolution field aliases
IchenDEV Jun 30, 2026
0d7595b
Extract structured LLM final text output
IchenDEV Jun 30, 2026
bd59b76
Parse remote LLM content blocks
IchenDEV Jun 30, 2026
8a8b796
Strip scaffolded LLM final sections
IchenDEV Jun 30, 2026
7edabaf
Recognize localized LLM final scaffolds
IchenDEV Jun 30, 2026
c3e36ff
Parse structured local ASR transcripts
IchenDEV Jun 30, 2026
120cf69
Harden LLM final text contracts
IchenDEV Jun 30, 2026
06e76ec
Extend LLM output contracts to commands
IchenDEV Jun 30, 2026
c127e73
Accept more LLM resolution aliases
IchenDEV Jun 30, 2026
2d2689a
Parse typed LLM final text blocks
IchenDEV Jun 30, 2026
e830b70
Add focused text context to voice prompts
IchenDEV Jun 30, 2026
f5c32a7
Escape focused context in voice prompts
IchenDEV Jun 30, 2026
f950ae3
Escape external voice prompt context
IchenDEV Jun 30, 2026
f8f2b63
Tolerate sparse OpenAI response choices
IchenDEV Jun 30, 2026
ea50d1d
Parse Responses output text blocks
IchenDEV Jun 30, 2026
7d259a5
Parse top-level LLM output arrays
IchenDEV Jun 30, 2026
b397486
Extract embedded LLM output arrays
IchenDEV Jun 30, 2026
d05b5c0
Parse OpenAI Responses text output
IchenDEV Jun 30, 2026
ae6d442
Harden LLM scaffold tag cleanup
IchenDEV Jun 30, 2026
84039bd
Fence selection edit memory context
IchenDEV Jun 30, 2026
ed5087e
Parse top-level LLM text block arrays
IchenDEV Jun 30, 2026
75be106
Prefer final LLM edit commands
IchenDEV Jun 30, 2026
3e213f5
Avoid copied example edit commands
IchenDEV Jun 30, 2026
902bb84
Parse remote tool call response text
IchenDEV Jun 30, 2026
8bdfb29
Parse decoded remote tool arguments
IchenDEV Jun 30, 2026
03ddbed
Parse Anthropic tool use outputs
IchenDEV Jun 30, 2026
e2ed216
Parse OpenAI parsed response payloads
IchenDEV Jun 30, 2026
c4d4173
Parse structured content object payloads
IchenDEV Jun 30, 2026
28dbcdd
Parse tool payload field variants
IchenDEV Jun 30, 2026
1a384ac
Extract final text from response wrappers
IchenDEV Jun 30, 2026
268e324
Extract final text from content wrappers
IchenDEV Jun 30, 2026
a25bdf9
Parse chunked local ASR transcripts
IchenDEV Jun 30, 2026
41e7035
Parse top-level ASR segment arrays
IchenDEV Jun 30, 2026
dc5edc2
Parse ASR alternative transcripts
IchenDEV Jun 30, 2026
e5f3c10
Parse ASR n-best transcripts
IchenDEV Jun 30, 2026
db8f044
Parse ASR channel transcripts
IchenDEV Jun 30, 2026
81615e0
Accept more local ASR transcript fields
IchenDEV Jun 30, 2026
4ecb00a
Choose confident ASR alternatives
IchenDEV Jun 30, 2026
cc9dfaa
Parse word-level ASR transcripts
IchenDEV Jun 30, 2026
9cd0b55
Decode nested final text payloads
IchenDEV Jun 30, 2026
b9baf72
Extract tool-wrapped final text
IchenDEV Jun 30, 2026
21652d6
Decode richer replacement payloads
IchenDEV Jun 30, 2026
0a14013
Decode richer edit intent payloads
IchenDEV Jun 30, 2026
71fbf8c
Decode top-level edit intent aliases
IchenDEV Jun 30, 2026
01c4225
Decode nested confidence aliases
IchenDEV Jun 30, 2026
8421cfe
Decode parsed response block payloads
IchenDEV Jun 30, 2026
992f403
Decode parsed tool argument payloads
IchenDEV Jun 30, 2026
f61af65
Prefer structured response payloads
IchenDEV Jun 30, 2026
99949f7
Prefer structured ASR transcript candidates
IchenDEV Jun 30, 2026
f16eb75
Prefer Anthropic tool payloads
IchenDEV Jun 30, 2026
1e77b15
Prefer OpenAI response payloads
IchenDEV Jun 30, 2026
33839bf
Ignore irrelevant LLM tool payloads
IchenDEV Jun 30, 2026
e52261d
Improve streaming transcript merging
IchenDEV Jun 30, 2026
0986f88
Fence personal prompt context
IchenDEV Jun 30, 2026
371e9fc
Avoid stale streaming transcripts
IchenDEV Jun 30, 2026
e2e294f
Tighten streaming overlap merging
IchenDEV Jun 30, 2026
f5a7c34
Parse structured JSON LLM blocks
IchenDEV Jun 30, 2026
40ee7a9
Ignore LLM value metadata
IchenDEV Jun 30, 2026
82baa82
Parse typed final text blocks
IchenDEV Jun 30, 2026
a2746d1
Normalize LLM block type names
IchenDEV Jun 30, 2026
df59621
Read LLM block keys case-insensitively
IchenDEV Jun 30, 2026
2680a74
Read LLM response envelope keys case-insensitively
IchenDEV Jun 30, 2026
5cdc957
Ignore non-output LLM content objects
IchenDEV Jun 30, 2026
367fad5
Skip non-object OpenAI choices
IchenDEV Jun 30, 2026
a1e2784
Accept non-array Anthropic content
IchenDEV Jun 30, 2026
ec5efd3
Prefer message tool calls in LLM responses
IchenDEV Jun 30, 2026
785e469
Parse common ASR transcript aliases
IchenDEV Jun 30, 2026
126dc86
Collapse repeated ASR transcripts
IchenDEV Jun 30, 2026
b6116ca
Parse token-level ASR transcripts
IchenDEV Jun 30, 2026
8476472
Improve ASR token joining
IchenDEV Jun 30, 2026
30adaf7
Join ASR connector tokens
IchenDEV Jun 30, 2026
5c1a25b
Normalize ASR tokenizer pieces
IchenDEV Jun 30, 2026
965a5bf
Prefer final ASR transcripts
IchenDEV Jun 30, 2026
d0ecfe3
Prefer final ASR array events
IchenDEV Jun 30, 2026
bbcdfb2
Parse ASR wrapper event outputs
IchenDEV Jun 30, 2026
abf981d
Handle ASR tokenizer status variants
IchenDEV Jun 30, 2026
cc4b3c6
Constrain command screen context facts
IchenDEV Jun 30, 2026
807aeb2
Keep edit context targets distinct
IchenDEV Jun 30, 2026
ce19432
Handle OpenAI delta response payloads
IchenDEV Jun 30, 2026
5c04f66
Parse OpenAI event stream responses
IchenDEV Jun 30, 2026
f3c4a4f
Parse Anthropic event stream responses
IchenDEV Jun 30, 2026
d5a893f
Parse OpenAI Responses event streams
IchenDEV Jun 30, 2026
1a106b2
Parse JSONL local ASR transcripts
IchenDEV Jun 30, 2026
bd4e6c7
Continue streaming word fragments
IchenDEV Jun 30, 2026
092aa65
Parse streamed OpenAI content blocks
IchenDEV Jun 30, 2026
d5c8815
Parse streamed tool argument objects
IchenDEV Jun 30, 2026
d3c3d06
Parse completed Responses stream output
IchenDEV Jun 30, 2026
49f78a4
Join final ASR transcript segments
IchenDEV Jun 30, 2026
bcb4342
Parse NDJSON LLM event streams
IchenDEV Jun 30, 2026
18c32ea
Join final ASR result alternatives
IchenDEV Jun 30, 2026
aaf2ae9
Join Japanese ASR token pieces
IchenDEV Jun 30, 2026
7ebe401
Skip ASR tokenizer control tokens
IchenDEV Jun 30, 2026
929e8fb
Keep JSONL transcripts before terminal events
IchenDEV Jun 30, 2026
94f0817
Share no-space script joining
IchenDEV Jun 30, 2026
66ebcf8
Parse speech-final channel transcripts
IchenDEV Jun 30, 2026
ca213da
Recognize ASR endpoint finality aliases
IchenDEV Jun 30, 2026
df21233
Parse compound ASR finality statuses
IchenDEV Jun 30, 2026
7309aa7
Parse ASR result wrapper aliases
IchenDEV Jun 30, 2026
dc2d44d
Parse endpoint ASR finality statuses
IchenDEV Jun 30, 2026
4acd3ba
Parse ASR confidence envelopes
IchenDEV Jun 30, 2026
fc540b1
Parse Responses content-part done events
IchenDEV Jun 30, 2026
67c5d88
Parse final text value envelopes
IchenDEV Jun 30, 2026
8c0653d
Parse wrapped replacement payloads
IchenDEV Jun 30, 2026
288d0a9
Parse singular streaming tool calls
IchenDEV Jun 30, 2026
4c41e2a
Parse Anthropic stream field aliases
IchenDEV Jun 30, 2026
5342088
Parse typed OpenAI stream deltas
IchenDEV Jun 30, 2026
55407e8
Preserve SSE event names for LLM streams
IchenDEV Jun 30, 2026
2c5c640
Parse Anthropic stream index aliases
IchenDEV Jun 30, 2026
6e76ada
Parse serialized ASR payload wrappers
IchenDEV Jun 30, 2026
7919efb
Skip ASR runner log envelopes
IchenDEV Jun 30, 2026
023913e
Share ASR transcript signal detection
IchenDEV Jun 30, 2026
a1142e8
Refine ASR transcript signal detection
IchenDEV Jun 30, 2026
88f4279
Parse LLM confidence percent aliases
IchenDEV Jun 30, 2026
90e9feb
Parse LLM command type aliases
IchenDEV Jun 30, 2026
33a3769
Parse top-level replacement aliases
IchenDEV Jun 30, 2026
b7fb302
Parse LLM intent goal aliases
IchenDEV Jun 30, 2026
e55d986
Decode quoted JSON command payloads
IchenDEV Jun 30, 2026
9b48945
Parse ASR candidate aliases
IchenDEV Jun 30, 2026
5718c19
Parse ASR confidence aliases
IchenDEV Jun 30, 2026
fcff840
Parse ASR typed value elements
IchenDEV Jun 30, 2026
39a6a7c
Parse ASR stable transcript wrappers
IchenDEV Jun 30, 2026
485787c
Parse LLM action target pairs
IchenDEV Jun 30, 2026
c25d8f6
Decode structured LLM edit targets
IchenDEV Jun 30, 2026
996ef62
Decode LLM target flags
IchenDEV Jun 30, 2026
fe1e0a3
Decode LLM action flags
IchenDEV Jun 30, 2026
7717b82
Decode nested LLM action targets
IchenDEV Jun 30, 2026
e4508ef
Decode LLM action parameter targets
IchenDEV Jun 30, 2026
3d9fa78
Decode LLM intent type presets
IchenDEV Jun 30, 2026
4b12362
Treat LLM certainty as metadata
IchenDEV Jun 30, 2026
a1b518f
Treat LLM justification as metadata
IchenDEV Jul 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Sources/App/VoicePipeline+EditCommands.swift
Original file line numberDiff line numberDiff line change
Expand Up@@ -233,7 +233,8 @@ extension VoicePipeline {

let context = InputContext.capture(
targetApp: targetApp,
screenContext: selectedText,
screenContext: "",
selectedTextOverride: selectedText,
outputMode: .command,
inputLanguage: settings.inputLanguage,
source: .menuBar
Expand Down
2 changes: 1 addition & 1 deletion Sources/App/VoicePipeline+RewriteLast.swift
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,7 +19,7 @@ extension VoicePipeline {

let context = InputContext.capture(
targetApp: targetApp,
screenContext: insertedText,
screenContext: "",
outputMode: .command,
inputLanguage: settings.inputLanguage,
source: .menuBar
Expand Down
17 changes: 2 additions & 15 deletions Sources/LLM/RemoteLLMClient.swift
Original file line numberDiff line numberDiff line change
Expand Up@@ -85,14 +85,7 @@ actor RemoteLLMClient {
let (data, response) = try await URLSession.shared.data(for: request)
try validateHTTP(response, data: data)

guard let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
let choices = json["choices"] as? [[String: Any]],
let first = choices.first,
let message = first["message"] as? [String: Any],
let content = message["content"] as? String else {
throw RemoteLLMError.invalidResponse
}
return content.trimmingCharacters(in: .whitespacesAndNewlines)
return try RemoteLLMResponseText.openAI(from: data)
}

// MARK: - Anthropic Messages format
Expand DownExpand Up@@ -133,13 +126,7 @@ actor RemoteLLMClient {
let (data, response) = try await URLSession.shared.data(for: request)
try validateHTTP(response, data: data)

guard let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
let contentArray = json["content"] as? [[String: Any]],
let first = contentArray.first(where: { ($0["type"] as? String) == "text" }),
let text = first["text"] as? String else {
throw RemoteLLMError.invalidResponse
}
return text.trimmingCharacters(in: .whitespacesAndNewlines)
return try RemoteLLMResponseText.anthropic(from: data)
}

// MARK: - Shared
Expand Down
81 changes: 81 additions & 0 deletions Sources/LLM/RemoteLLMEventPayloads.swift
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
import Foundation

enum RemoteLLMEventPayloads {
static func values(in text: String) -> [String] {
let normalized = text
.replacingOccurrences(of: "\r\n", with: "\n")
.replacingOccurrences(of: "\r", with: "\n")

let ssePayloads = sseValues(in: normalized)
if !ssePayloads.isEmpty { return ssePayloads }

return normalized
.split(separator: "\n", omittingEmptySubsequences: false)
.map { String($0).trimmingCharacters(in: .whitespacesAndNewlines) }
.filter { $0.hasPrefix("{") || $0.hasPrefix("[") }
}
}

private extension RemoteLLMEventPayloads {
static func sseValues(in text: String) -> [String] {
var payloads: [String] = []
var dataLines: [String] = []
var eventName: String?

func flush() {
guard !dataLines.isEmpty else { return }
let payload = dataLines.joined(separator: "\n").trimmingCharacters(in: .whitespacesAndNewlines)
payloads.append(payloadWithEventType(payload, eventName: eventName))
dataLines.removeAll()
eventName = nil
}

for line in text.split(separator: "\n", omittingEmptySubsequences: false) {
if line.isEmpty {
flush()
continue
}
let rawLine = String(line)
if rawLine.localizedCaseInsensitiveComparePrefix("event:") {
eventName = String(rawLine.dropFirst(6)).trimmingCharacters(in: .whitespacesAndNewlines)
continue
}
guard rawLine.localizedCaseInsensitiveComparePrefix("data:") else { continue }
dataLines.append(String(rawLine.dropFirst(5)).trimmingCharacters(in: .whitespaces))
}
flush()
return payloads.filter { !$0.isEmpty }
}

static func payloadWithEventType(_ payload: String, eventName: String?) -> String {
guard let eventName,
!eventName.isEmpty,
let data = payload.data(using: .utf8),
var object = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
object.value(forCaseInsensitiveKey: "type") == nil else {
return payload
}
object["type"] = eventName
guard JSONSerialization.isValidJSONObject(object),
let typedData = try? JSONSerialization.data(withJSONObject: object),
let typedPayload = String(data: typedData, encoding: .utf8) else {
return payload
}
return typedPayload
}
}

private extension String {
func localizedCaseInsensitiveComparePrefix(_ prefix: String) -> Bool {
range(of: prefix, options: [.anchored, .caseInsensitive]) != nil
}
}

private extension Dictionary where Key == String {
func value(forCaseInsensitiveKey key: String) -> Value? {
if let value = self[key] {
return value
}
return first { $0.key.localizedCaseInsensitiveCompare(key) == .orderedSame }?.value
}
}
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
141 commits
Select commit Hold shift + click to select a range
62ca999
Harden LLM command decoding
IchenDEV Jun 30, 2026
93b17cb
Handle case-insensitive LLM command fields
IchenDEV Jun 30, 2026
6f36634
Preserve preset intents with LLM metadata
IchenDEV Jun 30, 2026
8f1f967
Parse nested LLM command JSON candidates
IchenDEV Jun 30, 2026
9044d5a
Normalize LLM confidence percentages
IchenDEV Jun 30, 2026
241b6aa
Decode structured LLM replacement payloads
IchenDEV Jun 30, 2026
5206bdb
Prefer later valid LLM command candidates
IchenDEV Jun 30, 2026
856b4b3
Decode structured LLM action payloads
IchenDEV Jun 30, 2026
e7e35ac
Extract LLM JSON candidates from string fields
IchenDEV Jun 30, 2026
c89fb1a
Accept top-level LLM action aliases
IchenDEV Jun 30, 2026
5d9dfe0
Accept top-level LLM confidence aliases
IchenDEV Jun 30, 2026
3ddf6e6
Centralize LLM resolution field aliases
IchenDEV Jun 30, 2026
0d7595b
Extract structured LLM final text output
IchenDEV Jun 30, 2026
bd59b76
Parse remote LLM content blocks
IchenDEV Jun 30, 2026
8a8b796
Strip scaffolded LLM final sections
IchenDEV Jun 30, 2026
7edabaf
Recognize localized LLM final scaffolds
IchenDEV Jun 30, 2026
c3e36ff
Parse structured local ASR transcripts
IchenDEV Jun 30, 2026
120cf69
Harden LLM final text contracts
IchenDEV Jun 30, 2026
06e76ec
Extend LLM output contracts to commands
IchenDEV Jun 30, 2026
c127e73
Accept more LLM resolution aliases
IchenDEV Jun 30, 2026
2d2689a
Parse typed LLM final text blocks
IchenDEV Jun 30, 2026
e830b70
Add focused text context to voice prompts
IchenDEV Jun 30, 2026
f5c32a7
Escape focused context in voice prompts
IchenDEV Jun 30, 2026
f950ae3
Escape external voice prompt context
IchenDEV Jun 30, 2026
f8f2b63
Tolerate sparse OpenAI response choices
IchenDEV Jun 30, 2026
ea50d1d
Parse Responses output text blocks
IchenDEV Jun 30, 2026
7d259a5
Parse top-level LLM output arrays
IchenDEV Jun 30, 2026
b397486
Extract embedded LLM output arrays
IchenDEV Jun 30, 2026
d05b5c0
Parse OpenAI Responses text output
IchenDEV Jun 30, 2026
ae6d442
Harden LLM scaffold tag cleanup
IchenDEV Jun 30, 2026
84039bd
Fence selection edit memory context
IchenDEV Jun 30, 2026
ed5087e
Parse top-level LLM text block arrays
IchenDEV Jun 30, 2026
75be106
Prefer final LLM edit commands
IchenDEV Jun 30, 2026
3e213f5
Avoid copied example edit commands
IchenDEV Jun 30, 2026
902bb84
Parse remote tool call response text
IchenDEV Jun 30, 2026
8bdfb29
Parse decoded remote tool arguments
IchenDEV Jun 30, 2026
03ddbed
Parse Anthropic tool use outputs
IchenDEV Jun 30, 2026
e2ed216
Parse OpenAI parsed response payloads
IchenDEV Jun 30, 2026
c4d4173
Parse structured content object payloads
IchenDEV Jun 30, 2026
28dbcdd
Parse tool payload field variants
IchenDEV Jun 30, 2026
1a384ac
Extract final text from response wrappers
IchenDEV Jun 30, 2026
268e324
Extract final text from content wrappers
IchenDEV Jun 30, 2026
a25bdf9
Parse chunked local ASR transcripts
IchenDEV Jun 30, 2026
41e7035
Parse top-level ASR segment arrays
IchenDEV Jun 30, 2026
dc5edc2
Parse ASR alternative transcripts
IchenDEV Jun 30, 2026
e5f3c10
Parse ASR n-best transcripts
IchenDEV Jun 30, 2026
db8f044
Parse ASR channel transcripts
IchenDEV Jun 30, 2026
81615e0
Accept more local ASR transcript fields
IchenDEV Jun 30, 2026
4ecb00a
Choose confident ASR alternatives
IchenDEV Jun 30, 2026
cc9dfaa
Parse word-level ASR transcripts
IchenDEV Jun 30, 2026
9cd0b55
Decode nested final text payloads
IchenDEV Jun 30, 2026
b9baf72
Extract tool-wrapped final text
IchenDEV Jun 30, 2026
21652d6
Decode richer replacement payloads
IchenDEV Jun 30, 2026
0a14013
Decode richer edit intent payloads
IchenDEV Jun 30, 2026
71fbf8c
Decode top-level edit intent aliases
IchenDEV Jun 30, 2026
01c4225
Decode nested confidence aliases
IchenDEV Jun 30, 2026
8421cfe
Decode parsed response block payloads
IchenDEV Jun 30, 2026
992f403
Decode parsed tool argument payloads
IchenDEV Jun 30, 2026
f61af65
Prefer structured response payloads
IchenDEV Jun 30, 2026
99949f7
Prefer structured ASR transcript candidates
IchenDEV Jun 30, 2026
f16eb75
Prefer Anthropic tool payloads
IchenDEV Jun 30, 2026
1e77b15
Prefer OpenAI response payloads
IchenDEV Jun 30, 2026
33839bf
Ignore irrelevant LLM tool payloads
IchenDEV Jun 30, 2026
e52261d
Improve streaming transcript merging
IchenDEV Jun 30, 2026
0986f88
Fence personal prompt context
IchenDEV Jun 30, 2026
371e9fc
Avoid stale streaming transcripts
IchenDEV Jun 30, 2026
e2e294f
Tighten streaming overlap merging
IchenDEV Jun 30, 2026
f5a7c34
Parse structured JSON LLM blocks
IchenDEV Jun 30, 2026
40ee7a9
Ignore LLM value metadata
IchenDEV Jun 30, 2026
82baa82
Parse typed final text blocks
IchenDEV Jun 30, 2026
a2746d1
Normalize LLM block type names
IchenDEV Jun 30, 2026
df59621
Read LLM block keys case-insensitively
IchenDEV Jun 30, 2026
2680a74
Read LLM response envelope keys case-insensitively
IchenDEV Jun 30, 2026
5cdc957
Ignore non-output LLM content objects
IchenDEV Jun 30, 2026
367fad5
Skip non-object OpenAI choices
IchenDEV Jun 30, 2026
a1e2784
Accept non-array Anthropic content
IchenDEV Jun 30, 2026
ec5efd3
Prefer message tool calls in LLM responses
IchenDEV Jun 30, 2026
785e469
Parse common ASR transcript aliases
IchenDEV Jun 30, 2026
126dc86
Collapse repeated ASR transcripts
IchenDEV Jun 30, 2026
b6116ca
Parse token-level ASR transcripts
IchenDEV Jun 30, 2026
8476472
Improve ASR token joining
IchenDEV Jun 30, 2026
30adaf7
Join ASR connector tokens
IchenDEV Jun 30, 2026
5c1a25b
Normalize ASR tokenizer pieces
IchenDEV Jun 30, 2026
965a5bf
Prefer final ASR transcripts
IchenDEV Jun 30, 2026
d0ecfe3
Prefer final ASR array events
IchenDEV Jun 30, 2026
bbcdfb2
Parse ASR wrapper event outputs
IchenDEV Jun 30, 2026
abf981d
Handle ASR tokenizer status variants
IchenDEV Jun 30, 2026
cc4b3c6
Constrain command screen context facts
IchenDEV Jun 30, 2026
807aeb2
Keep edit context targets distinct
IchenDEV Jun 30, 2026
ce19432
Handle OpenAI delta response payloads
IchenDEV Jun 30, 2026
5c04f66
Parse OpenAI event stream responses
IchenDEV Jun 30, 2026
f3c4a4f
Parse Anthropic event stream responses
IchenDEV Jun 30, 2026
d5a893f
Parse OpenAI Responses event streams
IchenDEV Jun 30, 2026
1a106b2
Parse JSONL local ASR transcripts
IchenDEV Jun 30, 2026
bd4e6c7
Continue streaming word fragments
IchenDEV Jun 30, 2026
092aa65
Parse streamed OpenAI content blocks
IchenDEV Jun 30, 2026
d5c8815
Parse streamed tool argument objects
IchenDEV Jun 30, 2026
d3c3d06
Parse completed Responses stream output
IchenDEV Jun 30, 2026
49f78a4
Join final ASR transcript segments
IchenDEV Jun 30, 2026
bcb4342
Parse NDJSON LLM event streams
IchenDEV Jun 30, 2026
18c32ea
Join final ASR result alternatives
IchenDEV Jun 30, 2026
aaf2ae9
Join Japanese ASR token pieces
IchenDEV Jun 30, 2026
7ebe401
Skip ASR tokenizer control tokens
IchenDEV Jun 30, 2026
929e8fb
Keep JSONL transcripts before terminal events
IchenDEV Jun 30, 2026
94f0817
Share no-space script joining
IchenDEV Jun 30, 2026
66ebcf8
Parse speech-final channel transcripts
IchenDEV Jun 30, 2026
ca213da
Recognize ASR endpoint finality aliases
IchenDEV Jun 30, 2026
df21233
Parse compound ASR finality statuses
IchenDEV Jun 30, 2026
7309aa7
Parse ASR result wrapper aliases
IchenDEV Jun 30, 2026
dc2d44d
Parse endpoint ASR finality statuses
IchenDEV Jun 30, 2026
4acd3ba
Parse ASR confidence envelopes
IchenDEV Jun 30, 2026
fc540b1
Parse Responses content-part done events
IchenDEV Jun 30, 2026
67c5d88
Parse final text value envelopes
IchenDEV Jun 30, 2026
8c0653d
Parse wrapped replacement payloads
IchenDEV Jun 30, 2026
288d0a9
Parse singular streaming tool calls
IchenDEV Jun 30, 2026
4c41e2a
Parse Anthropic stream field aliases
IchenDEV Jun 30, 2026
5342088
Parse typed OpenAI stream deltas
IchenDEV Jun 30, 2026
55407e8
Preserve SSE event names for LLM streams
IchenDEV Jun 30, 2026
2c5c640
Parse Anthropic stream index aliases
IchenDEV Jun 30, 2026
6e76ada
Parse serialized ASR payload wrappers
IchenDEV Jun 30, 2026
7919efb
Skip ASR runner log envelopes
IchenDEV Jun 30, 2026
023913e
Share ASR transcript signal detection
IchenDEV Jun 30, 2026
a1142e8
Refine ASR transcript signal detection
IchenDEV Jun 30, 2026
88f4279
Parse LLM confidence percent aliases
IchenDEV Jun 30, 2026
90e9feb
Parse LLM command type aliases
IchenDEV Jun 30, 2026
33a3769
Parse top-level replacement aliases
IchenDEV Jun 30, 2026
b7fb302
Parse LLM intent goal aliases
IchenDEV Jun 30, 2026
e55d986
Decode quoted JSON command payloads
IchenDEV Jun 30, 2026
9b48945
Parse ASR candidate aliases
IchenDEV Jun 30, 2026
5718c19
Parse ASR confidence aliases
IchenDEV Jun 30, 2026
fcff840
Parse ASR typed value elements
IchenDEV Jun 30, 2026
39a6a7c
Parse ASR stable transcript wrappers
IchenDEV Jun 30, 2026
485787c
Parse LLM action target pairs
IchenDEV Jun 30, 2026
c25d8f6
Decode structured LLM edit targets
IchenDEV Jun 30, 2026
996ef62
Decode LLM target flags
IchenDEV Jun 30, 2026
fe1e0a3
Decode LLM action flags
IchenDEV Jun 30, 2026
7717b82
Decode nested LLM action targets
IchenDEV Jun 30, 2026
e4508ef
Decode LLM action parameter targets
IchenDEV Jun 30, 2026
3d9fa78
Decode LLM intent type presets
IchenDEV Jun 30, 2026
4b12362
Treat LLM certainty as metadata
IchenDEV Jun 30, 2026
a1b518f
Treat LLM justification as metadata
IchenDEV Jul 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Sources/App/VoicePipeline+EditCommands.swift
Original file line numberDiff line numberDiff line change
Expand Up@@ -233,7 +233,8 @@ extension VoicePipeline {

let context = InputContext.capture(
targetApp: targetApp,
screenContext: selectedText,
screenContext: "",
selectedTextOverride: selectedText,
outputMode: .command,
inputLanguage: settings.inputLanguage,
source: .menuBar
Expand Down
2 changes: 1 addition & 1 deletion Sources/App/VoicePipeline+RewriteLast.swift
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,7 +19,7 @@ extension VoicePipeline {

let context = InputContext.capture(
targetApp: targetApp,
screenContext: insertedText,
screenContext: "",
outputMode: .command,
inputLanguage: settings.inputLanguage,
source: .menuBar
Expand Down
17 changes: 2 additions & 15 deletions Sources/LLM/RemoteLLMClient.swift
Original file line numberDiff line numberDiff line change
Expand Up@@ -85,14 +85,7 @@ actor RemoteLLMClient {
let (data, response) = try await URLSession.shared.data(for: request)
try validateHTTP(response, data: data)

guard let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
let choices = json["choices"] as? [[String: Any]],
let first = choices.first,
let message = first["message"] as? [String: Any],
let content = message["content"] as? String else {
throw RemoteLLMError.invalidResponse
}
return content.trimmingCharacters(in: .whitespacesAndNewlines)
return try RemoteLLMResponseText.openAI(from: data)
}

// MARK: - Anthropic Messages format
Expand DownExpand Up@@ -133,13 +126,7 @@ actor RemoteLLMClient {
let (data, response) = try await URLSession.shared.data(for: request)
try validateHTTP(response, data: data)

guard let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
let contentArray = json["content"] as? [[String: Any]],
let first = contentArray.first(where: { ($0["type"] as? String) == "text" }),
let text = first["text"] as? String else {
throw RemoteLLMError.invalidResponse
}
return text.trimmingCharacters(in: .whitespacesAndNewlines)
return try RemoteLLMResponseText.anthropic(from: data)
}

// MARK: - Shared
Expand Down
81 changes: 81 additions & 0 deletions Sources/LLM/RemoteLLMEventPayloads.swift
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
import Foundation

enum RemoteLLMEventPayloads {
static func values(in text: String) -> [String] {
let normalized = text
.replacingOccurrences(of: "\r\n", with: "\n")
.replacingOccurrences(of: "\r", with: "\n")

let ssePayloads = sseValues(in: normalized)
if !ssePayloads.isEmpty { return ssePayloads }

return normalized
.split(separator: "\n", omittingEmptySubsequences: false)
.map { String($0).trimmingCharacters(in: .whitespacesAndNewlines) }
.filter { $0.hasPrefix("{") || $0.hasPrefix("[") }
}
}

private extension RemoteLLMEventPayloads {
static func sseValues(in text: String) -> [String] {
var payloads: [String] = []
var dataLines: [String] = []
var eventName: String?

func flush() {
guard !dataLines.isEmpty else { return }
let payload = dataLines.joined(separator: "\n").trimmingCharacters(in: .whitespacesAndNewlines)
payloads.append(payloadWithEventType(payload, eventName: eventName))
dataLines.removeAll()
eventName = nil
}

for line in text.split(separator: "\n", omittingEmptySubsequences: false) {
if line.isEmpty {
flush()
continue
}
let rawLine = String(line)
if rawLine.localizedCaseInsensitiveComparePrefix("event:") {
eventName = String(rawLine.dropFirst(6)).trimmingCharacters(in: .whitespacesAndNewlines)
continue
}
guard rawLine.localizedCaseInsensitiveComparePrefix("data:") else { continue }
dataLines.append(String(rawLine.dropFirst(5)).trimmingCharacters(in: .whitespaces))
}
flush()
return payloads.filter { !$0.isEmpty }
}

static func payloadWithEventType(_ payload: String, eventName: String?) -> String {
guard let eventName,
!eventName.isEmpty,
let data = payload.data(using: .utf8),
var object = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
object.value(forCaseInsensitiveKey: "type") == nil else {
return payload
}
object["type"] = eventName
guard JSONSerialization.isValidJSONObject(object),
let typedData = try? JSONSerialization.data(withJSONObject: object),
let typedPayload = String(data: typedData, encoding: .utf8) else {
return payload
}
return typedPayload
}
}

private extension String {
func localizedCaseInsensitiveComparePrefix(_ prefix: String) -> Bool {
range(of: prefix, options: [.anchored, .caseInsensitive]) != nil
}
}

private extension Dictionary where Key == String {
func value(forCaseInsensitiveKey key: String) -> Value? {
if let value = self[key] {
return value
}
return first { $0.key.localizedCaseInsensitiveCompare(key) == .orderedSame }?.value
}
}
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
141 commits
Select commit Hold shift + click to select a range
62ca999
Harden LLM command decoding
IchenDEV Jun 30, 2026
93b17cb
Handle case-insensitive LLM command fields
IchenDEV Jun 30, 2026
6f36634
Preserve preset intents with LLM metadata
IchenDEV Jun 30, 2026
8f1f967
Parse nested LLM command JSON candidates
IchenDEV Jun 30, 2026
9044d5a
Normalize LLM confidence percentages
IchenDEV Jun 30, 2026
241b6aa
Decode structured LLM replacement payloads
IchenDEV Jun 30, 2026
5206bdb
Prefer later valid LLM command candidates
IchenDEV Jun 30, 2026
856b4b3
Decode structured LLM action payloads
IchenDEV Jun 30, 2026
e7e35ac
Extract LLM JSON candidates from string fields
IchenDEV Jun 30, 2026
c89fb1a
Accept top-level LLM action aliases
IchenDEV Jun 30, 2026
5d9dfe0
Accept top-level LLM confidence aliases
IchenDEV Jun 30, 2026
3ddf6e6
Centralize LLM resolution field aliases
IchenDEV Jun 30, 2026
0d7595b
Extract structured LLM final text output
IchenDEV Jun 30, 2026
bd59b76
Parse remote LLM content blocks
IchenDEV Jun 30, 2026
8a8b796
Strip scaffolded LLM final sections
IchenDEV Jun 30, 2026
7edabaf
Recognize localized LLM final scaffolds
IchenDEV Jun 30, 2026
c3e36ff
Parse structured local ASR transcripts
IchenDEV Jun 30, 2026
120cf69
Harden LLM final text contracts
IchenDEV Jun 30, 2026
06e76ec
Extend LLM output contracts to commands
IchenDEV Jun 30, 2026
c127e73
Accept more LLM resolution aliases
IchenDEV Jun 30, 2026
2d2689a
Parse typed LLM final text blocks
IchenDEV Jun 30, 2026
e830b70
Add focused text context to voice prompts
IchenDEV Jun 30, 2026
f5c32a7
Escape focused context in voice prompts
IchenDEV Jun 30, 2026
f950ae3
Escape external voice prompt context
IchenDEV Jun 30, 2026
f8f2b63
Tolerate sparse OpenAI response choices
IchenDEV Jun 30, 2026
ea50d1d
Parse Responses output text blocks
IchenDEV Jun 30, 2026
7d259a5
Parse top-level LLM output arrays
IchenDEV Jun 30, 2026
b397486
Extract embedded LLM output arrays
IchenDEV Jun 30, 2026
d05b5c0
Parse OpenAI Responses text output
IchenDEV Jun 30, 2026
ae6d442
Harden LLM scaffold tag cleanup
IchenDEV Jun 30, 2026
84039bd
Fence selection edit memory context
IchenDEV Jun 30, 2026
ed5087e
Parse top-level LLM text block arrays
IchenDEV Jun 30, 2026
75be106
Prefer final LLM edit commands
IchenDEV Jun 30, 2026
3e213f5
Avoid copied example edit commands
IchenDEV Jun 30, 2026
902bb84
Parse remote tool call response text
IchenDEV Jun 30, 2026
8bdfb29
Parse decoded remote tool arguments
IchenDEV Jun 30, 2026
03ddbed
Parse Anthropic tool use outputs
IchenDEV Jun 30, 2026
e2ed216
Parse OpenAI parsed response payloads
IchenDEV Jun 30, 2026
c4d4173
Parse structured content object payloads
IchenDEV Jun 30, 2026
28dbcdd
Parse tool payload field variants
IchenDEV Jun 30, 2026
1a384ac
Extract final text from response wrappers
IchenDEV Jun 30, 2026
268e324
Extract final text from content wrappers
IchenDEV Jun 30, 2026
a25bdf9
Parse chunked local ASR transcripts
IchenDEV Jun 30, 2026
41e7035
Parse top-level ASR segment arrays
IchenDEV Jun 30, 2026
dc5edc2
Parse ASR alternative transcripts
IchenDEV Jun 30, 2026
e5f3c10
Parse ASR n-best transcripts
IchenDEV Jun 30, 2026
db8f044
Parse ASR channel transcripts
IchenDEV Jun 30, 2026
81615e0
Accept more local ASR transcript fields
IchenDEV Jun 30, 2026
4ecb00a
Choose confident ASR alternatives
IchenDEV Jun 30, 2026
cc9dfaa
Parse word-level ASR transcripts
IchenDEV Jun 30, 2026
9cd0b55
Decode nested final text payloads
IchenDEV Jun 30, 2026
b9baf72
Extract tool-wrapped final text
IchenDEV Jun 30, 2026
21652d6
Decode richer replacement payloads
IchenDEV Jun 30, 2026
0a14013
Decode richer edit intent payloads
IchenDEV Jun 30, 2026
71fbf8c
Decode top-level edit intent aliases
IchenDEV Jun 30, 2026
01c4225
Decode nested confidence aliases
IchenDEV Jun 30, 2026
8421cfe
Decode parsed response block payloads
IchenDEV Jun 30, 2026
992f403
Decode parsed tool argument payloads
IchenDEV Jun 30, 2026
f61af65
Prefer structured response payloads
IchenDEV Jun 30, 2026
99949f7
Prefer structured ASR transcript candidates
IchenDEV Jun 30, 2026
f16eb75
Prefer Anthropic tool payloads
IchenDEV Jun 30, 2026
1e77b15
Prefer OpenAI response payloads
IchenDEV Jun 30, 2026
33839bf
Ignore irrelevant LLM tool payloads
IchenDEV Jun 30, 2026
e52261d
Improve streaming transcript merging
IchenDEV Jun 30, 2026
0986f88
Fence personal prompt context
IchenDEV Jun 30, 2026
371e9fc
Avoid stale streaming transcripts
IchenDEV Jun 30, 2026
e2e294f
Tighten streaming overlap merging
IchenDEV Jun 30, 2026
f5a7c34
Parse structured JSON LLM blocks
IchenDEV Jun 30, 2026
40ee7a9
Ignore LLM value metadata
IchenDEV Jun 30, 2026
82baa82
Parse typed final text blocks
IchenDEV Jun 30, 2026
a2746d1
Normalize LLM block type names
IchenDEV Jun 30, 2026
df59621
Read LLM block keys case-insensitively
IchenDEV Jun 30, 2026
2680a74
Read LLM response envelope keys case-insensitively
IchenDEV Jun 30, 2026
5cdc957
Ignore non-output LLM content objects
IchenDEV Jun 30, 2026
367fad5
Skip non-object OpenAI choices
IchenDEV Jun 30, 2026
a1e2784
Accept non-array Anthropic content
IchenDEV Jun 30, 2026
ec5efd3
Prefer message tool calls in LLM responses
IchenDEV Jun 30, 2026
785e469
Parse common ASR transcript aliases
IchenDEV Jun 30, 2026
126dc86
Collapse repeated ASR transcripts
IchenDEV Jun 30, 2026
b6116ca
Parse token-level ASR transcripts
IchenDEV Jun 30, 2026
8476472
Improve ASR token joining
IchenDEV Jun 30, 2026
30adaf7
Join ASR connector tokens
IchenDEV Jun 30, 2026
5c1a25b
Normalize ASR tokenizer pieces
IchenDEV Jun 30, 2026
965a5bf
Prefer final ASR transcripts
IchenDEV Jun 30, 2026
d0ecfe3
Prefer final ASR array events
IchenDEV Jun 30, 2026
bbcdfb2
Parse ASR wrapper event outputs
IchenDEV Jun 30, 2026
abf981d
Handle ASR tokenizer status variants
IchenDEV Jun 30, 2026
cc4b3c6
Constrain command screen context facts
IchenDEV Jun 30, 2026
807aeb2
Keep edit context targets distinct
IchenDEV Jun 30, 2026
ce19432
Handle OpenAI delta response payloads
IchenDEV Jun 30, 2026
5c04f66
Parse OpenAI event stream responses
IchenDEV Jun 30, 2026
f3c4a4f
Parse Anthropic event stream responses
IchenDEV Jun 30, 2026
d5a893f
Parse OpenAI Responses event streams
IchenDEV Jun 30, 2026
1a106b2
Parse JSONL local ASR transcripts
IchenDEV Jun 30, 2026
bd4e6c7
Continue streaming word fragments
IchenDEV Jun 30, 2026
092aa65
Parse streamed OpenAI content blocks
IchenDEV Jun 30, 2026
d5c8815
Parse streamed tool argument objects
IchenDEV Jun 30, 2026
d3c3d06
Parse completed Responses stream output
IchenDEV Jun 30, 2026
49f78a4
Join final ASR transcript segments
IchenDEV Jun 30, 2026
bcb4342
Parse NDJSON LLM event streams
IchenDEV Jun 30, 2026
18c32ea
Join final ASR result alternatives
IchenDEV Jun 30, 2026
aaf2ae9
Join Japanese ASR token pieces
IchenDEV Jun 30, 2026
7ebe401
Skip ASR tokenizer control tokens
IchenDEV Jun 30, 2026
929e8fb
Keep JSONL transcripts before terminal events
IchenDEV Jun 30, 2026
94f0817
Share no-space script joining
IchenDEV Jun 30, 2026
66ebcf8
Parse speech-final channel transcripts
IchenDEV Jun 30, 2026
ca213da
Recognize ASR endpoint finality aliases
IchenDEV Jun 30, 2026
df21233
Parse compound ASR finality statuses
IchenDEV Jun 30, 2026
7309aa7
Parse ASR result wrapper aliases
IchenDEV Jun 30, 2026
dc2d44d
Parse endpoint ASR finality statuses
IchenDEV Jun 30, 2026
4acd3ba
Parse ASR confidence envelopes
IchenDEV Jun 30, 2026
fc540b1
Parse Responses content-part done events
IchenDEV Jun 30, 2026
67c5d88
Parse final text value envelopes
IchenDEV Jun 30, 2026
8c0653d
Parse wrapped replacement payloads
IchenDEV Jun 30, 2026
288d0a9
Parse singular streaming tool calls
IchenDEV Jun 30, 2026
4c41e2a
Parse Anthropic stream field aliases
IchenDEV Jun 30, 2026
5342088
Parse typed OpenAI stream deltas
IchenDEV Jun 30, 2026
55407e8
Preserve SSE event names for LLM streams
IchenDEV Jun 30, 2026
2c5c640
Parse Anthropic stream index aliases
IchenDEV Jun 30, 2026
6e76ada
Parse serialized ASR payload wrappers
IchenDEV Jun 30, 2026
7919efb
Skip ASR runner log envelopes
IchenDEV Jun 30, 2026
023913e
Share ASR transcript signal detection
IchenDEV Jun 30, 2026
a1142e8
Refine ASR transcript signal detection
IchenDEV Jun 30, 2026
88f4279
Parse LLM confidence percent aliases
IchenDEV Jun 30, 2026
90e9feb
Parse LLM command type aliases
IchenDEV Jun 30, 2026
33a3769
Parse top-level replacement aliases
IchenDEV Jun 30, 2026
b7fb302
Parse LLM intent goal aliases
IchenDEV Jun 30, 2026
e55d986
Decode quoted JSON command payloads
IchenDEV Jun 30, 2026
9b48945
Parse ASR candidate aliases
IchenDEV Jun 30, 2026
5718c19
Parse ASR confidence aliases
IchenDEV Jun 30, 2026
fcff840
Parse ASR typed value elements
IchenDEV Jun 30, 2026
39a6a7c
Parse ASR stable transcript wrappers
IchenDEV Jun 30, 2026
485787c
Parse LLM action target pairs
IchenDEV Jun 30, 2026
c25d8f6
Decode structured LLM edit targets
IchenDEV Jun 30, 2026
996ef62
Decode LLM target flags
IchenDEV Jun 30, 2026
fe1e0a3
Decode LLM action flags
IchenDEV Jun 30, 2026
7717b82
Decode nested LLM action targets
IchenDEV Jun 30, 2026
e4508ef
Decode LLM action parameter targets
IchenDEV Jun 30, 2026
3d9fa78
Decode LLM intent type presets
IchenDEV Jun 30, 2026
4b12362
Treat LLM certainty as metadata
IchenDEV Jun 30, 2026
a1b518f
Treat LLM justification as metadata
IchenDEV Jul 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Sources/App/VoicePipeline+EditCommands.swift
Original file line numberDiff line numberDiff line change
Expand Up@@ -233,7 +233,8 @@ extension VoicePipeline {

let context = InputContext.capture(
targetApp: targetApp,
screenContext: selectedText,
screenContext: "",
selectedTextOverride: selectedText,
outputMode: .command,
inputLanguage: settings.inputLanguage,
source: .menuBar
Expand Down
2 changes: 1 addition & 1 deletion Sources/App/VoicePipeline+RewriteLast.swift
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,7 +19,7 @@ extension VoicePipeline {

let context = InputContext.capture(
targetApp: targetApp,
screenContext: insertedText,
screenContext: "",
outputMode: .command,
inputLanguage: settings.inputLanguage,
source: .menuBar
Expand Down
17 changes: 2 additions & 15 deletions Sources/LLM/RemoteLLMClient.swift
Original file line numberDiff line numberDiff line change
Expand Up@@ -85,14 +85,7 @@ actor RemoteLLMClient {
let (data, response) = try await URLSession.shared.data(for: request)
try validateHTTP(response, data: data)

guard let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
let choices = json["choices"] as? [[String: Any]],
let first = choices.first,
let message = first["message"] as? [String: Any],
let content = message["content"] as? String else {
throw RemoteLLMError.invalidResponse
}
return content.trimmingCharacters(in: .whitespacesAndNewlines)
return try RemoteLLMResponseText.openAI(from: data)
}

// MARK: - Anthropic Messages format
Expand DownExpand Up@@ -133,13 +126,7 @@ actor RemoteLLMClient {
let (data, response) = try await URLSession.shared.data(for: request)
try validateHTTP(response, data: data)

guard let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
let contentArray = json["content"] as? [[String: Any]],
let first = contentArray.first(where: { ($0["type"] as? String) == "text" }),
let text = first["text"] as? String else {
throw RemoteLLMError.invalidResponse
}
return text.trimmingCharacters(in: .whitespacesAndNewlines)
return try RemoteLLMResponseText.anthropic(from: data)
}

// MARK: - Shared
Expand Down
81 changes: 81 additions & 0 deletions Sources/LLM/RemoteLLMEventPayloads.swift
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
import Foundation

enum RemoteLLMEventPayloads {
static func values(in text: String) -> [String] {
let normalized = text
.replacingOccurrences(of: "\r\n", with: "\n")
.replacingOccurrences(of: "\r", with: "\n")

let ssePayloads = sseValues(in: normalized)
if !ssePayloads.isEmpty { return ssePayloads }

return normalized
.split(separator: "\n", omittingEmptySubsequences: false)
.map { String($0).trimmingCharacters(in: .whitespacesAndNewlines) }
.filter { $0.hasPrefix("{") || $0.hasPrefix("[") }
}
}

private extension RemoteLLMEventPayloads {
static func sseValues(in text: String) -> [String] {
var payloads: [String] = []
var dataLines: [String] = []
var eventName: String?

func flush() {
guard !dataLines.isEmpty else { return }
let payload = dataLines.joined(separator: "\n").trimmingCharacters(in: .whitespacesAndNewlines)
payloads.append(payloadWithEventType(payload, eventName: eventName))
dataLines.removeAll()
eventName = nil
}

for line in text.split(separator: "\n", omittingEmptySubsequences: false) {
if line.isEmpty {
flush()
continue
}
let rawLine = String(line)
if rawLine.localizedCaseInsensitiveComparePrefix("event:") {
eventName = String(rawLine.dropFirst(6)).trimmingCharacters(in: .whitespacesAndNewlines)
continue
}
guard rawLine.localizedCaseInsensitiveComparePrefix("data:") else { continue }
dataLines.append(String(rawLine.dropFirst(5)).trimmingCharacters(in: .whitespaces))
}
flush()
return payloads.filter { !$0.isEmpty }
}

static func payloadWithEventType(_ payload: String, eventName: String?) -> String {
guard let eventName,
!eventName.isEmpty,
let data = payload.data(using: .utf8),
var object = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
object.value(forCaseInsensitiveKey: "type") == nil else {
return payload
}
object["type"] = eventName
guard JSONSerialization.isValidJSONObject(object),
let typedData = try? JSONSerialization.data(withJSONObject: object),
let typedPayload = String(data: typedData, encoding: .utf8) else {
return payload
}
return typedPayload
}
}

private extension String {
func localizedCaseInsensitiveComparePrefix(_ prefix: String) -> Bool {
range(of: prefix, options: [.anchored, .caseInsensitive]) != nil
}
}

private extension Dictionary where Key == String {
func value(forCaseInsensitiveKey key: String) -> Value? {
if let value = self[key] {
return value
}
return first { $0.key.localizedCaseInsensitiveCompare(key) == .orderedSame }?.value
}
}
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
141 commits
Select commit Hold shift + click to select a range
62ca999
Harden LLM command decoding
IchenDEV Jun 30, 2026
93b17cb
Handle case-insensitive LLM command fields
IchenDEV Jun 30, 2026
6f36634
Preserve preset intents with LLM metadata
IchenDEV Jun 30, 2026
8f1f967
Parse nested LLM command JSON candidates
IchenDEV Jun 30, 2026
9044d5a
Normalize LLM confidence percentages
IchenDEV Jun 30, 2026
241b6aa
Decode structured LLM replacement payloads
IchenDEV Jun 30, 2026
5206bdb
Prefer later valid LLM command candidates
IchenDEV Jun 30, 2026
856b4b3
Decode structured LLM action payloads
IchenDEV Jun 30, 2026
e7e35ac
Extract LLM JSON candidates from string fields
IchenDEV Jun 30, 2026
c89fb1a
Accept top-level LLM action aliases
IchenDEV Jun 30, 2026
5d9dfe0
Accept top-level LLM confidence aliases
IchenDEV Jun 30, 2026
3ddf6e6
Centralize LLM resolution field aliases
IchenDEV Jun 30, 2026
0d7595b
Extract structured LLM final text output
IchenDEV Jun 30, 2026
bd59b76
Parse remote LLM content blocks
IchenDEV Jun 30, 2026
8a8b796
Strip scaffolded LLM final sections
IchenDEV Jun 30, 2026
7edabaf
Recognize localized LLM final scaffolds
IchenDEV Jun 30, 2026
c3e36ff
Parse structured local ASR transcripts
IchenDEV Jun 30, 2026
120cf69
Harden LLM final text contracts
IchenDEV Jun 30, 2026
06e76ec
Extend LLM output contracts to commands
IchenDEV Jun 30, 2026
c127e73
Accept more LLM resolution aliases
IchenDEV Jun 30, 2026
2d2689a
Parse typed LLM final text blocks
IchenDEV Jun 30, 2026
e830b70
Add focused text context to voice prompts
IchenDEV Jun 30, 2026
f5c32a7
Escape focused context in voice prompts
IchenDEV Jun 30, 2026
f950ae3
Escape external voice prompt context
IchenDEV Jun 30, 2026
f8f2b63
Tolerate sparse OpenAI response choices
IchenDEV Jun 30, 2026
ea50d1d
Parse Responses output text blocks
IchenDEV Jun 30, 2026
7d259a5
Parse top-level LLM output arrays
IchenDEV Jun 30, 2026
b397486
Extract embedded LLM output arrays
IchenDEV Jun 30, 2026
d05b5c0
Parse OpenAI Responses text output
IchenDEV Jun 30, 2026
ae6d442
Harden LLM scaffold tag cleanup
IchenDEV Jun 30, 2026
84039bd
Fence selection edit memory context
IchenDEV Jun 30, 2026
ed5087e
Parse top-level LLM text block arrays
IchenDEV Jun 30, 2026
75be106
Prefer final LLM edit commands
IchenDEV Jun 30, 2026
3e213f5
Avoid copied example edit commands
IchenDEV Jun 30, 2026
902bb84
Parse remote tool call response text
IchenDEV Jun 30, 2026
8bdfb29
Parse decoded remote tool arguments
IchenDEV Jun 30, 2026
03ddbed
Parse Anthropic tool use outputs
IchenDEV Jun 30, 2026
e2ed216
Parse OpenAI parsed response payloads
IchenDEV Jun 30, 2026
c4d4173
Parse structured content object payloads
IchenDEV Jun 30, 2026
28dbcdd
Parse tool payload field variants
IchenDEV Jun 30, 2026
1a384ac
Extract final text from response wrappers
IchenDEV Jun 30, 2026
268e324
Extract final text from content wrappers
IchenDEV Jun 30, 2026
a25bdf9
Parse chunked local ASR transcripts
IchenDEV Jun 30, 2026
41e7035
Parse top-level ASR segment arrays
IchenDEV Jun 30, 2026
dc5edc2
Parse ASR alternative transcripts
IchenDEV Jun 30, 2026
e5f3c10
Parse ASR n-best transcripts
IchenDEV Jun 30, 2026
db8f044
Parse ASR channel transcripts
IchenDEV Jun 30, 2026
81615e0
Accept more local ASR transcript fields
IchenDEV Jun 30, 2026
4ecb00a
Choose confident ASR alternatives
IchenDEV Jun 30, 2026
cc9dfaa
Parse word-level ASR transcripts
IchenDEV Jun 30, 2026
9cd0b55
Decode nested final text payloads
IchenDEV Jun 30, 2026
b9baf72
Extract tool-wrapped final text
IchenDEV Jun 30, 2026
21652d6
Decode richer replacement payloads
IchenDEV Jun 30, 2026
0a14013
Decode richer edit intent payloads
IchenDEV Jun 30, 2026
71fbf8c
Decode top-level edit intent aliases
IchenDEV Jun 30, 2026
01c4225
Decode nested confidence aliases
IchenDEV Jun 30, 2026
8421cfe
Decode parsed response block payloads
IchenDEV Jun 30, 2026
992f403
Decode parsed tool argument payloads
IchenDEV Jun 30, 2026
f61af65
Prefer structured response payloads
IchenDEV Jun 30, 2026
99949f7
Prefer structured ASR transcript candidates
IchenDEV Jun 30, 2026
f16eb75
Prefer Anthropic tool payloads
IchenDEV Jun 30, 2026
1e77b15
Prefer OpenAI response payloads
IchenDEV Jun 30, 2026
33839bf
Ignore irrelevant LLM tool payloads
IchenDEV Jun 30, 2026
e52261d
Improve streaming transcript merging
IchenDEV Jun 30, 2026
0986f88
Fence personal prompt context
IchenDEV Jun 30, 2026
371e9fc
Avoid stale streaming transcripts
IchenDEV Jun 30, 2026
e2e294f
Tighten streaming overlap merging
IchenDEV Jun 30, 2026
f5a7c34
Parse structured JSON LLM blocks
IchenDEV Jun 30, 2026
40ee7a9
Ignore LLM value metadata
IchenDEV Jun 30, 2026
82baa82
Parse typed final text blocks
IchenDEV Jun 30, 2026
a2746d1
Normalize LLM block type names
IchenDEV Jun 30, 2026
df59621
Read LLM block keys case-insensitively
IchenDEV Jun 30, 2026
2680a74
Read LLM response envelope keys case-insensitively
IchenDEV Jun 30, 2026
5cdc957
Ignore non-output LLM content objects
IchenDEV Jun 30, 2026
367fad5
Skip non-object OpenAI choices
IchenDEV Jun 30, 2026
a1e2784
Accept non-array Anthropic content
IchenDEV Jun 30, 2026
ec5efd3
Prefer message tool calls in LLM responses
IchenDEV Jun 30, 2026
785e469
Parse common ASR transcript aliases
IchenDEV Jun 30, 2026
126dc86
Collapse repeated ASR transcripts
IchenDEV Jun 30, 2026
b6116ca
Parse token-level ASR transcripts
IchenDEV Jun 30, 2026
8476472
Improve ASR token joining
IchenDEV Jun 30, 2026
30adaf7
Join ASR connector tokens
IchenDEV Jun 30, 2026
5c1a25b
Normalize ASR tokenizer pieces
IchenDEV Jun 30, 2026
965a5bf
Prefer final ASR transcripts
IchenDEV Jun 30, 2026
d0ecfe3
Prefer final ASR array events
IchenDEV Jun 30, 2026
bbcdfb2
Parse ASR wrapper event outputs
IchenDEV Jun 30, 2026
abf981d
Handle ASR tokenizer status variants
IchenDEV Jun 30, 2026
cc4b3c6
Constrain command screen context facts
IchenDEV Jun 30, 2026
807aeb2
Keep edit context targets distinct
IchenDEV Jun 30, 2026
ce19432
Handle OpenAI delta response payloads
IchenDEV Jun 30, 2026
5c04f66
Parse OpenAI event stream responses
IchenDEV Jun 30, 2026
f3c4a4f
Parse Anthropic event stream responses
IchenDEV Jun 30, 2026
d5a893f
Parse OpenAI Responses event streams
IchenDEV Jun 30, 2026
1a106b2
Parse JSONL local ASR transcripts
IchenDEV Jun 30, 2026
bd4e6c7
Continue streaming word fragments
IchenDEV Jun 30, 2026
092aa65
Parse streamed OpenAI content blocks
IchenDEV Jun 30, 2026
d5c8815
Parse streamed tool argument objects
IchenDEV Jun 30, 2026
d3c3d06
Parse completed Responses stream output
IchenDEV Jun 30, 2026
49f78a4
Join final ASR transcript segments
IchenDEV Jun 30, 2026
bcb4342
Parse NDJSON LLM event streams
IchenDEV Jun 30, 2026
18c32ea
Join final ASR result alternatives
IchenDEV Jun 30, 2026
aaf2ae9
Join Japanese ASR token pieces
IchenDEV Jun 30, 2026
7ebe401
Skip ASR tokenizer control tokens
IchenDEV Jun 30, 2026
929e8fb
Keep JSONL transcripts before terminal events
IchenDEV Jun 30, 2026
94f0817
Share no-space script joining
IchenDEV Jun 30, 2026
66ebcf8
Parse speech-final channel transcripts
IchenDEV Jun 30, 2026
ca213da
Recognize ASR endpoint finality aliases
IchenDEV Jun 30, 2026
df21233
Parse compound ASR finality statuses
IchenDEV Jun 30, 2026
7309aa7
Parse ASR result wrapper aliases
IchenDEV Jun 30, 2026
dc2d44d
Parse endpoint ASR finality statuses
IchenDEV Jun 30, 2026
4acd3ba
Parse ASR confidence envelopes
IchenDEV Jun 30, 2026
fc540b1
Parse Responses content-part done events
IchenDEV Jun 30, 2026
67c5d88
Parse final text value envelopes
IchenDEV Jun 30, 2026
8c0653d
Parse wrapped replacement payloads
IchenDEV Jun 30, 2026
288d0a9
Parse singular streaming tool calls
IchenDEV Jun 30, 2026
4c41e2a
Parse Anthropic stream field aliases
IchenDEV Jun 30, 2026
5342088
Parse typed OpenAI stream deltas
IchenDEV Jun 30, 2026
55407e8
Preserve SSE event names for LLM streams
IchenDEV Jun 30, 2026
2c5c640
Parse Anthropic stream index aliases
IchenDEV Jun 30, 2026
6e76ada
Parse serialized ASR payload wrappers
IchenDEV Jun 30, 2026
7919efb
Skip ASR runner log envelopes
IchenDEV Jun 30, 2026
023913e
Share ASR transcript signal detection
IchenDEV Jun 30, 2026
a1142e8
Refine ASR transcript signal detection
IchenDEV Jun 30, 2026
88f4279
Parse LLM confidence percent aliases
IchenDEV Jun 30, 2026
90e9feb
Parse LLM command type aliases
IchenDEV Jun 30, 2026
33a3769
Parse top-level replacement aliases
IchenDEV Jun 30, 2026
b7fb302
Parse LLM intent goal aliases
IchenDEV Jun 30, 2026
e55d986
Decode quoted JSON command payloads
IchenDEV Jun 30, 2026
9b48945
Parse ASR candidate aliases
IchenDEV Jun 30, 2026
5718c19
Parse ASR confidence aliases
IchenDEV Jun 30, 2026
fcff840
Parse ASR typed value elements
IchenDEV Jun 30, 2026
39a6a7c
Parse ASR stable transcript wrappers
IchenDEV Jun 30, 2026
485787c
Parse LLM action target pairs
IchenDEV Jun 30, 2026
c25d8f6
Decode structured LLM edit targets
IchenDEV Jun 30, 2026
996ef62
Decode LLM target flags
IchenDEV Jun 30, 2026
fe1e0a3
Decode LLM action flags
IchenDEV Jun 30, 2026
7717b82
Decode nested LLM action targets
IchenDEV Jun 30, 2026
e4508ef
Decode LLM action parameter targets
IchenDEV Jun 30, 2026
3d9fa78
Decode LLM intent type presets
IchenDEV Jun 30, 2026
4b12362
Treat LLM certainty as metadata
IchenDEV Jun 30, 2026
a1b518f
Treat LLM justification as metadata
IchenDEV Jul 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Sources/App/VoicePipeline+EditCommands.swift
Original file line numberDiff line numberDiff line change
Expand Up@@ -233,7 +233,8 @@ extension VoicePipeline {

let context = InputContext.capture(
targetApp: targetApp,
screenContext: selectedText,
screenContext: "",
selectedTextOverride: selectedText,
outputMode: .command,
inputLanguage: settings.inputLanguage,
source: .menuBar
Expand Down
2 changes: 1 addition & 1 deletion Sources/App/VoicePipeline+RewriteLast.swift
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,7 +19,7 @@ extension VoicePipeline {

let context = InputContext.capture(
targetApp: targetApp,
screenContext: insertedText,
screenContext: "",
outputMode: .command,
inputLanguage: settings.inputLanguage,
source: .menuBar
Expand Down
17 changes: 2 additions & 15 deletions Sources/LLM/RemoteLLMClient.swift
Original file line numberDiff line numberDiff line change
Expand Up@@ -85,14 +85,7 @@ actor RemoteLLMClient {
let (data, response) = try await URLSession.shared.data(for: request)
try validateHTTP(response, data: data)

guard let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
let choices = json["choices"] as? [[String: Any]],
let first = choices.first,
let message = first["message"] as? [String: Any],
let content = message["content"] as? String else {
throw RemoteLLMError.invalidResponse
}
return content.trimmingCharacters(in: .whitespacesAndNewlines)
return try RemoteLLMResponseText.openAI(from: data)
}

// MARK: - Anthropic Messages format
Expand DownExpand Up@@ -133,13 +126,7 @@ actor RemoteLLMClient {
let (data, response) = try await URLSession.shared.data(for: request)
try validateHTTP(response, data: data)

guard let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
let contentArray = json["content"] as? [[String: Any]],
let first = contentArray.first(where: { ($0["type"] as? String) == "text" }),
let text = first["text"] as? String else {
throw RemoteLLMError.invalidResponse
}
return text.trimmingCharacters(in: .whitespacesAndNewlines)
return try RemoteLLMResponseText.anthropic(from: data)
}

// MARK: - Shared
Expand Down
81 changes: 81 additions & 0 deletions Sources/LLM/RemoteLLMEventPayloads.swift
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
import Foundation

enum RemoteLLMEventPayloads {
static func values(in text: String) -> [String] {
let normalized = text
.replacingOccurrences(of: "\r\n", with: "\n")
.replacingOccurrences(of: "\r", with: "\n")

let ssePayloads = sseValues(in: normalized)
if !ssePayloads.isEmpty { return ssePayloads }

return normalized
.split(separator: "\n", omittingEmptySubsequences: false)
.map { String($0).trimmingCharacters(in: .whitespacesAndNewlines) }
.filter { $0.hasPrefix("{") || $0.hasPrefix("[") }
}
}

private extension RemoteLLMEventPayloads {
static func sseValues(in text: String) -> [String] {
var payloads: [String] = []
var dataLines: [String] = []
var eventName: String?

func flush() {
guard !dataLines.isEmpty else { return }
let payload = dataLines.joined(separator: "\n").trimmingCharacters(in: .whitespacesAndNewlines)
payloads.append(payloadWithEventType(payload, eventName: eventName))
dataLines.removeAll()
eventName = nil
}

for line in text.split(separator: "\n", omittingEmptySubsequences: false) {
if line.isEmpty {
flush()
continue
}
let rawLine = String(line)
if rawLine.localizedCaseInsensitiveComparePrefix("event:") {
eventName = String(rawLine.dropFirst(6)).trimmingCharacters(in: .whitespacesAndNewlines)
continue
}
guard rawLine.localizedCaseInsensitiveComparePrefix("data:") else { continue }
dataLines.append(String(rawLine.dropFirst(5)).trimmingCharacters(in: .whitespaces))
}
flush()
return payloads.filter { !$0.isEmpty }
}

static func payloadWithEventType(_ payload: String, eventName: String?) -> String {
guard let eventName,
!eventName.isEmpty,
let data = payload.data(using: .utf8),
var object = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
object.value(forCaseInsensitiveKey: "type") == nil else {
return payload
}
object["type"] = eventName
guard JSONSerialization.isValidJSONObject(object),
let typedData = try? JSONSerialization.data(withJSONObject: object),
let typedPayload = String(data: typedData, encoding: .utf8) else {
return payload
}
return typedPayload
}
}

private extension String {
func localizedCaseInsensitiveComparePrefix(_ prefix: String) -> Bool {
range(of: prefix, options: [.anchored, .caseInsensitive]) != nil
}
}

private extension Dictionary where Key == String {
func value(forCaseInsensitiveKey key: String) -> Value? {
if let value = self[key] {
return value
}
return first { $0.key.localizedCaseInsensitiveCompare(key) == .orderedSame }?.value
}
}
Loading
Loading