@hasna/recordings 0.1.16 → 0.1.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/index.js CHANGED
@@ -7,7 +7,7 @@ var __require = import.meta.require;
7
7
  var require_package = __commonJS((exports, module) => {
8
8
  module.exports = {
9
9
  name: "@hasna/recordings",
10
- version: "0.1.16",
10
+ version: "0.1.17",
11
11
  type: "module",
12
12
  description: "Speech-to-text recording tool with MCP and CLI \u2014 records, transcribes, and optionally enhances text using AI",
13
13
  repository: {
@@ -10466,7 +10466,7 @@ async function processText(rawText, config, systemPrompt) {
10466
10466
  }
10467
10467
 
10468
10468
  // src/version.ts
10469
- var VERSION = "0.1.16";
10469
+ var VERSION = "0.1.17";
10470
10470
 
10471
10471
  // src/cli/index.ts
10472
10472
  var program = new Command;
package/dist/mcp/index.js CHANGED
@@ -21,7 +21,7 @@ var __require = import.meta.require;
21
21
  var require_package = __commonJS((exports, module) => {
22
22
  module.exports = {
23
23
  name: "@hasna/recordings",
24
- version: "0.1.16",
24
+ version: "0.1.17",
25
25
  type: "module",
26
26
  description: "Speech-to-text recording tool with MCP and CLI \u2014 records, transcribes, and optionally enhances text using AI",
27
27
  repository: {
@@ -14983,7 +14983,7 @@ async function processText(rawText, config, systemPrompt) {
14983
14983
  }
14984
14984
 
14985
14985
  // src/version.ts
14986
- var VERSION = "0.1.16";
14986
+ var VERSION = "0.1.17";
14987
14987
 
14988
14988
  // src/mcp/index.ts
14989
14989
  var config = loadConfig();
package/dist/version.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export declare const VERSION = "0.1.16";
1
+ export declare const VERSION = "0.1.17";
2
2
  //# sourceMappingURL=version.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/recordings",
3
- "version": "0.1.16",
3
+ "version": "0.1.17",
4
4
  "type": "module",
5
5
  "description": "Speech-to-text recording tool with MCP and CLI — records, transcribes, and optionally enhances text using AI",
6
6
  "repository": {
@@ -17,7 +17,6 @@ public final class RealtimeTranscriptionClient: ObservableObject, @unchecked Sen
17
17
  private var ws: URLSessionWebSocketTask?
18
18
  private var receiveTask: Task<Void, Never>?
19
19
  private var outboundEventTask: Task<Void, Never>?
20
- private var liveCommitTask: Task<Void, Never>?
21
20
  private var isConfigured = false
22
21
  private var pendingAudioChunks: [Data] = []
23
22
  private var itemOrder: [String] = []
@@ -29,11 +28,8 @@ public final class RealtimeTranscriptionClient: ObservableObject, @unchecked Sen
29
28
  private var queuedCommitCount = 0
30
29
  private var uncommittedAudioBytes = 0
31
30
  private var lastRealtimeEventAt = Date.distantPast
32
- private var lastCommitQueuedAt = Date.distantPast
33
31
 
34
32
  private nonisolated static let minimumManualCommitBytes = 5_760
35
- private nonisolated static let minimumLiveCommitBytes = 38_400
36
- private nonisolated static let liveCommitIntervalSeconds: TimeInterval = 0.8
37
33
 
38
34
  private let apiKey: String
39
35
  private let homePath: String
@@ -70,7 +66,6 @@ public final class RealtimeTranscriptionClient: ObservableObject, @unchecked Sen
70
66
  queuedCommitCount = 0
71
67
  uncommittedAudioBytes = 0
72
68
  lastRealtimeEventAt = Date()
73
- lastCommitQueuedAt = Date()
74
69
  error = nil
75
70
 
76
71
  var request = URLRequest(url: Self.transcriptionURL)
@@ -122,7 +117,6 @@ public final class RealtimeTranscriptionClient: ObservableObject, @unchecked Sen
122
117
  "audio": base64,
123
118
  ]
124
119
  enqueueOutboundEvent(msg)
125
- scheduleLiveCommitIfNeeded()
126
120
  }
127
121
 
128
122
  /// Signal end of input — triggers final transcription completion.
@@ -140,7 +134,6 @@ public final class RealtimeTranscriptionClient: ObservableObject, @unchecked Sen
140
134
  uncommittedAudioBytes = 0
141
135
  queuedCommitCount += 1
142
136
  lastRealtimeEventAt = Date()
143
- lastCommitQueuedAt = Date()
144
137
  NativeAppLog.write("realtime commit queued reason=\(reason) bytes=\(bytesToCommit)", homePath: homePath)
145
138
  let commitTask = enqueueOutboundEvent(msg)
146
139
  await commitTask?.value
@@ -178,11 +171,9 @@ public final class RealtimeTranscriptionClient: ObservableObject, @unchecked Sen
178
171
  receiveTask?.cancel()
179
172
  ws?.cancel(with: .normalClosure, reason: nil)
180
173
  outboundEventTask?.cancel()
181
- liveCommitTask?.cancel()
182
174
  ws = nil
183
175
  receiveTask = nil
184
176
  outboundEventTask = nil
185
- liveCommitTask = nil
186
177
  pendingAudioChunks.removeAll(keepingCapacity: true)
187
178
  let text = accumulatedText
188
179
  return text
@@ -285,25 +276,6 @@ public final class RealtimeTranscriptionClient: ObservableObject, @unchecked Sen
285
276
  return task
286
277
  }
287
278
 
288
- private func scheduleLiveCommitIfNeeded(now: Date = Date()) {
289
- guard liveCommitTask == nil else { return }
290
- let secondsSinceLastCommit = now.timeIntervalSince(lastCommitQueuedAt)
291
- guard Self.shouldAutoCommitLiveInput(
292
- uncommittedAudioBytes: uncommittedAudioBytes,
293
- secondsSinceLastCommit: secondsSinceLastCommit
294
- ) else { return }
295
-
296
- liveCommitTask = Task { [weak self] in
297
- _ = await self?.commitLiveInput()
298
- }
299
- }
300
-
301
- @discardableResult
302
- private func commitLiveInput() async -> Bool {
303
- defer { liveCommitTask = nil }
304
- return await commitInput(reason: "live")
305
- }
306
-
307
279
  private func registerItem(_ itemID: String, previousItemID: String?) {
308
280
  guard !itemOrder.contains(itemID) else { return }
309
281
  if let previousItemID, let previousIndex = itemOrder.firstIndex(of: previousItemID) {
@@ -383,14 +355,6 @@ public final class RealtimeTranscriptionClient: ObservableObject, @unchecked Sen
383
355
  uncommittedAudioBytes >= minimumManualCommitBytes
384
356
  }
385
357
 
386
- private nonisolated static func shouldAutoCommitLiveInput(
387
- uncommittedAudioBytes: Int,
388
- secondsSinceLastCommit: TimeInterval
389
- ) -> Bool {
390
- uncommittedAudioBytes >= minimumLiveCommitBytes &&
391
- secondsSinceLastCommit >= liveCommitIntervalSeconds
392
- }
393
-
394
358
  private nonisolated static func transcriptionSessionUpdateEvent(transcription: [String: Any]) -> [String: Any] {
395
359
  [
396
360
  "type": "session.update",
@@ -403,7 +367,12 @@ public final class RealtimeTranscriptionClient: ObservableObject, @unchecked Sen
403
367
  "rate": 24_000,
404
368
  ],
405
369
  "transcription": transcription,
406
- "turn_detection": NSNull(),
370
+ "turn_detection": [
371
+ "type": "server_vad",
372
+ "threshold": 0.5,
373
+ "prefix_padding_ms": 300,
374
+ "silence_duration_ms": 350,
375
+ ],
407
376
  "noise_reduction": [
408
377
  "type": "near_field",
409
378
  ],
@@ -458,16 +427,6 @@ extension RealtimeTranscriptionClient {
458
427
  shouldManuallyCommit(uncommittedAudioBytes: uncommittedAudioBytes)
459
428
  }
460
429
 
461
- public nonisolated static func shouldAutoCommitLiveInputTestHelper(
462
- uncommittedAudioBytes: Int,
463
- secondsSinceLastCommit: TimeInterval
464
- ) -> Bool {
465
- shouldAutoCommitLiveInput(
466
- uncommittedAudioBytes: uncommittedAudioBytes,
467
- secondsSinceLastCommit: secondsSinceLastCommit
468
- )
469
- }
470
-
471
430
  public nonisolated static func sessionUpdateTestHelper(prompt: String, language: String = "") -> [String: Any] {
472
431
  var transcription: [String: Any] = [
473
432
  "model": modelID,
@@ -382,13 +382,12 @@ public final class RecordingEngine: ObservableObject {
382
382
  // MARK: - Real-time Streaming
383
383
 
384
384
  private func startRealtimeStreaming(apiKey: String) {
385
- let systemPrompt = projectStore?.effectiveSystemPrompt ?? ""
386
385
  let client = RealtimeTranscriptionClient(apiKey: apiKey, homePath: home)
387
386
  realtimeClient = client
388
387
  log("realtime streaming task starting")
389
388
 
390
389
  streamingTask = Task {
391
- await client.startStreaming(systemPrompt: systemPrompt)
390
+ await client.startStreaming()
392
391
  self.log("realtime start completed streaming=\(client.isStreaming) error=\(client.error ?? "")")
393
392
 
394
393
  // Receive deltas
@@ -453,25 +452,15 @@ public final class RecordingEngine: ObservableObject {
453
452
  self.streamingTask?.cancel()
454
453
  self.streamingTask = nil
455
454
 
456
- let text = streamingResult.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty ? nil : streamingResult
455
+ let realtimeText = streamingResult.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty ? nil : streamingResult
457
456
 
458
457
  self.liveTranscriptionText = ""
459
458
 
460
- if let text, !Self.shouldFallbackFromPartialRealtime(text: text, pcmByteCount: self.recordedPCM.count) {
461
- self.log("finish using realtime text chars=\(text.count)")
462
- self.isTranscribing = false
463
- self.finishWithText(
464
- text,
465
- curMode: curMode,
466
- targetAppBundleIdentifier: targetAppBundleIdentifier,
467
- activeProjectId: activeProjectId,
468
- activeProjectName: activeProjectName
469
- )
470
- } else if let audioPath, self.writeCapturedWAV(to: audioPath) {
471
- if let text {
472
- self.log("realtime text looked partial chars=\(text.count); falling back to CLI")
459
+ if let audioPath, self.writeCapturedWAV(to: audioPath) {
460
+ if let realtimeText {
461
+ self.log("realtime preview ignored for final chars=\(realtimeText.count)")
473
462
  }
474
- self.log("falling back to CLI transcription audioPath=\(audioPath)")
463
+ self.log("transcribing captured full audio audioPath=\(audioPath)")
475
464
  self.fallbackTranscribe(
476
465
  audioPath: audioPath,
477
466
  curMode: curMode,
@@ -1,4 +1,3 @@
1
- import Foundation
2
1
  import Testing
3
2
  @testable import RecordingsLib
4
3
 
@@ -99,7 +98,8 @@ struct RealtimeTranscriptionTests {
99
98
  #expect(transcription?["prompt"] as? String == "Use Alumia as vocabulary")
100
99
  #expect(transcription?["language"] as? String == "en")
101
100
 
102
- #expect(input?["turn_detection"] is NSNull)
101
+ let turnDetection = input?["turn_detection"] as? [String: Any]
102
+ #expect(turnDetection?["type"] as? String == "server_vad")
103
103
 
104
104
  let include = session?["include"] as? [String]
105
105
  #expect(include?.contains("item.input_audio_transcription.logprobs") == true)
@@ -117,22 +117,6 @@ struct RealtimeTranscriptionTests {
117
117
  #expect(RealtimeTranscriptionClient.shouldManuallyCommitTestHelper(uncommittedAudioBytes: 5_760) == true)
118
118
  }
119
119
 
120
- @Test("Live commits wait for enough audio and elapsed time")
121
- func liveCommitThreshold() {
122
- #expect(RealtimeTranscriptionClient.shouldAutoCommitLiveInputTestHelper(
123
- uncommittedAudioBytes: 38_399,
124
- secondsSinceLastCommit: 1.0
125
- ) == false)
126
- #expect(RealtimeTranscriptionClient.shouldAutoCommitLiveInputTestHelper(
127
- uncommittedAudioBytes: 38_400,
128
- secondsSinceLastCommit: 0.79
129
- ) == false)
130
- #expect(RealtimeTranscriptionClient.shouldAutoCommitLiveInputTestHelper(
131
- uncommittedAudioBytes: 38_400,
132
- secondsSinceLastCommit: 0.8
133
- ) == true)
134
- }
135
-
136
120
  @Test("Partial realtime text falls back for longer recordings")
137
121
  func partialRealtimeFallback() {
138
122
  #expect(RecordingEngine.shouldFallbackFromPartialRealtime(text: "Hi", pcmByteCount: 96_000) == true)