@ai-matrx/desktop-protocol 0.7.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/client.d.cts CHANGED
@@ -1,8 +1,8 @@
1
1
  import { BackoffOptions } from '@ai-matrx/realtime';
2
2
  export { RECONNECT_ALARM_ATTEMPTS } from '@ai-matrx/realtime';
3
3
  import { z } from 'zod';
4
- import { W as Welcome, D as DesktopProtocolError, C as CloseOutcome, O as OpName, a as OpParams, b as OpResult, c as OPS, S as StreamMeta, E as EndReason, d as EVENTS, e as ClientType } from './registry-Duc_VFN8.cjs';
5
- export { i as isDesktopProtocolError } from './registry-Duc_VFN8.cjs';
4
+ import { W as Welcome, D as DesktopProtocolError, C as CloseOutcome, O as OpName, a as OpParams, b as OpResult, c as OPS, S as StreamMeta, E as EndReason, d as EVENTS, e as ClientType, M as ModelsTranscribeResult, T as TranscriptionModelId, f as MlProgress } from './models-CxAHZMAM.cjs';
5
+ export { i as isDesktopProtocolError } from './models-CxAHZMAM.cjs';
6
6
  import { FrameHeader } from './frame.cjs';
7
7
 
8
8
  /** The subset of the WHATWG WebSocket the client uses — browser, React Native, `ws` and Workers all fit. */
