@opengeni/core 1.3.0 → 1.4.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/domain/connector-tool-permissions.d.ts +1 -1
- package/dist/domain/knowledge-messages.d.ts +2 -2
- package/dist/domain/realtime-voice-billing.d.ts +81 -0
- package/dist/domain/scheduled-task-access.d.ts +2 -2
- package/dist/domain/scheduled-tasks.d.ts +2 -2
- package/dist/domain/session-connection-accounts.d.ts +1 -1
- package/dist/domain/session-tool-policy.d.ts +4 -4
- package/dist/domain/voice-input-billing.d.ts +76 -0
- package/dist/domain/web-search-billing.d.ts +45 -0
- package/dist/goal-admission.d.ts +24 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +790 -142
- package/dist/index.js.map +1 -1
- package/dist/transcription.d.ts +119 -2
- package/package.json +12 -12
- package/src/domain/connector-tool-permissions.ts +24 -8
- package/src/domain/realtime-voice-billing.ts +330 -0
- package/src/domain/scheduled-task-access.ts +2 -1
- package/src/domain/scheduled-tasks.ts +3 -2
- package/src/domain/session-connection-accounts.ts +7 -3
- package/src/domain/session-tool-policy.ts +14 -2
- package/src/domain/sessions.ts +15 -2
- package/src/domain/voice-input-billing.ts +310 -0
- package/src/domain/web-search-billing.ts +154 -0
- package/src/index.ts +3 -0
- package/src/sandbox/routing.ts +59 -25
- package/src/transcription.ts +122 -3
package/src/sandbox/routing.ts
CHANGED
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
readActiveSandbox,
|
|
29
29
|
retainWorkspaceMutationProcess,
|
|
30
30
|
SandboxRetainedProcessPromotionFencedError,
|
|
31
|
+
SandboxWorkspaceMutationOutputRejectedError,
|
|
31
32
|
retainedProcessSettlementIdentity,
|
|
32
33
|
settleRetainedProcess,
|
|
33
34
|
verifyDirectWorkspaceMutationSettlement,
|
|
@@ -43,6 +44,7 @@ import {
|
|
|
43
44
|
NatsControlRpc,
|
|
44
45
|
NatsOpStreamTransport,
|
|
45
46
|
RoutingSandboxSession,
|
|
47
|
+
RoutingMutationOutputRejectedError,
|
|
46
48
|
resolveModalCheckpointProviderBindingForSession,
|
|
47
49
|
type ControlRpc,
|
|
48
50
|
type EstablishedSandboxSession,
|
|
@@ -359,22 +361,38 @@ export function wrapChannelABoxWithRouting(
|
|
|
359
361
|
throw new Error(
|
|
360
362
|
"Outcome-unknown command settlement requires its exact retained invocation",
|
|
361
363
|
);
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
364
|
+
try {
|
|
365
|
+
await verifyDirectWorkspaceMutationSettlement(db, {
|
|
366
|
+
accountId: ids.accountId,
|
|
367
|
+
workspaceId: ids.workspaceId,
|
|
368
|
+
sessionId: ids.sessionId,
|
|
369
|
+
requestId: ids.directRequest.requestId,
|
|
370
|
+
holderId: ids.directRequest.holderId,
|
|
371
|
+
initiatorSubjectId: ids.resourceSubjectId,
|
|
372
|
+
sandboxGroupId: homeLease.sandboxGroupId,
|
|
373
|
+
expectedEpoch: backend.leaseEpoch,
|
|
374
|
+
expectedInstanceId: backend.providerInstanceId,
|
|
375
|
+
routeTargetId: exactAdmission.routeTargetId,
|
|
376
|
+
routeEpoch: exactAdmission.routeEpoch,
|
|
377
|
+
admission: exactAdmission,
|
|
378
|
+
operation: op,
|
|
379
|
+
outcome,
|
|
380
|
+
});
|
|
381
|
+
} catch (error) {
|
|
382
|
+
if (
|
|
383
|
+
error instanceof SandboxWorkspaceMutationOutputRejectedError &&
|
|
384
|
+
error.matchesPhysicalSettlement({
|
|
385
|
+
accountId: ids.accountId,
|
|
386
|
+
workspaceId: ids.workspaceId,
|
|
387
|
+
admission: exactAdmission,
|
|
388
|
+
operation: op,
|
|
389
|
+
outcome,
|
|
390
|
+
})
|
|
391
|
+
) {
|
|
392
|
+
throw new RoutingMutationOutputRejectedError(op, error.code, { cause: error });
|
|
393
|
+
}
|
|
394
|
+
throw error;
|
|
395
|
+
}
|
|
378
396
|
}
|
|
379
397
|
: undefined;
|
|
380
398
|
const beforeProcessMutation = homeLease
|
|
@@ -419,15 +437,31 @@ export function wrapChannelABoxWithRouting(
|
|
|
419
437
|
) {
|
|
420
438
|
throw new Error("API retained-process mutation settlement lacked its exact admission");
|
|
421
439
|
}
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
440
|
+
try {
|
|
441
|
+
await verifyRetainedProcessMutationSettlement(db, {
|
|
442
|
+
accountId: ids.accountId,
|
|
443
|
+
workspaceId: ids.workspaceId,
|
|
444
|
+
sessionId: ids.sessionId,
|
|
445
|
+
processId: process.id,
|
|
446
|
+
admission: admission as SandboxWorkspaceMutationAdmission,
|
|
447
|
+
operation: op,
|
|
448
|
+
outcome,
|
|
449
|
+
});
|
|
450
|
+
} catch (error) {
|
|
451
|
+
if (
|
|
452
|
+
error instanceof SandboxWorkspaceMutationOutputRejectedError &&
|
|
453
|
+
error.matchesPhysicalSettlement({
|
|
454
|
+
accountId: ids.accountId,
|
|
455
|
+
workspaceId: ids.workspaceId,
|
|
456
|
+
admission: admission as SandboxWorkspaceMutationAdmission,
|
|
457
|
+
operation: op,
|
|
458
|
+
outcome,
|
|
459
|
+
})
|
|
460
|
+
) {
|
|
461
|
+
throw new RoutingMutationOutputRejectedError(op, error.code, { cause: error });
|
|
462
|
+
}
|
|
463
|
+
throw error;
|
|
464
|
+
}
|
|
431
465
|
}
|
|
432
466
|
: undefined;
|
|
433
467
|
const settleProcess = homeLease
|
package/src/transcription.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import type { VoiceInputPricing, VoiceInputUsage } from "@opengeni/config";
|
|
1
2
|
import type { TranscribeAudioResponse, VoiceInputErrorCode } from "@opengeni/contracts";
|
|
3
|
+
import type { CreditDebitAttribution } from "@opengeni/db";
|
|
2
4
|
|
|
3
5
|
/**
|
|
4
6
|
* Server-owned upstream budget for one provider attempt. Resumable recording
|
|
@@ -34,6 +36,39 @@ export type TranscriptionRequest = {
|
|
|
34
36
|
preferredProvider?: string | null | undefined;
|
|
35
37
|
fallbackEnabled?: boolean | undefined;
|
|
36
38
|
excludedProviders?: readonly string[] | undefined;
|
|
39
|
+
/**
|
|
40
|
+
* Credit settlement facts for a deployment-funded provider. Required when
|
|
41
|
+
* the selected provider is deployment-funded and credit billing is active;
|
|
42
|
+
* the service fails closed without it.
|
|
43
|
+
*/
|
|
44
|
+
billing?: TranscriptionBillingContext | undefined;
|
|
45
|
+
/**
|
|
46
|
+
* Internal: return `settleBilling` instead of settling before returning, so
|
|
47
|
+
* a resumable segment's transcript commits before any billing write.
|
|
48
|
+
*/
|
|
49
|
+
deferBillingSettlement?: boolean;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Trusted, server-built settlement identity for one transcription unit. The
|
|
54
|
+
* usage receipt and debit idempotency keys derive from `workspaceId` +
|
|
55
|
+
* `sourceId` (see `voiceInputSettlementKeys`), so a retry or a later
|
|
56
|
+
* reconciliation settles the same unit exactly once.
|
|
57
|
+
*/
|
|
58
|
+
export type TranscriptionBillingContext = {
|
|
59
|
+
/**
|
|
60
|
+
* Server-derived unit identity. Never a client-chosen value: a reused id
|
|
61
|
+
* settles once, so a client-controlled id would make later calls free.
|
|
62
|
+
*/
|
|
63
|
+
sourceId: string;
|
|
64
|
+
/** Trusted payer facts from the authenticated request boundary. */
|
|
65
|
+
attribution: CreditDebitAttribution;
|
|
66
|
+
/**
|
|
67
|
+
* Audio duration measured by the server from WAV bytes it produced (the
|
|
68
|
+
* resumable segment, or the normalized one-shot upload). Bills whenever the
|
|
69
|
+
* provider reports no usage this deployment has a price for.
|
|
70
|
+
*/
|
|
71
|
+
trustedDurationSeconds?: number | undefined;
|
|
37
72
|
};
|
|
38
73
|
|
|
39
74
|
export type TranscriptionResult = TranscribeAudioResponse & {
|
|
@@ -41,17 +76,79 @@ export type TranscriptionResult = TranscribeAudioResponse & {
|
|
|
41
76
|
providerId: string;
|
|
42
77
|
audioSeconds: number;
|
|
43
78
|
latencyMs: number;
|
|
79
|
+
/** Opengeni credits charged for this call (0 for subscription/free providers). */
|
|
80
|
+
creditCostMicros?: number | undefined;
|
|
81
|
+
/**
|
|
82
|
+
* Server-only settlement callback; never serialized into a public response.
|
|
83
|
+
* Never rejects: a settlement failure after provider success is logged and
|
|
84
|
+
* retried, and the transcript is still delivered.
|
|
85
|
+
*/
|
|
86
|
+
settleBilling?: () => Promise<void>;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
export type TranscriptionBillingRefusalCode =
|
|
90
|
+
| "insufficient_credits"
|
|
91
|
+
| "allowance_exhausted"
|
|
92
|
+
| "monthly_model_cost_limit";
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Pre-use refusal for deployment-funded transcription: 402 for credits and
|
|
96
|
+
* allowances (same codes as session admission), 429 for a static monthly cap.
|
|
97
|
+
*/
|
|
98
|
+
export class TranscriptionBillingRefusedError extends Error {
|
|
99
|
+
readonly status: 402 | 429;
|
|
100
|
+
readonly code: TranscriptionBillingRefusalCode;
|
|
101
|
+
readonly details: { scope?: string; resetsAt?: string | null; subjectId?: string };
|
|
102
|
+
|
|
103
|
+
constructor(input: {
|
|
104
|
+
code: TranscriptionBillingRefusalCode;
|
|
105
|
+
message: string;
|
|
106
|
+
details?: { scope?: string; resetsAt?: string | null; subjectId?: string };
|
|
107
|
+
}) {
|
|
108
|
+
super(input.message);
|
|
109
|
+
this.name = "TranscriptionBillingRefusedError";
|
|
110
|
+
this.code = input.code;
|
|
111
|
+
this.status = input.code === "monthly_model_cost_limit" ? 429 : 402;
|
|
112
|
+
this.details = input.details ?? {};
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Credit admission and settlement for deployment-funded transcription.
|
|
118
|
+
* Subscription providers (Codex, SuperGrok) never reach this port.
|
|
119
|
+
*/
|
|
120
|
+
export type TranscriptionBilling = {
|
|
121
|
+
/** Refuse before any audio is sent when the payer cannot fund the call. */
|
|
122
|
+
admit(input: {
|
|
123
|
+
accountId: string;
|
|
124
|
+
workspaceId: string;
|
|
125
|
+
attribution: CreditDebitAttribution;
|
|
126
|
+
}): Promise<void>;
|
|
127
|
+
/**
|
|
128
|
+
* Record usage and debit credits once per unit, after use. The usage receipt
|
|
129
|
+
* commits before the debit, so a failed debit leaves a durable receipt that
|
|
130
|
+
* `admit` reconciles on the workspace's next voice request.
|
|
131
|
+
*/
|
|
132
|
+
settle(input: {
|
|
133
|
+
accountId: string;
|
|
134
|
+
workspaceId: string;
|
|
135
|
+
providerId: string;
|
|
136
|
+
model: string;
|
|
137
|
+
pricing: VoiceInputPricing;
|
|
138
|
+
usage: VoiceInputUsage | null;
|
|
139
|
+
billing: TranscriptionBillingContext;
|
|
140
|
+
}): Promise<{ creditCostMicros: number }>;
|
|
44
141
|
};
|
|
45
142
|
|
|
46
143
|
export class TranscriptionServiceError extends Error {
|
|
47
|
-
readonly code: VoiceInputErrorCode
|
|
144
|
+
readonly code: Exclude<VoiceInputErrorCode, TranscriptionBillingRefusalCode>;
|
|
48
145
|
readonly status: number;
|
|
49
146
|
readonly retryable: boolean;
|
|
50
147
|
/** Explicit rejection before any transcription result; safe to try another provider. */
|
|
51
148
|
readonly fallbackSafe: boolean;
|
|
52
149
|
|
|
53
150
|
constructor(input: {
|
|
54
|
-
code: VoiceInputErrorCode
|
|
151
|
+
code: Exclude<VoiceInputErrorCode, TranscriptionBillingRefusalCode>;
|
|
55
152
|
message: string;
|
|
56
153
|
status?: number;
|
|
57
154
|
retryable?: boolean;
|
|
@@ -114,6 +211,14 @@ export type TranscriptionProvider = {
|
|
|
114
211
|
/** The adapter guarantees that its upstream transport honors AbortSignal. */
|
|
115
212
|
readonly supportsServerDeadline: true;
|
|
116
213
|
readonly experimental?: boolean | undefined;
|
|
214
|
+
/**
|
|
215
|
+
* Present when the deployment pays the upstream provider (OpenAI/Azure):
|
|
216
|
+
* the call is admitted against and settled in Opengeni credits. Absent for
|
|
217
|
+
* the workspace's own subscriptions, which are never charged.
|
|
218
|
+
*/
|
|
219
|
+
readonly deploymentFunded?:
|
|
220
|
+
| { readonly model: string; readonly pricing: VoiceInputPricing | null }
|
|
221
|
+
| undefined;
|
|
117
222
|
/**
|
|
118
223
|
* Deployment readiness when called without a workspace. When `workspaceId` is
|
|
119
224
|
* provided, providers may require a workspace-attached credential (e.g. Codex).
|
|
@@ -128,17 +233,29 @@ export type TranscriptionProvider = {
|
|
|
128
233
|
subjectId: string;
|
|
129
234
|
requestId: string;
|
|
130
235
|
signal?: AbortSignal | undefined;
|
|
131
|
-
}): Promise<{ text: string; languages: string[] }>;
|
|
236
|
+
}): Promise<{ text: string; languages: string[]; usage?: VoiceInputUsage | null | undefined }>;
|
|
132
237
|
};
|
|
133
238
|
|
|
134
239
|
export type TranscriptionService = {
|
|
135
240
|
limits(): TranscriptionLimits;
|
|
136
241
|
/** True when at least one ready provider can serve requests. */
|
|
137
242
|
available(context?: TranscriptionAvailabilityContext): boolean | Promise<boolean>;
|
|
243
|
+
availableProviderIds?(context: TranscriptionAvailabilityContext): Promise<string[]>;
|
|
138
244
|
/** Select one provider before a durable segment attempt; retries pin this id. */
|
|
139
245
|
selectProvider?(
|
|
140
246
|
context: TranscriptionAvailabilityContext,
|
|
141
247
|
): string | null | Promise<string | null>;
|
|
248
|
+
/**
|
|
249
|
+
* Credit admission for an exact provider before durable work is claimed.
|
|
250
|
+
* Resolves for subscription providers; throws TranscriptionBillingRefusedError
|
|
251
|
+
* when a deployment-funded provider cannot be paid for.
|
|
252
|
+
*/
|
|
253
|
+
admit?(input: {
|
|
254
|
+
providerId: string;
|
|
255
|
+
accountId: string;
|
|
256
|
+
workspaceId: string;
|
|
257
|
+
attribution: CreditDebitAttribution;
|
|
258
|
+
}): Promise<void>;
|
|
142
259
|
transcribe(request: TranscriptionRequest): Promise<TranscriptionResult>;
|
|
143
260
|
};
|
|
144
261
|
|
|
@@ -156,6 +273,8 @@ export type TranscriptionSegmenter = {
|
|
|
156
273
|
sourceMimeType: string;
|
|
157
274
|
totalDurationMilliseconds: number;
|
|
158
275
|
providerSegmentSeconds: number;
|
|
276
|
+
/** Hard decode ceiling (`ffmpeg -t`); audio past it is never decoded. */
|
|
277
|
+
maxDecodeSeconds?: number | undefined;
|
|
159
278
|
chunks: AsyncIterable<Uint8Array>;
|
|
160
279
|
signal?: AbortSignal | undefined;
|
|
161
280
|
}): AsyncIterable<PreparedTranscriptionSegment>;
|