@ai-matrx/api 0.1.24 → 0.1.26
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/CHANGELOG.md +10 -1
- package/dist/account.d.cts +2 -2
- package/dist/account.d.ts +2 -2
- package/dist/agents.cjs +11 -11
- package/dist/agents.d.cts +2 -2
- package/dist/agents.d.ts +2 -2
- package/dist/agents.js +1 -1
- package/dist/ai.cjs +2 -2
- package/dist/ai.d.cts +2 -2
- package/dist/ai.d.ts +2 -2
- package/dist/ai.js +1 -1
- package/dist/apps.cjs +5 -5
- package/dist/apps.d.cts +2 -2
- package/dist/apps.d.ts +2 -2
- package/dist/apps.js +1 -1
- package/dist/architecture.cjs +92 -40
- package/dist/architecture.d.cts +236 -40
- package/dist/architecture.d.ts +236 -40
- package/dist/architecture.js +71 -28
- package/dist/audio.cjs +151 -220
- package/dist/audio.d.cts +2 -2
- package/dist/audio.d.ts +2 -2
- package/dist/audio.js +2 -186
- package/dist/chat.cjs +11 -11
- package/dist/chat.d.cts +2 -2
- package/dist/chat.d.ts +2 -2
- package/dist/chat.js +1 -1
- package/dist/chunk-6GYZDL2H.js +186 -0
- package/dist/chunk-76S6F7O3.cjs +224 -0
- package/dist/{chunk-ZOZOF5DN.cjs → chunk-BHSHMV22.cjs} +1 -1
- package/dist/chunk-G6FXF44G.js +128 -0
- package/dist/{chunk-E3PGCGP7.js → chunk-MFZXWYK6.js} +1 -1
- package/dist/chunk-MKN4EXSQ.cjs +1317 -0
- package/dist/chunk-OVQM6CSX.cjs +154 -0
- package/dist/chunk-OWI6FDHV.js +1090 -0
- package/dist/code.cjs +6 -6
- package/dist/code.d.cts +5 -4
- package/dist/code.d.ts +5 -4
- package/dist/code.js +1 -1
- package/dist/commerce.d.cts +2 -2
- package/dist/commerce.d.ts +2 -2
- package/dist/communications.cjs +4 -4
- package/dist/communications.d.cts +4 -2
- package/dist/communications.d.ts +4 -2
- package/dist/communications.js +1 -1
- package/dist/computer.cjs +4 -4
- package/dist/computer.d.cts +2 -2
- package/dist/computer.d.ts +2 -2
- package/dist/computer.js +1 -1
- package/dist/content.cjs +10 -4
- package/dist/content.d.cts +14 -3
- package/dist/content.d.ts +14 -3
- package/dist/content.js +7 -2
- package/dist/crm.cjs +2 -2
- package/dist/crm.d.cts +2 -2
- package/dist/crm.d.ts +2 -2
- package/dist/crm.js +1 -1
- package/dist/data.cjs +50 -3
- package/dist/data.d.cts +199 -3
- package/dist/data.d.ts +199 -3
- package/dist/data.js +41 -2
- package/dist/education.cjs +4 -4
- package/dist/education.d.cts +2 -2
- package/dist/education.d.ts +2 -2
- package/dist/education.js +1 -1
- package/dist/files.cjs +907 -1330
- package/dist/files.d.cts +36 -104
- package/dist/files.d.ts +36 -104
- package/dist/files.js +2 -1107
- package/dist/human-resources.d.cts +2 -2
- package/dist/human-resources.d.ts +2 -2
- package/dist/{index-BgjdVvrS.d.cts → index-DseWSnYe.d.cts} +2 -2
- package/dist/{index-BgjdVvrS.d.ts → index-DseWSnYe.d.ts} +2 -2
- package/dist/index.cjs +62 -58
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +7 -3
- package/dist/integrations.cjs +51 -5
- package/dist/integrations.d.cts +136 -4
- package/dist/integrations.d.ts +136 -4
- package/dist/integrations.js +42 -4
- package/dist/intelligence.cjs +6 -6
- package/dist/intelligence.d.cts +2 -2
- package/dist/intelligence.d.ts +2 -2
- package/dist/intelligence.js +1 -1
- package/dist/knowledge.cjs +42 -42
- package/dist/knowledge.d.cts +3 -3
- package/dist/knowledge.d.ts +3 -3
- package/dist/knowledge.js +1 -1
- package/dist/legal.cjs +6 -6
- package/dist/legal.d.cts +2 -2
- package/dist/legal.d.ts +2 -2
- package/dist/legal.js +1 -1
- package/dist/marketing.cjs +97 -81
- package/dist/marketing.d.cts +81 -8
- package/dist/marketing.d.ts +81 -8
- package/dist/marketing.js +19 -6
- package/dist/masterwork.cjs +32 -32
- package/dist/masterwork.d.cts +2 -2
- package/dist/masterwork.d.ts +2 -2
- package/dist/masterwork.js +1 -1
- package/dist/media.cjs +11 -11
- package/dist/media.d.cts +9 -9
- package/dist/media.d.ts +9 -9
- package/dist/media.js +1 -1
- package/dist/platform.cjs +103 -150
- package/dist/platform.d.cts +2 -2
- package/dist/platform.d.ts +2 -2
- package/dist/platform.js +2 -128
- package/dist/publish.cjs +7 -7
- package/dist/publish.d.cts +2 -2
- package/dist/publish.d.ts +2 -2
- package/dist/publish.js +1 -1
- package/dist/recording.cjs +278 -0
- package/dist/recording.d.cts +44 -0
- package/dist/recording.d.ts +44 -0
- package/dist/recording.js +275 -0
- package/dist/schemas.d.cts +4 -1
- package/dist/schemas.d.ts +4 -1
- package/dist/stream.cjs +10 -10
- package/dist/stream.d.cts +1 -1
- package/dist/stream.d.ts +1 -1
- package/dist/stream.js +1 -1
- package/dist/types.d.cts +735 -32
- package/dist/types.d.ts +735 -32
- package/dist/web.cjs +26 -26
- package/dist/web.d.cts +2 -2
- package/dist/web.d.ts +2 -2
- package/dist/web.js +1 -1
- package/dist/workflows.cjs +32 -14
- package/dist/workflows.d.cts +132 -40
- package/dist/workflows.d.ts +132 -40
- package/dist/workflows.js +18 -3
- package/package.json +14 -3
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var chunkOVQM6CSX_cjs = require('./chunk-OVQM6CSX.cjs');
|
|
4
|
+
var chunkMKN4EXSQ_cjs = require('./chunk-MKN4EXSQ.cjs');
|
|
5
|
+
var chunk76S6F7O3_cjs = require('./chunk-76S6F7O3.cjs');
|
|
6
|
+
var chunkBHSHMV22_cjs = require('./chunk-BHSHMV22.cjs');
|
|
7
|
+
require('./chunk-2SXAAKGV.cjs');
|
|
8
|
+
|
|
9
|
+
// src/recording.ts
|
|
10
|
+
var MatrxAudioClient = class {
|
|
11
|
+
#api;
|
|
12
|
+
#getAccessToken;
|
|
13
|
+
#getOrganizationId;
|
|
14
|
+
#requestTimeoutMs;
|
|
15
|
+
#journalFolder;
|
|
16
|
+
#fullAudioUploads = /* @__PURE__ */ new Map();
|
|
17
|
+
#organizationToken = null;
|
|
18
|
+
#organizationPromise = null;
|
|
19
|
+
constructor(options) {
|
|
20
|
+
const baseUrl = options.baseUrl.replace(/\/$/, "");
|
|
21
|
+
this.#getAccessToken = options.getAccessToken ?? (() => null);
|
|
22
|
+
this.#getOrganizationId = options.getOrganizationId;
|
|
23
|
+
this.#requestTimeoutMs = options.requestTimeoutMs ?? 3e4;
|
|
24
|
+
this.#journalFolder = options.journalFolder ?? "Transcripts/Browser Audio";
|
|
25
|
+
this.#api = chunkBHSHMV22_cjs.createApi({
|
|
26
|
+
credentials: async () => {
|
|
27
|
+
const accessToken = await this.#getAccessToken();
|
|
28
|
+
return accessToken ? { kind: "user", accessToken } : null;
|
|
29
|
+
},
|
|
30
|
+
// One host-supplied origin answers every deployment.
|
|
31
|
+
resolveBaseUrl: () => baseUrl,
|
|
32
|
+
connectTimeoutMs: this.#requestTimeoutMs,
|
|
33
|
+
totalTimeoutMs: this.#requestTimeoutMs
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
async #resolveOrganizationId(token) {
|
|
37
|
+
const supplied = await this.#getOrganizationId?.();
|
|
38
|
+
if (supplied) return supplied;
|
|
39
|
+
if (this.#getOrganizationId) {
|
|
40
|
+
throw new Error(
|
|
41
|
+
"Audio recovery requires an authenticated organization scope."
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
if (this.#organizationToken !== token || !this.#organizationPromise) {
|
|
45
|
+
this.#organizationToken = token;
|
|
46
|
+
const organizationPromise = (async () => {
|
|
47
|
+
const identity = await chunkOVQM6CSX_cjs.whoami(this.#api);
|
|
48
|
+
if (!identity.authenticated || !identity.organization_id) {
|
|
49
|
+
throw new Error(
|
|
50
|
+
"Audio recovery requires an authenticated organization scope."
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
return identity.organization_id;
|
|
54
|
+
})();
|
|
55
|
+
const cachedPromise = organizationPromise.catch((error) => {
|
|
56
|
+
if (this.#organizationPromise === cachedPromise) {
|
|
57
|
+
this.#organizationPromise = null;
|
|
58
|
+
}
|
|
59
|
+
throw error;
|
|
60
|
+
});
|
|
61
|
+
this.#organizationPromise = cachedPromise;
|
|
62
|
+
}
|
|
63
|
+
return this.#organizationPromise;
|
|
64
|
+
}
|
|
65
|
+
/** Per-call options: the organization this signed-in person acts in. */
|
|
66
|
+
async #call(signal) {
|
|
67
|
+
const token = await this.#getAccessToken();
|
|
68
|
+
const options = signal ? { signal } : {};
|
|
69
|
+
if (token) options.organizationId = await this.#resolveOrganizationId(token);
|
|
70
|
+
return options;
|
|
71
|
+
}
|
|
72
|
+
async transcribe(file, signal) {
|
|
73
|
+
const form = new FormData();
|
|
74
|
+
form.append("file", file);
|
|
75
|
+
const result = await chunk76S6F7O3_cjs.transcribeUpload(
|
|
76
|
+
this.#api,
|
|
77
|
+
{ body: form },
|
|
78
|
+
await this.#call(signal)
|
|
79
|
+
);
|
|
80
|
+
return { ...result, text: result.text.trim() };
|
|
81
|
+
}
|
|
82
|
+
async transcribeFull(file, options) {
|
|
83
|
+
const recordingId = options?.recordingId ?? file.name.replace(/\.[^.]+$/, "");
|
|
84
|
+
const uploaded = await this.#upload(
|
|
85
|
+
file,
|
|
86
|
+
`${this.#journalFolder}/${recordingId}--recording.${file.name.split(".").pop() ?? "webm"}`,
|
|
87
|
+
{
|
|
88
|
+
audio_input_stage: "fallback",
|
|
89
|
+
recording_id: recordingId,
|
|
90
|
+
recording_origin: options?.origin,
|
|
91
|
+
ephemeral: false
|
|
92
|
+
}
|
|
93
|
+
);
|
|
94
|
+
this.#fullAudioUploads.set(recordingId, uploaded);
|
|
95
|
+
const result = await chunk76S6F7O3_cjs.transcribeFile(
|
|
96
|
+
this.#api,
|
|
97
|
+
{ body: { file_id: uploaded.file_id } },
|
|
98
|
+
await this.#call(options?.signal)
|
|
99
|
+
);
|
|
100
|
+
return { ...result, text: result.text.trim() };
|
|
101
|
+
}
|
|
102
|
+
async #upload(file, filePath, metadata) {
|
|
103
|
+
const form = new FormData();
|
|
104
|
+
form.append("file", file);
|
|
105
|
+
form.append("file_path", filePath);
|
|
106
|
+
form.append("published_to_web", "false");
|
|
107
|
+
form.append("shown_to", "only_me");
|
|
108
|
+
form.append("visibility", "personal");
|
|
109
|
+
form.append("metadata_json", JSON.stringify({ origin: "transcripts", ...metadata }));
|
|
110
|
+
return chunkMKN4EXSQ_cjs.uploadFile(this.#api, { body: form }, await this.#call());
|
|
111
|
+
}
|
|
112
|
+
async journalChunk(input) {
|
|
113
|
+
const index = String(input.chunkIndex).padStart(4, "0");
|
|
114
|
+
const uploaded = await this.#upload(
|
|
115
|
+
input.file,
|
|
116
|
+
`${this.#journalFolder}/${input.recordingId}--chunk-${index}.${input.file.name.split(".").pop() ?? "webm"}`,
|
|
117
|
+
{
|
|
118
|
+
audio_input_stage: "chunk",
|
|
119
|
+
recording_id: input.recordingId,
|
|
120
|
+
chunk_index: input.chunkIndex,
|
|
121
|
+
recording_origin: input.origin,
|
|
122
|
+
ephemeral: true
|
|
123
|
+
}
|
|
124
|
+
);
|
|
125
|
+
try {
|
|
126
|
+
const journal = await chunk76S6F7O3_cjs.createRecordingJournalChunk(
|
|
127
|
+
this.#api,
|
|
128
|
+
{
|
|
129
|
+
body: {
|
|
130
|
+
recording_id: input.recordingId,
|
|
131
|
+
chunk_index: input.chunkIndex,
|
|
132
|
+
file_id: uploaded.file_id,
|
|
133
|
+
mime_type: input.file.type,
|
|
134
|
+
size_bytes: input.file.size,
|
|
135
|
+
...input.origin ? { origin: { ...input.origin } } : {}
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
await this.#call()
|
|
139
|
+
);
|
|
140
|
+
if (journal.replaced_file_id) {
|
|
141
|
+
await this.#deleteFile(journal.replaced_file_id);
|
|
142
|
+
}
|
|
143
|
+
} catch (error) {
|
|
144
|
+
await this.#deleteFile(uploaded.file_id);
|
|
145
|
+
throw error;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
async finalize(input) {
|
|
149
|
+
const audioResult = this.#fullAudioUploads.get(input.recordingId) ?? await this.#upload(
|
|
150
|
+
input.audio,
|
|
151
|
+
`${this.#journalFolder}/${input.recordingId}--recording.${input.audio.name.split(".").pop() ?? "webm"}`,
|
|
152
|
+
{
|
|
153
|
+
audio_input_stage: "complete",
|
|
154
|
+
recording_id: input.recordingId,
|
|
155
|
+
recording_origin: input.origin,
|
|
156
|
+
ephemeral: false
|
|
157
|
+
}
|
|
158
|
+
);
|
|
159
|
+
const completion = await chunk76S6F7O3_cjs.completeRecordingJournal(
|
|
160
|
+
this.#api,
|
|
161
|
+
{
|
|
162
|
+
path: { recording_id: input.recordingId },
|
|
163
|
+
body: {
|
|
164
|
+
transcript: input.transcript,
|
|
165
|
+
audio_file_id: audioResult.file_id,
|
|
166
|
+
duration_ms: input.durationMs,
|
|
167
|
+
...input.origin ? { origin: { ...input.origin } } : {}
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
await this.#call()
|
|
171
|
+
);
|
|
172
|
+
const manifest = new File(
|
|
173
|
+
[
|
|
174
|
+
JSON.stringify({
|
|
175
|
+
recording_id: input.recordingId,
|
|
176
|
+
transcript: input.transcript,
|
|
177
|
+
audio_file_id: audioResult.file_id,
|
|
178
|
+
transcript_id: completion.transcript_id,
|
|
179
|
+
origin: input.origin,
|
|
180
|
+
completed_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
181
|
+
})
|
|
182
|
+
],
|
|
183
|
+
`${input.recordingId}--complete.json`,
|
|
184
|
+
{ type: "application/json" }
|
|
185
|
+
);
|
|
186
|
+
await this.#upload(
|
|
187
|
+
manifest,
|
|
188
|
+
`${this.#journalFolder}/${manifest.name}`,
|
|
189
|
+
{
|
|
190
|
+
audio_input_stage: "manifest",
|
|
191
|
+
recording_id: input.recordingId,
|
|
192
|
+
ephemeral: false
|
|
193
|
+
}
|
|
194
|
+
);
|
|
195
|
+
this.#fullAudioUploads.delete(input.recordingId);
|
|
196
|
+
}
|
|
197
|
+
async listRecoverable() {
|
|
198
|
+
if (!await this.#getAccessToken()) return [];
|
|
199
|
+
const recordings = await chunk76S6F7O3_cjs.getRecordingJournals(
|
|
200
|
+
this.#api,
|
|
201
|
+
void 0,
|
|
202
|
+
await this.#call()
|
|
203
|
+
);
|
|
204
|
+
return recordings.map((recording) => ({
|
|
205
|
+
recordingId: recording.recording_id,
|
|
206
|
+
updatedAt: Date.parse(recording.updated_at)
|
|
207
|
+
}));
|
|
208
|
+
}
|
|
209
|
+
async loadRecoverable(recordingId) {
|
|
210
|
+
const journal = await chunk76S6F7O3_cjs.getRecordingJournal(
|
|
211
|
+
this.#api,
|
|
212
|
+
{ path: { recording_id: recordingId } },
|
|
213
|
+
await this.#call()
|
|
214
|
+
);
|
|
215
|
+
const chunks = [...journal];
|
|
216
|
+
if (chunks.length === 0) return null;
|
|
217
|
+
chunks.sort((a, b) => a.chunk_index - b.chunk_index);
|
|
218
|
+
const parts = [];
|
|
219
|
+
const missingChunkIndices = [];
|
|
220
|
+
const highestIndex = chunks.at(-1)?.chunk_index ?? -1;
|
|
221
|
+
const journaledIndices = new Set(chunks.map((chunk) => chunk.chunk_index));
|
|
222
|
+
for (let index = 0; index <= highestIndex; index += 1) {
|
|
223
|
+
if (!journaledIndices.has(index)) missingChunkIndices.push(index);
|
|
224
|
+
}
|
|
225
|
+
for (const chunk of chunks) {
|
|
226
|
+
let blob;
|
|
227
|
+
try {
|
|
228
|
+
const response = await chunkMKN4EXSQ_cjs.downloadFile(
|
|
229
|
+
this.#api,
|
|
230
|
+
{ path: { file_id: chunk.file_id }, query: { inline: true } },
|
|
231
|
+
await this.#call()
|
|
232
|
+
);
|
|
233
|
+
blob = await response.blob();
|
|
234
|
+
} catch (error) {
|
|
235
|
+
if (!(error instanceof chunkBHSHMV22_cjs.MatrxApiError)) throw error;
|
|
236
|
+
missingChunkIndices.push(chunk.chunk_index);
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
if (blob.size === 0) {
|
|
240
|
+
missingChunkIndices.push(chunk.chunk_index);
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
parts.push(blob);
|
|
244
|
+
}
|
|
245
|
+
if (parts.length === 0) return null;
|
|
246
|
+
return {
|
|
247
|
+
blob: new Blob(parts, { type: chunks[0]?.mime_type || "audio/webm" }),
|
|
248
|
+
missingChunkIndices: [...new Set(missingChunkIndices)].sort((a, b) => a - b)
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
async discard(recordingId) {
|
|
252
|
+
const result = await chunk76S6F7O3_cjs.deleteRecordingJournal(
|
|
253
|
+
this.#api,
|
|
254
|
+
{ path: { recording_id: recordingId } },
|
|
255
|
+
await this.#call()
|
|
256
|
+
);
|
|
257
|
+
for (const fileId of result.file_ids) {
|
|
258
|
+
await this.#deleteFile(fileId);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
/** Best-effort cleanup of an orphaned file; a failed delete never masks the real outcome. */
|
|
262
|
+
async #deleteFile(fileId) {
|
|
263
|
+
try {
|
|
264
|
+
await chunkMKN4EXSQ_cjs.filesDeleteFile(
|
|
265
|
+
this.#api,
|
|
266
|
+
{ path: { file_id: fileId } },
|
|
267
|
+
await this.#call()
|
|
268
|
+
);
|
|
269
|
+
} catch {
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
};
|
|
273
|
+
function createMatrxAudioClient(options) {
|
|
274
|
+
return new MatrxAudioClient(options);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
exports.MatrxAudioClient = MatrxAudioClient;
|
|
278
|
+
exports.createMatrxAudioClient = createMatrxAudioClient;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { TranscriptionTransport, DurableAudioPersistence, TranscriptionResult, RecordingOrigin } from '@ai-matrx/browser-audio/core';
|
|
2
|
+
|
|
3
|
+
interface MatrxAudioClientOptions {
|
|
4
|
+
baseUrl: string;
|
|
5
|
+
getAccessToken?: () => string | null | Promise<string | null>;
|
|
6
|
+
getOrganizationId?: () => string | null | Promise<string | null>;
|
|
7
|
+
requestTimeoutMs?: number;
|
|
8
|
+
journalFolder?: string;
|
|
9
|
+
}
|
|
10
|
+
declare class MatrxAudioClient implements TranscriptionTransport, DurableAudioPersistence {
|
|
11
|
+
#private;
|
|
12
|
+
constructor(options: MatrxAudioClientOptions);
|
|
13
|
+
transcribe(file: File, signal?: AbortSignal): Promise<TranscriptionResult>;
|
|
14
|
+
transcribeFull(file: File, options?: {
|
|
15
|
+
recordingId?: string;
|
|
16
|
+
origin?: RecordingOrigin;
|
|
17
|
+
signal?: AbortSignal;
|
|
18
|
+
}): Promise<TranscriptionResult>;
|
|
19
|
+
journalChunk(input: {
|
|
20
|
+
recordingId: string;
|
|
21
|
+
chunkIndex: number;
|
|
22
|
+
file: File;
|
|
23
|
+
origin?: RecordingOrigin;
|
|
24
|
+
}): Promise<void>;
|
|
25
|
+
finalize(input: {
|
|
26
|
+
recordingId: string;
|
|
27
|
+
audio: File;
|
|
28
|
+
transcript: string;
|
|
29
|
+
durationMs: number;
|
|
30
|
+
origin?: RecordingOrigin;
|
|
31
|
+
}): Promise<void>;
|
|
32
|
+
listRecoverable(): Promise<Array<{
|
|
33
|
+
recordingId: string;
|
|
34
|
+
updatedAt: number;
|
|
35
|
+
}>>;
|
|
36
|
+
loadRecoverable(recordingId: string): Promise<{
|
|
37
|
+
blob: Blob;
|
|
38
|
+
missingChunkIndices: number[];
|
|
39
|
+
} | null>;
|
|
40
|
+
discard(recordingId: string): Promise<void>;
|
|
41
|
+
}
|
|
42
|
+
declare function createMatrxAudioClient(options: MatrxAudioClientOptions): MatrxAudioClient;
|
|
43
|
+
|
|
44
|
+
export { MatrxAudioClient, type MatrxAudioClientOptions, createMatrxAudioClient };
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { TranscriptionTransport, DurableAudioPersistence, TranscriptionResult, RecordingOrigin } from '@ai-matrx/browser-audio/core';
|
|
2
|
+
|
|
3
|
+
interface MatrxAudioClientOptions {
|
|
4
|
+
baseUrl: string;
|
|
5
|
+
getAccessToken?: () => string | null | Promise<string | null>;
|
|
6
|
+
getOrganizationId?: () => string | null | Promise<string | null>;
|
|
7
|
+
requestTimeoutMs?: number;
|
|
8
|
+
journalFolder?: string;
|
|
9
|
+
}
|
|
10
|
+
declare class MatrxAudioClient implements TranscriptionTransport, DurableAudioPersistence {
|
|
11
|
+
#private;
|
|
12
|
+
constructor(options: MatrxAudioClientOptions);
|
|
13
|
+
transcribe(file: File, signal?: AbortSignal): Promise<TranscriptionResult>;
|
|
14
|
+
transcribeFull(file: File, options?: {
|
|
15
|
+
recordingId?: string;
|
|
16
|
+
origin?: RecordingOrigin;
|
|
17
|
+
signal?: AbortSignal;
|
|
18
|
+
}): Promise<TranscriptionResult>;
|
|
19
|
+
journalChunk(input: {
|
|
20
|
+
recordingId: string;
|
|
21
|
+
chunkIndex: number;
|
|
22
|
+
file: File;
|
|
23
|
+
origin?: RecordingOrigin;
|
|
24
|
+
}): Promise<void>;
|
|
25
|
+
finalize(input: {
|
|
26
|
+
recordingId: string;
|
|
27
|
+
audio: File;
|
|
28
|
+
transcript: string;
|
|
29
|
+
durationMs: number;
|
|
30
|
+
origin?: RecordingOrigin;
|
|
31
|
+
}): Promise<void>;
|
|
32
|
+
listRecoverable(): Promise<Array<{
|
|
33
|
+
recordingId: string;
|
|
34
|
+
updatedAt: number;
|
|
35
|
+
}>>;
|
|
36
|
+
loadRecoverable(recordingId: string): Promise<{
|
|
37
|
+
blob: Blob;
|
|
38
|
+
missingChunkIndices: number[];
|
|
39
|
+
} | null>;
|
|
40
|
+
discard(recordingId: string): Promise<void>;
|
|
41
|
+
}
|
|
42
|
+
declare function createMatrxAudioClient(options: MatrxAudioClientOptions): MatrxAudioClient;
|
|
43
|
+
|
|
44
|
+
export { MatrxAudioClient, type MatrxAudioClientOptions, createMatrxAudioClient };
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import { whoami } from './chunk-G6FXF44G.js';
|
|
2
|
+
import { uploadFile, downloadFile, filesDeleteFile } from './chunk-OWI6FDHV.js';
|
|
3
|
+
import { transcribeUpload, transcribeFile, createRecordingJournalChunk, completeRecordingJournal, getRecordingJournals, getRecordingJournal, deleteRecordingJournal } from './chunk-6GYZDL2H.js';
|
|
4
|
+
import { createApi, MatrxApiError } from './chunk-MFZXWYK6.js';
|
|
5
|
+
import './chunk-KIEAFMKB.js';
|
|
6
|
+
|
|
7
|
+
// src/recording.ts
|
|
8
|
+
var MatrxAudioClient = class {
|
|
9
|
+
#api;
|
|
10
|
+
#getAccessToken;
|
|
11
|
+
#getOrganizationId;
|
|
12
|
+
#requestTimeoutMs;
|
|
13
|
+
#journalFolder;
|
|
14
|
+
#fullAudioUploads = /* @__PURE__ */ new Map();
|
|
15
|
+
#organizationToken = null;
|
|
16
|
+
#organizationPromise = null;
|
|
17
|
+
constructor(options) {
|
|
18
|
+
const baseUrl = options.baseUrl.replace(/\/$/, "");
|
|
19
|
+
this.#getAccessToken = options.getAccessToken ?? (() => null);
|
|
20
|
+
this.#getOrganizationId = options.getOrganizationId;
|
|
21
|
+
this.#requestTimeoutMs = options.requestTimeoutMs ?? 3e4;
|
|
22
|
+
this.#journalFolder = options.journalFolder ?? "Transcripts/Browser Audio";
|
|
23
|
+
this.#api = createApi({
|
|
24
|
+
credentials: async () => {
|
|
25
|
+
const accessToken = await this.#getAccessToken();
|
|
26
|
+
return accessToken ? { kind: "user", accessToken } : null;
|
|
27
|
+
},
|
|
28
|
+
// One host-supplied origin answers every deployment.
|
|
29
|
+
resolveBaseUrl: () => baseUrl,
|
|
30
|
+
connectTimeoutMs: this.#requestTimeoutMs,
|
|
31
|
+
totalTimeoutMs: this.#requestTimeoutMs
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
async #resolveOrganizationId(token) {
|
|
35
|
+
const supplied = await this.#getOrganizationId?.();
|
|
36
|
+
if (supplied) return supplied;
|
|
37
|
+
if (this.#getOrganizationId) {
|
|
38
|
+
throw new Error(
|
|
39
|
+
"Audio recovery requires an authenticated organization scope."
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
if (this.#organizationToken !== token || !this.#organizationPromise) {
|
|
43
|
+
this.#organizationToken = token;
|
|
44
|
+
const organizationPromise = (async () => {
|
|
45
|
+
const identity = await whoami(this.#api);
|
|
46
|
+
if (!identity.authenticated || !identity.organization_id) {
|
|
47
|
+
throw new Error(
|
|
48
|
+
"Audio recovery requires an authenticated organization scope."
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
return identity.organization_id;
|
|
52
|
+
})();
|
|
53
|
+
const cachedPromise = organizationPromise.catch((error) => {
|
|
54
|
+
if (this.#organizationPromise === cachedPromise) {
|
|
55
|
+
this.#organizationPromise = null;
|
|
56
|
+
}
|
|
57
|
+
throw error;
|
|
58
|
+
});
|
|
59
|
+
this.#organizationPromise = cachedPromise;
|
|
60
|
+
}
|
|
61
|
+
return this.#organizationPromise;
|
|
62
|
+
}
|
|
63
|
+
/** Per-call options: the organization this signed-in person acts in. */
|
|
64
|
+
async #call(signal) {
|
|
65
|
+
const token = await this.#getAccessToken();
|
|
66
|
+
const options = signal ? { signal } : {};
|
|
67
|
+
if (token) options.organizationId = await this.#resolveOrganizationId(token);
|
|
68
|
+
return options;
|
|
69
|
+
}
|
|
70
|
+
async transcribe(file, signal) {
|
|
71
|
+
const form = new FormData();
|
|
72
|
+
form.append("file", file);
|
|
73
|
+
const result = await transcribeUpload(
|
|
74
|
+
this.#api,
|
|
75
|
+
{ body: form },
|
|
76
|
+
await this.#call(signal)
|
|
77
|
+
);
|
|
78
|
+
return { ...result, text: result.text.trim() };
|
|
79
|
+
}
|
|
80
|
+
async transcribeFull(file, options) {
|
|
81
|
+
const recordingId = options?.recordingId ?? file.name.replace(/\.[^.]+$/, "");
|
|
82
|
+
const uploaded = await this.#upload(
|
|
83
|
+
file,
|
|
84
|
+
`${this.#journalFolder}/${recordingId}--recording.${file.name.split(".").pop() ?? "webm"}`,
|
|
85
|
+
{
|
|
86
|
+
audio_input_stage: "fallback",
|
|
87
|
+
recording_id: recordingId,
|
|
88
|
+
recording_origin: options?.origin,
|
|
89
|
+
ephemeral: false
|
|
90
|
+
}
|
|
91
|
+
);
|
|
92
|
+
this.#fullAudioUploads.set(recordingId, uploaded);
|
|
93
|
+
const result = await transcribeFile(
|
|
94
|
+
this.#api,
|
|
95
|
+
{ body: { file_id: uploaded.file_id } },
|
|
96
|
+
await this.#call(options?.signal)
|
|
97
|
+
);
|
|
98
|
+
return { ...result, text: result.text.trim() };
|
|
99
|
+
}
|
|
100
|
+
async #upload(file, filePath, metadata) {
|
|
101
|
+
const form = new FormData();
|
|
102
|
+
form.append("file", file);
|
|
103
|
+
form.append("file_path", filePath);
|
|
104
|
+
form.append("published_to_web", "false");
|
|
105
|
+
form.append("shown_to", "only_me");
|
|
106
|
+
form.append("visibility", "personal");
|
|
107
|
+
form.append("metadata_json", JSON.stringify({ origin: "transcripts", ...metadata }));
|
|
108
|
+
return uploadFile(this.#api, { body: form }, await this.#call());
|
|
109
|
+
}
|
|
110
|
+
async journalChunk(input) {
|
|
111
|
+
const index = String(input.chunkIndex).padStart(4, "0");
|
|
112
|
+
const uploaded = await this.#upload(
|
|
113
|
+
input.file,
|
|
114
|
+
`${this.#journalFolder}/${input.recordingId}--chunk-${index}.${input.file.name.split(".").pop() ?? "webm"}`,
|
|
115
|
+
{
|
|
116
|
+
audio_input_stage: "chunk",
|
|
117
|
+
recording_id: input.recordingId,
|
|
118
|
+
chunk_index: input.chunkIndex,
|
|
119
|
+
recording_origin: input.origin,
|
|
120
|
+
ephemeral: true
|
|
121
|
+
}
|
|
122
|
+
);
|
|
123
|
+
try {
|
|
124
|
+
const journal = await createRecordingJournalChunk(
|
|
125
|
+
this.#api,
|
|
126
|
+
{
|
|
127
|
+
body: {
|
|
128
|
+
recording_id: input.recordingId,
|
|
129
|
+
chunk_index: input.chunkIndex,
|
|
130
|
+
file_id: uploaded.file_id,
|
|
131
|
+
mime_type: input.file.type,
|
|
132
|
+
size_bytes: input.file.size,
|
|
133
|
+
...input.origin ? { origin: { ...input.origin } } : {}
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
await this.#call()
|
|
137
|
+
);
|
|
138
|
+
if (journal.replaced_file_id) {
|
|
139
|
+
await this.#deleteFile(journal.replaced_file_id);
|
|
140
|
+
}
|
|
141
|
+
} catch (error) {
|
|
142
|
+
await this.#deleteFile(uploaded.file_id);
|
|
143
|
+
throw error;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
async finalize(input) {
|
|
147
|
+
const audioResult = this.#fullAudioUploads.get(input.recordingId) ?? await this.#upload(
|
|
148
|
+
input.audio,
|
|
149
|
+
`${this.#journalFolder}/${input.recordingId}--recording.${input.audio.name.split(".").pop() ?? "webm"}`,
|
|
150
|
+
{
|
|
151
|
+
audio_input_stage: "complete",
|
|
152
|
+
recording_id: input.recordingId,
|
|
153
|
+
recording_origin: input.origin,
|
|
154
|
+
ephemeral: false
|
|
155
|
+
}
|
|
156
|
+
);
|
|
157
|
+
const completion = await completeRecordingJournal(
|
|
158
|
+
this.#api,
|
|
159
|
+
{
|
|
160
|
+
path: { recording_id: input.recordingId },
|
|
161
|
+
body: {
|
|
162
|
+
transcript: input.transcript,
|
|
163
|
+
audio_file_id: audioResult.file_id,
|
|
164
|
+
duration_ms: input.durationMs,
|
|
165
|
+
...input.origin ? { origin: { ...input.origin } } : {}
|
|
166
|
+
}
|
|
167
|
+
},
|
|
168
|
+
await this.#call()
|
|
169
|
+
);
|
|
170
|
+
const manifest = new File(
|
|
171
|
+
[
|
|
172
|
+
JSON.stringify({
|
|
173
|
+
recording_id: input.recordingId,
|
|
174
|
+
transcript: input.transcript,
|
|
175
|
+
audio_file_id: audioResult.file_id,
|
|
176
|
+
transcript_id: completion.transcript_id,
|
|
177
|
+
origin: input.origin,
|
|
178
|
+
completed_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
179
|
+
})
|
|
180
|
+
],
|
|
181
|
+
`${input.recordingId}--complete.json`,
|
|
182
|
+
{ type: "application/json" }
|
|
183
|
+
);
|
|
184
|
+
await this.#upload(
|
|
185
|
+
manifest,
|
|
186
|
+
`${this.#journalFolder}/${manifest.name}`,
|
|
187
|
+
{
|
|
188
|
+
audio_input_stage: "manifest",
|
|
189
|
+
recording_id: input.recordingId,
|
|
190
|
+
ephemeral: false
|
|
191
|
+
}
|
|
192
|
+
);
|
|
193
|
+
this.#fullAudioUploads.delete(input.recordingId);
|
|
194
|
+
}
|
|
195
|
+
async listRecoverable() {
|
|
196
|
+
if (!await this.#getAccessToken()) return [];
|
|
197
|
+
const recordings = await getRecordingJournals(
|
|
198
|
+
this.#api,
|
|
199
|
+
void 0,
|
|
200
|
+
await this.#call()
|
|
201
|
+
);
|
|
202
|
+
return recordings.map((recording) => ({
|
|
203
|
+
recordingId: recording.recording_id,
|
|
204
|
+
updatedAt: Date.parse(recording.updated_at)
|
|
205
|
+
}));
|
|
206
|
+
}
|
|
207
|
+
async loadRecoverable(recordingId) {
|
|
208
|
+
const journal = await getRecordingJournal(
|
|
209
|
+
this.#api,
|
|
210
|
+
{ path: { recording_id: recordingId } },
|
|
211
|
+
await this.#call()
|
|
212
|
+
);
|
|
213
|
+
const chunks = [...journal];
|
|
214
|
+
if (chunks.length === 0) return null;
|
|
215
|
+
chunks.sort((a, b) => a.chunk_index - b.chunk_index);
|
|
216
|
+
const parts = [];
|
|
217
|
+
const missingChunkIndices = [];
|
|
218
|
+
const highestIndex = chunks.at(-1)?.chunk_index ?? -1;
|
|
219
|
+
const journaledIndices = new Set(chunks.map((chunk) => chunk.chunk_index));
|
|
220
|
+
for (let index = 0; index <= highestIndex; index += 1) {
|
|
221
|
+
if (!journaledIndices.has(index)) missingChunkIndices.push(index);
|
|
222
|
+
}
|
|
223
|
+
for (const chunk of chunks) {
|
|
224
|
+
let blob;
|
|
225
|
+
try {
|
|
226
|
+
const response = await downloadFile(
|
|
227
|
+
this.#api,
|
|
228
|
+
{ path: { file_id: chunk.file_id }, query: { inline: true } },
|
|
229
|
+
await this.#call()
|
|
230
|
+
);
|
|
231
|
+
blob = await response.blob();
|
|
232
|
+
} catch (error) {
|
|
233
|
+
if (!(error instanceof MatrxApiError)) throw error;
|
|
234
|
+
missingChunkIndices.push(chunk.chunk_index);
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
if (blob.size === 0) {
|
|
238
|
+
missingChunkIndices.push(chunk.chunk_index);
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
241
|
+
parts.push(blob);
|
|
242
|
+
}
|
|
243
|
+
if (parts.length === 0) return null;
|
|
244
|
+
return {
|
|
245
|
+
blob: new Blob(parts, { type: chunks[0]?.mime_type || "audio/webm" }),
|
|
246
|
+
missingChunkIndices: [...new Set(missingChunkIndices)].sort((a, b) => a - b)
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
async discard(recordingId) {
|
|
250
|
+
const result = await deleteRecordingJournal(
|
|
251
|
+
this.#api,
|
|
252
|
+
{ path: { recording_id: recordingId } },
|
|
253
|
+
await this.#call()
|
|
254
|
+
);
|
|
255
|
+
for (const fileId of result.file_ids) {
|
|
256
|
+
await this.#deleteFile(fileId);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
/** Best-effort cleanup of an orphaned file; a failed delete never masks the real outcome. */
|
|
260
|
+
async #deleteFile(fileId) {
|
|
261
|
+
try {
|
|
262
|
+
await filesDeleteFile(
|
|
263
|
+
this.#api,
|
|
264
|
+
{ path: { file_id: fileId } },
|
|
265
|
+
await this.#call()
|
|
266
|
+
);
|
|
267
|
+
} catch {
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
};
|
|
271
|
+
function createMatrxAudioClient(options) {
|
|
272
|
+
return new MatrxAudioClient(options);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
export { MatrxAudioClient, createMatrxAudioClient };
|
package/dist/schemas.d.cts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* GENERATED by @ai-matrx/api scripts/generate.mjs from aidream/api-contract — never edit by hand.
|
|
3
|
-
* CONTRACT_SHA256
|
|
3
|
+
* CONTRACT_SHA256 ed1fe1df59e1acd74be297d45d5b1b9f1bb7a637bdc54c53928db1f876875ce9
|
|
4
4
|
*/
|
|
5
5
|
/** Every schema reached from more than one domain (119). Types only — importing this costs nothing at runtime. */
|
|
6
6
|
/** ActionNeeded */
|
|
@@ -896,6 +896,8 @@ type SandboxBindingRequest = {
|
|
|
896
896
|
access_token: string;
|
|
897
897
|
/** Workspace root inside the sandbox */
|
|
898
898
|
root_path?: string;
|
|
899
|
+
/** local_machine: the computer's app_instances id (who the binding reaches), carried, never parsed from base_url. */
|
|
900
|
+
device_id?: string | null;
|
|
899
901
|
/** Semantic filesystem namespace. This is explicit so routing never guesses the target OS or machine kind from path syntax. */
|
|
900
902
|
target_kind?: "sandbox" | "local_machine";
|
|
901
903
|
};
|
|
@@ -1219,6 +1221,7 @@ type UserOverrides = {
|
|
|
1219
1221
|
remove?: string[];
|
|
1220
1222
|
apply_policy?: ("auto" | "ask" | "off") | null;
|
|
1221
1223
|
auto_tools?: boolean | null;
|
|
1224
|
+
search_types?: string[];
|
|
1222
1225
|
};
|
|
1223
1226
|
/** UserVideoMediaPart */
|
|
1224
1227
|
type UserVideoMediaPart = {
|
package/dist/schemas.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* GENERATED by @ai-matrx/api scripts/generate.mjs from aidream/api-contract — never edit by hand.
|
|
3
|
-
* CONTRACT_SHA256
|
|
3
|
+
* CONTRACT_SHA256 ed1fe1df59e1acd74be297d45d5b1b9f1bb7a637bdc54c53928db1f876875ce9
|
|
4
4
|
*/
|
|
5
5
|
/** Every schema reached from more than one domain (119). Types only — importing this costs nothing at runtime. */
|
|
6
6
|
/** ActionNeeded */
|
|
@@ -896,6 +896,8 @@ type SandboxBindingRequest = {
|
|
|
896
896
|
access_token: string;
|
|
897
897
|
/** Workspace root inside the sandbox */
|
|
898
898
|
root_path?: string;
|
|
899
|
+
/** local_machine: the computer's app_instances id (who the binding reaches), carried, never parsed from base_url. */
|
|
900
|
+
device_id?: string | null;
|
|
899
901
|
/** Semantic filesystem namespace. This is explicit so routing never guesses the target OS or machine kind from path syntax. */
|
|
900
902
|
target_kind?: "sandbox" | "local_machine";
|
|
901
903
|
};
|
|
@@ -1219,6 +1221,7 @@ type UserOverrides = {
|
|
|
1219
1221
|
remove?: string[];
|
|
1220
1222
|
apply_policy?: ("auto" | "ask" | "off") | null;
|
|
1221
1223
|
auto_tools?: boolean | null;
|
|
1224
|
+
search_types?: string[];
|
|
1222
1225
|
};
|
|
1223
1226
|
/** UserVideoMediaPart */
|
|
1224
1227
|
type UserVideoMediaPart = {
|