@@ -114,6 +114,11 @@ interface DesktopClientOptions {
114
114
  interface RequestOptions {
115
115
  signal?: AbortSignal;
116
116
  timeoutMs?: number;
117
+ /**
118
+ * Called with the request's id before it is sent, so progress events for it (e.g.
119
+ * `models.progress`, whose payload carries `request_id`) can be matched to this call.
120
+ */
121
+ onRequestId?: (id: string) => void;
117
122
  }
118
123
  type StreamOpName = {
119
124
  [N in OpName]: (typeof OPS)[N]["kind"] extends "unary" ? never : N;
@@ -208,4 +213,44 @@ declare function bindDesktopWake(client: Pick<DesktopClient, "wake">, options?:
208
213
  targets?: WakeTargets;
209
214
  }): () => void;
210
215
 
211
- export { type ClientTimers, type DesktopClient, type DesktopClientDiagnostic, type DesktopClientOptions, type DesktopClientState, type DesktopClientStatus, DesktopProtocolError, type DesktopStream, type EventHandler, type EventName, type EventPayload, OpName, OpParams, OpResult, type RequestOptions, type StreamHandlers, type StreamOpName, type StreamOptions, type WakeOptions, type WakeTargets, type WebSocketFactory, type WebSocketLike, bindDesktopWake, createDesktopClient };
216
+ /** fs.write carries at most 4 MiB of bytes (base64 in one message). */
217
+ declare const DESKTOP_TRANSCRIBE_MAX_BYTES: number;
218
+ interface DesktopTranscriberOptions {
219
+ /** Whisper size; omitted = the device's default. */
220
+ model?: TranscriptionModelId;
221
+ /** ISO 639-1; omitted = detect. */
222
+ language?: string;
223
+ /** Per call (first use installs the runtime and a model): default 15 minutes. */
224
+ timeoutMs?: number;
225
+ /** Progress for each call (install steps, then each segment as it is recognised). */
226
+ onProgress?: (progress: MlProgress) => void;
227
+ }
228
+ /** What a transcription returns: the shape `@ai-matrx/browser-audio`'s TranscriptionTransport expects. */
229
+ interface DesktopTranscript {
230
+ text: string;
231
+ language: string;
232
+ duration: number;
233
+ segments: Array<{
234
+ start: number;
235
+ end: number;
236
+ text: string;
237
+ }>;
238
+ result: ModelsTranscribeResult;
239
+ }
240
+ interface DesktopTranscriber {
241
+ /** Transcribe recorded audio on the device: written to its temp folder, transcribed, removed. */
242
+ transcribe(file: Blob, signal?: AbortSignal): Promise<DesktopTranscript>;
243
+ }
244
+ /**
245
+ * Speech to text on the person's own computer, from any surface connected to it (the app's
246
+ * screen, the web app or the phone through the relay). Structurally a
247
+ * `@ai-matrx/browser-audio` TranscriptionTransport, so every voice input on the platform can
248
+ * transcribe locally: `new BrowserAudioKernel({ transport: createDesktopTranscriber(client) })`.
249
+ *
250
+ * Audio travels as one `fs.write` into the device's temp folder (at most 4 MiB; longer
251
+ * recordings are chunked by the kernel), then `models.transcribe`, then `fs.delete` — every step
252
+ * an ordinary op, so this works through every transport with no special path.
253
+ */
254
+ declare function createDesktopTranscriber(client: DesktopClient, options?: DesktopTranscriberOptions): DesktopTranscriber;
255
+
256
+ export { type ClientTimers, DESKTOP_TRANSCRIBE_MAX_BYTES, type DesktopClient, type DesktopClientDiagnostic, type DesktopClientOptions, type DesktopClientState, type DesktopClientStatus, DesktopProtocolError, type DesktopStream, type DesktopTranscriber, type DesktopTranscriberOptions, type DesktopTranscript, type EventHandler, type EventName, type EventPayload, OpName, OpParams, OpResult, type RequestOptions, type StreamHandlers, type StreamOpName, type StreamOptions, type WakeOptions, type WakeTargets, type WebSocketFactory, type WebSocketLike, bindDesktopWake, createDesktopClient, createDesktopTranscriber };
package/dist/client.d.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import { BackoffOptions } from '@ai-matrx/realtime';
2
2
  export { RECONNECT_ALARM_ATTEMPTS } from '@ai-matrx/realtime';
3
3
  import { z } from 'zod';
4
- import { W as Welcome, D as DesktopProtocolError, C as CloseOutcome, O as OpName, a as OpParams, b as OpResult, c as OPS, S as StreamMeta, E as EndReason, d as EVENTS, e as ClientType } from './registry-Duc_VFN8.js';
5
- export { i as isDesktopProtocolError } from './registry-Duc_VFN8.js';
4
+ import { W as Welcome, D as DesktopProtocolError, C as CloseOutcome, O as OpName, a as OpParams, b as OpResult, c as OPS, S as StreamMeta, E as EndReason, d as EVENTS, e as ClientType, M as ModelsTranscribeResult, T as TranscriptionModelId, f as MlProgress } from './models-CxAHZMAM.js';
5
+ export { i as isDesktopProtocolError } from './models-CxAHZMAM.js';
6
6
  import { FrameHeader } from './frame.js';
7
7
 
8
8
  /** The subset of the WHATWG WebSocket the client uses — browser, React Native, `ws` and Workers all fit. */
@@ -114,6 +114,11 @@ interface DesktopClientOptions {
114
114
  interface RequestOptions {
115
115
  signal?: AbortSignal;
116
116
  timeoutMs?: number;
117
+ /**
118
+ * Called with the request's id before it is sent, so progress events for it (e.g.
119
+ * `models.progress`, whose payload carries `request_id`) can be matched to this call.
120
+ */
121
+ onRequestId?: (id: string) => void;
117
122
  }
118
123
  type StreamOpName = {
119
124
  [N in OpName]: (typeof OPS)[N]["kind"] extends "unary" ? never : N;
@@ -208,4 +213,44 @@ declare function bindDesktopWake(client: Pick<DesktopClient, "wake">, options?:
208
213
  targets?: WakeTargets;
209
214
  }): () => void;
210
215
 
211
- export { type ClientTimers, type DesktopClient, type DesktopClientDiagnostic, type DesktopClientOptions, type DesktopClientState, type DesktopClientStatus, DesktopProtocolError, type DesktopStream, type EventHandler, type EventName, type EventPayload, OpName, OpParams, OpResult, type RequestOptions, type StreamHandlers, type StreamOpName, type StreamOptions, type WakeOptions, type WakeTargets, type WebSocketFactory, type WebSocketLike, bindDesktopWake, createDesktopClient };
216
+ /** fs.write carries at most 4 MiB of bytes (base64 in one message). */
217
+ declare const DESKTOP_TRANSCRIBE_MAX_BYTES: number;
218
+ interface DesktopTranscriberOptions {
219
+ /** Whisper size; omitted = the device's default. */
220
+ model?: TranscriptionModelId;
221
+ /** ISO 639-1; omitted = detect. */
222
+ language?: string;
223
+ /** Per call (first use installs the runtime and a model): default 15 minutes. */
224
+ timeoutMs?: number;
225
+ /** Progress for each call (install steps, then each segment as it is recognised). */
226
+ onProgress?: (progress: MlProgress) => void;
227
+ }
228
+ /** What a transcription returns: the shape `@ai-matrx/browser-audio`'s TranscriptionTransport expects. */
229
+ interface DesktopTranscript {
230
+ text: string;
231
+ language: string;
232
+ duration: number;
233
+ segments: Array<{
234
+ start: number;
235
+ end: number;
236
+ text: string;
237
+ }>;
238
+ result: ModelsTranscribeResult;
239
+ }
240
+ interface DesktopTranscriber {
241
+ /** Transcribe recorded audio on the device: written to its temp folder, transcribed, removed. */
242
+ transcribe(file: Blob, signal?: AbortSignal): Promise<DesktopTranscript>;
243
+ }
244
+ /**
245
+ * Speech to text on the person's own computer, from any surface connected to it (the app's
246
+ * screen, the web app or the phone through the relay). Structurally a
247
+ * `@ai-matrx/browser-audio` TranscriptionTransport, so every voice input on the platform can
248
+ * transcribe locally: `new BrowserAudioKernel({ transport: createDesktopTranscriber(client) })`.
249
+ *
250
+ * Audio travels as one `fs.write` into the device's temp folder (at most 4 MiB; longer
251
+ * recordings are chunked by the kernel), then `models.transcribe`, then `fs.delete` — every step
252
+ * an ordinary op, so this works through every transport with no special path.
253
+ */
254
+ declare function createDesktopTranscriber(client: DesktopClient, options?: DesktopTranscriberOptions): DesktopTranscriber;
255
+
256
+ export { type ClientTimers, DESKTOP_TRANSCRIBE_MAX_BYTES, type DesktopClient, type DesktopClientDiagnostic, type DesktopClientOptions, type DesktopClientState, type DesktopClientStatus, DesktopProtocolError, type DesktopStream, type DesktopTranscriber, type DesktopTranscriberOptions, type DesktopTranscript, type EventHandler, type EventName, type EventPayload, OpName, OpParams, OpResult, type RequestOptions, type StreamHandlers, type StreamOpName, type StreamOptions, type WakeOptions, type WakeTargets, type WebSocketFactory, type WebSocketLike, bindDesktopWake, createDesktopClient, createDesktopTranscriber };
package/dist/client.js CHANGED
@@ -94,7 +94,7 @@ import { z as z2 } from "zod";
94
94
  // src/schema/primitives.ts
95
95
  import { z } from "zod";
96
96
  var PROTOCOL_MAJOR = 1;
97
- var PROTOCOL_MINOR = 1;
97
+ var PROTOCOL_MINOR = 2;
98
98
  var PROTOCOL_VERSION = `${PROTOCOL_MAJOR}.${PROTOCOL_MINOR}`;
99
99
  var ProtocolVersion = z.string().regex(/^[1-9][0-9]{0,2}\.[0-9]{1,3}$/);
100
100
  var RequestId = z.string().min(1).max(64).regex(/^[A-Za-z0-9_-]+$/);
@@ -152,7 +152,22 @@ var ErrorReason = z2.enum([
152
152
  "resource_expired",
153
153
  "control_taken",
154
154
  // another client of the same user took control
155
- "window_exhausted"
155
+ "window_exhausted",
156
+ // 1.2 (REASONS_SINCE): the `models` capability and its supervised Python service
157
+ "runtime_not_installed",
158
+ // NOT_FOUND: install the inference runtime first (models.install)
159
+ "model_not_installed",
160
+ // NOT_FOUND: that model's weights are not on the device
161
+ "unsupported_media",
162
+ // INVALID_PARAMS: the file is not audio or video the decoder reads
163
+ "download_failed",
164
+ // install could not fetch a package or model file
165
+ "service_starting",
166
+ // RESOURCE_BUSY (retryable): the service is starting or restarting
167
+ "service_restarted",
168
+ // RESOURCE_GONE: the service died while this request ran
169
+ "service_failed"
170
+ // INTERNAL: the service could not be started (see models.status)
156
171
  ]);
157
172
  var FsPatchEditFailure = z2.strictObject({ edit_index: nonNegInt, reason: z2.string().max(300) });
158
173
  var ProtocolErrorBody = z2.strictObject({
@@ -635,34 +650,115 @@ var SessionListParams = z8.strictObject({});
635
650
  var SessionListResult = z8.strictObject({ resources: z8.array(ResourceInfo) });
636
651
  var SessionControlChangedEvent = z8.strictObject({ controller_client_id: z8.string().max(64).nullable(), your_mode: z8.enum(["control", "read_only"]) });
637
652
 
638
- // src/schema/relay.ts
653
+ // src/schema/models.ts
639
654
  import { z as z9 } from "zod";
640
- var RelayDeviceStatusEvent = z9.strictObject({
641
- online: z9.boolean(),
655
+ var TranscriptionModelId = z9.enum(["tiny", "base", "small", "large-v3-turbo"]);
656
+ var MlRuntimeState = z9.enum(["not_installed", "installing", "installed", "failed"]);
657
+ var MlInstallPhase = z9.enum(["packages", "model", "verifying"]);
658
+ var MlInstallProgress = z9.strictObject({
659
+ phase: MlInstallPhase,
660
+ /** What is being fetched now: a package or a model file name. */
661
+ item: z9.string().max(200),
662
+ bytes_done: nonNegInt,
663
+ /** Null while the total is not known yet. */
664
+ bytes_total: nonNegInt.nullable()
665
+ });
666
+ var TranscriptionModelInfo = z9.strictObject({
667
+ id: TranscriptionModelId,
668
+ /** Download size of the weights. */
669
+ size_bytes: nonNegInt,
670
+ multilingual: z9.boolean(),
671
+ installed: z9.boolean()
672
+ });
673
+ var TranscriptSegment = z9.strictObject({
674
+ start_s: z9.number().min(0),
675
+ end_s: z9.number().min(0),
676
+ text: z9.string().max(4e3)
677
+ });
678
+ var MlStatusParams = z9.strictObject({});
679
+ var MlStatusResult = z9.strictObject({
680
+ runtime: MlRuntimeState,
681
+ /** Non-null while runtime or model files are being installed. */
682
+ install: MlInstallProgress.nullable(),
683
+ /** The last install failure, one human line; null when the last install succeeded or none ran. */
684
+ error: z9.string().max(300).nullable(),
685
+ /** Where inference runs once the runtime is installed: `cuda` only when a usable GPU is present. */
686
+ accelerator: z9.enum(["cpu", "cuda"]),
687
+ models: z9.array(TranscriptionModelInfo).max(16),
688
+ default_model: TranscriptionModelId
689
+ });
690
+ var ModelsInstallParams = z9.strictObject({ model: TranscriptionModelId.optional() });
691
+ var ModelsInstallResult = z9.strictObject({ model: TranscriptionModelId, elapsed_ms: nonNegInt });
692
+ var ModelsTranscribeParams = z9.strictObject({
693
+ path: AbsPath,
694
+ model: TranscriptionModelId.optional(),
695
+ language: z9.string().regex(/^[a-z]{2,3}$/).optional(),
696
+ task: z9.enum(["transcribe", "translate"]).default("transcribe")
697
+ });
698
+ var ModelsTranscribeResult = z9.strictObject({
699
+ text: z9.string().max(4e5),
700
+ language: z9.string().max(8),
701
+ language_probability: z9.number().min(0).max(1),
702
+ duration_s: z9.number().min(0),
703
+ model: TranscriptionModelId,
704
+ segments: z9.array(TranscriptSegment).max(2e4),
705
+ elapsed_ms: nonNegInt
706
+ });
707
+ var MlProgress = z9.strictObject({
708
+ kind: z9.enum(["install", "transcribe"]),
709
+ /** 0–1 when known (transcribe: audio covered so far; install: bytes so far). */
710
+ fraction: z9.number().min(0).max(1).nullable(),
711
+ install: MlInstallProgress.nullable(),
712
+ /** The segment just transcribed (transcribe only): text appears as it is recognised. */
713
+ segment: TranscriptSegment.nullable()
714
+ });
715
+ var MlServiceState = z9.enum(["starting", "ready", "restarting", "failed", "stopped"]);
716
+ var MlServiceStatus = z9.strictObject({
717
+ state: MlServiceState,
718
+ pid: nonNegInt.nullable(),
719
+ /** Restarts since the app started (crashes, missed health checks). */
720
+ restarts: nonNegInt,
721
+ /** The bundled interpreter's version once the service has answered, e.g. "3.13.15". */
722
+ python: z9.string().max(32).nullable(),
723
+ /** Why the service is restarting or failed, one human line. */
724
+ error: z9.string().max(300).nullable()
725
+ });
726
+ var ModelsStatusParams = z9.strictObject({});
727
+ var ModelsStatusResult = z9.strictObject({
728
+ service: MlServiceStatus,
729
+ /** Null until the service has answered (starting, restarting, failed). */
730
+ transcription: MlStatusResult.nullable()
731
+ });
732
+ var ModelsProgressEvent = z9.strictObject({ request_id: RequestId, progress: MlProgress });
733
+
734
+ // src/schema/relay.ts
735
+ import { z as z10 } from "zod";
736
+ var RelayDeviceStatusEvent = z10.strictObject({
737
+ online: z10.boolean(),
642
738
  /**
643
739
  * When the device last went online/offline; null = it has never connected (since 1.1 —
644
740
  * NULL_SINCE: a 1.0 reader is sent 0, its old meaning).
645
741
  */
646
742
  since_ms: epochMs.nullable(),
647
- app_version: z9.string().max(32).nullable(),
743
+ app_version: z10.string().max(32).nullable(),
648
744
  protocol_version: ProtocolVersion.nullable()
649
745
  });
650
- var RelayAuthExpiringEvent = z9.strictObject({ expires_at_ms: epochMs });
651
- var RelayCallRequest = z9.strictObject({
652
- cap: z9.string().min(1).max(32),
653
- op: z9.string().min(1).max(64),
654
- params: z9.unknown(),
655
- timeout_ms: z9.int().min(100).max(37e5).default(7e4)
746
+ var RelayAuthExpiringEvent = z10.strictObject({ expires_at_ms: epochMs });
747
+ var RelayCallRequest = z10.strictObject({
748
+ cap: z10.string().min(1).max(32),
749
+ op: z10.string().min(1).max(64),
750
+ params: z10.unknown(),
751
+ timeout_ms: z10.int().min(100).max(37e5).default(7e4)
656
752
  });
657
- var RelayCallResponse = z9.discriminatedUnion("ok", [
658
- z9.strictObject({
659
- ok: z9.literal(true),
660
- result: z9.unknown(),
753
+ var RelayCallResponse = z10.discriminatedUnion("ok", [
754
+ z10.strictObject({
755
+ ok: z10.literal(true),
756
+ result: z10.unknown(),
661
757
  /** Since 1.1: the version the device negotiated for this call (its welcome). */
662
758
  protocol_version: ProtocolVersion.optional()
663
759
  }),
664
- z9.strictObject({
665
- ok: z9.literal(false),
760
+ z10.strictObject({
761
+ ok: z10.literal(false),
666
762
  error: ProtocolErrorBody,
667
763
  /** Since 1.1, as above (absent when the device never answered hello). */
668
764
  protocol_version: ProtocolVersion.optional()
@@ -670,19 +766,111 @@ var RelayCallResponse = z9.discriminatedUnion("ok", [
670
766
  ]);
671
767
  var RELAY_CALL_HELLO_ID = "hello";
672
768
  var RELAY_CALL_REQUEST_ID = "call";
673
- var RelayCallHello = z9.strictObject({
674
- type: z9.literal("hello"),
675
- id: z9.literal(RELAY_CALL_HELLO_ID),
769
+ var RelayCallHello = z10.strictObject({
770
+ type: z10.literal("hello"),
771
+ id: z10.literal(RELAY_CALL_HELLO_ID),
676
772
  protocolVersion: ProtocolVersion,
677
- clientType: z9.literal("server"),
678
- clientVersion: z9.string().min(1).max(32)
773
+ clientType: z10.literal("server"),
774
+ clientVersion: z10.string().min(1).max(32)
679
775
  });
680
- var RelayCallEnvelope = z9.strictObject({
681
- id: z9.literal(RELAY_CALL_REQUEST_ID),
682
- type: z9.literal("request"),
683
- cap: z9.string().min(1).max(32),
684
- op: z9.string().min(1).max(64),
685
- params: z9.unknown()
776
+ var RelayCallEnvelope = z10.strictObject({
777
+ id: z10.literal(RELAY_CALL_REQUEST_ID),
778
+ type: z10.literal("request"),
779
+ cap: z10.string().min(1).max(32),
780
+ op: z10.string().min(1).max(64),
781
+ params: z10.unknown()
782
+ });
783
+
784
+ // src/schema/apps.ts
785
+ import { z as z11 } from "zod";
786
+ var AppKey = z11.string().regex(/^[a-z][a-z0-9_-]{0,39}$/);
787
+ var AppProfileId = z11.string().regex(/^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/);
788
+ var AppProfileLocator = z11.string().min(1).max(512).regex(/^[A-Za-z0-9 _()+@,-][A-Za-z0-9 ._()+@,-]{0,127}(\/[A-Za-z0-9 _()+@,-][A-Za-z0-9 ._()+@,-]{0,127}){0,7}$/);
789
+ var AppSupport = z11.enum(["supported", "partial", "unsupported"]);
790
+ var AppFeatureStatus = z11.strictObject({
791
+ status: AppSupport,
792
+ /** One line, set whenever status is not "supported". */
793
+ reason: z11.string().max(140).nullable()
794
+ });
795
+ var AppInfo = z11.strictObject({
796
+ app_key: AppKey,
797
+ name: z11.string().max(64),
798
+ installed: z11.boolean(),
799
+ /** The app's own version string, or null when it is not installed or does not say. */
800
+ version: z11.string().max(64).nullable(),
801
+ /** The install found (an .app bundle, an .exe, a binary), or null. */
802
+ location: AbsPath.nullable(),
803
+ /** Open the app at all on this OS. */
804
+ open: AppFeatureStatus,
805
+ /** Separate profiles (one account each) on this OS. */
806
+ profiles: AppFeatureStatus,
807
+ /** Plan usage read from the app's own files on this OS. */
808
+ usage: AppFeatureStatus
809
+ });
810
+ var AppUsageReading = z11.strictObject({
811
+ window: z11.enum(["five_hour", "weekly", "extra"]),
812
+ /** 0–100 for five_hour and weekly; extra-usage spend as a percent of its cap (may pass 100). */
813
+ percent: z11.number().min(0).max(1e4),
814
+ /** When the app recorded it. */
815
+ sampled_at_ms: epochMs,
816
+ /** True when the window has rolled over since the sample, so the figure has reset. */
817
+ reset_since: z11.boolean()
818
+ });
819
+ var AppProfile = z11.strictObject({
820
+ /** The row's id; null for a folder found on disk that no row names yet. */
821
+ id: AppProfileId.nullable(),
822
+ app_key: AppKey,
823
+ display_name: z11.string().min(1).max(120),
824
+ /** The account the person means to be signed in here; null when not stated (never guessed). */
825
+ account_identity: z11.string().min(3).max(320).nullable(),
826
+ profile_locator: AppProfileLocator.nullable(),
827
+ /** The device the row is tied to; null when the row names none. */
828
+ device_id: DeviceId.nullable(),
829
+ /** The folder this profile resolves to on this device; null for a row tied to another device. */
830
+ folder: AbsPath.nullable(),
831
+ /** The folder exists on this device. */
832
+ on_disk: z11.boolean(),
833
+ /** An instance with this profile is running; null when it could not be determined. */
834
+ running: z11.boolean().nullable(),
835
+ /** Usage the app recorded for this profile; null when the app exposes none here. */
836
+ usage: z11.array(AppUsageReading).nullable(),
837
+ last_opened_ms: epochMs.nullable()
838
+ });
839
+ var AppsListParams = z11.strictObject({});
840
+ var AppsListResult = z11.strictObject({ apps: z11.array(AppInfo) });
841
+ var AppsProfilesListParams = z11.strictObject({
842
+ /** Only this app's profiles; every app's when absent. */
843
+ app_key: AppKey.optional()
844
+ });
845
+ var AppsProfilesListResult = z11.strictObject({
846
+ profiles: z11.array(AppProfile),
847
+ /**
848
+ * Whether the saved rows could be read: false while signed out or offline, when only folders
849
+ * found on disk are listed.
850
+ */
851
+ registry: z11.enum(["ok", "signed_out", "unavailable"])
852
+ });
853
+ var AppsProfilesSaveParams = z11.strictObject({
854
+ id: AppProfileId.optional(),
855
+ app_key: AppKey,
856
+ display_name: z11.string().min(1).max(120),
857
+ account_identity: z11.string().min(3).max(320).nullable(),
858
+ /** On create: the folder to use (one found on disk), or null for the app's default profile. Ignored on update. */
859
+ profile_locator: AppProfileLocator.nullable(),
860
+ /** On create with profile_locator null: make a new folder named after display_name instead. */
861
+ new_folder: z11.boolean().default(false)
862
+ });
863
+ var AppsProfilesSaveResult = AppProfile;
864
+ var AppsProfilesRemoveParams = z11.strictObject({ id: AppProfileId });
865
+ var AppsProfilesRemoveResult = z11.strictObject({ removed: z11.boolean() });
866
+ var AppsOpenParams = z11.strictObject({
867
+ app_key: AppKey,
868
+ profile_id: AppProfileId.optional()
869
+ });
870
+ var AppsOpenResult = z11.strictObject({
871
+ /** True when an instance with this profile was already running; it is brought forward. */
872
+ already_running: z11.boolean(),
873
+ folder: AbsPath.nullable()
686
874
  });
687
875
 
688
876
  // src/schema/registry.ts
@@ -711,7 +899,17 @@ var OPS = {
711
899
  "session.attach": { params: SessionAttachParams, result: SessionAttachResult, kind: "stream", since: "1.0", errors: ["RESOURCE_GONE", "AUTH_FORBIDDEN", "RESOURCE_BUSY"] },
712
900
  "session.detach": { params: SessionDetachParams, result: SessionDetachResult, kind: "unary", since: "1.0", errors: ["RESOURCE_GONE"] },
713
901
  "session.release": { params: SessionReleaseParams, result: SessionReleaseResult, kind: "unary", since: "1.0", errors: ["RESOURCE_GONE", "AUTH_FORBIDDEN"] },
714
- "session.list": { params: SessionListParams, result: SessionListResult, kind: "unary", since: "1.0", errors: ["INTERNAL"] }
902
+ "session.list": { params: SessionListParams, result: SessionListResult, kind: "unary", since: "1.0", errors: ["INTERNAL"] },
903
+ // Local models (SPEC §6), served by the `ml` Python service through SERVICES below.
904
+ "models.status": { params: ModelsStatusParams, result: ModelsStatusResult, kind: "unary", since: "1.2", errors: ["INTERNAL"] },
905
+ "models.install": { params: ModelsInstallParams, result: ModelsInstallResult, kind: "unary", since: "1.2", errors: ["RESOURCE_BUSY", "RESOURCE_GONE", "TIMEOUT", "CANCELLED", "INTERNAL"] },
906
+ "models.transcribe": { params: ModelsTranscribeParams, result: ModelsTranscribeResult, kind: "unary", since: "1.2", errors: ["NOT_FOUND", "INVALID_PARAMS", "PERMISSION_DENIED_OS", "RESOURCE_BUSY", "RESOURCE_GONE", "CANCELLED", "PAYLOAD_TOO_LARGE", "INTERNAL"] },
907
+ // Desktop apps and their profiles (SPEC §6 apps); profiles are rows in users.desktop_app_profile.
908
+ "apps.list": { params: AppsListParams, result: AppsListResult, kind: "unary", since: "1.2", errors: ["INTERNAL"] },
909
+ "apps.profiles.list": { params: AppsProfilesListParams, result: AppsProfilesListResult, kind: "unary", since: "1.2", errors: ["NOT_FOUND", "INVALID_PARAMS", "INTERNAL"] },
910
+ "apps.profiles.save": { params: AppsProfilesSaveParams, result: AppsProfilesSaveResult, kind: "unary", since: "1.2", errors: ["AUTH_REQUIRED", "AUTH_FORBIDDEN", "NOT_FOUND", "INVALID_PARAMS", "CONFLICT", "PLATFORM_UNSUPPORTED", "DEVICE_OFFLINE", "INTERNAL"] },
911
+ "apps.profiles.remove": { params: AppsProfilesRemoveParams, result: AppsProfilesRemoveResult, kind: "unary", since: "1.2", errors: ["AUTH_REQUIRED", "AUTH_FORBIDDEN", "NOT_FOUND", "DEVICE_OFFLINE", "INTERNAL"] },
912
+ "apps.open": { params: AppsOpenParams, result: AppsOpenResult, kind: "unary", since: "1.2", errors: ["NOT_FOUND", "PLATFORM_UNSUPPORTED", "AUTH_REQUIRED", "DEVICE_OFFLINE", "INTERNAL"] }
715
913
  };
716
914
  var EVENTS = {
717
915
  "fs.changed": FsChangedEvent,
@@ -719,6 +917,7 @@ var EVENTS = {
719
917
  "exec.title": ExecTitleEvent,
720
918
  "exec.cwd": ExecCwdEvent,
721
919
  "session.control_changed": SessionControlChangedEvent,
920
+ "models.progress": ModelsProgressEvent,
722
921
  "relay.device_status": RelayDeviceStatusEvent,
723
922
  "relay.auth_expiring": RelayAuthExpiringEvent
724
923
  };
@@ -1232,6 +1431,7 @@ function createDesktopClient(options) {
1232
1431
  }
1233
1432
  await ready(opts);
1234
1433
  const id = newId("r");
1434
+ if (opts.onRequestId) safeCall("onRequestId", () => opts.onRequestId?.(id));
1235
1435
  return new Promise((resolve, reject) => {
1236
1436
  const timeoutMs = opts.timeoutMs ?? requestTimeoutMs;
1237
1437
  const timer = timers.setTimeout(() => {
@@ -1439,11 +1639,68 @@ function bindDesktopWake(client, options = {}) {
1439
1639
  targets.window.removeEventListener("focus", onWake);
1440
1640
  };
1441
1641
  }
1642
+
1643
+ // src/client/transcribe.ts
1644
+ var DESKTOP_TRANSCRIBE_MAX_BYTES = 4 * 1024 * 1024;
1645
+ var EXT = {
1646
+ "audio/webm": "webm",
1647
+ "audio/ogg": "ogg",
1648
+ "audio/mp4": "m4a",
1649
+ "audio/mpeg": "mp3",
1650
+ "audio/wav": "wav",
1651
+ "audio/x-wav": "wav",
1652
+ "video/webm": "webm",
1653
+ "video/mp4": "mp4"
1654
+ };
1655
+ function base64(bytes) {
1656
+ let binary = "";
1657
+ for (let i = 0; i < bytes.length; i += 32768) binary += String.fromCharCode(...bytes.subarray(i, i + 32768));
1658
+ return btoa(binary);
1659
+ }
1660
+ function createDesktopTranscriber(client, options = {}) {
1661
+ let temp = null;
1662
+ const tempDir = () => temp ??= client.request("sysinfo.get", {}).then((s) => s.paths.temp.replace(/\/$/, ""));
1663
+ return {
1664
+ async transcribe(file, signal) {
1665
+ if (file.size > DESKTOP_TRANSCRIBE_MAX_BYTES) {
1666
+ throw new DesktopProtocolError("PAYLOAD_TOO_LARGE", "Recording is larger than 4 MB", { retryable: false, data: { limit_bytes: DESKTOP_TRANSCRIBE_MAX_BYTES } });
1667
+ }
1668
+ const type = file.type.split(";")[0]?.trim() ?? "";
1669
+ const name = `matrx-transcribe-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}.${EXT[type] ?? "webm"}`;
1670
+ const path = `${await tempDir()}/${name}`;
1671
+ const bytes = new Uint8Array(await file.arrayBuffer());
1672
+ await client.request("fs.write", { path, content: base64(bytes), encoding: "base64", mode: 384 }, signal ? { signal } : {});
1673
+ let requestId = "";
1674
+ const off = options.onProgress ? client.on("models.progress", (payload) => {
1675
+ if (payload.request_id === requestId) options.onProgress?.(payload.progress);
1676
+ }) : () => void 0;
1677
+ try {
1678
+ const result = await client.request(
1679
+ "models.transcribe",
1680
+ { path, ...options.model ? { model: options.model } : {}, ...options.language ? { language: options.language } : {} },
1681
+ { timeoutMs: options.timeoutMs ?? 15 * 6e4, onRequestId: (id) => requestId = id, ...signal ? { signal } : {} }
1682
+ );
1683
+ return {
1684
+ text: result.text,
1685
+ language: result.language,
1686
+ duration: result.duration_s,
1687
+ segments: result.segments.map((s) => ({ start: s.start_s, end: s.end_s, text: s.text })),
1688
+ result
1689
+ };
1690
+ } finally {
1691
+ off();
1692
+ void client.request("fs.delete", { path, to_trash: false }).catch(() => void 0);
1693
+ }
1694
+ }
1695
+ };
1696
+ }
1442
1697
  export {
1698
+ DESKTOP_TRANSCRIBE_MAX_BYTES,
1443
1699
  DesktopProtocolError,
1444
1700
  RECONNECT_ALARM_ATTEMPTS,
1445
1701
  bindDesktopWake,
1446
1702
  createDesktopClient,
1703
+ createDesktopTranscriber,
1447
1704
  isDesktopProtocolError
1448
1705
  };
1449
1706
  //# sourceMappingURL=client.js.map