@opengeni/sdk 0.15.0 → 0.23.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/README.md +49 -6
- package/dist/index.d.ts +781 -29
- package/dist/index.js +562 -42
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/client.ts +468 -27
- package/src/index.ts +85 -0
- package/src/transcription.ts +496 -0
- package/src/types.ts +701 -23
package/src/index.ts
CHANGED
|
@@ -2,8 +2,10 @@ export { OpenGeniClient } from "./client";
|
|
|
2
2
|
export type {
|
|
3
3
|
FetchLike,
|
|
4
4
|
OpenGeniClientOptions,
|
|
5
|
+
OpenGeniRequestOptions,
|
|
5
6
|
SendMessageInput,
|
|
6
7
|
SteerMessageResult,
|
|
8
|
+
WorkspaceControlEventPage,
|
|
7
9
|
} from "./client";
|
|
8
10
|
export {
|
|
9
11
|
OpenGeniApiContractMismatchError,
|
|
@@ -50,11 +52,41 @@ export type {
|
|
|
50
52
|
} from "./stream";
|
|
51
53
|
export { streamWorkspaceControlEvents } from "./workspace-control-stream";
|
|
52
54
|
export type { WorkspaceControlStreamTransport } from "./workspace-control-stream";
|
|
55
|
+
export {
|
|
56
|
+
DEFAULT_WORKSPACE_TRANSCRIPTION_POLICY,
|
|
57
|
+
authorizeTranscriptionAdapter,
|
|
58
|
+
createTranscriptionSessionRequest,
|
|
59
|
+
resolveWorkspaceTranscriptionPolicy,
|
|
60
|
+
} from "./transcription";
|
|
61
|
+
export type {
|
|
62
|
+
TranscriptionAdapter,
|
|
63
|
+
TranscriptionAdapterDescriptor,
|
|
64
|
+
TranscriptionAdapterStartContext,
|
|
65
|
+
TranscriptionAuthorization,
|
|
66
|
+
TranscriptionCredentialMode,
|
|
67
|
+
TranscriptionDiagnostic,
|
|
68
|
+
TranscriptionErrorCode,
|
|
69
|
+
TranscriptionEvent,
|
|
70
|
+
TranscriptionEventListener,
|
|
71
|
+
TranscriptionLifecycleStatus,
|
|
72
|
+
TranscriptionPolicyBlockReason,
|
|
73
|
+
TranscriptionResultMetadata,
|
|
74
|
+
TranscriptionSession,
|
|
75
|
+
TranscriptionSessionRequest,
|
|
76
|
+
TranscriptionSpeaker,
|
|
77
|
+
TranscriptionTargetSelection,
|
|
78
|
+
TranscriptionTimeSpan,
|
|
79
|
+
TranscriptionWord,
|
|
80
|
+
WorkspaceTranscriptionPolicy,
|
|
81
|
+
WorkspaceTranscriptionTarget,
|
|
82
|
+
} from "./transcription";
|
|
53
83
|
export {
|
|
54
84
|
KNOWN_PERMISSIONS,
|
|
55
85
|
KNOWN_USAGE_EVENT_TYPES,
|
|
56
86
|
OPENGENI_API_CONTRACT_HEADER,
|
|
57
87
|
OPENGENI_API_CONTRACT_REVISION,
|
|
88
|
+
RETAINED_OUTPUT_DEFAULT_PAGE_BYTES,
|
|
89
|
+
RETAINED_OUTPUT_MAX_PAGE_BYTES,
|
|
58
90
|
SESSION_EVENT_TYPES,
|
|
59
91
|
} from "./types";
|
|
60
92
|
export type {
|
|
@@ -91,12 +123,28 @@ export type {
|
|
|
91
123
|
CapabilityUnavailableReason,
|
|
92
124
|
ClientConfig,
|
|
93
125
|
ClientModel,
|
|
126
|
+
ModelAvailabilityV1,
|
|
127
|
+
ModelBillingAttributionV1,
|
|
128
|
+
ModelCapabilitiesV1,
|
|
129
|
+
ModelCapabilityStateV1,
|
|
130
|
+
ModelCapabilitySupportV1,
|
|
131
|
+
ModelCredentialReadinessV1,
|
|
132
|
+
ModelCredentialSourceV1,
|
|
133
|
+
ModelPricingScheduleV1,
|
|
134
|
+
ModelPricingV1,
|
|
135
|
+
WorkspaceModelCatalogModel,
|
|
136
|
+
WorkspaceModelCatalogResponse,
|
|
94
137
|
CodexAccount,
|
|
138
|
+
CodexAccountOverview,
|
|
95
139
|
CodexAccountsResponse,
|
|
140
|
+
CodexAllocatorUpdate,
|
|
96
141
|
CodexAccountSwitchedPayload,
|
|
97
142
|
CodexConnectionStatus,
|
|
98
143
|
CodexConnectStart,
|
|
99
144
|
CodexConnectPoll,
|
|
145
|
+
CodexOverviewResponse,
|
|
146
|
+
CodexResetCredit,
|
|
147
|
+
CodexResetRedemptionRecovery,
|
|
100
148
|
CodexRotationSettings,
|
|
101
149
|
CodexUsage,
|
|
102
150
|
CodexUsageMap,
|
|
@@ -141,14 +189,21 @@ export type {
|
|
|
141
189
|
EntitlementValue,
|
|
142
190
|
EntitlementsMode,
|
|
143
191
|
FileAsset,
|
|
192
|
+
HumanInputAnswer,
|
|
193
|
+
HumanInputOption,
|
|
194
|
+
HumanInputQuestion,
|
|
195
|
+
HumanInputQuestionKind,
|
|
196
|
+
HumanInputResponse,
|
|
144
197
|
FileDownloadUrlResponse,
|
|
145
198
|
FileResourceRef,
|
|
146
199
|
FileStatus,
|
|
147
200
|
FileUploadData,
|
|
148
201
|
GetPackResponse,
|
|
149
202
|
GitHubAppInfo,
|
|
203
|
+
GitHubInstallationBinding,
|
|
150
204
|
GitHubRepositoriesResponse,
|
|
151
205
|
GitHubRepository,
|
|
206
|
+
GitHubRepositoryScope,
|
|
152
207
|
GoalSpec,
|
|
153
208
|
IntegrationClientMetadata,
|
|
154
209
|
KnownPermission,
|
|
@@ -161,6 +216,8 @@ export type {
|
|
|
161
216
|
KnowledgeSourceKind,
|
|
162
217
|
KnowledgeSourceRef,
|
|
163
218
|
GitCredentialProvider,
|
|
219
|
+
GitCredentialBindingId,
|
|
220
|
+
GitRepositoryAccess,
|
|
164
221
|
ListApiKeysResponse,
|
|
165
222
|
ListConnectionsResponse,
|
|
166
223
|
ListPacksResponse,
|
|
@@ -173,6 +230,13 @@ export type {
|
|
|
173
230
|
Permission,
|
|
174
231
|
ProductAccessMode,
|
|
175
232
|
ReasoningEffort,
|
|
233
|
+
RetainedArtifactContent,
|
|
234
|
+
RetainedArtifactContentOptions,
|
|
235
|
+
RetainedArtifactMetadata,
|
|
236
|
+
RetainedArtifactReference,
|
|
237
|
+
RetainedArtifactUnavailable,
|
|
238
|
+
RetainedOutputKind,
|
|
239
|
+
RetainedOutputUnavailableReason,
|
|
176
240
|
RecordingAvailablePayload,
|
|
177
241
|
RecordingCodec,
|
|
178
242
|
RecordingContentType,
|
|
@@ -201,6 +265,7 @@ export type {
|
|
|
201
265
|
SessionCapabilities,
|
|
202
266
|
SessionListResponse,
|
|
203
267
|
SessionLineageResponse,
|
|
268
|
+
SessionEffectiveToolPolicy,
|
|
204
269
|
SessionQueueMutationResponse,
|
|
205
270
|
SessionQueueSnapshot,
|
|
206
271
|
SessionControlResponse,
|
|
@@ -221,8 +286,10 @@ export type {
|
|
|
221
286
|
SessionSummary,
|
|
222
287
|
LineageNode,
|
|
223
288
|
SessionMcpCredentialUpdateInput,
|
|
289
|
+
SessionMcpApprovalPolicy,
|
|
224
290
|
SessionMcpServerInput,
|
|
225
291
|
SessionMcpServerMetadata,
|
|
292
|
+
SessionToolPolicy,
|
|
226
293
|
// Per-surface capability cell aliases (views of SessionCapabilities).
|
|
227
294
|
FileSystemCapability,
|
|
228
295
|
TerminalCapability,
|
|
@@ -244,16 +311,31 @@ export type {
|
|
|
244
311
|
ViewerHeartbeatRequest,
|
|
245
312
|
ViewerHeartbeatResponse,
|
|
246
313
|
SessionEvent,
|
|
314
|
+
SessionEventCompactResult,
|
|
315
|
+
SessionEventCompactResultOptions,
|
|
316
|
+
SessionEventListOptions,
|
|
317
|
+
SessionEventLatestClass,
|
|
318
|
+
SessionEventPage,
|
|
319
|
+
SessionEventPayloadMode,
|
|
320
|
+
SessionEventReadDirection,
|
|
321
|
+
SessionEventReadMode,
|
|
322
|
+
SessionEventResultMode,
|
|
323
|
+
SessionEventSemanticClass,
|
|
247
324
|
SessionEventType,
|
|
248
325
|
SessionGoal,
|
|
249
326
|
SessionGoalCreatedBy,
|
|
250
327
|
SessionGoalStatus,
|
|
328
|
+
SessionHumanInputRequest,
|
|
251
329
|
SessionStatus,
|
|
252
330
|
SessionStatusChangedPayload,
|
|
253
331
|
SessionStructuredCapabilities,
|
|
254
332
|
SessionTurn,
|
|
333
|
+
ServiceTurnInitiator,
|
|
334
|
+
ServiceTurnInitiatorContext,
|
|
255
335
|
SessionTurnSource,
|
|
256
336
|
SessionTurnStatus,
|
|
337
|
+
SubmitHumanInputResponseRequest,
|
|
338
|
+
TurnInitiator,
|
|
257
339
|
ToolAuthNeededPayload,
|
|
258
340
|
UpdateConnectionRequest,
|
|
259
341
|
// Channel-A structured services (P4.4) — A1 payloads + A2 request/response.
|
|
@@ -316,6 +398,8 @@ export type {
|
|
|
316
398
|
UpdateKnowledgeMemoryRequest,
|
|
317
399
|
UpdateScheduledTaskRequest,
|
|
318
400
|
UpdateSessionGoalRequest,
|
|
401
|
+
UpdateSessionMcpApprovalPolicyRequest,
|
|
402
|
+
UpdateSessionMcpApprovalPolicyResponse,
|
|
319
403
|
UpdateSessionPinRequest,
|
|
320
404
|
UpdateSessionRequest,
|
|
321
405
|
UpdateVariableSetRequest,
|
|
@@ -327,6 +411,7 @@ export type {
|
|
|
327
411
|
UsageEvent,
|
|
328
412
|
UsageEventType,
|
|
329
413
|
UserApprovalDecisionEventInput,
|
|
414
|
+
UserHumanInputResponseEventInput,
|
|
330
415
|
UserMessageEventInput,
|
|
331
416
|
Workspace,
|
|
332
417
|
WorkspaceControlEvent,
|
|
@@ -0,0 +1,496 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Framework- and transport-agnostic speech-to-text capability contract.
|
|
3
|
+
*
|
|
4
|
+
* Audio transport, microphone access, credentials, and provider SDKs belong to
|
|
5
|
+
* host-supplied adapters. This module deliberately contains no browser globals
|
|
6
|
+
* and no provider implementation.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export type TranscriptionCredentialMode = "managed" | "byok";
|
|
10
|
+
|
|
11
|
+
export type WorkspaceTranscriptionTarget = {
|
|
12
|
+
provider: string;
|
|
13
|
+
model: string | null;
|
|
14
|
+
credentialMode: TranscriptionCredentialMode;
|
|
15
|
+
/** Workspace-scoped connection reference. This is never a secret. */
|
|
16
|
+
credentialConnectionId: string | null;
|
|
17
|
+
region: string | null;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export type WorkspaceTranscriptionPolicy = {
|
|
21
|
+
enabled: boolean;
|
|
22
|
+
/** Exact admin-accepted policy identity; required whenever enabled. */
|
|
23
|
+
acceptanceId: string | null;
|
|
24
|
+
primary: WorkspaceTranscriptionTarget | null;
|
|
25
|
+
/** Explicit language preference. Required when automatic detection is not accepted. */
|
|
26
|
+
language: string | null;
|
|
27
|
+
/** Whether the accepted adapter may automatically detect the spoken language. */
|
|
28
|
+
autoDetectLanguage: boolean;
|
|
29
|
+
/** Whether the accepted adapter may identify distinct speakers. */
|
|
30
|
+
diarization: {
|
|
31
|
+
enabled: boolean;
|
|
32
|
+
maxSpeakers: number | null;
|
|
33
|
+
};
|
|
34
|
+
retention: {
|
|
35
|
+
mode: "none" | "provider-policy";
|
|
36
|
+
maxDays: number | null;
|
|
37
|
+
};
|
|
38
|
+
privacy: {
|
|
39
|
+
allowProviderLogging: boolean;
|
|
40
|
+
allowProviderTraining: boolean;
|
|
41
|
+
};
|
|
42
|
+
fallback: {
|
|
43
|
+
mode: "disabled" | "explicit";
|
|
44
|
+
targets: WorkspaceTranscriptionTarget[];
|
|
45
|
+
};
|
|
46
|
+
cost: {
|
|
47
|
+
currency: "USD";
|
|
48
|
+
maxPerHour: number | null;
|
|
49
|
+
maxPerMonth: number | null;
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export const DEFAULT_WORKSPACE_TRANSCRIPTION_POLICY: WorkspaceTranscriptionPolicy = {
|
|
54
|
+
enabled: false,
|
|
55
|
+
acceptanceId: null,
|
|
56
|
+
primary: null,
|
|
57
|
+
language: null,
|
|
58
|
+
autoDetectLanguage: false,
|
|
59
|
+
diarization: { enabled: false, maxSpeakers: null },
|
|
60
|
+
retention: { mode: "none", maxDays: null },
|
|
61
|
+
privacy: { allowProviderLogging: false, allowProviderTraining: false },
|
|
62
|
+
fallback: { mode: "disabled", targets: [] },
|
|
63
|
+
cost: { currency: "USD", maxPerHour: null, maxPerMonth: null },
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export type TranscriptionAdapterDescriptor = {
|
|
67
|
+
provider: string;
|
|
68
|
+
model: string | null;
|
|
69
|
+
credentialMode: TranscriptionCredentialMode;
|
|
70
|
+
region: string | null;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
export type TranscriptionTargetSelection =
|
|
74
|
+
| { kind: "primary" }
|
|
75
|
+
| { kind: "fallback"; index: number };
|
|
76
|
+
|
|
77
|
+
export type TranscriptionPolicyBlockReason =
|
|
78
|
+
| "disabled"
|
|
79
|
+
| "unaccepted"
|
|
80
|
+
| "target_missing"
|
|
81
|
+
| "fallback_disabled"
|
|
82
|
+
| "fallback_unaccepted"
|
|
83
|
+
| "provider_mismatch"
|
|
84
|
+
| "model_mismatch"
|
|
85
|
+
| "credential_mode_mismatch"
|
|
86
|
+
| "region_mismatch";
|
|
87
|
+
|
|
88
|
+
export type TranscriptionAuthorization =
|
|
89
|
+
| {
|
|
90
|
+
authorized: true;
|
|
91
|
+
acceptanceId: string;
|
|
92
|
+
target: WorkspaceTranscriptionTarget;
|
|
93
|
+
selection: TranscriptionTargetSelection;
|
|
94
|
+
}
|
|
95
|
+
| { authorized: false; reason: TranscriptionPolicyBlockReason };
|
|
96
|
+
|
|
97
|
+
export type TranscriptionLifecycleStatus =
|
|
98
|
+
| "idle"
|
|
99
|
+
| "requesting-permission"
|
|
100
|
+
| "listening"
|
|
101
|
+
| "reconnecting"
|
|
102
|
+
| "cancelling"
|
|
103
|
+
| "closed"
|
|
104
|
+
| "error";
|
|
105
|
+
|
|
106
|
+
export type TranscriptionErrorCode =
|
|
107
|
+
| "permission_denied"
|
|
108
|
+
| "not_supported"
|
|
109
|
+
| "network"
|
|
110
|
+
| "provider"
|
|
111
|
+
| "policy_blocked"
|
|
112
|
+
| "timeout"
|
|
113
|
+
| "cancelled"
|
|
114
|
+
| "unknown";
|
|
115
|
+
|
|
116
|
+
export type TranscriptionTimeSpan = {
|
|
117
|
+
startMilliseconds: number;
|
|
118
|
+
endMilliseconds: number;
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
export type TranscriptionSpeaker = {
|
|
122
|
+
/** Provider-neutral identity stable within the local transcription session. */
|
|
123
|
+
id: string;
|
|
124
|
+
label?: string | undefined;
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
export type TranscriptionWord = {
|
|
128
|
+
text: string;
|
|
129
|
+
span: TranscriptionTimeSpan;
|
|
130
|
+
confidence?: number | undefined;
|
|
131
|
+
speaker?: TranscriptionSpeaker | undefined;
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
/** Optional result detail; adapters omit fields their provider cannot supply. */
|
|
135
|
+
export type TranscriptionResultMetadata = {
|
|
136
|
+
detectedLanguage?: string | undefined;
|
|
137
|
+
span?: TranscriptionTimeSpan | undefined;
|
|
138
|
+
confidence?: number | undefined;
|
|
139
|
+
speaker?: TranscriptionSpeaker | undefined;
|
|
140
|
+
words?: TranscriptionWord[] | undefined;
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
export type TranscriptionDiagnostic = {
|
|
144
|
+
operation: "start" | "session" | "cancel" | "close";
|
|
145
|
+
code: TranscriptionErrorCode;
|
|
146
|
+
/** Diagnostic-only detail. React sanitizes and bounds this before forwarding it. */
|
|
147
|
+
detail: string;
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
type TranscriptionEventBase = {
|
|
151
|
+
/** Stable across reconnects and explicitly accepted fallback attempts. */
|
|
152
|
+
localSessionId: string;
|
|
153
|
+
/** Adapter-monotonic across the entire local session, including replay. */
|
|
154
|
+
sequence: number;
|
|
155
|
+
occurredAt: string;
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
export type TranscriptionEvent =
|
|
159
|
+
| (TranscriptionEventBase & { type: "permission.requested" })
|
|
160
|
+
| (TranscriptionEventBase & {
|
|
161
|
+
type: "session.opened";
|
|
162
|
+
providerSessionId: string;
|
|
163
|
+
})
|
|
164
|
+
| (TranscriptionEventBase & {
|
|
165
|
+
type: "transcript.partial";
|
|
166
|
+
segmentId: string;
|
|
167
|
+
text: string;
|
|
168
|
+
metadata?: TranscriptionResultMetadata | undefined;
|
|
169
|
+
})
|
|
170
|
+
| (TranscriptionEventBase & {
|
|
171
|
+
type: "transcript.final";
|
|
172
|
+
segmentId: string;
|
|
173
|
+
text: string;
|
|
174
|
+
/** Stable provider/coordinator acceptance identity used for dedupe. */
|
|
175
|
+
providerAcceptanceId: string;
|
|
176
|
+
metadata?: TranscriptionResultMetadata | undefined;
|
|
177
|
+
})
|
|
178
|
+
| (TranscriptionEventBase & {
|
|
179
|
+
type: "usage";
|
|
180
|
+
audioMilliseconds: number;
|
|
181
|
+
costUsd: number | null;
|
|
182
|
+
})
|
|
183
|
+
| (TranscriptionEventBase & {
|
|
184
|
+
type: "session.reconnecting";
|
|
185
|
+
attempt: number;
|
|
186
|
+
reason: string;
|
|
187
|
+
})
|
|
188
|
+
| (TranscriptionEventBase & {
|
|
189
|
+
type: "session.error";
|
|
190
|
+
code: TranscriptionErrorCode;
|
|
191
|
+
recoverable: boolean;
|
|
192
|
+
})
|
|
193
|
+
| (TranscriptionEventBase & {
|
|
194
|
+
type: "session.closed";
|
|
195
|
+
reason: "completed" | "cancelled" | "error" | "replaced";
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
export type TranscriptionSessionRequest = {
|
|
199
|
+
localSessionId: string;
|
|
200
|
+
policyAcceptanceId: string;
|
|
201
|
+
selection: TranscriptionTargetSelection;
|
|
202
|
+
target: WorkspaceTranscriptionTarget;
|
|
203
|
+
language: string | null;
|
|
204
|
+
autoDetectLanguage: boolean;
|
|
205
|
+
diarization: WorkspaceTranscriptionPolicy["diarization"];
|
|
206
|
+
retention: WorkspaceTranscriptionPolicy["retention"];
|
|
207
|
+
privacy: WorkspaceTranscriptionPolicy["privacy"];
|
|
208
|
+
cost: WorkspaceTranscriptionPolicy["cost"];
|
|
209
|
+
/** A replacement/reconnect adapter must emit events above this floor. */
|
|
210
|
+
sequenceFloor: number;
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
export type TranscriptionEventListener = (event: TranscriptionEvent) => void;
|
|
214
|
+
|
|
215
|
+
export type TranscriptionAdapterStartContext = {
|
|
216
|
+
/** Aborted on local cancellation, policy replacement, timeout, or unmount. */
|
|
217
|
+
signal: AbortSignal;
|
|
218
|
+
/** Non-UI observability seam; callers receive only bounded, redacted detail. */
|
|
219
|
+
reportDiagnostic: (diagnostic: TranscriptionDiagnostic) => void;
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
export type TranscriptionSession = {
|
|
223
|
+
readonly localSessionId: string;
|
|
224
|
+
cancel(reason?: string): Promise<void>;
|
|
225
|
+
close(): Promise<void>;
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
export type TranscriptionAdapter = {
|
|
229
|
+
readonly descriptor: TranscriptionAdapterDescriptor;
|
|
230
|
+
start(
|
|
231
|
+
request: TranscriptionSessionRequest,
|
|
232
|
+
listener: TranscriptionEventListener,
|
|
233
|
+
context: TranscriptionAdapterStartContext,
|
|
234
|
+
): Promise<TranscriptionSession>;
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
/** Invalid or absent settings always resolve to the fail-closed default. */
|
|
238
|
+
export function resolveWorkspaceTranscriptionPolicy(
|
|
239
|
+
settings: unknown,
|
|
240
|
+
): WorkspaceTranscriptionPolicy {
|
|
241
|
+
if (!isRecord(settings)) return cloneDefaultPolicy();
|
|
242
|
+
const candidate = settings.transcription;
|
|
243
|
+
if (!isWorkspaceTranscriptionPolicy(candidate)) return cloneDefaultPolicy();
|
|
244
|
+
return {
|
|
245
|
+
...candidate,
|
|
246
|
+
primary: candidate.primary ? normalizeTarget(candidate.primary) : null,
|
|
247
|
+
language: candidate.language?.trim() ?? null,
|
|
248
|
+
diarization: { ...candidate.diarization },
|
|
249
|
+
retention: { ...candidate.retention },
|
|
250
|
+
privacy: { ...candidate.privacy },
|
|
251
|
+
fallback: {
|
|
252
|
+
mode: candidate.fallback.mode,
|
|
253
|
+
targets: candidate.fallback.targets.map(normalizeTarget),
|
|
254
|
+
},
|
|
255
|
+
cost: { ...candidate.cost },
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Speech authorization is intentionally independent from turn model policy.
|
|
261
|
+
* Every selected adapter must match one exact admin-accepted target.
|
|
262
|
+
*/
|
|
263
|
+
export function authorizeTranscriptionAdapter(
|
|
264
|
+
policy: WorkspaceTranscriptionPolicy,
|
|
265
|
+
descriptor: TranscriptionAdapterDescriptor,
|
|
266
|
+
selection: TranscriptionTargetSelection = { kind: "primary" },
|
|
267
|
+
): TranscriptionAuthorization {
|
|
268
|
+
if (!isWorkspaceTranscriptionPolicy(policy)) {
|
|
269
|
+
return { authorized: false, reason: "unaccepted" };
|
|
270
|
+
}
|
|
271
|
+
if (!policy.enabled) return { authorized: false, reason: "disabled" };
|
|
272
|
+
if (!policy.acceptanceId) return { authorized: false, reason: "unaccepted" };
|
|
273
|
+
let target: WorkspaceTranscriptionTarget | null | undefined;
|
|
274
|
+
if (selection.kind === "primary") {
|
|
275
|
+
target = policy.primary;
|
|
276
|
+
} else {
|
|
277
|
+
if (policy.fallback.mode !== "explicit") {
|
|
278
|
+
return { authorized: false, reason: "fallback_disabled" };
|
|
279
|
+
}
|
|
280
|
+
target = policy.fallback.targets[selection.index];
|
|
281
|
+
if (!target) return { authorized: false, reason: "fallback_unaccepted" };
|
|
282
|
+
}
|
|
283
|
+
if (!target) return { authorized: false, reason: "target_missing" };
|
|
284
|
+
const acceptedTarget = normalizeTarget(target);
|
|
285
|
+
if (acceptedTarget.provider !== descriptor.provider) {
|
|
286
|
+
return { authorized: false, reason: "provider_mismatch" };
|
|
287
|
+
}
|
|
288
|
+
if (acceptedTarget.model !== descriptor.model) {
|
|
289
|
+
return { authorized: false, reason: "model_mismatch" };
|
|
290
|
+
}
|
|
291
|
+
if (acceptedTarget.credentialMode !== descriptor.credentialMode) {
|
|
292
|
+
return { authorized: false, reason: "credential_mode_mismatch" };
|
|
293
|
+
}
|
|
294
|
+
if (acceptedTarget.region !== descriptor.region) {
|
|
295
|
+
return { authorized: false, reason: "region_mismatch" };
|
|
296
|
+
}
|
|
297
|
+
return {
|
|
298
|
+
authorized: true,
|
|
299
|
+
acceptanceId: policy.acceptanceId,
|
|
300
|
+
target: acceptedTarget,
|
|
301
|
+
selection,
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
export function createTranscriptionSessionRequest(input: {
|
|
306
|
+
policy: WorkspaceTranscriptionPolicy;
|
|
307
|
+
adapter: TranscriptionAdapter;
|
|
308
|
+
localSessionId: string;
|
|
309
|
+
selection?: TranscriptionTargetSelection | undefined;
|
|
310
|
+
sequenceFloor?: number | undefined;
|
|
311
|
+
}): TranscriptionSessionRequest | null {
|
|
312
|
+
const sequenceFloor = input.sequenceFloor ?? 0;
|
|
313
|
+
if (!Number.isSafeInteger(sequenceFloor) || sequenceFloor < 0) return null;
|
|
314
|
+
const authorization = authorizeTranscriptionAdapter(
|
|
315
|
+
input.policy,
|
|
316
|
+
input.adapter.descriptor,
|
|
317
|
+
input.selection,
|
|
318
|
+
);
|
|
319
|
+
if (!authorization.authorized) return null;
|
|
320
|
+
return {
|
|
321
|
+
localSessionId: input.localSessionId,
|
|
322
|
+
policyAcceptanceId: authorization.acceptanceId,
|
|
323
|
+
selection: authorization.selection,
|
|
324
|
+
target: { ...authorization.target },
|
|
325
|
+
language: input.policy.language?.trim() ?? null,
|
|
326
|
+
autoDetectLanguage: input.policy.autoDetectLanguage,
|
|
327
|
+
diarization: { ...input.policy.diarization },
|
|
328
|
+
retention: { ...input.policy.retention },
|
|
329
|
+
privacy: { ...input.policy.privacy },
|
|
330
|
+
cost: { ...input.policy.cost },
|
|
331
|
+
sequenceFloor,
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function cloneDefaultPolicy(): WorkspaceTranscriptionPolicy {
|
|
336
|
+
return {
|
|
337
|
+
...DEFAULT_WORKSPACE_TRANSCRIPTION_POLICY,
|
|
338
|
+
diarization: { ...DEFAULT_WORKSPACE_TRANSCRIPTION_POLICY.diarization },
|
|
339
|
+
retention: { ...DEFAULT_WORKSPACE_TRANSCRIPTION_POLICY.retention },
|
|
340
|
+
privacy: { ...DEFAULT_WORKSPACE_TRANSCRIPTION_POLICY.privacy },
|
|
341
|
+
fallback: { mode: "disabled", targets: [] },
|
|
342
|
+
cost: { ...DEFAULT_WORKSPACE_TRANSCRIPTION_POLICY.cost },
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function isWorkspaceTranscriptionPolicy(value: unknown): value is WorkspaceTranscriptionPolicy {
|
|
347
|
+
if (!isRecord(value) || typeof value.enabled !== "boolean") return false;
|
|
348
|
+
if (
|
|
349
|
+
!hasOnlyKeys(value, [
|
|
350
|
+
"enabled",
|
|
351
|
+
"acceptanceId",
|
|
352
|
+
"primary",
|
|
353
|
+
"language",
|
|
354
|
+
"autoDetectLanguage",
|
|
355
|
+
"diarization",
|
|
356
|
+
"retention",
|
|
357
|
+
"privacy",
|
|
358
|
+
"fallback",
|
|
359
|
+
"cost",
|
|
360
|
+
])
|
|
361
|
+
) {
|
|
362
|
+
return false;
|
|
363
|
+
}
|
|
364
|
+
if (!(value.acceptanceId === null || isUuid(value.acceptanceId))) return false;
|
|
365
|
+
if (!(value.primary === null || isTarget(value.primary))) return false;
|
|
366
|
+
if (!(value.language === null || isBoundedString(value.language, 64))) return false;
|
|
367
|
+
if (typeof value.autoDetectLanguage !== "boolean") return false;
|
|
368
|
+
if (
|
|
369
|
+
!isRecord(value.diarization) ||
|
|
370
|
+
!hasOnlyKeys(value.diarization, ["enabled", "maxSpeakers"]) ||
|
|
371
|
+
typeof value.diarization.enabled !== "boolean" ||
|
|
372
|
+
!(
|
|
373
|
+
value.diarization.maxSpeakers === null ||
|
|
374
|
+
(isBoundedInteger(value.diarization.maxSpeakers, 100) && value.diarization.maxSpeakers >= 2)
|
|
375
|
+
)
|
|
376
|
+
) {
|
|
377
|
+
return false;
|
|
378
|
+
}
|
|
379
|
+
if (!value.diarization.enabled && value.diarization.maxSpeakers !== null) return false;
|
|
380
|
+
if (!isRecord(value.retention) || !hasOnlyKeys(value.retention, ["mode", "maxDays"])) {
|
|
381
|
+
return false;
|
|
382
|
+
}
|
|
383
|
+
if (value.retention.mode !== "none" && value.retention.mode !== "provider-policy") return false;
|
|
384
|
+
if (!(value.retention.maxDays === null || isBoundedInteger(value.retention.maxDays, 3650))) {
|
|
385
|
+
return false;
|
|
386
|
+
}
|
|
387
|
+
if (
|
|
388
|
+
!isRecord(value.privacy) ||
|
|
389
|
+
!hasOnlyKeys(value.privacy, ["allowProviderLogging", "allowProviderTraining"]) ||
|
|
390
|
+
typeof value.privacy.allowProviderLogging !== "boolean" ||
|
|
391
|
+
typeof value.privacy.allowProviderTraining !== "boolean"
|
|
392
|
+
) {
|
|
393
|
+
return false;
|
|
394
|
+
}
|
|
395
|
+
if (!isRecord(value.fallback) || !hasOnlyKeys(value.fallback, ["mode", "targets"])) {
|
|
396
|
+
return false;
|
|
397
|
+
}
|
|
398
|
+
if (value.fallback.mode !== "disabled" && value.fallback.mode !== "explicit") return false;
|
|
399
|
+
if (
|
|
400
|
+
!Array.isArray(value.fallback.targets) ||
|
|
401
|
+
value.fallback.targets.length > 8 ||
|
|
402
|
+
!value.fallback.targets.every(isTarget)
|
|
403
|
+
) {
|
|
404
|
+
return false;
|
|
405
|
+
}
|
|
406
|
+
if (value.fallback.mode === "disabled" && value.fallback.targets.length !== 0) return false;
|
|
407
|
+
if (value.fallback.mode === "explicit" && value.fallback.targets.length === 0) return false;
|
|
408
|
+
if (
|
|
409
|
+
!isRecord(value.cost) ||
|
|
410
|
+
!hasOnlyKeys(value.cost, ["currency", "maxPerHour", "maxPerMonth"]) ||
|
|
411
|
+
value.cost.currency !== "USD"
|
|
412
|
+
) {
|
|
413
|
+
return false;
|
|
414
|
+
}
|
|
415
|
+
if (!isNullableBoundedNumber(value.cost.maxPerHour, 10_000)) return false;
|
|
416
|
+
if (!isNullableBoundedNumber(value.cost.maxPerMonth, 1_000_000)) return false;
|
|
417
|
+
if (value.enabled && (!value.acceptanceId || !value.primary)) return false;
|
|
418
|
+
if (value.enabled && !value.autoDetectLanguage && value.language === null) return false;
|
|
419
|
+
if (value.autoDetectLanguage && value.language !== null) return false;
|
|
420
|
+
const targets = [value.primary, ...value.fallback.targets].filter(
|
|
421
|
+
(target): target is WorkspaceTranscriptionTarget => target !== null,
|
|
422
|
+
);
|
|
423
|
+
if (new Set(targets.map(targetKey)).size !== targets.length) return false;
|
|
424
|
+
return true;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
function targetKey(target: WorkspaceTranscriptionTarget): string {
|
|
428
|
+
return [
|
|
429
|
+
target.provider.trim(),
|
|
430
|
+
target.model?.trim() ?? "",
|
|
431
|
+
target.credentialMode,
|
|
432
|
+
target.credentialConnectionId ?? "",
|
|
433
|
+
target.region?.trim() ?? "",
|
|
434
|
+
].join("\u0000");
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
function isTarget(value: unknown): value is WorkspaceTranscriptionTarget {
|
|
438
|
+
if (!isRecord(value)) return false;
|
|
439
|
+
if (
|
|
440
|
+
!hasOnlyKeys(value, ["provider", "model", "credentialMode", "credentialConnectionId", "region"])
|
|
441
|
+
) {
|
|
442
|
+
return false;
|
|
443
|
+
}
|
|
444
|
+
if (!isBoundedString(value.provider, 128)) return false;
|
|
445
|
+
if (!(value.model === null || isBoundedString(value.model, 256))) return false;
|
|
446
|
+
if (value.credentialMode !== "managed" && value.credentialMode !== "byok") return false;
|
|
447
|
+
if (value.provider.trim() === "azure-speech" && value.credentialMode !== "byok") return false;
|
|
448
|
+
if (!(value.credentialConnectionId === null || isUuid(value.credentialConnectionId))) {
|
|
449
|
+
return false;
|
|
450
|
+
}
|
|
451
|
+
if (!(value.region === null || isBoundedString(value.region, 128))) return false;
|
|
452
|
+
if (value.credentialMode === "byok" && value.credentialConnectionId === null) return false;
|
|
453
|
+
if (value.credentialMode === "managed" && value.credentialConnectionId !== null) return false;
|
|
454
|
+
return true;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
function normalizeTarget(target: WorkspaceTranscriptionTarget): WorkspaceTranscriptionTarget {
|
|
458
|
+
return {
|
|
459
|
+
provider: target.provider.trim(),
|
|
460
|
+
model: target.model?.trim() ?? null,
|
|
461
|
+
credentialMode: target.credentialMode,
|
|
462
|
+
credentialConnectionId: target.credentialConnectionId,
|
|
463
|
+
region: target.region?.trim() ?? null,
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
468
|
+
return typeof value === "object" && value !== null;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
function hasOnlyKeys(value: Record<string, unknown>, keys: readonly string[]): boolean {
|
|
472
|
+
const accepted = new Set(keys);
|
|
473
|
+
return Object.keys(value).every((key) => accepted.has(key));
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
function isBoundedString(value: unknown, maximum: number): value is string {
|
|
477
|
+
return typeof value === "string" && value.trim().length > 0 && value.length <= maximum;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
function isUuid(value: unknown): value is string {
|
|
481
|
+
return (
|
|
482
|
+
typeof value === "string" &&
|
|
483
|
+
/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value)
|
|
484
|
+
);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
function isBoundedInteger(value: unknown, maximum: number): value is number {
|
|
488
|
+
return Number.isInteger(value) && (value as number) >= 0 && (value as number) <= maximum;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
function isNullableBoundedNumber(value: unknown, maximum: number): boolean {
|
|
492
|
+
return (
|
|
493
|
+
value === null ||
|
|
494
|
+
(typeof value === "number" && Number.isFinite(value) && value >= 0 && value <= maximum)
|
|
495
|
+
);
|
|
496
|
+
}
|