@opengeni/api-router 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/access-grant-rls.d.ts +7 -0
- package/dist/app.d.ts +2 -2
- package/dist/app.js +1 -1
- package/dist/azure-live.d.ts +28 -0
- package/dist/{chunk-IZEGLLKZ.js → chunk-KKEXIBQG.js} +1188 -387
- package/dist/{chunk-IZEGLLKZ.js.map → chunk-KKEXIBQG.js.map} +1 -1
- package/dist/codemode.d.ts +3 -3
- package/dist/index.js +1 -1
- package/dist/integrations/oauth-profiles.d.ts +3 -1
- package/dist/mcp/scheduled-task-view.d.ts +4 -4
- package/dist/mcp/server.d.ts +1 -1
- package/dist/transcription/billing-refusal.d.ts +8 -0
- package/dist/transcription/normalize.d.ts +23 -0
- package/dist/transcription/providers/azure-mai.d.ts +12 -0
- package/dist/transcription/providers/azure-openai.d.ts +4 -0
- package/dist/transcription/providers/openai.d.ts +16 -0
- package/dist/transcription/service.d.ts +10 -1
- package/dist/transcription/wav-duration.d.ts +8 -0
- package/package.json +20 -20
- package/src/access-grant-rls.ts +18 -10
- package/src/app.ts +45 -7
- package/src/azure-live.ts +103 -0
- package/src/codemode.ts +3 -5
- package/src/integrations/oauth-client.ts +1 -0
- package/src/integrations/oauth-profiles.ts +17 -6
- package/src/routes/editable-artifacts.ts +8 -0
- package/src/routes/session-artifact-associations.ts +80 -6
- package/src/routes/sessions.ts +185 -25
- package/src/routes/transcription-recordings.ts +71 -4
- package/src/routes/transcriptions.ts +26 -2
- package/src/routes/workspaces.ts +44 -7
- package/src/sandbox/channel-a.ts +22 -0
- package/src/transcription/billing-refusal.ts +14 -0
- package/src/transcription/normalize.ts +77 -0
- package/src/transcription/providers/azure-mai.ts +98 -0
- package/src/transcription/providers/azure-openai.ts +16 -7
- package/src/transcription/providers/openai.ts +79 -5
- package/src/transcription/segmenter.ts +7 -1
- package/src/transcription/service.ts +215 -10
- package/src/transcription/wav-duration.ts +39 -0
|
@@ -6517,8 +6517,8 @@ import {
|
|
|
6517
6517
|
canonicalPublicOrigin as canonicalPublicOrigin3,
|
|
6518
6518
|
configuredAllowedReasoningEfforts,
|
|
6519
6519
|
resolveFirstPartyMcpToolPolicy as resolveFirstPartyMcpToolPolicy2,
|
|
6520
|
-
|
|
6521
|
-
|
|
6520
|
+
UnsupportedLatencyModeError as UnsupportedLatencyModeError2,
|
|
6521
|
+
voiceInputPricingIssues
|
|
6522
6522
|
} from "@opengeni/config";
|
|
6523
6523
|
import {
|
|
6524
6524
|
AGENT_CAPABILITY_IDS,
|
|
@@ -6532,6 +6532,7 @@ import {
|
|
|
6532
6532
|
resolveWorkspaceMemoryEnabled,
|
|
6533
6533
|
resolveWorkspaceMemoryPromptMode,
|
|
6534
6534
|
VOICE_INPUT_ACCEPTED_MIME_TYPES as VOICE_INPUT_ACCEPTED_MIME_TYPES2,
|
|
6535
|
+
WorkspaceVoiceInputSettings as WorkspaceVoiceInputSettings3,
|
|
6535
6536
|
TRANSCRIPTION_RECORDING_PROVIDER_SEGMENT_SECONDS as TRANSCRIPTION_RECORDING_PROVIDER_SEGMENT_SECONDS2,
|
|
6536
6537
|
ToolGatewayApprovalRequest as ToolGatewayApprovalRequest2,
|
|
6537
6538
|
ToolGatewayCallRequest as ToolGatewayCallRequest2
|
|
@@ -7065,8 +7066,8 @@ import {
|
|
|
7065
7066
|
CodexCompactionV2ProviderLockedError,
|
|
7066
7067
|
ManagedAuthActorLeaseOutcomeUnknownError,
|
|
7067
7068
|
hasPermission as hasPermission34,
|
|
7068
|
-
requireAccessGrant as
|
|
7069
|
-
requireAccessGrantAuthorization as
|
|
7069
|
+
requireAccessGrant as requireAccessGrant53,
|
|
7070
|
+
requireAccessGrantAuthorization as requireAccessGrantAuthorization41,
|
|
7070
7071
|
accountScopedApiKeyWorkspaceAuthority as accountScopedApiKeyWorkspaceAuthority10,
|
|
7071
7072
|
requireAccessContext as requireAccessContext15,
|
|
7072
7073
|
requirePermission as requirePermission13,
|
|
@@ -8515,7 +8516,7 @@ function startManagedSignInNotificationDelivery(db, transport) {
|
|
|
8515
8516
|
if (active) return;
|
|
8516
8517
|
active = true;
|
|
8517
8518
|
try {
|
|
8518
|
-
for (let
|
|
8519
|
+
for (let count2 = 0; count2 < 10; count2++)
|
|
8519
8520
|
if (await deliverManagedSignInNotification(db, transport) === "outcome_unknown") break;
|
|
8520
8521
|
} finally {
|
|
8521
8522
|
active = false;
|
|
@@ -10277,9 +10278,9 @@ function dispositionFilenameParameters(filename) {
|
|
|
10277
10278
|
const cleaned = Array.from(
|
|
10278
10279
|
filename.replace(/[\u0000-\u001f\u007f/\\]+/g, "_").replace(/[\ud800-\udbff](?![\udc00-\udfff])|(?<![\ud800-\udbff])[\udc00-\udfff]/g, "_").trim()
|
|
10279
10280
|
).slice(0, DISPOSITION_FILENAME_MAX_CHARS).join("");
|
|
10280
|
-
const
|
|
10281
|
-
if (!cleaned ||
|
|
10282
|
-
return `filename="${
|
|
10281
|
+
const ascii3 = cleaned.replace(/[^A-Za-z0-9._ -]+/g, "_").replace(/\s+/g, " ").trim().slice(0, DISPOSITION_FILENAME_MAX_CHARS) || "download";
|
|
10282
|
+
if (!cleaned || ascii3 === cleaned) return `filename="${ascii3}"`;
|
|
10283
|
+
return `filename="${ascii3}"; filename*=UTF-8''${encodeExtendedParameterValue(cleaned)}`;
|
|
10283
10284
|
}
|
|
10284
10285
|
function userContentDispositionHeaders(contentType, filename) {
|
|
10285
10286
|
if (!isActiveUserContentType(contentType)) return {};
|
|
@@ -10817,13 +10818,18 @@ import {
|
|
|
10817
10818
|
import {
|
|
10818
10819
|
freezeAgentLearningPolicy,
|
|
10819
10820
|
withSessionRlsActorContext,
|
|
10820
|
-
withCreditDebitAttribution
|
|
10821
|
+
withCreditDebitAttribution,
|
|
10822
|
+
currentCreditDebitAttribution
|
|
10821
10823
|
} from "@opengeni/db";
|
|
10822
10824
|
import { HTTPException as HTTPException21 } from "hono/http-exception";
|
|
10825
|
+
function creditDebitAttributionForGrant(grant) {
|
|
10826
|
+
if (grant.principalKind === "agent_attempt") return currentCreditDebitAttribution();
|
|
10827
|
+
return grant.principalKind === "human_session" ? { kind: "human", initiatingHumanSubjectId: grant.subjectId } : ["service", "api_key", "configured_key", "mcp_gateway"].includes(grant.principalKind ?? "") ? { kind: "service" } : { kind: "unknown" };
|
|
10828
|
+
}
|
|
10823
10829
|
async function withAccessGrantSessionRlsContext(deps, grant, fn) {
|
|
10824
10830
|
if (grant.principalKind !== "agent_attempt") {
|
|
10825
10831
|
return await withCreditDebitAttribution(
|
|
10826
|
-
grant
|
|
10832
|
+
creditDebitAttributionForGrant(grant),
|
|
10827
10833
|
() => withSessionRlsActorContext({ subjectId: grant.subjectId }, fn)
|
|
10828
10834
|
);
|
|
10829
10835
|
}
|
|
@@ -11270,6 +11276,13 @@ function registerEditableArtifactRoutes(app, deps) {
|
|
|
11270
11276
|
const body3 = await parseBoundedMaterializationRequest(context);
|
|
11271
11277
|
const scope = editableArtifactScope({ accountId: grant.accountId, workspaceId });
|
|
11272
11278
|
const actor = editableArtifactActorForGrant(grant, body3.replicaId);
|
|
11279
|
+
if (!deps.settings.artifactMaterializerDeployed) {
|
|
11280
|
+
throw new ApiHttpError(503, {
|
|
11281
|
+
code: "upstream_unavailable",
|
|
11282
|
+
message: "File export is not available on this deployment.",
|
|
11283
|
+
retryable: false
|
|
11284
|
+
});
|
|
11285
|
+
}
|
|
11273
11286
|
try {
|
|
11274
11287
|
const result = await requireEditableArtifactExports(deps).enqueueMaterialization({
|
|
11275
11288
|
scope,
|
|
@@ -12498,6 +12511,7 @@ import {
|
|
|
12498
12511
|
SandboxResumeIdentityMismatchError,
|
|
12499
12512
|
SandboxResumeIdentityUnavailableError,
|
|
12500
12513
|
RoutingActiveRouteChangedError,
|
|
12514
|
+
RoutingMutationOutputRejectedError,
|
|
12501
12515
|
RoutingWorkspaceRootChangedError,
|
|
12502
12516
|
SelfhostedWorkspaceRootChangedError,
|
|
12503
12517
|
ChannelAConflictError,
|
|
@@ -13594,6 +13608,20 @@ function mapChannelAError(error, waitSignal) {
|
|
|
13594
13608
|
});
|
|
13595
13609
|
if (error instanceof SandboxResumeIdentityMismatchError || error instanceof SandboxResumeIdentityUnavailableError)
|
|
13596
13610
|
return new HTTPException22(409, { message: error.message });
|
|
13611
|
+
if (error instanceof RoutingMutationOutputRejectedError) {
|
|
13612
|
+
const revoked = error.reasonCode === "authority_revoked";
|
|
13613
|
+
return new ApiHttpError(revoked ? 403 : 409, {
|
|
13614
|
+
code: revoked ? "forbidden" : "conflict",
|
|
13615
|
+
message: error.message,
|
|
13616
|
+
retryable: false,
|
|
13617
|
+
outcomeUnknown: false,
|
|
13618
|
+
details: {
|
|
13619
|
+
code: error.code,
|
|
13620
|
+
reasonCode: error.reasonCode,
|
|
13621
|
+
physicalOutcome: "resolved"
|
|
13622
|
+
}
|
|
13623
|
+
});
|
|
13624
|
+
}
|
|
13597
13625
|
if (error instanceof RoutingActiveRouteChangedError || error instanceof RoutingWorkspaceRootChangedError || error instanceof SelfhostedWorkspaceRootChangedError || error instanceof ChannelAFileSystemRouteChangedError)
|
|
13598
13626
|
return new ApiHttpError(409, {
|
|
13599
13627
|
code: "conflict",
|
|
@@ -13660,6 +13688,13 @@ function channelAOperationFailureDiagnostic(error, waitSignal) {
|
|
|
13660
13688
|
errorCode: "sandbox_channel_a_cancelled"
|
|
13661
13689
|
};
|
|
13662
13690
|
}
|
|
13691
|
+
if (error instanceof RoutingMutationOutputRejectedError) {
|
|
13692
|
+
return {
|
|
13693
|
+
reason: "request_rejected",
|
|
13694
|
+
status: error.reasonCode === "authority_revoked" ? 403 : 409,
|
|
13695
|
+
errorCode: "sandbox_channel_a_operation_failed"
|
|
13696
|
+
};
|
|
13697
|
+
}
|
|
13663
13698
|
if (error instanceof SandboxProviderReadLockUnavailableError) {
|
|
13664
13699
|
return {
|
|
13665
13700
|
reason: "provider_read_busy",
|
|
@@ -15507,8 +15542,8 @@ function tokenHash(value) {
|
|
|
15507
15542
|
function mcpOAuthRegistrationClientKey(c, trust) {
|
|
15508
15543
|
return `mcp-oauth-registration:${trustedRequestSourceRateLimitKey(c, trust)}`;
|
|
15509
15544
|
}
|
|
15510
|
-
function expiresIn(
|
|
15511
|
-
return new Date(Date.now() +
|
|
15545
|
+
function expiresIn(seconds2) {
|
|
15546
|
+
return new Date(Date.now() + seconds2 * 1e3);
|
|
15512
15547
|
}
|
|
15513
15548
|
function authorizationRedirect(redirectUri, params) {
|
|
15514
15549
|
const url = new URL(redirectUri);
|
|
@@ -17002,7 +17037,11 @@ import {
|
|
|
17002
17037
|
import { OPENGENI_PERSONAL_SLACK_MCP_URL } from "@opengeni/contracts";
|
|
17003
17038
|
import { HTTPException as HTTPException31 } from "hono/http-exception";
|
|
17004
17039
|
import { z as z25 } from "zod";
|
|
17005
|
-
import {
|
|
17040
|
+
import {
|
|
17041
|
+
GMAIL_REST_MCP_TOOLS,
|
|
17042
|
+
gmailToolAvailableOnDeployment,
|
|
17043
|
+
gmailToolSupportsScopes
|
|
17044
|
+
} from "@opengeni/runtime/gmail-rest-mcp";
|
|
17006
17045
|
|
|
17007
17046
|
// src/integrations/provider-domain.ts
|
|
17008
17047
|
import { HTTPException as HTTPException30 } from "hono/http-exception";
|
|
@@ -17137,10 +17176,12 @@ var OFFICIAL_GMAIL_PROFILE = {
|
|
|
17137
17176
|
}
|
|
17138
17177
|
return { gmailEmail: payload.emailAddress };
|
|
17139
17178
|
},
|
|
17140
|
-
toolsForScopes: (scopes) => {
|
|
17141
|
-
return GMAIL_REST_MCP_TOOLS.filter(
|
|
17142
|
-
(
|
|
17143
|
-
|
|
17179
|
+
toolsForScopes: (scopes, deployment) => {
|
|
17180
|
+
return GMAIL_REST_MCP_TOOLS.filter(
|
|
17181
|
+
(tool) => gmailToolSupportsScopes(tool.name, scopes) && gmailToolAvailableOnDeployment(tool.name, {
|
|
17182
|
+
watchTopicName: deployment.gmailWatchTopicName
|
|
17183
|
+
})
|
|
17184
|
+
).map(({ name, description }) => ({ name, ...description ? { description } : {} }));
|
|
17144
17185
|
}
|
|
17145
17186
|
}
|
|
17146
17187
|
};
|
|
@@ -19122,7 +19163,8 @@ async function verifyMcpToolsListNonFatal(observability, settings, state, token,
|
|
|
19122
19163
|
grantRejected = local.isRejectedGrant?.(payload) ?? false;
|
|
19123
19164
|
providerIdentity = local.validateIdentity(payload);
|
|
19124
19165
|
const verifiedTools = local.toolsForScopes(
|
|
19125
|
-
grantedScopes(token.scopeText, state.authorizeScopes, profile)
|
|
19166
|
+
grantedScopes(token.scopeText, state.authorizeScopes, profile),
|
|
19167
|
+
settings
|
|
19126
19168
|
);
|
|
19127
19169
|
if (local.required && verifiedTools.length === 0)
|
|
19128
19170
|
throw new Error("Connector verification did not report an authorized tool scope");
|
|
@@ -20502,15 +20544,15 @@ import { COMMAND_OUTPUT_MAX_BYTES } from "@opengeni/db/session-background-comman
|
|
|
20502
20544
|
var COMMAND_READ_MAX_WAIT_SECONDS = 50;
|
|
20503
20545
|
var COMMAND_WAIT_DEFAULT_SECONDS = 45;
|
|
20504
20546
|
async function readCommandWithWait(input) {
|
|
20505
|
-
const
|
|
20506
|
-
if (!Number.isInteger(
|
|
20547
|
+
const seconds2 = input.waitSeconds ?? 0;
|
|
20548
|
+
if (!Number.isInteger(seconds2) || seconds2 < 0 || seconds2 > COMMAND_READ_MAX_WAIT_SECONDS) {
|
|
20507
20549
|
throw new Error("waitSeconds must be between 0 and 50");
|
|
20508
20550
|
}
|
|
20509
20551
|
if (input.maxOutputBytes !== void 0 && (!Number.isInteger(input.maxOutputBytes) || input.maxOutputBytes < 4 || input.maxOutputBytes > COMMAND_OUTPUT_MAX_BYTES)) {
|
|
20510
20552
|
throw new Error("maxOutputBytes must be between 4 and 65536");
|
|
20511
20553
|
}
|
|
20512
20554
|
const started2 = Date.now();
|
|
20513
|
-
const deadline = started2 +
|
|
20555
|
+
const deadline = started2 + seconds2 * 1e3;
|
|
20514
20556
|
let unsubscribe;
|
|
20515
20557
|
let wake;
|
|
20516
20558
|
let changed2 = false;
|
|
@@ -20521,7 +20563,7 @@ async function readCommandWithWait(input) {
|
|
|
20521
20563
|
};
|
|
20522
20564
|
input.signal?.addEventListener("abort", notify);
|
|
20523
20565
|
try {
|
|
20524
|
-
if (
|
|
20566
|
+
if (seconds2 > 0) {
|
|
20525
20567
|
try {
|
|
20526
20568
|
unsubscribe = await input.subscribe((events) => {
|
|
20527
20569
|
if (events.some(
|
|
@@ -20541,7 +20583,7 @@ async function readCommandWithWait(input) {
|
|
|
20541
20583
|
return {
|
|
20542
20584
|
...result,
|
|
20543
20585
|
waitedMs: Date.now() - started2,
|
|
20544
|
-
timedOut:
|
|
20586
|
+
timedOut: seconds2 > 0 && expired && !result.terminal && result.chunks.length === 0 && !result.hasMore,
|
|
20545
20587
|
aborted: input.signal?.aborted ?? false,
|
|
20546
20588
|
liveFanout
|
|
20547
20589
|
};
|
|
@@ -22021,7 +22063,7 @@ function buildScheduledTaskDetailMcp(task, budget, reducedForBytes) {
|
|
|
22021
22063
|
deliveredCount: resources.length,
|
|
22022
22064
|
originalBytes: jsonBytes(task.agentConfig.resources),
|
|
22023
22065
|
truncatedIdentityFieldCount: resourceProjections.reduce(
|
|
22024
|
-
(
|
|
22066
|
+
(count2, projection) => count2 + projection.truncatedFieldCount,
|
|
22025
22067
|
0
|
|
22026
22068
|
)
|
|
22027
22069
|
},
|
|
@@ -22030,7 +22072,7 @@ function buildScheduledTaskDetailMcp(task, budget, reducedForBytes) {
|
|
|
22030
22072
|
deliveredCount: tools.length,
|
|
22031
22073
|
originalBytes: jsonBytes(task.agentConfig.tools),
|
|
22032
22074
|
truncatedIdentityFieldCount: toolProjections.reduce(
|
|
22033
|
-
(
|
|
22075
|
+
(count2, projection) => count2 + projection.truncatedFieldCount,
|
|
22034
22076
|
0
|
|
22035
22077
|
)
|
|
22036
22078
|
},
|
|
@@ -25033,9 +25075,9 @@ function slackRetryAfterMs(response) {
|
|
|
25033
25075
|
if (response.status !== 429) return null;
|
|
25034
25076
|
const raw = response.headers.get("retry-after");
|
|
25035
25077
|
if (!raw || !/^\d+$/.test(raw)) return null;
|
|
25036
|
-
const
|
|
25037
|
-
if (!Number.isSafeInteger(
|
|
25038
|
-
return Math.min(
|
|
25078
|
+
const seconds2 = Number(raw);
|
|
25079
|
+
if (!Number.isSafeInteger(seconds2) || seconds2 < 1) return null;
|
|
25080
|
+
return Math.min(seconds2, 3600) * 1e3;
|
|
25039
25081
|
}
|
|
25040
25082
|
function assertOpenGeniSlackBotScopes(grantedScopes2) {
|
|
25041
25083
|
const policy = evaluateOpenGeniSlackBotScopes(grantedScopes2);
|
|
@@ -25613,11 +25655,11 @@ function nextSlackFilesListPage(payload, requested, returnedFileCount) {
|
|
|
25613
25655
|
if (responseMetadata && Object.hasOwn(responseMetadata, "next_cursor")) {
|
|
25614
25656
|
throw new SlackBotProviderError("invalid_files_paging");
|
|
25615
25657
|
}
|
|
25616
|
-
const
|
|
25658
|
+
const count2 = paging?.count;
|
|
25617
25659
|
const total = paging?.total;
|
|
25618
25660
|
const page2 = paging?.page;
|
|
25619
25661
|
const pages = paging?.pages;
|
|
25620
|
-
if (!positiveSafeInt(
|
|
25662
|
+
if (!positiveSafeInt(count2) || !nonNegativeSafeInt(total) || !positiveSafeInt(page2) || !nonNegativeSafeInt(pages) || count2 !== requested.count || page2 !== requested.page || returnedFileCount > count2 || pages !== (total === 0 ? 0 : Math.ceil(total / count2)) || page2 > Math.max(1, pages)) {
|
|
25621
25663
|
throw new SlackBotProviderError("invalid_files_paging");
|
|
25622
25664
|
}
|
|
25623
25665
|
if (page2 >= pages) return null;
|
|
@@ -83461,12 +83503,12 @@ function creditScopeMetadata(scope) {
|
|
|
83461
83503
|
}
|
|
83462
83504
|
function creditScopeFromMetadata(metadata) {
|
|
83463
83505
|
if (!metadata || !Object.keys(metadata).some((key) => key.startsWith(prefix))) return void 0;
|
|
83464
|
-
const
|
|
83465
|
-
if (!Number.isInteger(
|
|
83506
|
+
const count2 = Number(metadata[countKey]);
|
|
83507
|
+
if (!Number.isInteger(count2) || count2 < 1 || count2 > 24) {
|
|
83466
83508
|
throw new Error("Invalid promotional credit snapshot");
|
|
83467
83509
|
}
|
|
83468
83510
|
let encoded = "";
|
|
83469
|
-
for (let index = 0; index <
|
|
83511
|
+
for (let index = 0; index < count2; index++) {
|
|
83470
83512
|
const chunk = metadata[`${prefix}${index}`];
|
|
83471
83513
|
if (!chunk) throw new Error("Incomplete promotional credit snapshot");
|
|
83472
83514
|
encoded += chunk;
|
|
@@ -100182,219 +100224,10 @@ function registerScheduledTaskRoutes(app, deps) {
|
|
|
100182
100224
|
});
|
|
100183
100225
|
}
|
|
100184
100226
|
|
|
100185
|
-
// src/
|
|
100186
|
-
import { getRetainedProviderCommand } from "@opengeni/db/retained-provider-commands";
|
|
100187
|
-
import { assertGoalResumeAllowed as assertGoalResumeAllowed2, GoalResumeBlockedError } from "@opengeni/core";
|
|
100188
|
-
import { UnsupportedLatencyModeError } from "@opengeni/config";
|
|
100189
|
-
import { searchSessionMessagesForSubject, SessionMessageSearchCursorError } from "@opengeni/db";
|
|
100190
|
-
import { listSessionEventSlices } from "@opengeni/db/session-event-slices";
|
|
100191
|
-
import * as sessionPreviewSchema from "@opengeni/db/schema";
|
|
100192
|
-
import { SessionMessageSearchRequest } from "@opengeni/contracts";
|
|
100193
|
-
import { scheduledSessionIds } from "@opengeni/db";
|
|
100194
|
-
import { isVerifiedDelegatedHumanAuthorization as isVerifiedDelegatedHumanAuthorization4, withSiteSessionOrigin as withSiteSessionOrigin3 } from "@opengeni/core";
|
|
100195
|
-
import { freezeSessionRealtimeConnectionAccounts } from "@opengeni/core";
|
|
100196
|
-
import { SandboxRecoveryRequest } from "@opengeni/contracts";
|
|
100197
|
-
import { getManagedHumanSandboxRecovery, consentManagedHumanSandboxRecovery } from "@opengeni/core";
|
|
100198
|
-
import { SandboxRecoveryConflictError } from "@opengeni/db";
|
|
100199
|
-
import { getSessionCodexAccounts } from "@opengeni/db";
|
|
100200
|
-
import { getToolActionReview, getToolReviewDetailsPage } from "@opengeni/db";
|
|
100201
|
-
import {
|
|
100202
|
-
AcknowledgeStreamRequest,
|
|
100203
|
-
ApplySessionGoalRevisionRequest,
|
|
100204
|
-
ListSessionGoalRevisionsQuery,
|
|
100205
|
-
RejectSessionGoalRevisionRequest,
|
|
100206
|
-
RollbackSessionGoalRevisionRequest,
|
|
100207
|
-
ActivateCodexRealtimeConnectionRequest,
|
|
100208
|
-
AttachViewerRequest,
|
|
100209
|
-
BeginSessionRealtimeRequest,
|
|
100210
|
-
ClearSessionContextRequest,
|
|
100211
|
-
CodexRealtimeWebrtcRequest,
|
|
100212
|
-
GatewayRealtimeConnectRequest,
|
|
100213
|
-
ClientSessionEvent,
|
|
100214
|
-
CompactSessionContextRequest,
|
|
100215
|
-
CreateSessionRequest as CreateSessionRequest2,
|
|
100216
|
-
DeleteSessionQueueItemRequest,
|
|
100217
|
-
EditSessionQueueItemRequest,
|
|
100218
|
-
EndSessionRealtimeRequest,
|
|
100219
|
-
FsDeleteRequest,
|
|
100220
|
-
FsListBatchRequest,
|
|
100221
|
-
FsListRequest,
|
|
100222
|
-
FsMkdirRequest,
|
|
100223
|
-
FsMoveRequest,
|
|
100224
|
-
FsReadRequest,
|
|
100225
|
-
FsWriteRequest,
|
|
100226
|
-
ForkSessionRequest,
|
|
100227
|
-
HumanInputRequestStatus,
|
|
100228
|
-
GitDiffRequest,
|
|
100229
|
-
GitReadBatchRequest,
|
|
100230
|
-
GitLogRequest,
|
|
100231
|
-
GitShowRequest,
|
|
100232
|
-
GitStatusRequest,
|
|
100233
|
-
MoveSessionQueueItemRequest,
|
|
100234
|
-
PtyCloseRequest,
|
|
100235
|
-
PtyOpenRequest,
|
|
100236
|
-
PtyResizeRequest,
|
|
100237
|
-
PtyWriteRequest,
|
|
100238
|
-
PublishSandboxFileArtifactRequest,
|
|
100239
|
-
RenewSessionRealtimeRequest,
|
|
100240
|
-
SyncSessionRealtimeLedgerRequest,
|
|
100241
|
-
SessionControlRequest,
|
|
100242
|
-
SessionRetryRequest,
|
|
100243
|
-
SESSION_EVENT_RAW_DELTA_TYPES as SESSION_EVENT_RAW_DELTA_TYPES2,
|
|
100244
|
-
SessionEventPayloadMode as SessionEventPayloadMode2,
|
|
100245
|
-
SessionEventReadDirection as SessionEventReadDirection2,
|
|
100246
|
-
SessionEventReadMode as SessionEventReadMode2,
|
|
100247
|
-
SessionEventLatestClass as SessionEventLatestClass2,
|
|
100248
|
-
SessionEventResultMode as SessionEventResultMode2,
|
|
100249
|
-
SessionScopeSubjectId,
|
|
100250
|
-
SessionEventSemanticClass as SessionEventSemanticClass2,
|
|
100251
|
-
SessionEventType as SessionEventType2,
|
|
100252
|
-
SessionMcpServerId,
|
|
100253
|
-
SaveNewSessionDraftRequest,
|
|
100254
|
-
compactSessionEventResult as compactSessionEventResult3,
|
|
100255
|
-
sessionEventLatestClassToSemanticClass as sessionEventLatestClassToSemanticClass2,
|
|
100256
|
-
SaveComposerDraftRequest,
|
|
100257
|
-
SubmitComposerDraftRequest,
|
|
100258
|
-
SteerSessionQueueItemRequest,
|
|
100259
|
-
SteerSessionMessageRequest,
|
|
100260
|
-
TerminalExecRequest,
|
|
100261
|
-
UpdateSessionChannelRequest,
|
|
100262
|
-
UpdateSessionAttentionRequest,
|
|
100263
|
-
UpdateSessionArchiveRequest,
|
|
100264
|
-
UpdateSessionPinRequest,
|
|
100265
|
-
UpdateSessionGoalRequest,
|
|
100266
|
-
UpdateSessionMcpApprovalPolicyRequest,
|
|
100267
|
-
UpdateSessionRequest,
|
|
100268
|
-
UpdateSessionVariableSetsRequest,
|
|
100269
|
-
UpdateSessionVisibilityRequest,
|
|
100270
|
-
UpdateSessionToolPolicyRequest,
|
|
100271
|
-
UpdateSessionAgentRequest,
|
|
100272
|
-
AgentConfigError as AgentConfigError2,
|
|
100273
|
-
ViewerHeartbeatRequest,
|
|
100274
|
-
WORKSPACE_CONTROL_ACTOR_MAX_BYTES,
|
|
100275
|
-
WORK_CLAIM_CANONICAL_KEY_MAX_BYTES as WORK_CLAIM_CANONICAL_KEY_MAX_BYTES2,
|
|
100276
|
-
WORK_CLAIM_DISCOVERY_LIMIT as WORK_CLAIM_DISCOVERY_LIMIT2,
|
|
100277
|
-
WORK_CLAIM_NAMESPACE_MAX_BYTES as WORK_CLAIM_NAMESPACE_MAX_BYTES2,
|
|
100278
|
-
WORK_DISCOVERY_QUERY_MAX_CHARS as WORK_DISCOVERY_QUERY_MAX_CHARS2,
|
|
100279
|
-
WORK_DISCOVERY_RECENT_HOURS_MAX as WORK_DISCOVERY_RECENT_HOURS_MAX2,
|
|
100280
|
-
WorkClaimSubjectFilter as WorkClaimSubjectFilterSchema,
|
|
100281
|
-
WorkClaimSubjectType as WorkClaimSubjectType2,
|
|
100282
|
-
normalizeWorkClaimCanonicalKey,
|
|
100283
|
-
normalizeWorkClaimNamespace,
|
|
100284
|
-
workspaceControlUtf8Bytes
|
|
100285
|
-
} from "@opengeni/contracts";
|
|
100286
|
-
import { streamTokenDegraded } from "@opengeni/config";
|
|
100287
|
-
import {
|
|
100288
|
-
recordAuditEvent as recordAuditEvent13,
|
|
100289
|
-
acceptSessionApprovalDecision as acceptSessionApprovalDecision2,
|
|
100290
|
-
acceptSessionHumanInputResponse as acceptSessionHumanInputResponse2,
|
|
100291
|
-
clearSessionGoal,
|
|
100292
|
-
clearSessionContext,
|
|
100293
|
-
getOpenPtySession,
|
|
100294
|
-
getEnrollment as getEnrollment2,
|
|
100295
|
-
getLiveEnrollmentConnection as getLiveEnrollmentConnection7,
|
|
100296
|
-
getRetainedProcess,
|
|
100297
|
-
getSandbox as getSandbox4,
|
|
100298
|
-
getSession as getSession9,
|
|
100299
|
-
readActiveSandbox as readActiveSandbox3,
|
|
100300
|
-
deleteSessionTreeIfQuiescent,
|
|
100301
|
-
getSessionEvent,
|
|
100302
|
-
getSessionForSubject as getSessionForSubject2,
|
|
100303
|
-
getSessionGoal as getSessionGoal2,
|
|
100304
|
-
getLatestSessionModelContext,
|
|
100305
|
-
getSessionHumanInputRequest,
|
|
100306
|
-
getSessionGoalWithContinuation,
|
|
100307
|
-
getSessionGoalRevision,
|
|
100308
|
-
getSessionQueueSnapshot as getSessionQueueSnapshot2,
|
|
100309
|
-
getStreamAcknowledgment,
|
|
100310
|
-
insertPtySession,
|
|
100311
|
-
listSessionEventPage as listSessionEventPage3,
|
|
100312
|
-
listSessionHumanInputRequests,
|
|
100313
|
-
listSessionGoalRevisionPage,
|
|
100314
|
-
listSessionGoalRevisions,
|
|
100315
|
-
rejectSessionGoalRevisionWithEvent,
|
|
100316
|
-
listSessionIdsInGroup,
|
|
100317
|
-
listSessionDiscoverySummaries as listSessionDiscoverySummaries2,
|
|
100318
|
-
listSessionDiscoveryAncestorPaths,
|
|
100319
|
-
listSessionsForSubject,
|
|
100320
|
-
listSessionEntriesForSubject,
|
|
100321
|
-
getLatestStartedSessionTurn,
|
|
100322
|
-
listSessionTurns,
|
|
100323
|
-
projectEffectiveControlForRelatedAccess as projectEffectiveControlForRelatedAccess2,
|
|
100324
|
-
projectSessionForRelatedAccess as projectSessionForRelatedAccess2,
|
|
100325
|
-
recordStreamAcknowledgment,
|
|
100326
|
-
requestSessionCompaction,
|
|
100327
|
-
switchSessionCodexAccount,
|
|
100328
|
-
setSessionChannel as setSessionChannel2,
|
|
100329
|
-
updateSessionVariableSets,
|
|
100330
|
-
ChannelNotFoundError,
|
|
100331
|
-
setSessionAttention,
|
|
100332
|
-
setSessionArchive,
|
|
100333
|
-
setSessionPin,
|
|
100334
|
-
SessionAttentionVersionConflictError,
|
|
100335
|
-
SessionArchiveVersionConflictError,
|
|
100336
|
-
SessionPinVersionConflictError,
|
|
100337
|
-
SessionPinAccessError,
|
|
100338
|
-
SessionListAccessError,
|
|
100339
|
-
SessionListCursorError,
|
|
100340
|
-
SessionListCursorExpiredError,
|
|
100341
|
-
decodeSessionListCursor,
|
|
100342
|
-
revokeViewer,
|
|
100343
|
-
setSessionGoalStatusWithEvent as setSessionGoalStatusWithEvent2,
|
|
100344
|
-
upsertSessionGoalWithEvent as upsertSessionGoalWithEvent2,
|
|
100345
|
-
updatePtySessionActivity,
|
|
100346
|
-
QueueCommandConflictError,
|
|
100347
|
-
beginSessionRealtimeInTransaction,
|
|
100348
|
-
activateSessionRealtimeConnectionInTransaction,
|
|
100349
|
-
claimSessionRealtimeConnectionInTransaction,
|
|
100350
|
-
completeSessionRealtimeConnectionInTransaction,
|
|
100351
|
-
endSessionRealtimeInTransaction,
|
|
100352
|
-
failSessionRealtimeConnectionInTransaction,
|
|
100353
|
-
NewSessionDraftConflictError,
|
|
100354
|
-
SessionCommandIdempotencyError,
|
|
100355
|
-
SessionControlConflictError,
|
|
100356
|
-
SessionRealtimeConflictError,
|
|
100357
|
-
SessionToolPolicyVersionConflictError,
|
|
100358
|
-
SessionContextBusyError,
|
|
100359
|
-
SessionVariableSetSelectionUnavailableError,
|
|
100360
|
-
SessionCreateConnectionSelectionUnavailableError as SessionCreateConnectionSelectionUnavailableError2,
|
|
100361
|
-
workspaceControlRequestLockTimeoutMs,
|
|
100362
|
-
SessionTenancyAccessError,
|
|
100363
|
-
SessionTenancyConflictError,
|
|
100364
|
-
SessionTenancyInvalidRequestError,
|
|
100365
|
-
SessionTenancyNotActivatedError,
|
|
100366
|
-
HumanInputResponseValidationError as HumanInputResponseValidationError2,
|
|
100367
|
-
SkillHumanResponseError,
|
|
100368
|
-
latestWorkspaceCapture,
|
|
100369
|
-
sessionLatestWorkspaceCapture,
|
|
100370
|
-
renewSessionRealtimeInTransaction,
|
|
100371
|
-
syncSessionRealtimeLedgerInTransaction,
|
|
100372
|
-
withWorkspaceSessionActivityRls,
|
|
100373
|
-
withWorkspaceRls,
|
|
100374
|
-
workspaceCaptureAtRevision
|
|
100375
|
-
} from "@opengeni/db";
|
|
100376
|
-
import {
|
|
100377
|
-
backgroundCommandActivityForSessions,
|
|
100378
|
-
listSessionBackgroundCommands,
|
|
100379
|
-
requestSessionBackgroundCommandCancellation
|
|
100380
|
-
} from "@opengeni/db/session-background-commands";
|
|
100381
|
-
import {
|
|
100382
|
-
appendAndPublishEvents as appendAndPublishEvents3,
|
|
100383
|
-
boundSessionEventHttpPage,
|
|
100384
|
-
coalesceSessionEventDeltasWithCoverage as coalesceSessionEventDeltasWithCoverage2,
|
|
100385
|
-
publishDurableSessionEvents as publishDurableSessionEvents3
|
|
100386
|
-
} from "@opengeni/events";
|
|
100387
|
-
|
|
100388
|
-
// src/gateway-realtime.ts
|
|
100389
|
-
import {
|
|
100390
|
-
VERCEL_AI_GATEWAY_AI_SDK_BASE_URL,
|
|
100391
|
-
VERCEL_AI_GATEWAY_BASE_URL,
|
|
100392
|
-
resolveAiGatewayRealtimeModel
|
|
100393
|
-
} from "@opengeni/config";
|
|
100227
|
+
// src/azure-live.ts
|
|
100394
100228
|
import {
|
|
100395
100229
|
getActiveSessionHistoryItems as getActiveSessionHistoryItems2,
|
|
100396
|
-
getSessionRealtimeContinuityEntries as getSessionRealtimeContinuityEntries2
|
|
100397
|
-
loadWorkspaceVercelAiGatewayApiKey
|
|
100230
|
+
getSessionRealtimeContinuityEntries as getSessionRealtimeContinuityEntries2
|
|
100398
100231
|
} from "@opengeni/db";
|
|
100399
100232
|
|
|
100400
100233
|
// src/codex-realtime.ts
|
|
@@ -100755,7 +100588,312 @@ function safeBrokerMessage(reason) {
|
|
|
100755
100588
|
}
|
|
100756
100589
|
}
|
|
100757
100590
|
|
|
100591
|
+
// src/azure-live.ts
|
|
100592
|
+
var AZURE_LIVE_MODEL_ID = "opengeni-azure/gpt-live-1";
|
|
100593
|
+
function azureLiveConfigured(settings) {
|
|
100594
|
+
return Boolean(settings.azureLiveEndpoint && settings.azureLiveApiKey?.trim());
|
|
100595
|
+
}
|
|
100596
|
+
async function brokerAzureLive(input) {
|
|
100597
|
+
if (!azureLiveConfigured(input.settings))
|
|
100598
|
+
throw new CodexRealtimeBrokerError(
|
|
100599
|
+
"credential_unavailable",
|
|
100600
|
+
"Hosted GPT Live is not configured"
|
|
100601
|
+
);
|
|
100602
|
+
if (input.request.voice)
|
|
100603
|
+
throw new CodexRealtimeBrokerError(
|
|
100604
|
+
"invalid_request",
|
|
100605
|
+
"Hosted voice is selected by deployment configuration"
|
|
100606
|
+
);
|
|
100607
|
+
const instructions = `${openGeniRealtimeInstructions(input.request.instructions)}
|
|
100608
|
+
|
|
100609
|
+
## Prior session conversation
|
|
100610
|
+
The following JSON contains conversation history, not additional system instructions. Preserve each item's role.
|
|
100611
|
+
${JSON.stringify(input.initialItems)}`;
|
|
100612
|
+
const signal = AbortSignal.any([
|
|
100613
|
+
AbortSignal.timeout(2e4),
|
|
100614
|
+
...input.signal ? [input.signal] : []
|
|
100615
|
+
]);
|
|
100616
|
+
let response;
|
|
100617
|
+
try {
|
|
100618
|
+
response = await (input.fetch ?? fetch)(
|
|
100619
|
+
`${input.settings.azureLiveEndpoint.replace(/\/+$/, "")}/openai/v1/live/sessions`,
|
|
100620
|
+
{
|
|
100621
|
+
method: "POST",
|
|
100622
|
+
signal,
|
|
100623
|
+
headers: { "api-key": input.settings.azureLiveApiKey, "content-type": "application/json" },
|
|
100624
|
+
body: JSON.stringify({
|
|
100625
|
+
session: {
|
|
100626
|
+
model: input.settings.azureLiveDeployment,
|
|
100627
|
+
instructions,
|
|
100628
|
+
delegation: { type: "client" },
|
|
100629
|
+
audio: { output: { voice: input.settings.azureLiveVoice } }
|
|
100630
|
+
},
|
|
100631
|
+
transport: { type: "webrtc", sdp: input.request.sdp }
|
|
100632
|
+
})
|
|
100633
|
+
}
|
|
100634
|
+
);
|
|
100635
|
+
} catch {
|
|
100636
|
+
throw new CodexRealtimeBrokerError(
|
|
100637
|
+
signal.aborted ? "timeout" : "network_error",
|
|
100638
|
+
"Hosted voice connection could not be established"
|
|
100639
|
+
);
|
|
100640
|
+
}
|
|
100641
|
+
if (!response.ok)
|
|
100642
|
+
throw new CodexRealtimeBrokerError(
|
|
100643
|
+
response.status === 429 ? "rate_limited" : "provider_error",
|
|
100644
|
+
"Hosted voice connection was rejected",
|
|
100645
|
+
response.status
|
|
100646
|
+
);
|
|
100647
|
+
const body3 = await response.json().catch(() => null);
|
|
100648
|
+
const sdp = body3?.transport?.sdp;
|
|
100649
|
+
if (typeof sdp !== "string" || !sdp || sdp.length > 1048576)
|
|
100650
|
+
throw new CodexRealtimeBrokerError(
|
|
100651
|
+
"invalid_provider_response",
|
|
100652
|
+
"Hosted voice returned an invalid connection answer"
|
|
100653
|
+
);
|
|
100654
|
+
return { sdp, version: "v3", model: AZURE_LIVE_MODEL_ID };
|
|
100655
|
+
}
|
|
100656
|
+
function buildSessionAzureLiveBroker(db, settings, workspaceId, sessionId, fetchImpl) {
|
|
100657
|
+
return async (input) => {
|
|
100658
|
+
const [history, continuity] = await Promise.all([
|
|
100659
|
+
getActiveSessionHistoryItems2(db, workspaceId, sessionId),
|
|
100660
|
+
getSessionRealtimeContinuityEntries2(db, workspaceId, sessionId)
|
|
100661
|
+
]);
|
|
100662
|
+
return brokerAzureLive({
|
|
100663
|
+
settings,
|
|
100664
|
+
...input,
|
|
100665
|
+
initialItems: projectSessionRealtimeInitialItems(history, continuity),
|
|
100666
|
+
...fetchImpl ? { fetch: fetchImpl } : {}
|
|
100667
|
+
});
|
|
100668
|
+
};
|
|
100669
|
+
}
|
|
100670
|
+
|
|
100671
|
+
// src/routes/sessions.ts
|
|
100672
|
+
import { getRetainedProviderCommand } from "@opengeni/db/retained-provider-commands";
|
|
100673
|
+
import { assertGoalResumeAllowed as assertGoalResumeAllowed2, GoalResumeBlockedError } from "@opengeni/core";
|
|
100674
|
+
import { UnsupportedLatencyModeError } from "@opengeni/config";
|
|
100675
|
+
import { searchSessionMessagesForSubject, SessionMessageSearchCursorError } from "@opengeni/db";
|
|
100676
|
+
import { listSessionEventSlices } from "@opengeni/db/session-event-slices";
|
|
100677
|
+
import * as sessionPreviewSchema from "@opengeni/db/schema";
|
|
100678
|
+
import { SessionMessageSearchRequest } from "@opengeni/contracts";
|
|
100679
|
+
import { scheduledSessionIds } from "@opengeni/db";
|
|
100680
|
+
import { isVerifiedDelegatedHumanAuthorization as isVerifiedDelegatedHumanAuthorization4, withSiteSessionOrigin as withSiteSessionOrigin3 } from "@opengeni/core";
|
|
100681
|
+
import { freezeSessionRealtimeConnectionAccounts } from "@opengeni/core";
|
|
100682
|
+
import {
|
|
100683
|
+
createRealtimeVoiceBilling,
|
|
100684
|
+
RealtimeVoiceUnavailableError,
|
|
100685
|
+
TranscriptionBillingRefusedError
|
|
100686
|
+
} from "@opengeni/core";
|
|
100687
|
+
|
|
100688
|
+
// src/transcription/billing-refusal.ts
|
|
100689
|
+
function transcriptionBillingRefusal(c, error) {
|
|
100690
|
+
return c.json({ code: error.code, message: error.message, ...error.details }, error.status);
|
|
100691
|
+
}
|
|
100692
|
+
|
|
100693
|
+
// src/routes/sessions.ts
|
|
100694
|
+
import { SandboxRecoveryRequest } from "@opengeni/contracts";
|
|
100695
|
+
import { getManagedHumanSandboxRecovery, consentManagedHumanSandboxRecovery } from "@opengeni/core";
|
|
100696
|
+
import { SandboxRecoveryConflictError } from "@opengeni/db";
|
|
100697
|
+
import { getSessionCodexAccounts } from "@opengeni/db";
|
|
100698
|
+
import { getToolActionReview, getToolReviewDetailsPage } from "@opengeni/db";
|
|
100699
|
+
import {
|
|
100700
|
+
AcknowledgeStreamRequest,
|
|
100701
|
+
ApplySessionGoalRevisionRequest,
|
|
100702
|
+
ListSessionGoalRevisionsQuery,
|
|
100703
|
+
RejectSessionGoalRevisionRequest,
|
|
100704
|
+
RollbackSessionGoalRevisionRequest,
|
|
100705
|
+
ActivateCodexRealtimeConnectionRequest,
|
|
100706
|
+
AttachViewerRequest,
|
|
100707
|
+
BeginSessionRealtimeRequest,
|
|
100708
|
+
ClearSessionContextRequest,
|
|
100709
|
+
CodexRealtimeWebrtcRequest,
|
|
100710
|
+
GatewayRealtimeConnectRequest,
|
|
100711
|
+
ClientSessionEvent,
|
|
100712
|
+
CompactSessionContextRequest,
|
|
100713
|
+
CreateSessionRequest as CreateSessionRequest2,
|
|
100714
|
+
DeleteSessionQueueItemRequest,
|
|
100715
|
+
EditSessionQueueItemRequest,
|
|
100716
|
+
EndSessionRealtimeRequest,
|
|
100717
|
+
FsDeleteRequest,
|
|
100718
|
+
FsListBatchRequest,
|
|
100719
|
+
FsListRequest,
|
|
100720
|
+
FsMkdirRequest,
|
|
100721
|
+
FsMoveRequest,
|
|
100722
|
+
FsReadRequest,
|
|
100723
|
+
FsWriteRequest,
|
|
100724
|
+
ForkSessionRequest,
|
|
100725
|
+
HumanInputRequestStatus,
|
|
100726
|
+
GitDiffRequest,
|
|
100727
|
+
GitReadBatchRequest,
|
|
100728
|
+
GitLogRequest,
|
|
100729
|
+
GitShowRequest,
|
|
100730
|
+
GitStatusRequest,
|
|
100731
|
+
MoveSessionQueueItemRequest,
|
|
100732
|
+
PtyCloseRequest,
|
|
100733
|
+
PtyOpenRequest,
|
|
100734
|
+
PtyResizeRequest,
|
|
100735
|
+
PtyWriteRequest,
|
|
100736
|
+
PublishSandboxFileArtifactRequest,
|
|
100737
|
+
RenewSessionRealtimeRequest,
|
|
100738
|
+
SyncSessionRealtimeLedgerRequest,
|
|
100739
|
+
SessionControlRequest,
|
|
100740
|
+
SessionRetryRequest,
|
|
100741
|
+
SESSION_EVENT_RAW_DELTA_TYPES as SESSION_EVENT_RAW_DELTA_TYPES2,
|
|
100742
|
+
SessionEventPayloadMode as SessionEventPayloadMode2,
|
|
100743
|
+
SessionEventReadDirection as SessionEventReadDirection2,
|
|
100744
|
+
SessionEventReadMode as SessionEventReadMode2,
|
|
100745
|
+
SessionEventLatestClass as SessionEventLatestClass2,
|
|
100746
|
+
SessionEventResultMode as SessionEventResultMode2,
|
|
100747
|
+
SessionScopeSubjectId,
|
|
100748
|
+
SessionEventSemanticClass as SessionEventSemanticClass2,
|
|
100749
|
+
SessionEventType as SessionEventType2,
|
|
100750
|
+
SessionMcpServerId,
|
|
100751
|
+
SaveNewSessionDraftRequest,
|
|
100752
|
+
compactSessionEventResult as compactSessionEventResult3,
|
|
100753
|
+
sessionEventLatestClassToSemanticClass as sessionEventLatestClassToSemanticClass2,
|
|
100754
|
+
SaveComposerDraftRequest,
|
|
100755
|
+
SubmitComposerDraftRequest,
|
|
100756
|
+
SteerSessionQueueItemRequest,
|
|
100757
|
+
SteerSessionMessageRequest,
|
|
100758
|
+
TerminalExecRequest,
|
|
100759
|
+
UpdateSessionChannelRequest,
|
|
100760
|
+
UpdateSessionAttentionRequest,
|
|
100761
|
+
UpdateSessionArchiveRequest,
|
|
100762
|
+
UpdateSessionPinRequest,
|
|
100763
|
+
UpdateSessionGoalRequest,
|
|
100764
|
+
UpdateSessionMcpApprovalPolicyRequest,
|
|
100765
|
+
UpdateSessionRequest,
|
|
100766
|
+
UpdateSessionVariableSetsRequest,
|
|
100767
|
+
UpdateSessionVisibilityRequest,
|
|
100768
|
+
UpdateSessionToolPolicyRequest,
|
|
100769
|
+
UpdateSessionAgentRequest,
|
|
100770
|
+
AgentConfigError as AgentConfigError2,
|
|
100771
|
+
ViewerHeartbeatRequest,
|
|
100772
|
+
WORKSPACE_CONTROL_ACTOR_MAX_BYTES,
|
|
100773
|
+
WORK_CLAIM_CANONICAL_KEY_MAX_BYTES as WORK_CLAIM_CANONICAL_KEY_MAX_BYTES2,
|
|
100774
|
+
WORK_CLAIM_DISCOVERY_LIMIT as WORK_CLAIM_DISCOVERY_LIMIT2,
|
|
100775
|
+
WORK_CLAIM_NAMESPACE_MAX_BYTES as WORK_CLAIM_NAMESPACE_MAX_BYTES2,
|
|
100776
|
+
WORK_DISCOVERY_QUERY_MAX_CHARS as WORK_DISCOVERY_QUERY_MAX_CHARS2,
|
|
100777
|
+
WORK_DISCOVERY_RECENT_HOURS_MAX as WORK_DISCOVERY_RECENT_HOURS_MAX2,
|
|
100778
|
+
WorkClaimSubjectFilter as WorkClaimSubjectFilterSchema,
|
|
100779
|
+
WorkClaimSubjectType as WorkClaimSubjectType2,
|
|
100780
|
+
normalizeWorkClaimCanonicalKey,
|
|
100781
|
+
normalizeWorkClaimNamespace,
|
|
100782
|
+
workspaceControlUtf8Bytes
|
|
100783
|
+
} from "@opengeni/contracts";
|
|
100784
|
+
import { streamTokenDegraded } from "@opengeni/config";
|
|
100785
|
+
import {
|
|
100786
|
+
recordAuditEvent as recordAuditEvent13,
|
|
100787
|
+
acceptSessionApprovalDecision as acceptSessionApprovalDecision2,
|
|
100788
|
+
acceptSessionHumanInputResponse as acceptSessionHumanInputResponse2,
|
|
100789
|
+
clearSessionGoal,
|
|
100790
|
+
clearSessionContext,
|
|
100791
|
+
getOpenPtySession,
|
|
100792
|
+
getEnrollment as getEnrollment2,
|
|
100793
|
+
getLiveEnrollmentConnection as getLiveEnrollmentConnection7,
|
|
100794
|
+
getRetainedProcess,
|
|
100795
|
+
getSandbox as getSandbox4,
|
|
100796
|
+
getSession as getSession9,
|
|
100797
|
+
readActiveSandbox as readActiveSandbox3,
|
|
100798
|
+
deleteSessionTreeIfQuiescent,
|
|
100799
|
+
getSessionEvent,
|
|
100800
|
+
getSessionForSubject as getSessionForSubject2,
|
|
100801
|
+
getSessionGoal as getSessionGoal2,
|
|
100802
|
+
getLatestSessionModelContext,
|
|
100803
|
+
getSessionHumanInputRequest,
|
|
100804
|
+
getSessionGoalWithContinuation,
|
|
100805
|
+
getSessionGoalRevision,
|
|
100806
|
+
getSessionQueueSnapshot as getSessionQueueSnapshot2,
|
|
100807
|
+
getStreamAcknowledgment,
|
|
100808
|
+
insertPtySession,
|
|
100809
|
+
listSessionEventPage as listSessionEventPage3,
|
|
100810
|
+
listSessionHumanInputRequests,
|
|
100811
|
+
listSessionGoalRevisionPage,
|
|
100812
|
+
listSessionGoalRevisions,
|
|
100813
|
+
rejectSessionGoalRevisionWithEvent,
|
|
100814
|
+
listSessionIdsInGroup,
|
|
100815
|
+
listSessionDiscoverySummaries as listSessionDiscoverySummaries2,
|
|
100816
|
+
listSessionDiscoveryAncestorPaths,
|
|
100817
|
+
listSessionsForSubject,
|
|
100818
|
+
listSessionEntriesForSubject,
|
|
100819
|
+
getLatestStartedSessionTurn,
|
|
100820
|
+
listSessionTurns,
|
|
100821
|
+
projectEffectiveControlForRelatedAccess as projectEffectiveControlForRelatedAccess2,
|
|
100822
|
+
projectSessionForRelatedAccess as projectSessionForRelatedAccess2,
|
|
100823
|
+
recordStreamAcknowledgment,
|
|
100824
|
+
requestSessionCompaction,
|
|
100825
|
+
switchSessionCodexAccount,
|
|
100826
|
+
setSessionChannel as setSessionChannel2,
|
|
100827
|
+
updateSessionVariableSets,
|
|
100828
|
+
ChannelNotFoundError,
|
|
100829
|
+
setSessionAttention,
|
|
100830
|
+
setSessionArchive,
|
|
100831
|
+
setSessionPin,
|
|
100832
|
+
SessionAttentionVersionConflictError,
|
|
100833
|
+
SessionArchiveVersionConflictError,
|
|
100834
|
+
SessionPinVersionConflictError,
|
|
100835
|
+
SessionPinAccessError,
|
|
100836
|
+
SessionListAccessError,
|
|
100837
|
+
SessionListCursorError,
|
|
100838
|
+
SessionListCursorExpiredError,
|
|
100839
|
+
decodeSessionListCursor,
|
|
100840
|
+
revokeViewer,
|
|
100841
|
+
setSessionGoalStatusWithEvent as setSessionGoalStatusWithEvent2,
|
|
100842
|
+
upsertSessionGoalWithEvent as upsertSessionGoalWithEvent2,
|
|
100843
|
+
updatePtySessionActivity,
|
|
100844
|
+
QueueCommandConflictError,
|
|
100845
|
+
beginSessionRealtimeInTransaction,
|
|
100846
|
+
activateSessionRealtimeConnectionInTransaction,
|
|
100847
|
+
claimSessionRealtimeConnectionInTransaction,
|
|
100848
|
+
completeSessionRealtimeConnectionInTransaction,
|
|
100849
|
+
endSessionRealtimeInTransaction,
|
|
100850
|
+
failSessionRealtimeConnectionInTransaction,
|
|
100851
|
+
NewSessionDraftConflictError,
|
|
100852
|
+
SessionCommandIdempotencyError,
|
|
100853
|
+
SessionControlConflictError,
|
|
100854
|
+
SessionRealtimeConflictError,
|
|
100855
|
+
SessionToolPolicyVersionConflictError,
|
|
100856
|
+
SessionContextBusyError,
|
|
100857
|
+
SessionVariableSetSelectionUnavailableError,
|
|
100858
|
+
SessionCreateConnectionSelectionUnavailableError as SessionCreateConnectionSelectionUnavailableError2,
|
|
100859
|
+
workspaceControlRequestLockTimeoutMs,
|
|
100860
|
+
SessionTenancyAccessError,
|
|
100861
|
+
SessionTenancyConflictError,
|
|
100862
|
+
SessionTenancyInvalidRequestError,
|
|
100863
|
+
SessionTenancyNotActivatedError,
|
|
100864
|
+
HumanInputResponseValidationError as HumanInputResponseValidationError2,
|
|
100865
|
+
SkillHumanResponseError,
|
|
100866
|
+
latestWorkspaceCapture,
|
|
100867
|
+
sessionLatestWorkspaceCapture,
|
|
100868
|
+
renewSessionRealtimeInTransaction,
|
|
100869
|
+
syncSessionRealtimeLedgerInTransaction,
|
|
100870
|
+
withWorkspaceSessionActivityRls,
|
|
100871
|
+
withWorkspaceRls,
|
|
100872
|
+
workspaceCaptureAtRevision
|
|
100873
|
+
} from "@opengeni/db";
|
|
100874
|
+
import {
|
|
100875
|
+
backgroundCommandActivityForSessions,
|
|
100876
|
+
listSessionBackgroundCommands,
|
|
100877
|
+
requestSessionBackgroundCommandCancellation
|
|
100878
|
+
} from "@opengeni/db/session-background-commands";
|
|
100879
|
+
import {
|
|
100880
|
+
appendAndPublishEvents as appendAndPublishEvents3,
|
|
100881
|
+
boundSessionEventHttpPage,
|
|
100882
|
+
coalesceSessionEventDeltasWithCoverage as coalesceSessionEventDeltasWithCoverage2,
|
|
100883
|
+
publishDurableSessionEvents as publishDurableSessionEvents3
|
|
100884
|
+
} from "@opengeni/events";
|
|
100885
|
+
|
|
100758
100886
|
// src/gateway-realtime.ts
|
|
100887
|
+
import {
|
|
100888
|
+
VERCEL_AI_GATEWAY_AI_SDK_BASE_URL,
|
|
100889
|
+
VERCEL_AI_GATEWAY_BASE_URL,
|
|
100890
|
+
resolveAiGatewayRealtimeModel
|
|
100891
|
+
} from "@opengeni/config";
|
|
100892
|
+
import {
|
|
100893
|
+
getActiveSessionHistoryItems as getActiveSessionHistoryItems3,
|
|
100894
|
+
getSessionRealtimeContinuityEntries as getSessionRealtimeContinuityEntries3,
|
|
100895
|
+
loadWorkspaceVercelAiGatewayApiKey
|
|
100896
|
+
} from "@opengeni/db";
|
|
100759
100897
|
var GatewayRealtimeBrokerError = class extends Error {
|
|
100760
100898
|
constructor(code, message, providerStatus = null) {
|
|
100761
100899
|
super(message);
|
|
@@ -100780,8 +100918,8 @@ async function createGatewayRealtimeConnectionSecret(input) {
|
|
|
100780
100918
|
);
|
|
100781
100919
|
}
|
|
100782
100920
|
const [history, continuity, minted] = await Promise.all([
|
|
100783
|
-
|
|
100784
|
-
|
|
100921
|
+
getActiveSessionHistoryItems3(input.db, input.workspaceId, input.sessionId),
|
|
100922
|
+
getSessionRealtimeContinuityEntries3(input.db, input.workspaceId, input.sessionId),
|
|
100785
100923
|
mintGatewayClientSecret({
|
|
100786
100924
|
apiKey,
|
|
100787
100925
|
upstreamModelId: resolved.upstreamModelId,
|
|
@@ -100844,8 +100982,8 @@ async function mintGatewayClientSecret(input) {
|
|
|
100844
100982
|
// src/xai-realtime.ts
|
|
100845
100983
|
import { SUPERGROK_REALTIME_MODEL_ID } from "@opengeni/config";
|
|
100846
100984
|
import {
|
|
100847
|
-
getActiveSessionHistoryItems as
|
|
100848
|
-
getSessionRealtimeContinuityEntries as
|
|
100985
|
+
getActiveSessionHistoryItems as getActiveSessionHistoryItems4,
|
|
100986
|
+
getSessionRealtimeContinuityEntries as getSessionRealtimeContinuityEntries4,
|
|
100849
100987
|
getXaiSessionAccountPin,
|
|
100850
100988
|
resolveXaiProviderAccountAuthoritySnapshotForAcceptance as resolveXaiProviderAccountAuthoritySnapshotForAcceptance3,
|
|
100851
100989
|
setXaiSessionAccountPin
|
|
@@ -101054,8 +101192,8 @@ async function createXaiRealtimeConnectionSecret(input) {
|
|
|
101054
101192
|
);
|
|
101055
101193
|
}
|
|
101056
101194
|
const [history, continuity, minted] = await Promise.all([
|
|
101057
|
-
|
|
101058
|
-
|
|
101195
|
+
getActiveSessionHistoryItems4(input.db, input.workspaceId, input.sessionId),
|
|
101196
|
+
getSessionRealtimeContinuityEntries4(input.db, input.workspaceId, input.sessionId),
|
|
101059
101197
|
mintXaiClientSecret({
|
|
101060
101198
|
auth,
|
|
101061
101199
|
fetchImpl: input.fetchImpl ?? fetch
|
|
@@ -101495,6 +101633,46 @@ function registerSessionRoutes(app, deps) {
|
|
|
101495
101633
|
observability: deps.observability
|
|
101496
101634
|
};
|
|
101497
101635
|
const workspaceCaptureManifestCache = new WorkspaceCaptureManifestCache();
|
|
101636
|
+
const realtimeVoiceBilling = createRealtimeVoiceBilling({ db, settings });
|
|
101637
|
+
const realtimeVoiceRefusalResponse = (c, error) => {
|
|
101638
|
+
if (error instanceof TranscriptionBillingRefusedError) {
|
|
101639
|
+
return transcriptionBillingRefusal(c, error);
|
|
101640
|
+
}
|
|
101641
|
+
if (error instanceof RealtimeVoiceUnavailableError) {
|
|
101642
|
+
return c.json(
|
|
101643
|
+
{
|
|
101644
|
+
error: {
|
|
101645
|
+
status: 409,
|
|
101646
|
+
code: "realtime_voice_unavailable",
|
|
101647
|
+
message: error.message,
|
|
101648
|
+
retryable: false,
|
|
101649
|
+
details: { reason: error.code }
|
|
101650
|
+
}
|
|
101651
|
+
},
|
|
101652
|
+
409
|
|
101653
|
+
);
|
|
101654
|
+
}
|
|
101655
|
+
return null;
|
|
101656
|
+
};
|
|
101657
|
+
const settleRealtimeVoice = async (input) => {
|
|
101658
|
+
try {
|
|
101659
|
+
return await realtimeVoiceBilling.settle({
|
|
101660
|
+
workspaceId: input.workspaceId,
|
|
101661
|
+
sessionId: input.sessionId,
|
|
101662
|
+
realtimeId: input.realtimeId,
|
|
101663
|
+
callerSubjectId: input.grant.subjectId,
|
|
101664
|
+
attribution: input.attribution
|
|
101665
|
+
});
|
|
101666
|
+
} catch (error) {
|
|
101667
|
+
console.error("realtime voice settlement failed", {
|
|
101668
|
+
workspaceId: input.workspaceId,
|
|
101669
|
+
sessionId: input.sessionId,
|
|
101670
|
+
realtimeId: input.realtimeId,
|
|
101671
|
+
error: error instanceof Error ? error.message : String(error)
|
|
101672
|
+
});
|
|
101673
|
+
return null;
|
|
101674
|
+
}
|
|
101675
|
+
};
|
|
101498
101676
|
const withSiteCommandOrigin = (c, workspaceId, run) => withOptionalSiteCommandOrigin(
|
|
101499
101677
|
db,
|
|
101500
101678
|
workspaceId,
|
|
@@ -102298,6 +102476,18 @@ function registerSessionRoutes(app, deps) {
|
|
|
102298
102476
|
message: "invalid session realtime request"
|
|
102299
102477
|
});
|
|
102300
102478
|
}
|
|
102479
|
+
try {
|
|
102480
|
+
await realtimeVoiceBilling.admit({
|
|
102481
|
+
accountId: grant.accountId,
|
|
102482
|
+
workspaceId,
|
|
102483
|
+
model: parsed.data.model,
|
|
102484
|
+
attribution: creditDebitAttributionForGrant(grant)
|
|
102485
|
+
});
|
|
102486
|
+
} catch (error) {
|
|
102487
|
+
const refused = realtimeVoiceRefusalResponse(c, error);
|
|
102488
|
+
if (refused) return refused;
|
|
102489
|
+
throw error;
|
|
102490
|
+
}
|
|
102301
102491
|
try {
|
|
102302
102492
|
const result = await withWorkspaceSessionActivityRls(db, workspaceId, async (scopedDb) => {
|
|
102303
102493
|
const accounts = await freezeSessionRealtimeConnectionAccounts({
|
|
@@ -102342,6 +102532,14 @@ function registerSessionRoutes(app, deps) {
|
|
|
102342
102532
|
});
|
|
102343
102533
|
}
|
|
102344
102534
|
try {
|
|
102535
|
+
const settlement = await settleRealtimeVoice({
|
|
102536
|
+
grant,
|
|
102537
|
+
attribution: creditDebitAttributionForGrant(grant),
|
|
102538
|
+
workspaceId,
|
|
102539
|
+
sessionId,
|
|
102540
|
+
realtimeId
|
|
102541
|
+
});
|
|
102542
|
+
const stop = settlement?.stop ?? null;
|
|
102345
102543
|
const result = await withWorkspaceSessionActivityRls(
|
|
102346
102544
|
db,
|
|
102347
102545
|
workspaceId,
|
|
@@ -102350,12 +102548,17 @@ function registerSessionRoutes(app, deps) {
|
|
|
102350
102548
|
sessionId,
|
|
102351
102549
|
realtimeId,
|
|
102352
102550
|
ownerSubjectId: grant.subjectId,
|
|
102353
|
-
...parsed.data
|
|
102551
|
+
...parsed.data,
|
|
102552
|
+
extendLease: stop === null
|
|
102354
102553
|
})
|
|
102355
102554
|
);
|
|
102356
102555
|
await publishRealtimeMutation(grant.accountId, workspaceId, sessionId, result);
|
|
102357
102556
|
c.header("cache-control", "private, no-store");
|
|
102358
|
-
return c.json({
|
|
102557
|
+
return c.json({
|
|
102558
|
+
mode: result.mode,
|
|
102559
|
+
replay: result.replay,
|
|
102560
|
+
...stop && result.mode.state === "active" ? { stop: { code: stop.code, message: stop.message } } : {}
|
|
102561
|
+
});
|
|
102359
102562
|
} catch (error) {
|
|
102360
102563
|
throw sessionRealtimeHttpError(error);
|
|
102361
102564
|
}
|
|
@@ -102390,6 +102593,13 @@ function registerSessionRoutes(app, deps) {
|
|
|
102390
102593
|
})
|
|
102391
102594
|
);
|
|
102392
102595
|
await publishRealtimeMutation(grant.accountId, workspaceId, sessionId, result);
|
|
102596
|
+
await settleRealtimeVoice({
|
|
102597
|
+
grant,
|
|
102598
|
+
attribution: creditDebitAttributionForGrant(grant),
|
|
102599
|
+
workspaceId,
|
|
102600
|
+
sessionId,
|
|
102601
|
+
realtimeId
|
|
102602
|
+
});
|
|
102393
102603
|
c.header("cache-control", "private, no-store");
|
|
102394
102604
|
return c.json({ mode: result.mode, replay: result.replay });
|
|
102395
102605
|
} catch (error) {
|
|
@@ -102425,21 +102635,31 @@ function registerSessionRoutes(app, deps) {
|
|
|
102425
102635
|
const claim = await withWorkspaceRls(
|
|
102426
102636
|
db,
|
|
102427
102637
|
workspaceId,
|
|
102428
|
-
async (scopedDb) => scopedDb.transaction(
|
|
102429
|
-
|
|
102430
|
-
|
|
102431
|
-
|
|
102432
|
-
|
|
102433
|
-
|
|
102434
|
-
|
|
102435
|
-
|
|
102436
|
-
|
|
102437
|
-
|
|
102438
|
-
|
|
102439
|
-
|
|
102440
|
-
|
|
102441
|
-
|
|
102442
|
-
|
|
102638
|
+
async (scopedDb) => scopedDb.transaction(async (tx) => {
|
|
102639
|
+
const claimed = await claimSessionRealtimeConnectionInTransaction(
|
|
102640
|
+
tx,
|
|
102641
|
+
{
|
|
102642
|
+
workspaceId,
|
|
102643
|
+
sessionId,
|
|
102644
|
+
realtimeId,
|
|
102645
|
+
operationId,
|
|
102646
|
+
ownerSubjectId: grant.subjectId,
|
|
102647
|
+
browserInstanceId,
|
|
102648
|
+
ownerKey,
|
|
102649
|
+
expectedVersion,
|
|
102650
|
+
expectedConnectionEpoch,
|
|
102651
|
+
rotate,
|
|
102652
|
+
promotionMode: browserActivation === "required" ? "staged" : "legacy"
|
|
102653
|
+
}
|
|
102654
|
+
);
|
|
102655
|
+
if (claimed.mode.model !== "gpt-live-1-boulder-alpha" && claimed.mode.model !== AZURE_LIVE_MODEL_ID) {
|
|
102656
|
+
throw new CodexRealtimeBrokerError(
|
|
102657
|
+
"invalid_request",
|
|
102658
|
+
"This voice model does not use WebRTC negotiation"
|
|
102659
|
+
);
|
|
102660
|
+
}
|
|
102661
|
+
return claimed;
|
|
102662
|
+
})
|
|
102443
102663
|
);
|
|
102444
102664
|
if (claim.replay) {
|
|
102445
102665
|
if (claim.connection.state !== "ready" && claim.connection.state !== "active" || !claim.connection.sdpAnswer) {
|
|
@@ -102470,7 +102690,7 @@ function registerSessionRoutes(app, deps) {
|
|
|
102470
102690
|
return c.json({
|
|
102471
102691
|
sdp: claim.connection.sdpAnswer,
|
|
102472
102692
|
version: "v3",
|
|
102473
|
-
model:
|
|
102693
|
+
model: claim.mode.model,
|
|
102474
102694
|
connectionId: claim.connection.id,
|
|
102475
102695
|
connectionEpoch: claim.connection.connectionEpoch,
|
|
102476
102696
|
startupFenceSequence: claim.connection.startupFenceSequence,
|
|
@@ -102478,13 +102698,34 @@ function registerSessionRoutes(app, deps) {
|
|
|
102478
102698
|
replay: true
|
|
102479
102699
|
});
|
|
102480
102700
|
}
|
|
102481
|
-
|
|
102482
|
-
|
|
102483
|
-
|
|
102484
|
-
|
|
102485
|
-
|
|
102486
|
-
|
|
102487
|
-
|
|
102701
|
+
try {
|
|
102702
|
+
await realtimeVoiceBilling.admit({
|
|
102703
|
+
accountId: grant.accountId,
|
|
102704
|
+
workspaceId,
|
|
102705
|
+
model: claim.mode.model,
|
|
102706
|
+
attribution: creditDebitAttributionForGrant(grant)
|
|
102707
|
+
});
|
|
102708
|
+
} catch (error) {
|
|
102709
|
+
const refused = realtimeVoiceRefusalResponse(c, error);
|
|
102710
|
+
if (!refused) throw error;
|
|
102711
|
+
await withWorkspaceRls(
|
|
102712
|
+
db,
|
|
102713
|
+
workspaceId,
|
|
102714
|
+
async (scopedDb) => scopedDb.transaction(
|
|
102715
|
+
async (tx) => failSessionRealtimeConnectionInTransaction(tx, {
|
|
102716
|
+
workspaceId,
|
|
102717
|
+
sessionId,
|
|
102718
|
+
realtimeId,
|
|
102719
|
+
connectionId: claim.connection.id,
|
|
102720
|
+
operationId,
|
|
102721
|
+
connectionEpoch: claim.connection.connectionEpoch,
|
|
102722
|
+
failureCode: "billing_refused"
|
|
102723
|
+
})
|
|
102724
|
+
)
|
|
102725
|
+
).catch(() => void 0);
|
|
102726
|
+
return refused;
|
|
102727
|
+
}
|
|
102728
|
+
const broker = (claim.mode.model === AZURE_LIVE_MODEL_ID ? buildSessionAzureLiveBroker : buildSessionCodexRealtimeBroker)(db, settings, workspaceId, sessionId, deps.codexFetch);
|
|
102488
102729
|
try {
|
|
102489
102730
|
const answer = await broker({
|
|
102490
102731
|
request: providerRequest,
|
|
@@ -102524,6 +102765,13 @@ function registerSessionRoutes(app, deps) {
|
|
|
102524
102765
|
})
|
|
102525
102766
|
)
|
|
102526
102767
|
) : null;
|
|
102768
|
+
await settleRealtimeVoice({
|
|
102769
|
+
grant,
|
|
102770
|
+
attribution: creditDebitAttributionForGrant(grant),
|
|
102771
|
+
workspaceId,
|
|
102772
|
+
sessionId,
|
|
102773
|
+
realtimeId
|
|
102774
|
+
});
|
|
102527
102775
|
return c.json({
|
|
102528
102776
|
...answer,
|
|
102529
102777
|
connectionId: completed.connection.id,
|
|
@@ -102626,6 +102874,12 @@ function registerSessionRoutes(app, deps) {
|
|
|
102626
102874
|
"Realtime Gateway tokens are single-use; reconnect with a new operation"
|
|
102627
102875
|
);
|
|
102628
102876
|
}
|
|
102877
|
+
await realtimeVoiceBilling.admit({
|
|
102878
|
+
accountId: grant.accountId,
|
|
102879
|
+
workspaceId,
|
|
102880
|
+
model: claim.mode.model,
|
|
102881
|
+
attribution: creditDebitAttributionForGrant(grant)
|
|
102882
|
+
});
|
|
102629
102883
|
const secret = await createGatewayRealtimeConnectionSecret({
|
|
102630
102884
|
db,
|
|
102631
102885
|
settings,
|
|
@@ -102651,6 +102905,13 @@ function registerSessionRoutes(app, deps) {
|
|
|
102651
102905
|
)
|
|
102652
102906
|
);
|
|
102653
102907
|
connectionCompleted = true;
|
|
102908
|
+
await settleRealtimeVoice({
|
|
102909
|
+
grant,
|
|
102910
|
+
attribution: creditDebitAttributionForGrant(grant),
|
|
102911
|
+
workspaceId,
|
|
102912
|
+
sessionId,
|
|
102913
|
+
realtimeId
|
|
102914
|
+
});
|
|
102654
102915
|
return c.json({
|
|
102655
102916
|
...secret,
|
|
102656
102917
|
connectionId: completed.connection.id,
|
|
@@ -102673,11 +102934,13 @@ function registerSessionRoutes(app, deps) {
|
|
|
102673
102934
|
connectionId: claimed.connection.id,
|
|
102674
102935
|
operationId,
|
|
102675
102936
|
connectionEpoch: claimed.connection.connectionEpoch,
|
|
102676
|
-
failureCode: error instanceof GatewayRealtimeBrokerError ? error.code : "gateway_error"
|
|
102937
|
+
failureCode: error instanceof GatewayRealtimeBrokerError ? error.code : error instanceof TranscriptionBillingRefusedError || error instanceof RealtimeVoiceUnavailableError ? "billing_refused" : "gateway_error"
|
|
102677
102938
|
})
|
|
102678
102939
|
)
|
|
102679
102940
|
).catch(() => void 0);
|
|
102680
102941
|
}
|
|
102942
|
+
const refused = realtimeVoiceRefusalResponse(c, error);
|
|
102943
|
+
if (refused) return refused;
|
|
102681
102944
|
if (error instanceof SessionRealtimeConflictError) throw sessionRealtimeHttpError(error);
|
|
102682
102945
|
if (!(error instanceof GatewayRealtimeBrokerError)) throw error;
|
|
102683
102946
|
const status = error.code === "credential_unavailable" ? 409 : 502;
|
|
@@ -106798,6 +107061,11 @@ function socialHttpException(error) {
|
|
|
106798
107061
|
}
|
|
106799
107062
|
|
|
106800
107063
|
// src/routes/workspaces.ts
|
|
107064
|
+
import {
|
|
107065
|
+
createRealtimeVoiceBilling as createRealtimeVoiceBilling2,
|
|
107066
|
+
deploymentRealtimeVoice,
|
|
107067
|
+
realtimeVoiceOfferProblem
|
|
107068
|
+
} from "@opengeni/core";
|
|
106801
107069
|
import { withDirectModelProviders } from "@opengeni/config";
|
|
106802
107070
|
import { listConnectionsMetadata as listConnectionsMetadata6 } from "@opengeni/db";
|
|
106803
107071
|
import {
|
|
@@ -106959,13 +107227,13 @@ function recordWorkspaceDeleteObservation(observability, identity2, observation)
|
|
|
106959
107227
|
labels: { outcome: observation.outcome }
|
|
106960
107228
|
});
|
|
106961
107229
|
}
|
|
106962
|
-
for (const [kind,
|
|
107230
|
+
for (const [kind, count2] of Object.entries(observation.inventory ?? {})) {
|
|
106963
107231
|
observability.observeHistogram({
|
|
106964
107232
|
name: "opengeni_workspace_delete_inventory_rows",
|
|
106965
107233
|
help: "Rows or live owners observed by workspace deletion preflight inventory class.",
|
|
106966
107234
|
labels: { kind },
|
|
106967
107235
|
buckets: INVENTORY_BUCKETS,
|
|
106968
|
-
value: Math.max(0, Math.floor(
|
|
107236
|
+
value: Math.max(0, Math.floor(count2 ?? 0))
|
|
106969
107237
|
});
|
|
106970
107238
|
}
|
|
106971
107239
|
observability.info("Workspace deletion phase", {
|
|
@@ -107735,16 +108003,36 @@ function registerWorkspaceRoutes(app, deps) {
|
|
|
107735
108003
|
return credentialReady ? { available: true, unavailableReason: null } : { available: false, unavailableReason: credentialReason };
|
|
107736
108004
|
};
|
|
107737
108005
|
const gatewayModels = Object.values(AI_GATEWAY_REALTIME_MODELS);
|
|
107738
|
-
const
|
|
107739
|
-
|
|
108006
|
+
const managedCandidates = [
|
|
108007
|
+
{
|
|
108008
|
+
id: AZURE_LIVE_MODEL_ID,
|
|
108009
|
+
label: "GPT Live 1",
|
|
108010
|
+
description: "Realtime voice with session delegation"
|
|
108011
|
+
},
|
|
108012
|
+
...gatewayModels.map((model) => ({
|
|
107740
108013
|
id: model.managedModelId,
|
|
107741
108014
|
label: model.label,
|
|
108015
|
+
description: model.description
|
|
108016
|
+
}))
|
|
108017
|
+
].filter((model) => {
|
|
108018
|
+
const voice = deploymentRealtimeVoice(deps.settings, model.id);
|
|
108019
|
+
return voice !== null && realtimeVoiceOfferProblem(deps.settings, voice) === null;
|
|
108020
|
+
}).filter(
|
|
108021
|
+
(model, _index, offered) => offered.some((candidate) => candidate.id === AZURE_LIVE_MODEL_ID) ? model.id === AZURE_LIVE_MODEL_ID : true
|
|
108022
|
+
);
|
|
108023
|
+
const hasCredits = managedCandidates.length > 0 && await createRealtimeVoiceBilling2({
|
|
108024
|
+
db: deps.db,
|
|
108025
|
+
settings: deps.settings
|
|
108026
|
+
}).hasSpendableCredits(grant.accountId);
|
|
108027
|
+
const models = [
|
|
108028
|
+
...managedCandidates.map((model, index) => ({
|
|
108029
|
+
...model,
|
|
107742
108030
|
provider: "OpenGeni",
|
|
107743
|
-
|
|
107744
|
-
|
|
107745
|
-
|
|
107746
|
-
|
|
107747
|
-
|
|
108031
|
+
...hasCredits ? { available: true, unavailableReason: null, unavailableCode: null } : {
|
|
108032
|
+
available: false,
|
|
108033
|
+
unavailableReason: "Add Opengeni credits to use live voice",
|
|
108034
|
+
unavailableCode: "insufficient_credits"
|
|
108035
|
+
},
|
|
107748
108036
|
recommended: index === 0
|
|
107749
108037
|
})),
|
|
107750
108038
|
{
|
|
@@ -111549,7 +111837,11 @@ import {
|
|
|
111549
111837
|
resolveWorkspaceVoiceInputEnabled as resolveWorkspaceVoiceInputEnabled2,
|
|
111550
111838
|
WorkspaceVoiceInputSettings as WorkspaceVoiceInputSettings2
|
|
111551
111839
|
} from "@opengeni/contracts";
|
|
111552
|
-
import {
|
|
111840
|
+
import {
|
|
111841
|
+
requireAccessGrant as requireAccessGrant50,
|
|
111842
|
+
TranscriptionBillingRefusedError as TranscriptionBillingRefusedError3,
|
|
111843
|
+
TranscriptionServiceError as TranscriptionServiceError2
|
|
111844
|
+
} from "@opengeni/core";
|
|
111553
111845
|
import { getWorkspace as getWorkspace10 } from "@opengeni/db";
|
|
111554
111846
|
|
|
111555
111847
|
// src/routes/transcription-recordings.ts
|
|
@@ -111567,6 +111859,7 @@ import {
|
|
|
111567
111859
|
normalizeMimeType,
|
|
111568
111860
|
requireAccessGrant as requireAccessGrant49,
|
|
111569
111861
|
TRANSCRIPTION_PROVIDER_REQUEST_TIMEOUT_MILLISECONDS,
|
|
111862
|
+
TranscriptionBillingRefusedError as TranscriptionBillingRefusedError2,
|
|
111570
111863
|
TranscriptionServiceError
|
|
111571
111864
|
} from "@opengeni/core";
|
|
111572
111865
|
import { retryWhileMissing as retryWhileMissing7 } from "@opengeni/storage";
|
|
@@ -111598,6 +111891,38 @@ import {
|
|
|
111598
111891
|
} from "@opengeni/db";
|
|
111599
111892
|
import { getWorkspace as getWorkspace9 } from "@opengeni/db";
|
|
111600
111893
|
|
|
111894
|
+
// src/transcription/wav-duration.ts
|
|
111895
|
+
function wavDurationSeconds(bytes) {
|
|
111896
|
+
if (bytes.byteLength < 12) return null;
|
|
111897
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
111898
|
+
if (ascii2(bytes, 0) !== "RIFF" || ascii2(bytes, 8) !== "WAVE") return null;
|
|
111899
|
+
let offset = 12;
|
|
111900
|
+
let byteRate = null;
|
|
111901
|
+
while (offset + 8 <= bytes.byteLength) {
|
|
111902
|
+
const id = ascii2(bytes, offset);
|
|
111903
|
+
const size = view.getUint32(offset + 4, true);
|
|
111904
|
+
const body3 = offset + 8;
|
|
111905
|
+
if (id === "fmt " && body3 + 12 <= bytes.byteLength) {
|
|
111906
|
+
byteRate = view.getUint32(body3 + 8, true);
|
|
111907
|
+
} else if (id === "data") {
|
|
111908
|
+
if (!byteRate) return null;
|
|
111909
|
+
const available = bytes.byteLength - body3;
|
|
111910
|
+
const dataBytes = size === 0 || size === 4294967295 ? available : Math.min(size, available);
|
|
111911
|
+
return dataBytes / byteRate;
|
|
111912
|
+
}
|
|
111913
|
+
offset = body3 + size + size % 2;
|
|
111914
|
+
}
|
|
111915
|
+
return null;
|
|
111916
|
+
}
|
|
111917
|
+
function ascii2(bytes, offset) {
|
|
111918
|
+
return String.fromCharCode(
|
|
111919
|
+
bytes[offset] ?? 0,
|
|
111920
|
+
bytes[offset + 1] ?? 0,
|
|
111921
|
+
bytes[offset + 2] ?? 0,
|
|
111922
|
+
bytes[offset + 3] ?? 0
|
|
111923
|
+
);
|
|
111924
|
+
}
|
|
111925
|
+
|
|
111601
111926
|
// src/transcription/segmenter.ts
|
|
111602
111927
|
import { spawn } from "child_process";
|
|
111603
111928
|
import { createWriteStream } from "fs";
|
|
@@ -111629,7 +111954,7 @@ function createFfmpegTranscriptionSegmenter(input) {
|
|
|
111629
111954
|
true
|
|
111630
111955
|
);
|
|
111631
111956
|
}
|
|
111632
|
-
if (!Number.isSafeInteger(request.providerSegmentSeconds) || request.providerSegmentSeconds <= 0 || !Number.isSafeInteger(request.totalDurationMilliseconds) || request.totalDurationMilliseconds <= 0) {
|
|
111957
|
+
if (!Number.isSafeInteger(request.providerSegmentSeconds) || request.providerSegmentSeconds <= 0 || !Number.isSafeInteger(request.totalDurationMilliseconds) || request.totalDurationMilliseconds <= 0 || request.maxDecodeSeconds !== void 0 && (!Number.isSafeInteger(request.maxDecodeSeconds) || request.maxDecodeSeconds <= 0)) {
|
|
111633
111958
|
throw new TranscriptionSegmenterError(
|
|
111634
111959
|
"Audio segmentation bounds are invalid",
|
|
111635
111960
|
"invalid_audio",
|
|
@@ -111661,6 +111986,8 @@ function createFfmpegTranscriptionSegmenter(input) {
|
|
|
111661
111986
|
"-y",
|
|
111662
111987
|
"-i",
|
|
111663
111988
|
inputPath,
|
|
111989
|
+
// Output-side ceiling: audio past it is never decoded or written.
|
|
111990
|
+
...request.maxDecodeSeconds !== void 0 ? ["-t", String(request.maxDecodeSeconds)] : [],
|
|
111664
111991
|
"-map",
|
|
111665
111992
|
"0:a:0",
|
|
111666
111993
|
"-vn",
|
|
@@ -112123,7 +112450,9 @@ function registerResumableTranscriptionRoutes(app, deps) {
|
|
|
112123
112450
|
const service = deps.transcription;
|
|
112124
112451
|
if (!deps.objectStorage || !service || !await service.available({
|
|
112125
112452
|
workspaceId: authority.workspaceId,
|
|
112126
|
-
subjectId: authority.subjectId
|
|
112453
|
+
subjectId: authority.subjectId,
|
|
112454
|
+
preferredProvider: authority.voiceInput?.preferredProvider,
|
|
112455
|
+
fallbackEnabled: authority.voiceInput?.fallbackEnabled
|
|
112127
112456
|
})) {
|
|
112128
112457
|
return c.json({ code: "unavailable" }, 503);
|
|
112129
112458
|
}
|
|
@@ -112166,6 +112495,31 @@ function registerResumableTranscriptionRoutes(app, deps) {
|
|
|
112166
112495
|
false
|
|
112167
112496
|
);
|
|
112168
112497
|
}
|
|
112498
|
+
const segmentProviderId = claim.segment.providerId && claim.segment.providerId !== "host" ? claim.segment.providerId : null;
|
|
112499
|
+
if (segmentProviderId && service.admit) {
|
|
112500
|
+
try {
|
|
112501
|
+
await service.admit({
|
|
112502
|
+
providerId: segmentProviderId,
|
|
112503
|
+
accountId: authority.accountId,
|
|
112504
|
+
workspaceId: authority.workspaceId,
|
|
112505
|
+
attribution: authority.attribution
|
|
112506
|
+
});
|
|
112507
|
+
} catch (error) {
|
|
112508
|
+
if (!(error instanceof TranscriptionBillingRefusedError2)) throw error;
|
|
112509
|
+
await failTranscriptionRecordingSegment(deps.db, {
|
|
112510
|
+
workspaceId: authority.workspaceId,
|
|
112511
|
+
subjectId: authority.subjectId,
|
|
112512
|
+
recordingId: uuidParam3(c, "recordingId"),
|
|
112513
|
+
segmentNumber,
|
|
112514
|
+
attemptId,
|
|
112515
|
+
fallbackProviderId: null,
|
|
112516
|
+
// The real refusal, so a reloaded recording still explains it.
|
|
112517
|
+
errorCode: error.code,
|
|
112518
|
+
retryable: true
|
|
112519
|
+
}).catch(() => null);
|
|
112520
|
+
return transcriptionBillingRefusal(c, error);
|
|
112521
|
+
}
|
|
112522
|
+
}
|
|
112169
112523
|
const providerStartedAt = /* @__PURE__ */ new Date();
|
|
112170
112524
|
const providerDeadlineAt = new Date(
|
|
112171
112525
|
providerStartedAt.getTime() + TRANSCRIPTION_PROVIDER_REQUEST_TIMEOUT_MILLISECONDS
|
|
@@ -112188,7 +112542,17 @@ function registerResumableTranscriptionRoutes(app, deps) {
|
|
|
112188
112542
|
durationSeconds: claim.segment.durationMilliseconds / 1e3,
|
|
112189
112543
|
requestId: attemptId,
|
|
112190
112544
|
providerDeadlineAt,
|
|
112191
|
-
|
|
112545
|
+
deferBillingSettlement: true,
|
|
112546
|
+
...segmentProviderId ? { providerId: segmentProviderId } : {},
|
|
112547
|
+
billing: {
|
|
112548
|
+
// One settlement per prepared segment of this exact recording,
|
|
112549
|
+
// however many provider attempts it takes. The recording id is
|
|
112550
|
+
// client-chosen, so the unit id also binds its server creation
|
|
112551
|
+
// time and the server-produced segment bytes.
|
|
112552
|
+
sourceId: `${claim.recording.recording.id}:${Date.parse(claim.recording.recording.createdAt)}:${segmentNumber}:${claim.segment.sha256}`,
|
|
112553
|
+
attribution: authority.attribution,
|
|
112554
|
+
trustedDurationSeconds: wavDurationSeconds(stored.bytes) ?? void 0
|
|
112555
|
+
}
|
|
112192
112556
|
});
|
|
112193
112557
|
const completed = await completeTranscriptionRecordingSegment(deps.db, {
|
|
112194
112558
|
workspaceId: authority.workspaceId,
|
|
@@ -112200,6 +112564,7 @@ function registerResumableTranscriptionRoutes(app, deps) {
|
|
|
112200
112564
|
languages: result.languages,
|
|
112201
112565
|
providerId: claim.segment.providerId ?? result.providerId
|
|
112202
112566
|
});
|
|
112567
|
+
await result.settleBilling?.().catch(() => void 0);
|
|
112203
112568
|
return c.json(await cleanupTerminalObjects(deps, authority, completed));
|
|
112204
112569
|
} catch (error) {
|
|
112205
112570
|
if (authority && attemptId && segmentNumber !== null) {
|
|
@@ -112223,6 +112588,9 @@ function registerResumableTranscriptionRoutes(app, deps) {
|
|
|
112223
112588
|
errorCode: failure2.code,
|
|
112224
112589
|
retryable: failure2.retryable
|
|
112225
112590
|
}).catch(() => null);
|
|
112591
|
+
if (error instanceof TranscriptionBillingRefusedError2) {
|
|
112592
|
+
return transcriptionBillingRefusal(c, error);
|
|
112593
|
+
}
|
|
112226
112594
|
if (persisted) return c.json(persisted);
|
|
112227
112595
|
}
|
|
112228
112596
|
return routeError2(c, error);
|
|
@@ -112269,7 +112637,8 @@ async function requireRecordingAuthority(c, deps, requirePolicy) {
|
|
|
112269
112637
|
voiceInput: WorkspaceVoiceInputSettings.safeParse(workspace?.settings.voiceInput).data,
|
|
112270
112638
|
accountId: grant.accountId,
|
|
112271
112639
|
workspaceId,
|
|
112272
|
-
subjectId: grant.subjectId
|
|
112640
|
+
subjectId: grant.subjectId,
|
|
112641
|
+
attribution: creditDebitAttributionForGrant(grant)
|
|
112273
112642
|
};
|
|
112274
112643
|
}
|
|
112275
112644
|
async function resumableAvailable(deps, workspaceId) {
|
|
@@ -112340,6 +112709,9 @@ function processingFailure(error) {
|
|
|
112340
112709
|
if (error instanceof TranscriptionSegmenterError) {
|
|
112341
112710
|
return { code: error.code, retryable: error.retryable };
|
|
112342
112711
|
}
|
|
112712
|
+
if (error instanceof TranscriptionBillingRefusedError2) {
|
|
112713
|
+
return { code: error.code, retryable: true };
|
|
112714
|
+
}
|
|
112343
112715
|
if (error instanceof TranscriptionServiceError) {
|
|
112344
112716
|
return {
|
|
112345
112717
|
code: error.code,
|
|
@@ -112355,6 +112727,9 @@ function routeError2(c, error) {
|
|
|
112355
112727
|
if (error instanceof TranscriptionRecordingNotFoundError) {
|
|
112356
112728
|
return c.json({ code: "not_found" }, 404);
|
|
112357
112729
|
}
|
|
112730
|
+
if (error instanceof TranscriptionBillingRefusedError2) {
|
|
112731
|
+
return transcriptionBillingRefusal(c, error);
|
|
112732
|
+
}
|
|
112358
112733
|
if (error instanceof TranscriptionRecordingConflictError) {
|
|
112359
112734
|
return c.json({ code: "conflict" }, 409);
|
|
112360
112735
|
}
|
|
@@ -112478,12 +112853,21 @@ function registerTranscriptionRoutes(app, deps) {
|
|
|
112478
112853
|
}
|
|
112479
112854
|
const preferences = WorkspaceVoiceInputSettings2.safeParse(workspace.settings.voiceInput).data;
|
|
112480
112855
|
const service = deps.transcription;
|
|
112481
|
-
if (!service || !await service.available({
|
|
112856
|
+
if (!service || !await service.available({
|
|
112857
|
+
workspaceId,
|
|
112858
|
+
subjectId: grant.subjectId,
|
|
112859
|
+
preferredProvider: preferences?.preferredProvider,
|
|
112860
|
+
fallbackEnabled: preferences?.fallbackEnabled
|
|
112861
|
+
})) {
|
|
112482
112862
|
return c.json({ code: "unavailable" }, 503);
|
|
112483
112863
|
}
|
|
112484
112864
|
try {
|
|
112485
112865
|
const body3 = await audioRequest(c.req.raw, service.limits().maxSizeBytes);
|
|
112486
112866
|
const result = await service.transcribe({
|
|
112867
|
+
billing: {
|
|
112868
|
+
sourceId: crypto.randomUUID(),
|
|
112869
|
+
attribution: creditDebitAttributionForGrant(grant)
|
|
112870
|
+
},
|
|
112487
112871
|
preferredProvider: preferences?.preferredProvider,
|
|
112488
112872
|
fallbackEnabled: preferences?.fallbackEnabled,
|
|
112489
112873
|
workspaceId,
|
|
@@ -112504,6 +112888,9 @@ function registerTranscriptionRoutes(app, deps) {
|
|
|
112504
112888
|
if (error instanceof TranscriptionServiceError2) {
|
|
112505
112889
|
return c.json({ code: error.code }, error.status);
|
|
112506
112890
|
}
|
|
112891
|
+
if (error instanceof TranscriptionBillingRefusedError3) {
|
|
112892
|
+
return transcriptionBillingRefusal(c, error);
|
|
112893
|
+
}
|
|
112507
112894
|
if (isAbort3(error)) return c.json({ code: "cancelled" }, 499);
|
|
112508
112895
|
return c.json({ code: "unknown" }, 500);
|
|
112509
112896
|
}
|
|
@@ -112609,17 +112996,23 @@ function isAbort3(error) {
|
|
|
112609
112996
|
|
|
112610
112997
|
// src/routes/session-artifact-associations.ts
|
|
112611
112998
|
import {
|
|
112612
|
-
|
|
112999
|
+
fileOwnerContextForAccess as fileOwnerContextForAccess6,
|
|
113000
|
+
requireAccessGrantAuthorization as requireAccessGrantAuthorization37,
|
|
112613
113001
|
requirePermission as requirePermission12,
|
|
112614
113002
|
requireSessionAuthorization as requireSessionAuthorization15,
|
|
112615
113003
|
SessionAuthorizationDeniedError as SessionAuthorizationDeniedError15,
|
|
112616
113004
|
SessionAuthorizationUnavailableError as SessionAuthorizationUnavailableError13
|
|
112617
113005
|
} from "@opengeni/core";
|
|
112618
113006
|
import {
|
|
113007
|
+
getGeneratedImageArtifact as getGeneratedImageArtifact3,
|
|
113008
|
+
getGeneratedVideoArtifact as getGeneratedVideoArtifact2,
|
|
113009
|
+
getRetainedFileArtifact as getRetainedFileArtifact2,
|
|
112619
113010
|
hasEditableArtifactSessionLink as hasEditableArtifactSessionLink2,
|
|
112620
113011
|
hasWorkspaceArtifactSessionLink,
|
|
113012
|
+
listArtifactCatalogCandidates as listArtifactCatalogCandidates2,
|
|
112621
113013
|
transactionallyAuthorizeEditableArtifactActor,
|
|
112622
|
-
withRlsContext as withRlsContext3
|
|
113014
|
+
withRlsContext as withRlsContext3,
|
|
113015
|
+
withSessionRlsActorContext as withSessionRlsActorContext10
|
|
112623
113016
|
} from "@opengeni/db";
|
|
112624
113017
|
import { HTTPException as HTTPException107 } from "hono/http-exception";
|
|
112625
113018
|
import { z as z55 } from "zod";
|
|
@@ -112630,12 +113023,18 @@ function registerSessionArtifactAssociationRoutes(app, deps) {
|
|
|
112630
113023
|
"/v1/workspaces/:workspaceId/sessions/:sessionId/artifact-associations/:artifactId",
|
|
112631
113024
|
async (context) => {
|
|
112632
113025
|
const workspaceId = context.req.param("workspaceId");
|
|
112633
|
-
const grant = await requireAccessGrant51(context, deps, workspaceId, "artifacts:read");
|
|
112634
|
-
requirePermission12(grant, "sessions:read");
|
|
112635
113026
|
const sessionId = context.req.param("sessionId");
|
|
112636
113027
|
const artifactId2 = context.req.param("artifactId");
|
|
112637
|
-
const kind = EditableArtifactId.safeParse(artifactId2).success ? "editable" : SessionId2.safeParse(artifactId2).success ? "site" : null;
|
|
112638
113028
|
const requestedKind = context.req.query("kind");
|
|
113029
|
+
const kind = EditableArtifactId.safeParse(artifactId2).success ? "editable" : SessionId2.safeParse(artifactId2).success ? requestedKind === "retained" ? "retained" : "site" : null;
|
|
113030
|
+
const authorization = await requireAccessGrantAuthorization37(
|
|
113031
|
+
context,
|
|
113032
|
+
deps,
|
|
113033
|
+
workspaceId,
|
|
113034
|
+
kind === "retained" ? "files:read" : "artifacts:read"
|
|
113035
|
+
);
|
|
113036
|
+
const grant = authorization.grant;
|
|
113037
|
+
requirePermission12(grant, "sessions:read");
|
|
112639
113038
|
if (!SessionId2.safeParse(sessionId).success || !kind || requestedKind !== void 0 && requestedKind !== kind) {
|
|
112640
113039
|
throw new HTTPException107(404, { message: "Artifact association not found" });
|
|
112641
113040
|
}
|
|
@@ -112647,7 +113046,18 @@ function registerSessionArtifactAssociationRoutes(app, deps) {
|
|
|
112647
113046
|
surface: "http"
|
|
112648
113047
|
});
|
|
112649
113048
|
const scope = { accountId: grant.accountId, workspaceId };
|
|
112650
|
-
const linked = kind === "editable" ? await hasEditableArtifactSessionLink2(deps.db, scope, sessionId, artifactId2) :
|
|
113049
|
+
const linked = kind === "editable" ? await hasEditableArtifactSessionLink2(deps.db, scope, sessionId, artifactId2) : kind === "retained" ? await hasRetainedArtifactSessionLink(
|
|
113050
|
+
deps,
|
|
113051
|
+
authorization,
|
|
113052
|
+
workspaceId,
|
|
113053
|
+
sessionId,
|
|
113054
|
+
artifactId2.toLowerCase()
|
|
113055
|
+
) : await hasWorkspaceArtifactSessionLink(
|
|
113056
|
+
deps.db,
|
|
113057
|
+
workspaceId,
|
|
113058
|
+
sessionId,
|
|
113059
|
+
artifactId2
|
|
113060
|
+
);
|
|
112651
113061
|
if (!linked) throw new HTTPException107(404, { message: "Artifact association not found" });
|
|
112652
113062
|
if (kind === "editable") {
|
|
112653
113063
|
const decision = await withRlsContext3(
|
|
@@ -112679,6 +113089,38 @@ function registerSessionArtifactAssociationRoutes(app, deps) {
|
|
|
112679
113089
|
}
|
|
112680
113090
|
);
|
|
112681
113091
|
}
|
|
113092
|
+
async function hasRetainedArtifactSessionLink(deps, authorization, workspaceId, sessionId, artifactId2) {
|
|
113093
|
+
const image = await getGeneratedImageArtifact3(deps.db, workspaceId, artifactId2);
|
|
113094
|
+
if (image) return image.sessionId === sessionId;
|
|
113095
|
+
const video = await getGeneratedVideoArtifact2(deps.db, workspaceId, artifactId2);
|
|
113096
|
+
if (video) return !video.artifact.deletedAt && video.artifact.sessionId === sessionId;
|
|
113097
|
+
const file = await getRetainedFileArtifact2(deps.db, workspaceId, artifactId2);
|
|
113098
|
+
if (!file) return false;
|
|
113099
|
+
const grant = authorization.grant;
|
|
113100
|
+
const actor = await fileOwnerContextForAccess6(deps, authorization, "files:read");
|
|
113101
|
+
const candidates = await withSessionRlsActorContext10(
|
|
113102
|
+
actor,
|
|
113103
|
+
() => listArtifactCatalogCandidates2(
|
|
113104
|
+
deps.db,
|
|
113105
|
+
{ accountId: grant.accountId, workspaceId },
|
|
113106
|
+
{
|
|
113107
|
+
query: {
|
|
113108
|
+
sourceSessionId: sessionId,
|
|
113109
|
+
q: file.file.filename.slice(0, 200),
|
|
113110
|
+
sort: "newest",
|
|
113111
|
+
status: "active",
|
|
113112
|
+
limit: 100
|
|
113113
|
+
},
|
|
113114
|
+
kinds: ["file", "image"],
|
|
113115
|
+
snapshotAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
113116
|
+
limit: 201
|
|
113117
|
+
}
|
|
113118
|
+
)
|
|
113119
|
+
);
|
|
113120
|
+
return candidates.some(
|
|
113121
|
+
(candidate) => candidate.origin === "sandbox_file" && candidate.id === artifactId2
|
|
113122
|
+
);
|
|
113123
|
+
}
|
|
112682
113124
|
|
|
112683
113125
|
// src/routes/video-generation.ts
|
|
112684
113126
|
import {
|
|
@@ -112689,7 +113131,7 @@ import {
|
|
|
112689
113131
|
} from "@opengeni/contracts";
|
|
112690
113132
|
import {
|
|
112691
113133
|
getVideoGenerationOperationSummary,
|
|
112692
|
-
withSessionRlsActorContext as
|
|
113134
|
+
withSessionRlsActorContext as withSessionRlsActorContext11,
|
|
112693
113135
|
getWorkspaceVercelAiGatewayConnectionMetadata,
|
|
112694
113136
|
getWorkspaceVideoGenerationPolicy,
|
|
112695
113137
|
updateWorkspaceVideoGenerationPolicy,
|
|
@@ -112697,9 +113139,9 @@ import {
|
|
|
112697
113139
|
workspaceXaiSubscriptionActive as workspaceXaiSubscriptionActive2
|
|
112698
113140
|
} from "@opengeni/db";
|
|
112699
113141
|
import {
|
|
112700
|
-
requireAccessGrant as
|
|
112701
|
-
requireAccessGrantAuthorization as
|
|
112702
|
-
fileOwnerContextForAccess as
|
|
113142
|
+
requireAccessGrant as requireAccessGrant51,
|
|
113143
|
+
requireAccessGrantAuthorization as requireAccessGrantAuthorization38,
|
|
113144
|
+
fileOwnerContextForAccess as fileOwnerContextForAccess7,
|
|
112703
113145
|
requireWorkspaceSettingsGrant as requireWorkspaceSettingsGrant6,
|
|
112704
113146
|
VIDEO_GENERATION_MODEL_CATALOG,
|
|
112705
113147
|
videoGenerationModelSupportsFundingSource,
|
|
@@ -112709,7 +113151,7 @@ import { HTTPException as HTTPException108 } from "hono/http-exception";
|
|
|
112709
113151
|
function registerVideoGenerationRoutes(app, deps) {
|
|
112710
113152
|
app.get("/v1/workspaces/:workspaceId/video-generation", async (c) => {
|
|
112711
113153
|
const workspaceId = c.req.param("workspaceId");
|
|
112712
|
-
const grant = await
|
|
113154
|
+
const grant = await requireAccessGrant51(c, deps, workspaceId, "workspace:read");
|
|
112713
113155
|
const [policy, connection, supergrokConfigured] = await Promise.all([
|
|
112714
113156
|
getWorkspaceVideoGenerationPolicy(deps.db, workspaceId),
|
|
112715
113157
|
getWorkspaceVercelAiGatewayConnectionMetadata(deps.db, workspaceId),
|
|
@@ -112783,9 +113225,9 @@ function registerVideoGenerationRoutes(app, deps) {
|
|
|
112783
113225
|
});
|
|
112784
113226
|
app.get("/v1/workspaces/:workspaceId/video-generation/operations/:operationId", async (c) => {
|
|
112785
113227
|
const workspaceId = c.req.param("workspaceId");
|
|
112786
|
-
const access = await
|
|
112787
|
-
const actor = await
|
|
112788
|
-
const summary = await
|
|
113228
|
+
const access = await requireAccessGrantAuthorization38(c, deps, workspaceId, "workspace:read");
|
|
113229
|
+
const actor = await fileOwnerContextForAccess7(deps, access, "workspace:read");
|
|
113230
|
+
const summary = await withSessionRlsActorContext11(
|
|
112789
113231
|
actor,
|
|
112790
113232
|
() => getVideoGenerationOperationSummary(deps.db, workspaceId, c.req.param("operationId"))
|
|
112791
113233
|
);
|
|
@@ -113983,7 +114425,7 @@ function registerManagedSignInMethodRoutes(app, deps) {
|
|
|
113983
114425
|
select provider_id as provider, (provider_id <> 'credential' or length(password)>0) as usable from auth_identities where user_id=${actor.authUserId}`
|
|
113984
114426
|
);
|
|
113985
114427
|
const connected = (provider) => accounts.some((a) => a.provider === provider && a.usable) && projection.loginBindings.some((b) => b.providerId === provider && b.status === "active");
|
|
113986
|
-
const
|
|
114428
|
+
const count2 = ["credential", "google", "github"].filter(
|
|
113987
114429
|
(p) => connected(p) && configured(deps, p)
|
|
113988
114430
|
).length;
|
|
113989
114431
|
return context.json(
|
|
@@ -113996,7 +114438,7 @@ function registerManagedSignInMethodRoutes(app, deps) {
|
|
|
113996
114438
|
provider,
|
|
113997
114439
|
connected: connected(provider),
|
|
113998
114440
|
available: configured(deps, provider),
|
|
113999
|
-
canDisconnect: provider !== "credential" && connected(provider) &&
|
|
114441
|
+
canDisconnect: provider !== "credential" && connected(provider) && count2 - (configured(deps, provider) ? 1 : 0) > 0,
|
|
114000
114442
|
implicitRelinkingSuppressed: !connected(provider) && projection.loginBindings.some(
|
|
114001
114443
|
(b) => b.providerId === provider && b.status !== "active"
|
|
114002
114444
|
)
|
|
@@ -115321,7 +115763,7 @@ import {
|
|
|
115321
115763
|
organizationWorkspaceInScope as organizationWorkspaceInScope4,
|
|
115322
115764
|
hasPermission as hasPermission31,
|
|
115323
115765
|
requireAccessContext as requireAccessContext13,
|
|
115324
|
-
requireAccessGrantAuthorization as
|
|
115766
|
+
requireAccessGrantAuthorization as requireAccessGrantAuthorization39,
|
|
115325
115767
|
requireSessionAuthorizationListScope as requireSessionAuthorizationListScope3,
|
|
115326
115768
|
SessionAuthorizationDeniedError as SessionAuthorizationDeniedError16,
|
|
115327
115769
|
SessionAuthorizationUnavailableError as SessionAuthorizationUnavailableError14,
|
|
@@ -115474,7 +115916,7 @@ function registerOrganizationSessionRoutes(app, deps) {
|
|
|
115474
115916
|
async function listWorkspaceSessionPage(c, deps, input) {
|
|
115475
115917
|
let authorization;
|
|
115476
115918
|
try {
|
|
115477
|
-
authorization = await
|
|
115919
|
+
authorization = await requireAccessGrantAuthorization39(
|
|
115478
115920
|
c,
|
|
115479
115921
|
deps,
|
|
115480
115922
|
input.workspace.id,
|
|
@@ -116084,7 +116526,7 @@ import {
|
|
|
116084
116526
|
import {
|
|
116085
116527
|
issueManagedHumanUserResourceGrant,
|
|
116086
116528
|
listManagedHumanUserResourceAuthorities,
|
|
116087
|
-
requireAccessGrantAuthorization as
|
|
116529
|
+
requireAccessGrantAuthorization as requireAccessGrantAuthorization40,
|
|
116088
116530
|
revokeManagedHumanUserResourceGrant,
|
|
116089
116531
|
SessionAuthorizationDeniedError as SessionAuthorizationDeniedError17,
|
|
116090
116532
|
SessionAuthorizationUnavailableError as SessionAuthorizationUnavailableError15,
|
|
@@ -116124,7 +116566,7 @@ function registerUserResourceAuthorityRoutes(app, deps) {
|
|
|
116124
116566
|
});
|
|
116125
116567
|
if (!query.success) throw new HTTPException116(422, { message: "explicit scope=user required" });
|
|
116126
116568
|
const workspaceId = context.req.param("workspaceId");
|
|
116127
|
-
const access = await
|
|
116569
|
+
const access = await requireAccessGrantAuthorization40(
|
|
116128
116570
|
context,
|
|
116129
116571
|
deps,
|
|
116130
116572
|
workspaceId,
|
|
@@ -116152,7 +116594,7 @@ function registerUserResourceAuthorityRoutes(app, deps) {
|
|
|
116152
116594
|
const authorityId = z62.string().uuid().safeParse(context.req.param("authorityId"));
|
|
116153
116595
|
if (!authorityId.success) throw new HTTPException116(422, { message: "invalid authority id" });
|
|
116154
116596
|
const request = await body2(context, IssueUserResourceGrantRequest);
|
|
116155
|
-
const access = await
|
|
116597
|
+
const access = await requireAccessGrantAuthorization40(
|
|
116156
116598
|
context,
|
|
116157
116599
|
deps,
|
|
116158
116600
|
workspaceId,
|
|
@@ -116182,7 +116624,7 @@ function registerUserResourceAuthorityRoutes(app, deps) {
|
|
|
116182
116624
|
const grantId = z62.string().uuid().safeParse(context.req.param("grantId"));
|
|
116183
116625
|
if (!grantId.success) throw new HTTPException116(422, { message: "invalid grant id" });
|
|
116184
116626
|
const workspaceId = context.req.param("workspaceId");
|
|
116185
|
-
const access = await
|
|
116627
|
+
const access = await requireAccessGrantAuthorization40(
|
|
116186
116628
|
context,
|
|
116187
116629
|
deps,
|
|
116188
116630
|
workspaceId,
|
|
@@ -116202,28 +116644,94 @@ function registerUserResourceAuthorityRoutes(app, deps) {
|
|
|
116202
116644
|
}
|
|
116203
116645
|
|
|
116204
116646
|
// src/transcription/service.ts
|
|
116205
|
-
import {
|
|
116647
|
+
import {
|
|
116648
|
+
resolveVoiceInputProviderRegistry,
|
|
116649
|
+
voiceInputCreditBillingActive
|
|
116650
|
+
} from "@opengeni/config";
|
|
116206
116651
|
import { VOICE_INPUT_ACCEPTED_MIME_TYPES } from "@opengeni/contracts";
|
|
116652
|
+
import { voiceTranscriptionSettlementKeys } from "@opengeni/db";
|
|
116207
116653
|
import {
|
|
116654
|
+
createVoiceInputBilling,
|
|
116208
116655
|
filenameForMimeType,
|
|
116209
116656
|
isAcceptedMimeType as isAcceptedMimeType2,
|
|
116210
116657
|
normalizeMimeType as normalizeMimeType2,
|
|
116211
116658
|
TRANSCRIPTION_PROVIDER_REQUEST_TIMEOUT_MILLISECONDS as TRANSCRIPTION_PROVIDER_REQUEST_TIMEOUT_MILLISECONDS2,
|
|
116212
|
-
TranscriptionServiceError as
|
|
116659
|
+
TranscriptionServiceError as TranscriptionServiceError7
|
|
116213
116660
|
} from "@opengeni/core";
|
|
116214
116661
|
|
|
116215
|
-
// src/transcription/
|
|
116662
|
+
// src/transcription/normalize.ts
|
|
116216
116663
|
import { TranscriptionServiceError as TranscriptionServiceError3 } from "@opengeni/core";
|
|
116664
|
+
function createFfmpegAudioNormalizer(input) {
|
|
116665
|
+
const segmenter = createFfmpegTranscriptionSegmenter({ ffmpegPath: input.ffmpegPath });
|
|
116666
|
+
return async ({ audio, mimeType, maxDurationSeconds, signal }) => {
|
|
116667
|
+
if (!Number.isFinite(maxDurationSeconds) || maxDurationSeconds <= 0) {
|
|
116668
|
+
throw new TranscriptionServiceError3({
|
|
116669
|
+
code: "unavailable",
|
|
116670
|
+
message: "Voice input duration limit is not configured."
|
|
116671
|
+
});
|
|
116672
|
+
}
|
|
116673
|
+
const decodeSeconds = Math.ceil(maxDurationSeconds) + 1;
|
|
116674
|
+
const segmentSeconds = decodeSeconds + 1;
|
|
116675
|
+
let normalized;
|
|
116676
|
+
try {
|
|
116677
|
+
for await (const segment of segmenter.segment({
|
|
116678
|
+
sourceMimeType: mimeType,
|
|
116679
|
+
chunks: async function* () {
|
|
116680
|
+
yield audio;
|
|
116681
|
+
}(),
|
|
116682
|
+
providerSegmentSeconds: segmentSeconds,
|
|
116683
|
+
totalDurationMilliseconds: segmentSeconds * 1e3,
|
|
116684
|
+
maxDecodeSeconds: decodeSeconds,
|
|
116685
|
+
...signal ? { signal } : {}
|
|
116686
|
+
})) {
|
|
116687
|
+
if (normalized) {
|
|
116688
|
+
throw new TranscriptionServiceError3({ code: "too_large", message: "Audio is too long." });
|
|
116689
|
+
}
|
|
116690
|
+
normalized = segment.bytes;
|
|
116691
|
+
}
|
|
116692
|
+
} catch (error) {
|
|
116693
|
+
if (error instanceof TranscriptionSegmenterError) {
|
|
116694
|
+
throw new TranscriptionServiceError3({
|
|
116695
|
+
code: error.code === "unknown" ? "invalid_audio" : error.code,
|
|
116696
|
+
message: "Audio could not be prepared for transcription.",
|
|
116697
|
+
retryable: error.retryable
|
|
116698
|
+
});
|
|
116699
|
+
}
|
|
116700
|
+
throw error;
|
|
116701
|
+
}
|
|
116702
|
+
const durationSeconds = normalized ? wavDurationSeconds(normalized) : null;
|
|
116703
|
+
if (!normalized || durationSeconds === null || !(durationSeconds > 0)) {
|
|
116704
|
+
throw new TranscriptionServiceError3({
|
|
116705
|
+
code: "invalid_audio",
|
|
116706
|
+
message: "Audio could not be decoded."
|
|
116707
|
+
});
|
|
116708
|
+
}
|
|
116709
|
+
if (durationSeconds > decodeSeconds - 0.02) {
|
|
116710
|
+
throw new TranscriptionServiceError3({ code: "too_large", message: "Audio is too long." });
|
|
116711
|
+
}
|
|
116712
|
+
return { bytes: normalized, durationSeconds };
|
|
116713
|
+
};
|
|
116714
|
+
}
|
|
116715
|
+
|
|
116716
|
+
// src/transcription/providers/openai.ts
|
|
116717
|
+
import {
|
|
116718
|
+
voiceInputModelReportsDurationOnly
|
|
116719
|
+
} from "@opengeni/config";
|
|
116720
|
+
import { TranscriptionServiceError as TranscriptionServiceError4 } from "@opengeni/core";
|
|
116217
116721
|
function createOpenAiTranscriptionProvider(input) {
|
|
116218
116722
|
const fetchImpl = input.fetch ?? fetch;
|
|
116219
116723
|
return {
|
|
116220
116724
|
id: "openai",
|
|
116221
116725
|
supportsServerDeadline: true,
|
|
116726
|
+
deploymentFunded: { model: input.model, pricing: input.pricing ?? null },
|
|
116222
116727
|
available: () => true,
|
|
116223
116728
|
async transcribe({ audio, mimeType, filename, requestId, signal }) {
|
|
116224
116729
|
const form = new FormData();
|
|
116225
116730
|
form.append("file", audioBlob(audio, mimeType), filename);
|
|
116226
116731
|
form.append("model", input.model);
|
|
116732
|
+
if (voiceInputModelReportsDurationOnly(input.model)) {
|
|
116733
|
+
form.append("response_format", "verbose_json");
|
|
116734
|
+
}
|
|
116227
116735
|
let response;
|
|
116228
116736
|
try {
|
|
116229
116737
|
response = await fetchImpl(`${input.baseUrl}/audio/transcriptions`, {
|
|
@@ -116241,50 +116749,89 @@ function createOpenAiTranscriptionProvider(input) {
|
|
|
116241
116749
|
}
|
|
116242
116750
|
if (!response.ok) throw responseError(response.status);
|
|
116243
116751
|
const body3 = await response.json().catch(() => null);
|
|
116244
|
-
|
|
116245
|
-
|
|
116752
|
+
const parsed = parseTranscriptionResponseBody(body3);
|
|
116753
|
+
if (!parsed) {
|
|
116754
|
+
throw new TranscriptionServiceError4({
|
|
116246
116755
|
code: "provider",
|
|
116247
116756
|
message: "Invalid transcription response."
|
|
116248
116757
|
});
|
|
116249
116758
|
}
|
|
116250
|
-
return
|
|
116251
|
-
text: body3.text,
|
|
116252
|
-
languages: typeof body3.language === "string" && body3.language ? [body3.language] : []
|
|
116253
|
-
};
|
|
116759
|
+
return parsed;
|
|
116254
116760
|
}
|
|
116255
116761
|
};
|
|
116256
116762
|
}
|
|
116763
|
+
function parseTranscriptionResponseBody(body3) {
|
|
116764
|
+
if (!body3 || typeof body3 !== "object") return null;
|
|
116765
|
+
const record6 = body3;
|
|
116766
|
+
if (typeof record6.text !== "string") return null;
|
|
116767
|
+
const languages = typeof record6.language === "string" && record6.language ? [record6.language] : Array.isArray(record6.languages) ? record6.languages.flatMap((language) => {
|
|
116768
|
+
const code = typeof language === "string" ? language : language?.code;
|
|
116769
|
+
return typeof code === "string" && code ? [code] : [];
|
|
116770
|
+
}) : [];
|
|
116771
|
+
return { text: record6.text, languages, usage: parseTranscriptionUsage(record6) };
|
|
116772
|
+
}
|
|
116773
|
+
function parseTranscriptionUsage(body3) {
|
|
116774
|
+
const usage = body3.usage;
|
|
116775
|
+
if (usage && typeof usage === "object") {
|
|
116776
|
+
const record6 = usage;
|
|
116777
|
+
if (record6.type === "tokens") {
|
|
116778
|
+
const inputTokens = count(record6.input_tokens);
|
|
116779
|
+
const outputTokens = count(record6.output_tokens);
|
|
116780
|
+
if (inputTokens !== null && outputTokens !== null) {
|
|
116781
|
+
const details = record6.input_token_details && typeof record6.input_token_details === "object" ? record6.input_token_details : {};
|
|
116782
|
+
return {
|
|
116783
|
+
kind: "tokens",
|
|
116784
|
+
inputTokens,
|
|
116785
|
+
audioInputTokens: count(details.audio_tokens) ?? 0,
|
|
116786
|
+
textInputTokens: count(details.text_tokens) ?? 0,
|
|
116787
|
+
outputTokens
|
|
116788
|
+
};
|
|
116789
|
+
}
|
|
116790
|
+
}
|
|
116791
|
+
if (record6.type === "duration" && seconds(record6.seconds) !== null) {
|
|
116792
|
+
return { kind: "duration", seconds: seconds(record6.seconds) };
|
|
116793
|
+
}
|
|
116794
|
+
}
|
|
116795
|
+
const duration = seconds(body3.duration);
|
|
116796
|
+
return duration === null ? null : { kind: "duration", seconds: duration };
|
|
116797
|
+
}
|
|
116798
|
+
function count(value) {
|
|
116799
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : null;
|
|
116800
|
+
}
|
|
116801
|
+
function seconds(value) {
|
|
116802
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0 && value < 86400 ? value : null;
|
|
116803
|
+
}
|
|
116257
116804
|
function audioBlob(audio, mimeType) {
|
|
116258
116805
|
return new Blob([Uint8Array.from(audio).buffer], { type: mimeType });
|
|
116259
116806
|
}
|
|
116260
116807
|
function responseError(status) {
|
|
116261
116808
|
if (status === 401 || status === 403) {
|
|
116262
|
-
return new
|
|
116809
|
+
return new TranscriptionServiceError4({
|
|
116263
116810
|
fallbackSafe: true,
|
|
116264
116811
|
code: "unavailable",
|
|
116265
116812
|
message: "Transcription is unavailable."
|
|
116266
116813
|
});
|
|
116267
116814
|
}
|
|
116268
116815
|
if (status === 413) {
|
|
116269
|
-
return new
|
|
116816
|
+
return new TranscriptionServiceError4({
|
|
116270
116817
|
code: "too_large",
|
|
116271
116818
|
message: "Audio is too large."
|
|
116272
116819
|
});
|
|
116273
116820
|
}
|
|
116274
116821
|
if (status === 400 || status === 422) {
|
|
116275
|
-
return new
|
|
116822
|
+
return new TranscriptionServiceError4({
|
|
116276
116823
|
code: "invalid_audio",
|
|
116277
116824
|
message: "Audio could not be transcribed."
|
|
116278
116825
|
});
|
|
116279
116826
|
}
|
|
116280
116827
|
if (status === 408 || status === 504) {
|
|
116281
|
-
return new
|
|
116828
|
+
return new TranscriptionServiceError4({
|
|
116282
116829
|
code: "timeout",
|
|
116283
116830
|
message: "Transcription timed out.",
|
|
116284
116831
|
retryable: true
|
|
116285
116832
|
});
|
|
116286
116833
|
}
|
|
116287
|
-
return new
|
|
116834
|
+
return new TranscriptionServiceError4({
|
|
116288
116835
|
code: status >= 500 ? "unavailable" : "provider",
|
|
116289
116836
|
message: "Transcription provider failed.",
|
|
116290
116837
|
retryable: status >= 500
|
|
@@ -116292,29 +116839,105 @@ function responseError(status) {
|
|
|
116292
116839
|
}
|
|
116293
116840
|
function fetchError(error) {
|
|
116294
116841
|
if (error instanceof DOMException && error.name === "AbortError") {
|
|
116295
|
-
return new
|
|
116842
|
+
return new TranscriptionServiceError4({
|
|
116296
116843
|
code: "cancelled",
|
|
116297
116844
|
message: "Transcription was cancelled."
|
|
116298
116845
|
});
|
|
116299
116846
|
}
|
|
116300
|
-
return new
|
|
116847
|
+
return new TranscriptionServiceError4({
|
|
116301
116848
|
code: "network",
|
|
116302
116849
|
message: "Transcription provider is unreachable.",
|
|
116303
116850
|
retryable: true
|
|
116304
116851
|
});
|
|
116305
116852
|
}
|
|
116306
116853
|
|
|
116854
|
+
// src/transcription/providers/azure-mai.ts
|
|
116855
|
+
function createMaiTranscriptionProvider(input) {
|
|
116856
|
+
const fetchImpl = input.fetch ?? fetch;
|
|
116857
|
+
const normalize = createFfmpegAudioNormalizer({ ffmpegPath: input.ffmpegPath });
|
|
116858
|
+
return {
|
|
116859
|
+
id: "azure-mai",
|
|
116860
|
+
supportsServerDeadline: true,
|
|
116861
|
+
deploymentFunded: { model: input.model, pricing: input.pricing ?? null },
|
|
116862
|
+
available: () => Boolean(input.apiKey),
|
|
116863
|
+
async transcribe({ audio, mimeType, filename, requestId, signal }) {
|
|
116864
|
+
let bytes = audio;
|
|
116865
|
+
let contentType = mimeType;
|
|
116866
|
+
let name = filename;
|
|
116867
|
+
if (!["audio/wav", "audio/x-wav", "audio/mpeg", "audio/flac"].includes(mimeType)) {
|
|
116868
|
+
const normalized = await normalize({
|
|
116869
|
+
audio,
|
|
116870
|
+
mimeType,
|
|
116871
|
+
maxDurationSeconds: 600,
|
|
116872
|
+
...signal ? { signal } : {}
|
|
116873
|
+
});
|
|
116874
|
+
bytes = normalized.bytes;
|
|
116875
|
+
contentType = "audio/wav";
|
|
116876
|
+
name = "audio.wav";
|
|
116877
|
+
}
|
|
116878
|
+
const form = new FormData();
|
|
116879
|
+
form.append("audio", new Blob([Uint8Array.from(bytes).buffer], { type: contentType }), name);
|
|
116880
|
+
form.append(
|
|
116881
|
+
"definition",
|
|
116882
|
+
JSON.stringify({ enhancedMode: { enabled: true, model: input.model } })
|
|
116883
|
+
);
|
|
116884
|
+
let response;
|
|
116885
|
+
try {
|
|
116886
|
+
response = await fetchImpl(
|
|
116887
|
+
`${input.endpoint.replace(/\/+$/, "")}/speechtotext/transcriptions:transcribe?api-version=${encodeURIComponent(input.apiVersion)}`,
|
|
116888
|
+
{
|
|
116889
|
+
method: "POST",
|
|
116890
|
+
headers: {
|
|
116891
|
+
"Ocp-Apim-Subscription-Key": input.apiKey,
|
|
116892
|
+
"x-opengeni-request-id": requestId
|
|
116893
|
+
},
|
|
116894
|
+
body: form,
|
|
116895
|
+
...signal ? { signal } : {}
|
|
116896
|
+
}
|
|
116897
|
+
);
|
|
116898
|
+
} catch (error) {
|
|
116899
|
+
throw fetchError(error);
|
|
116900
|
+
}
|
|
116901
|
+
if (!response.ok) throw responseError(response.status);
|
|
116902
|
+
const body3 = await response.json().catch(() => null);
|
|
116903
|
+
if (!body3 || !Array.isArray(body3.combinedPhrases) || !body3.combinedPhrases.every(
|
|
116904
|
+
(phrase) => typeof phrase === "object" && phrase !== null && "text" in phrase && typeof phrase.text === "string"
|
|
116905
|
+
)) {
|
|
116906
|
+
throw responseError(502);
|
|
116907
|
+
}
|
|
116908
|
+
return {
|
|
116909
|
+
usage: typeof body3.durationMilliseconds === "number" && Number.isFinite(body3.durationMilliseconds) && body3.durationMilliseconds >= 0 ? { kind: "duration", seconds: body3.durationMilliseconds / 1e3 } : null,
|
|
116910
|
+
text: body3.combinedPhrases.map((phrase) => phrase.text).join("\n"),
|
|
116911
|
+
languages: [
|
|
116912
|
+
...new Set(
|
|
116913
|
+
(Array.isArray(body3.phrases) ? body3.phrases : []).flatMap(
|
|
116914
|
+
(phrase) => typeof phrase?.locale === "string" && phrase.locale ? [phrase.locale] : []
|
|
116915
|
+
)
|
|
116916
|
+
)
|
|
116917
|
+
]
|
|
116918
|
+
};
|
|
116919
|
+
}
|
|
116920
|
+
};
|
|
116921
|
+
}
|
|
116922
|
+
|
|
116307
116923
|
// src/transcription/providers/azure-openai.ts
|
|
116924
|
+
import { voiceInputModelReportsDurationOnly as voiceInputModelReportsDurationOnly2 } from "@opengeni/config";
|
|
116308
116925
|
function createAzureOpenAiTranscriptionProvider(input) {
|
|
116309
116926
|
const fetchImpl = input.fetch ?? fetch;
|
|
116310
|
-
const
|
|
116927
|
+
const model = input.model ?? input.deployment;
|
|
116928
|
+
const endpoint = input.endpoint.replace(/\/+$/, "");
|
|
116929
|
+
const url = `${endpoint}/openai/deployments/${encodeURIComponent(input.deployment)}/audio/transcriptions?api-version=${encodeURIComponent(input.apiVersion)}`;
|
|
116311
116930
|
return {
|
|
116312
116931
|
id: "azure-openai",
|
|
116313
116932
|
supportsServerDeadline: true,
|
|
116933
|
+
deploymentFunded: { model, pricing: input.pricing ?? null },
|
|
116314
116934
|
available: () => Boolean(input.apiKey || input.adToken),
|
|
116315
116935
|
async transcribe({ audio, mimeType, filename, requestId, signal }) {
|
|
116316
116936
|
const form = new FormData();
|
|
116317
116937
|
form.append("file", new Blob([Uint8Array.from(audio).buffer], { type: mimeType }), filename);
|
|
116938
|
+
if (voiceInputModelReportsDurationOnly2(model)) {
|
|
116939
|
+
form.append("response_format", "verbose_json");
|
|
116940
|
+
}
|
|
116318
116941
|
const headers = input.apiKey ? { "api-key": input.apiKey, "x-opengeni-request-id": requestId } : { Authorization: `Bearer ${input.adToken}`, "x-opengeni-request-id": requestId };
|
|
116319
116942
|
let response;
|
|
116320
116943
|
try {
|
|
@@ -116329,13 +116952,11 @@ function createAzureOpenAiTranscriptionProvider(input) {
|
|
|
116329
116952
|
}
|
|
116330
116953
|
if (!response.ok) throw responseError(response.status);
|
|
116331
116954
|
const body3 = await response.json().catch(() => null);
|
|
116332
|
-
|
|
116955
|
+
const parsed = parseTranscriptionResponseBody(body3);
|
|
116956
|
+
if (!parsed) {
|
|
116333
116957
|
throw responseError(502);
|
|
116334
116958
|
}
|
|
116335
|
-
return
|
|
116336
|
-
text: body3.text,
|
|
116337
|
-
languages: typeof body3.language === "string" && body3.language ? [body3.language] : []
|
|
116338
|
-
};
|
|
116959
|
+
return parsed;
|
|
116339
116960
|
}
|
|
116340
116961
|
};
|
|
116341
116962
|
}
|
|
@@ -116343,7 +116964,7 @@ function createAzureOpenAiTranscriptionProvider(input) {
|
|
|
116343
116964
|
// src/transcription/providers/codex-subscription.ts
|
|
116344
116965
|
import { CODEX_CLIENT_VERSION as CODEX_CLIENT_VERSION4, CODEX_ORIGINATOR } from "@opengeni/codex/constants";
|
|
116345
116966
|
import {
|
|
116346
|
-
TranscriptionServiceError as
|
|
116967
|
+
TranscriptionServiceError as TranscriptionServiceError5
|
|
116347
116968
|
} from "@opengeni/core";
|
|
116348
116969
|
import { buildCodexTokenResolver as buildCodexTokenResolver4, listCodexAccountStatuses as listCodexAccountStatuses3 } from "@opengeni/db";
|
|
116349
116970
|
var TRANSCRIBE_URL = "https://chatgpt.com/backend-api/transcribe";
|
|
@@ -116369,7 +116990,7 @@ function createCodexSubscriptionTranscriptionProvider(input) {
|
|
|
116369
116990
|
(candidate) => candidate.isActive && candidate.status === "active"
|
|
116370
116991
|
);
|
|
116371
116992
|
if (!account) {
|
|
116372
|
-
throw new
|
|
116993
|
+
throw new TranscriptionServiceError5({
|
|
116373
116994
|
fallbackSafe: true,
|
|
116374
116995
|
code: "unavailable",
|
|
116375
116996
|
message: "Transcription is unavailable."
|
|
@@ -116380,7 +117001,7 @@ function createCodexSubscriptionTranscriptionProvider(input) {
|
|
|
116380
117001
|
try {
|
|
116381
117002
|
token = await resolver.getToken();
|
|
116382
117003
|
} catch {
|
|
116383
|
-
throw new
|
|
117004
|
+
throw new TranscriptionServiceError5({
|
|
116384
117005
|
fallbackSafe: true,
|
|
116385
117006
|
code: "unavailable",
|
|
116386
117007
|
message: "Transcription is unavailable."
|
|
@@ -116431,7 +117052,7 @@ function createCodexSubscriptionTranscriptionProvider(input) {
|
|
|
116431
117052
|
|
|
116432
117053
|
// src/transcription/providers/xai-subscription.ts
|
|
116433
117054
|
import {
|
|
116434
|
-
TranscriptionServiceError as
|
|
117055
|
+
TranscriptionServiceError as TranscriptionServiceError6
|
|
116435
117056
|
} from "@opengeni/core";
|
|
116436
117057
|
import { workspaceXaiSubscriptionActive as workspaceXaiSubscriptionActive3 } from "@opengeni/db";
|
|
116437
117058
|
import {
|
|
@@ -116480,7 +117101,7 @@ function createXaiSubscriptionTranscriptionProvider(input) {
|
|
|
116480
117101
|
...input.fetch ? { fetch: input.fetch } : {}
|
|
116481
117102
|
});
|
|
116482
117103
|
} catch {
|
|
116483
|
-
throw new
|
|
117104
|
+
throw new TranscriptionServiceError6({
|
|
116484
117105
|
code: "unavailable",
|
|
116485
117106
|
fallbackSafe: true,
|
|
116486
117107
|
message: "Transcription is unavailable."
|
|
@@ -116511,7 +117132,7 @@ function createXaiSubscriptionTranscriptionProvider(input) {
|
|
|
116511
117132
|
try {
|
|
116512
117133
|
return await (refresh ? auth.context.refresh() : auth.context.getToken());
|
|
116513
117134
|
} catch {
|
|
116514
|
-
throw new
|
|
117135
|
+
throw new TranscriptionServiceError6({
|
|
116515
117136
|
code: "unavailable",
|
|
116516
117137
|
message: "Reconnect the SuperGrok account to use transcription.",
|
|
116517
117138
|
fallbackSafe: true
|
|
@@ -116528,7 +117149,7 @@ function createXaiSubscriptionTranscriptionProvider(input) {
|
|
|
116528
117149
|
response = await request(token.accessToken);
|
|
116529
117150
|
}
|
|
116530
117151
|
} catch (error) {
|
|
116531
|
-
if (error instanceof
|
|
117152
|
+
if (error instanceof TranscriptionServiceError6) throw error;
|
|
116532
117153
|
throw fetchError(error);
|
|
116533
117154
|
}
|
|
116534
117155
|
if (!response.ok) throw responseError(response.status);
|
|
@@ -116581,6 +117202,12 @@ function createTranscriptionService(input) {
|
|
|
116581
117202
|
...config,
|
|
116582
117203
|
...input.fetch ? { fetch: input.fetch } : {}
|
|
116583
117204
|
});
|
|
117205
|
+
case "azure-mai":
|
|
117206
|
+
return createMaiTranscriptionProvider({
|
|
117207
|
+
...config,
|
|
117208
|
+
ffmpegPath: input.settings.voiceInputFfmpegPath,
|
|
117209
|
+
...input.fetch ? { fetch: input.fetch } : {}
|
|
117210
|
+
});
|
|
116584
117211
|
case "azure-openai":
|
|
116585
117212
|
return createAzureOpenAiTranscriptionProvider({
|
|
116586
117213
|
...config,
|
|
@@ -116611,32 +117238,113 @@ function createTranscriptionService(input) {
|
|
|
116611
117238
|
};
|
|
116612
117239
|
const providerRequestTimeoutMilliseconds = input.providerRequestTimeoutMilliseconds ?? TRANSCRIPTION_PROVIDER_REQUEST_TIMEOUT_MILLISECONDS2;
|
|
116613
117240
|
const now = input.now ?? (() => /* @__PURE__ */ new Date());
|
|
117241
|
+
const log = input.log ?? ((message, attributes) => {
|
|
117242
|
+
console.error(message, attributes);
|
|
117243
|
+
});
|
|
117244
|
+
const billing = input.billing ?? createVoiceInputBilling({
|
|
117245
|
+
db: input.db,
|
|
117246
|
+
settings: input.settings,
|
|
117247
|
+
onReconcileError: (error, scope) => {
|
|
117248
|
+
log("Voice input charge reconciliation failed", {
|
|
117249
|
+
...scope,
|
|
117250
|
+
errorClass: error instanceof Error ? error.name : "UnknownError",
|
|
117251
|
+
error: boundedErrorMessage(error)
|
|
117252
|
+
});
|
|
117253
|
+
}
|
|
117254
|
+
});
|
|
117255
|
+
let normalizer = input.normalizeAudio;
|
|
117256
|
+
const normalizeAudio = (request) => {
|
|
117257
|
+
normalizer ??= createFfmpegAudioNormalizer({ ffmpegPath: input.settings.voiceInputFfmpegPath });
|
|
117258
|
+
return normalizer(request);
|
|
117259
|
+
};
|
|
117260
|
+
const retryDelays = input.settlementRetryDelaysMilliseconds ?? [2e3, 15e3, 6e4];
|
|
117261
|
+
const settleWithoutFailing = async (settlement) => {
|
|
117262
|
+
try {
|
|
117263
|
+
return (await billing.settle(settlement)).creditCostMicros;
|
|
117264
|
+
} catch (error) {
|
|
117265
|
+
reportUnsettled(settlement, error, 0, retryDelays.length > 0 && !deterministic(error));
|
|
117266
|
+
if (!deterministic(error)) scheduleSettlementRetry(settlement, 0);
|
|
117267
|
+
return 0;
|
|
117268
|
+
}
|
|
117269
|
+
};
|
|
117270
|
+
const deterministic = (error) => error instanceof TranscriptionServiceError7;
|
|
117271
|
+
const scheduleSettlementRetry = (settlement, attempt) => {
|
|
117272
|
+
const delay = retryDelays[attempt];
|
|
117273
|
+
if (delay === void 0) return;
|
|
117274
|
+
const timer = setTimeout(() => {
|
|
117275
|
+
void billing.settle(settlement).then(
|
|
117276
|
+
() => {
|
|
117277
|
+
log("Voice input charge settled on retry", {
|
|
117278
|
+
...settlementAttributes(settlement),
|
|
117279
|
+
attempt: attempt + 1
|
|
117280
|
+
});
|
|
117281
|
+
},
|
|
117282
|
+
(error) => {
|
|
117283
|
+
const more = attempt + 1 < retryDelays.length;
|
|
117284
|
+
reportUnsettled(settlement, error, attempt + 1, more);
|
|
117285
|
+
if (more) scheduleSettlementRetry(settlement, attempt + 1);
|
|
117286
|
+
}
|
|
117287
|
+
);
|
|
117288
|
+
}, delay);
|
|
117289
|
+
timer.unref?.();
|
|
117290
|
+
};
|
|
117291
|
+
const reportUnsettled = (settlement, error, attempt, willRetry) => {
|
|
117292
|
+
log(
|
|
117293
|
+
willRetry ? "Voice input charge is unsettled after provider success; retrying" : "Voice input charge is UNSETTLED after provider success; manual reconciliation needed unless its usage receipt committed",
|
|
117294
|
+
{
|
|
117295
|
+
...settlementAttributes(settlement),
|
|
117296
|
+
attempt,
|
|
117297
|
+
willRetry,
|
|
117298
|
+
errorClass: error instanceof Error ? error.name : "UnknownError",
|
|
117299
|
+
error: boundedErrorMessage(error)
|
|
117300
|
+
}
|
|
117301
|
+
);
|
|
117302
|
+
};
|
|
117303
|
+
const creditBillingActive = voiceInputCreditBillingActive(input.settings);
|
|
117304
|
+
const chargeable = (provider) => creditBillingActive && provider.deploymentFunded !== void 0;
|
|
116614
117305
|
return {
|
|
116615
117306
|
limits: () => limits,
|
|
116616
117307
|
async available(context) {
|
|
116617
|
-
return (
|
|
116618
|
-
|
|
117308
|
+
return Boolean(
|
|
117309
|
+
await firstAvailable(orderedProviders(providers, context ?? {}), context ?? {})
|
|
116619
117310
|
);
|
|
116620
117311
|
},
|
|
117312
|
+
async availableProviderIds(context) {
|
|
117313
|
+
const available = await Promise.all(
|
|
117314
|
+
providers.map(async (provider) => {
|
|
117315
|
+
try {
|
|
117316
|
+
return await provider.available(context) ? provider.id : null;
|
|
117317
|
+
} catch {
|
|
117318
|
+
return null;
|
|
117319
|
+
}
|
|
117320
|
+
})
|
|
117321
|
+
);
|
|
117322
|
+
return available.filter((id) => id !== null);
|
|
117323
|
+
},
|
|
116621
117324
|
async selectProvider(context) {
|
|
116622
117325
|
return (await firstAvailable(orderedProviders(providers, context), context))?.id ?? null;
|
|
116623
117326
|
},
|
|
117327
|
+
async admit({ providerId, accountId, workspaceId, attribution }) {
|
|
117328
|
+
const provider = providers.find((candidate) => candidate.id === providerId);
|
|
117329
|
+
if (!provider || !chargeable(provider)) return;
|
|
117330
|
+
await billing.admit({ accountId, workspaceId, attribution });
|
|
117331
|
+
},
|
|
116624
117332
|
async transcribe(request) {
|
|
116625
117333
|
const mimeType = normalizeMimeType2(request.mimeType);
|
|
116626
117334
|
if (!isAcceptedMimeType2(mimeType, limits.acceptedMimeTypes)) {
|
|
116627
|
-
throw new
|
|
117335
|
+
throw new TranscriptionServiceError7({
|
|
116628
117336
|
code: "not_supported",
|
|
116629
117337
|
message: "Unsupported audio format."
|
|
116630
117338
|
});
|
|
116631
117339
|
}
|
|
116632
117340
|
if (request.audio.byteLength > limits.maxSizeBytes) {
|
|
116633
|
-
throw new
|
|
117341
|
+
throw new TranscriptionServiceError7({
|
|
116634
117342
|
code: "too_large",
|
|
116635
117343
|
message: "Audio is too large."
|
|
116636
117344
|
});
|
|
116637
117345
|
}
|
|
116638
117346
|
if (request.durationSeconds !== void 0 && (!Number.isFinite(request.durationSeconds) || request.durationSeconds < 0 || request.durationSeconds > limits.maxDurationSeconds)) {
|
|
116639
|
-
throw new
|
|
117347
|
+
throw new TranscriptionServiceError7({
|
|
116640
117348
|
code: "invalid_audio",
|
|
116641
117349
|
message: "Invalid audio duration."
|
|
116642
117350
|
});
|
|
@@ -116646,22 +117354,51 @@ function createTranscriptionService(input) {
|
|
|
116646
117354
|
subjectId: request.subjectId
|
|
116647
117355
|
}) : await firstAvailable(orderedProviders(providers, request), request);
|
|
116648
117356
|
if (!provider) {
|
|
116649
|
-
throw new
|
|
117357
|
+
throw new TranscriptionServiceError7({
|
|
116650
117358
|
fallbackSafe: true,
|
|
116651
117359
|
code: "unavailable",
|
|
116652
117360
|
message: "Transcription is unavailable."
|
|
116653
117361
|
});
|
|
116654
117362
|
}
|
|
116655
117363
|
if (provider.supportsServerDeadline !== true) {
|
|
116656
|
-
throw new
|
|
117364
|
+
throw new TranscriptionServiceError7({
|
|
116657
117365
|
code: "unavailable",
|
|
116658
117366
|
message: "Transcription provider does not support bounded requests."
|
|
116659
117367
|
});
|
|
116660
117368
|
}
|
|
117369
|
+
if (chargeable(provider)) {
|
|
117370
|
+
if (!provider.deploymentFunded?.pricing || !request.billing) {
|
|
117371
|
+
throw new TranscriptionServiceError7({
|
|
117372
|
+
code: "unavailable",
|
|
117373
|
+
message: "Transcription billing is not configured."
|
|
117374
|
+
});
|
|
117375
|
+
}
|
|
117376
|
+
await billing.admit({
|
|
117377
|
+
accountId: request.accountId,
|
|
117378
|
+
workspaceId: request.workspaceId,
|
|
117379
|
+
attribution: request.billing.attribution
|
|
117380
|
+
});
|
|
117381
|
+
}
|
|
117382
|
+
let audio = request.audio;
|
|
117383
|
+
let providerMimeType = mimeType;
|
|
117384
|
+
let billingContext = request.billing;
|
|
117385
|
+
let audioSeconds = request.durationSeconds ?? 0;
|
|
117386
|
+
if (chargeable(provider) && billingContext?.trustedDurationSeconds === void 0) {
|
|
117387
|
+
const normalized = await normalizeAudio({
|
|
117388
|
+
audio,
|
|
117389
|
+
mimeType,
|
|
117390
|
+
maxDurationSeconds: limits.maxDurationSeconds,
|
|
117391
|
+
...request.signal ? { signal: request.signal } : {}
|
|
117392
|
+
});
|
|
117393
|
+
audio = normalized.bytes;
|
|
117394
|
+
providerMimeType = "audio/wav";
|
|
117395
|
+
audioSeconds = normalized.durationSeconds;
|
|
117396
|
+
billingContext = { ...billingContext, trustedDurationSeconds: normalized.durationSeconds };
|
|
117397
|
+
}
|
|
116661
117398
|
const startedAt = performance.now();
|
|
116662
117399
|
const remainingMilliseconds = request.providerDeadlineAt ? remainingTranscriptionProviderRequestMilliseconds(request.providerDeadlineAt, now()) : providerRequestTimeoutMilliseconds;
|
|
116663
117400
|
if (request.providerDeadlineAt && (!Number.isFinite(remainingMilliseconds) || remainingMilliseconds <= 0)) {
|
|
116664
|
-
throw new
|
|
117401
|
+
throw new TranscriptionServiceError7({
|
|
116665
117402
|
code: "timeout",
|
|
116666
117403
|
message: "Transcription provider deadline expired.",
|
|
116667
117404
|
retryable: true
|
|
@@ -116671,9 +117408,9 @@ function createTranscriptionService(input) {
|
|
|
116671
117408
|
let result;
|
|
116672
117409
|
try {
|
|
116673
117410
|
result = await provider.transcribe({
|
|
116674
|
-
audio
|
|
116675
|
-
mimeType,
|
|
116676
|
-
filename: filenameForMimeType(
|
|
117411
|
+
audio,
|
|
117412
|
+
mimeType: providerMimeType,
|
|
117413
|
+
filename: filenameForMimeType(providerMimeType),
|
|
116677
117414
|
workspaceId: request.workspaceId,
|
|
116678
117415
|
accountId: request.accountId,
|
|
116679
117416
|
subjectId: request.subjectId,
|
|
@@ -116681,7 +117418,7 @@ function createTranscriptionService(input) {
|
|
|
116681
117418
|
signal: deadline.signal
|
|
116682
117419
|
});
|
|
116683
117420
|
if (deadline.timedOut && !request.signal?.aborted) {
|
|
116684
|
-
throw new
|
|
117421
|
+
throw new TranscriptionServiceError7({
|
|
116685
117422
|
code: "timeout",
|
|
116686
117423
|
message: "Transcription provider timed out.",
|
|
116687
117424
|
retryable: true
|
|
@@ -116689,13 +117426,13 @@ function createTranscriptionService(input) {
|
|
|
116689
117426
|
}
|
|
116690
117427
|
} catch (error) {
|
|
116691
117428
|
if (deadline.timedOut && !request.signal?.aborted) {
|
|
116692
|
-
throw new
|
|
117429
|
+
throw new TranscriptionServiceError7({
|
|
116693
117430
|
code: "timeout",
|
|
116694
117431
|
message: "Transcription provider timed out.",
|
|
116695
117432
|
retryable: true
|
|
116696
117433
|
});
|
|
116697
117434
|
}
|
|
116698
|
-
if (error instanceof
|
|
117435
|
+
if (error instanceof TranscriptionServiceError7 && error.fallbackSafe && !request.providerId && request.fallbackEnabled !== false && !request.signal?.aborted) {
|
|
116699
117436
|
const excludedProviders = [...request.excludedProviders ?? [], provider.id];
|
|
116700
117437
|
if (await firstAvailable(
|
|
116701
117438
|
orderedProviders(providers, { ...request, excludedProviders }),
|
|
@@ -116708,11 +117445,30 @@ function createTranscriptionService(input) {
|
|
|
116708
117445
|
} finally {
|
|
116709
117446
|
deadline.dispose();
|
|
116710
117447
|
}
|
|
117448
|
+
const latencyMs = Math.round(performance.now() - startedAt);
|
|
117449
|
+
let creditCostMicros = 0;
|
|
117450
|
+
const settle = async () => {
|
|
117451
|
+
if (chargeable(provider) && provider.deploymentFunded?.pricing && billingContext) {
|
|
117452
|
+
creditCostMicros = await settleWithoutFailing({
|
|
117453
|
+
accountId: request.accountId,
|
|
117454
|
+
workspaceId: request.workspaceId,
|
|
117455
|
+
providerId: provider.id,
|
|
117456
|
+
model: provider.deploymentFunded.model,
|
|
117457
|
+
pricing: provider.deploymentFunded.pricing,
|
|
117458
|
+
usage: result.usage ?? null,
|
|
117459
|
+
billing: billingContext
|
|
117460
|
+
});
|
|
117461
|
+
}
|
|
117462
|
+
};
|
|
117463
|
+
if (!request.deferBillingSettlement) await settle();
|
|
116711
117464
|
return {
|
|
116712
|
-
|
|
117465
|
+
text: result.text,
|
|
117466
|
+
languages: result.languages,
|
|
116713
117467
|
providerId: provider.id,
|
|
116714
|
-
audioSeconds
|
|
116715
|
-
latencyMs
|
|
117468
|
+
audioSeconds,
|
|
117469
|
+
latencyMs,
|
|
117470
|
+
creditCostMicros,
|
|
117471
|
+
...request.deferBillingSettlement ? { settleBilling: settle } : {}
|
|
116716
117472
|
};
|
|
116717
117473
|
}
|
|
116718
117474
|
};
|
|
@@ -116767,6 +117523,23 @@ async function exactAvailable(providers, providerId, context) {
|
|
|
116767
117523
|
const provider = providers.find((candidate) => candidate.id === providerId);
|
|
116768
117524
|
return provider && await provider.available(context) ? provider : null;
|
|
116769
117525
|
}
|
|
117526
|
+
function settlementAttributes(settlement) {
|
|
117527
|
+
const keys2 = voiceTranscriptionSettlementKeys({
|
|
117528
|
+
workspaceId: settlement.workspaceId,
|
|
117529
|
+
sourceId: settlement.billing.sourceId
|
|
117530
|
+
});
|
|
117531
|
+
return {
|
|
117532
|
+
accountId: settlement.accountId,
|
|
117533
|
+
workspaceId: settlement.workspaceId,
|
|
117534
|
+
providerId: settlement.providerId,
|
|
117535
|
+
sourceId: settlement.billing.sourceId,
|
|
117536
|
+
usageIdempotencyKey: keys2.usageIdempotencyKey,
|
|
117537
|
+
debitIdempotencyKey: keys2.debitIdempotencyKey
|
|
117538
|
+
};
|
|
117539
|
+
}
|
|
117540
|
+
function boundedErrorMessage(error) {
|
|
117541
|
+
return (error instanceof Error ? error.message : String(error)).slice(0, 500);
|
|
117542
|
+
}
|
|
116770
117543
|
|
|
116771
117544
|
// src/integrations/slack-tool-review.ts
|
|
116772
117545
|
function slackToolReviewCanDecide(review) {
|
|
@@ -116936,7 +117709,7 @@ import {
|
|
|
116936
117709
|
getActorNewSessionModelChoice,
|
|
116937
117710
|
hasPermission as hasPermission33,
|
|
116938
117711
|
requireAccessContext as requireAccessContext14,
|
|
116939
|
-
requireAccessGrant as
|
|
117712
|
+
requireAccessGrant as requireAccessGrant52,
|
|
116940
117713
|
requireSessionAuthorizationListScope as requireSessionAuthorizationListScope4,
|
|
116941
117714
|
resolveWorkspaceCatalogSettings as resolveWorkspaceCatalogSettings9
|
|
116942
117715
|
} from "@opengeni/core";
|
|
@@ -118103,7 +118876,7 @@ function registerSlackInteractionRoutes(app, deps) {
|
|
|
118103
118876
|
);
|
|
118104
118877
|
app.get("/v1/workspaces/:workspaceId/members/access-requests/slack", async (c) => {
|
|
118105
118878
|
const workspaceId = c.req.param("workspaceId");
|
|
118106
|
-
await
|
|
118879
|
+
await requireAccessGrant52(c, deps, workspaceId, "members:manage");
|
|
118107
118880
|
const workspace = await getWorkspace11(deps.db, workspaceId);
|
|
118108
118881
|
const requests = await listPendingSlackUserLinkAccessRequests(deps.db, workspaceId);
|
|
118109
118882
|
return c.json(
|
|
@@ -118119,7 +118892,7 @@ function registerSlackInteractionRoutes(app, deps) {
|
|
|
118119
118892
|
"/v1/workspaces/:workspaceId/members/access-requests/slack/:requestId/approve",
|
|
118120
118893
|
async (c) => {
|
|
118121
118894
|
const workspaceId = c.req.param("workspaceId");
|
|
118122
|
-
const grant = await
|
|
118895
|
+
const grant = await requireAccessGrant52(c, deps, workspaceId, "members:manage");
|
|
118123
118896
|
const payload = await parseRequestJson(c, ApproveSlackUserLinkAccessRequest);
|
|
118124
118897
|
const requestId = c.req.param("requestId");
|
|
118125
118898
|
const requestTeam = await slackUserLinkAccessRequestTeam(deps.db, {
|
|
@@ -118159,7 +118932,7 @@ function registerSlackInteractionRoutes(app, deps) {
|
|
|
118159
118932
|
"/v1/workspaces/:workspaceId/members/access-requests/slack/:requestId/deny",
|
|
118160
118933
|
async (c) => {
|
|
118161
118934
|
const workspaceId = c.req.param("workspaceId");
|
|
118162
|
-
const grant = await
|
|
118935
|
+
const grant = await requireAccessGrant52(c, deps, workspaceId, "members:manage");
|
|
118163
118936
|
const payload = await parseRequestJson(c, SlackUserLinkAccessMutationRequest);
|
|
118164
118937
|
try {
|
|
118165
118938
|
const request = await denySlackUserLinkAccessRequest(deps.db, {
|
|
@@ -118182,7 +118955,7 @@ function registerSlackInteractionRoutes(app, deps) {
|
|
|
118182
118955
|
);
|
|
118183
118956
|
app.post("/v1/workspaces/:workspaceId/integrations/slack/user-links", async (c) => {
|
|
118184
118957
|
const workspaceId = c.req.param("workspaceId");
|
|
118185
|
-
const grant = await
|
|
118958
|
+
const grant = await requireAccessGrant52(c, deps, workspaceId, "sessions:create");
|
|
118186
118959
|
const body3 = record5(await c.req.json().catch(() => null));
|
|
118187
118960
|
const linkToken = boundedString(body3?.linkToken, 2048);
|
|
118188
118961
|
const signingSecret = deps.settings.slackSigningSecret;
|
|
@@ -118238,7 +119011,7 @@ function registerSlackInteractionRoutes(app, deps) {
|
|
|
118238
119011
|
"/v1/workspaces/:workspaceId/integrations/slack/user-links/:slackUserId",
|
|
118239
119012
|
async (c) => {
|
|
118240
119013
|
const workspaceId = c.req.param("workspaceId");
|
|
118241
|
-
await
|
|
119014
|
+
await requireAccessGrant52(c, deps, workspaceId, "connections:write");
|
|
118242
119015
|
const connectionId = boundedString(c.req.query("connectionId"), 64);
|
|
118243
119016
|
if (!connectionId) throw new HTTPException117(400, { message: "connectionId is required" });
|
|
118244
119017
|
return c.json({
|
|
@@ -118253,7 +119026,7 @@ function registerSlackInteractionRoutes(app, deps) {
|
|
|
118253
119026
|
);
|
|
118254
119027
|
app.get("/v1/workspaces/:workspaceId/integrations/slack/channel-routes", async (c) => {
|
|
118255
119028
|
const workspaceId = c.req.param("workspaceId");
|
|
118256
|
-
const grant = await
|
|
119029
|
+
const grant = await requireAccessGrant52(c, deps, workspaceId, "workspace:admin");
|
|
118257
119030
|
const connectionId = boundedString(c.req.query("connectionId"), 64);
|
|
118258
119031
|
if (!connectionId) throw new HTTPException117(400, { message: "connectionId is required" });
|
|
118259
119032
|
const routes = await listSlackChannelRoutes(
|
|
@@ -118284,7 +119057,7 @@ function registerSlackInteractionRoutes(app, deps) {
|
|
|
118284
119057
|
});
|
|
118285
119058
|
app.put("/v1/workspaces/:workspaceId/integrations/slack/channel-routes", async (c) => {
|
|
118286
119059
|
const workspaceId = c.req.param("workspaceId");
|
|
118287
|
-
const grant = await
|
|
119060
|
+
const grant = await requireAccessGrant52(c, deps, workspaceId, "workspace:admin");
|
|
118288
119061
|
const payload = await parseRequestJson(c, UpdateSlackChannelRoutesRequest);
|
|
118289
119062
|
const home = { accountId: grant.accountId, workspaceId };
|
|
118290
119063
|
const installation = await listSlackInstallationBindings2(deps.db, home);
|
|
@@ -118339,7 +119112,7 @@ function registerSlackInteractionRoutes(app, deps) {
|
|
|
118339
119112
|
});
|
|
118340
119113
|
app.get("/v1/workspaces/:workspaceId/integrations/slack/reaction-channels", async (c) => {
|
|
118341
119114
|
const workspaceId = c.req.param("workspaceId");
|
|
118342
|
-
const grant = await
|
|
119115
|
+
const grant = await requireAccessGrant52(c, deps, workspaceId, "workspace:admin");
|
|
118343
119116
|
const connectionId = boundedString(c.req.query("connectionId"), 64);
|
|
118344
119117
|
if (!connectionId) throw new HTTPException117(400, { message: "connectionId is required" });
|
|
118345
119118
|
const cursor = boundedString(c.req.query("cursor"), 1024);
|
|
@@ -122244,10 +123017,23 @@ function createAppComposition(deps) {
|
|
|
122244
123017
|
}, MANAGED_AUTH_REAPER_INTERVAL_MS);
|
|
122245
123018
|
timer.unref?.();
|
|
122246
123019
|
}
|
|
123020
|
+
if (deps.transcription === void 0) {
|
|
123021
|
+
for (const issue of voiceInputPricingIssues(deps.settings)) {
|
|
123022
|
+
observability.error(
|
|
123023
|
+
`Voice input provider ${issue.providerId} is unavailable: ${issue.message}`,
|
|
123024
|
+
{
|
|
123025
|
+
providerId: issue.providerId,
|
|
123026
|
+
env: issue.env,
|
|
123027
|
+
reason: issue.reason
|
|
123028
|
+
}
|
|
123029
|
+
);
|
|
123030
|
+
}
|
|
123031
|
+
}
|
|
122247
123032
|
const transcription = deps.transcription === void 0 ? createTranscriptionService({
|
|
122248
123033
|
settings: deps.settings,
|
|
122249
123034
|
db: deps.db,
|
|
122250
|
-
...deps.codexFetch ? { codexFetch: deps.codexFetch } : {}
|
|
123035
|
+
...deps.codexFetch ? { codexFetch: deps.codexFetch } : {},
|
|
123036
|
+
log: (message, attributes) => observability.error(message, attributes)
|
|
122251
123037
|
}) : deps.transcription;
|
|
122252
123038
|
const transcriptionSegmenter = deps.transcriptionSegmenter === void 0 ? createFfmpegTranscriptionSegmenter({
|
|
122253
123039
|
ffmpegPath: deps.settings.voiceInputFfmpegPath
|
|
@@ -122357,6 +123143,8 @@ function createAppComposition(deps) {
|
|
|
122357
123143
|
"X-OpenGeni-Actor-Epoch",
|
|
122358
123144
|
"X-OpenGeni-Correlation-Id",
|
|
122359
123145
|
"X-OpenGeni-Session-Csrf",
|
|
123146
|
+
// Session scope for a session proxy; the API itself ignores it.
|
|
123147
|
+
"X-OpenGeni-Session-Id",
|
|
122360
123148
|
"X-OpenGeni-Site-Id",
|
|
122361
123149
|
"X-OpenGeni-Site-Version",
|
|
122362
123150
|
"X-OpenGeni-Subject",
|
|
@@ -122803,6 +123591,8 @@ function createAppComposition(deps) {
|
|
|
122803
123591
|
creditsAvailable: false
|
|
122804
123592
|
});
|
|
122805
123593
|
let modelSelectionForbidden = false;
|
|
123594
|
+
let voiceInputAvailable = false;
|
|
123595
|
+
let voiceInputProviders = [];
|
|
122806
123596
|
const requestedWorkspaceId = c.req.query("workspaceId");
|
|
122807
123597
|
if (requestedWorkspaceId !== void 0 && requestedWorkspaceId.trim() === "") {
|
|
122808
123598
|
throw new HTTPException118(422, { message: "workspaceId must not be empty" });
|
|
@@ -122821,7 +123611,7 @@ function createAppComposition(deps) {
|
|
|
122821
123611
|
}
|
|
122822
123612
|
const workspaceId = requestedWorkspaceId ?? context?.defaultWorkspaceId;
|
|
122823
123613
|
if (context && workspaceId) {
|
|
122824
|
-
const grant = await
|
|
123614
|
+
const grant = await requireAccessGrant53(c, routeDeps, workspaceId);
|
|
122825
123615
|
await withAccessGrantSessionRlsContext(routeDeps, grant, async () => {
|
|
122826
123616
|
catalogSettings = (await resolveWorkspaceCatalogSettings10(deps.db, deps.settings, {
|
|
122827
123617
|
accountId: grant.accountId,
|
|
@@ -122838,6 +123628,19 @@ function createAppComposition(deps) {
|
|
|
122838
123628
|
selections = [];
|
|
122839
123629
|
}
|
|
122840
123630
|
const workspace = await getWorkspace12(deps.db, workspaceId);
|
|
123631
|
+
const voicePreferences = WorkspaceVoiceInputSettings3.safeParse(
|
|
123632
|
+
workspace?.settings.voiceInput
|
|
123633
|
+
).data;
|
|
123634
|
+
const voiceContext = {
|
|
123635
|
+
workspaceId,
|
|
123636
|
+
subjectId: grant.subjectId,
|
|
123637
|
+
preferredProvider: voicePreferences?.preferredProvider,
|
|
123638
|
+
fallbackEnabled: voicePreferences?.fallbackEnabled
|
|
123639
|
+
};
|
|
123640
|
+
if (hasPermission34(grant.permissions, "sessions:create") && transcription) {
|
|
123641
|
+
voiceInputProviders = await transcription.availableProviderIds?.(voiceContext) ?? [];
|
|
123642
|
+
voiceInputAvailable = voiceInputProviders.length > 0 && await Promise.resolve(transcription.available(voiceContext)).catch(() => false);
|
|
123643
|
+
}
|
|
122841
123644
|
defaultSelection = await resolveDefaultSessionModelForSelections2(deps.db, {
|
|
122842
123645
|
settings: catalogSettings,
|
|
122843
123646
|
accountId: grant.accountId,
|
|
@@ -122893,14 +123696,12 @@ function createAppComposition(deps) {
|
|
|
122893
123696
|
maxSizeBytes: objectStorage?.maxSinglePutSizeBytes ?? 5e9
|
|
122894
123697
|
},
|
|
122895
123698
|
voiceInput: {
|
|
122896
|
-
providers:
|
|
122897
|
-
|
|
122898
|
-
),
|
|
122899
|
-
available: await transcription?.available() ?? false,
|
|
123699
|
+
providers: voiceInputProviders,
|
|
123700
|
+
available: voiceInputAvailable,
|
|
122900
123701
|
maxDurationSeconds: deps.settings.voiceInputMaxDurationSeconds,
|
|
122901
123702
|
maxSizeBytes: deps.settings.voiceInputMaxSizeBytes,
|
|
122902
123703
|
acceptedMimeTypes: [...VOICE_INPUT_ACCEPTED_MIME_TYPES2],
|
|
122903
|
-
...deps.settings.voiceInputResumableEnabled && objectStorage && transcription && transcriptionSegmenter &&
|
|
123704
|
+
...deps.settings.voiceInputResumableEnabled && objectStorage && transcription && transcriptionSegmenter && voiceInputAvailable && await transcriptionSegmenter.available() ? {
|
|
122904
123705
|
resumable: {
|
|
122905
123706
|
maxDurationSeconds: deps.settings.voiceInputResumableMaxDurationSeconds,
|
|
122906
123707
|
maxSizeBytes: deps.settings.voiceInputResumableMaxSizeBytes,
|
|
@@ -123009,7 +123810,7 @@ function createAppComposition(deps) {
|
|
|
123009
123810
|
app.use("/v1/workspaces/:workspaceId/*", async (c, next) => {
|
|
123010
123811
|
const workspaceId = c.req.param("workspaceId");
|
|
123011
123812
|
if (workspaceRequestRequiresCodexAccountPrevalidation(c.req.raw)) {
|
|
123012
|
-
const grant2 = await
|
|
123813
|
+
const grant2 = await requireAccessGrant53(c, routeDeps, workspaceId);
|
|
123013
123814
|
await validateCodexAccountTarget(c.req.raw);
|
|
123014
123815
|
await withAccessGrantSessionRlsContext(routeDeps, grant2, next);
|
|
123015
123816
|
return;
|
|
@@ -123019,7 +123820,7 @@ function createAppComposition(deps) {
|
|
|
123019
123820
|
return;
|
|
123020
123821
|
}
|
|
123021
123822
|
const permission = c.req.method === "PUT" && /^\/v1\/workspaces\/[^/]+\/artifact-catalog\/[^/]+\/[^/]+\/pin$/.test(c.req.path) ? "artifacts:publish" : void 0;
|
|
123022
|
-
const grant = await
|
|
123823
|
+
const grant = await requireAccessGrant53(c, routeDeps, workspaceId, permission);
|
|
123023
123824
|
await withAccessGrantSessionRlsContext(routeDeps, grant, next);
|
|
123024
123825
|
});
|
|
123025
123826
|
app.all("/v1/workspaces/:workspaceId/mcp", async (c) => {
|
|
@@ -123174,7 +123975,7 @@ function createAppComposition(deps) {
|
|
|
123174
123975
|
});
|
|
123175
123976
|
app.get("/v1/workspaces/:workspaceId/tools/catalog", async (c) => {
|
|
123176
123977
|
const workspaceId = c.req.param("workspaceId");
|
|
123177
|
-
const authorization = await
|
|
123978
|
+
const authorization = await requireAccessGrantAuthorization41(
|
|
123178
123979
|
c,
|
|
123179
123980
|
routeDeps,
|
|
123180
123981
|
workspaceId,
|
|
@@ -123190,7 +123991,7 @@ function createAppComposition(deps) {
|
|
|
123190
123991
|
});
|
|
123191
123992
|
app.post("/v1/workspaces/:workspaceId/tools/calls", async (c) => {
|
|
123192
123993
|
const workspaceId = c.req.param("workspaceId");
|
|
123193
|
-
const authorization = await
|
|
123994
|
+
const authorization = await requireAccessGrantAuthorization41(
|
|
123194
123995
|
c,
|
|
123195
123996
|
routeDeps,
|
|
123196
123997
|
workspaceId,
|
|
@@ -123219,7 +124020,7 @@ function createAppComposition(deps) {
|
|
|
123219
124020
|
});
|
|
123220
124021
|
app.post("/v1/workspaces/:workspaceId/tools/approvals", async (c) => {
|
|
123221
124022
|
const workspaceId = c.req.param("workspaceId");
|
|
123222
|
-
const authorization = await
|
|
124023
|
+
const authorization = await requireAccessGrantAuthorization41(
|
|
123223
124024
|
c,
|
|
123224
124025
|
routeDeps,
|
|
123225
124026
|
workspaceId,
|
|
@@ -123248,7 +124049,7 @@ function createAppComposition(deps) {
|
|
|
123248
124049
|
});
|
|
123249
124050
|
app.get("/v1/workspaces/:workspaceId/tools/declarations", async (c) => {
|
|
123250
124051
|
const workspaceId = c.req.param("workspaceId");
|
|
123251
|
-
const authorization = await
|
|
124052
|
+
const authorization = await requireAccessGrantAuthorization41(
|
|
123252
124053
|
c,
|
|
123253
124054
|
routeDeps,
|
|
123254
124055
|
workspaceId,
|
|
@@ -123264,7 +124065,7 @@ function createAppComposition(deps) {
|
|
|
123264
124065
|
});
|
|
123265
124066
|
app.all("/v1/workspaces/:workspaceId/codemode/sdk/*", async (c) => {
|
|
123266
124067
|
const workspaceId = c.req.param("workspaceId");
|
|
123267
|
-
const grant = await
|
|
124068
|
+
const grant = await requireAccessGrant53(c, routeDeps, workspaceId);
|
|
123268
124069
|
const url = new URL(c.req.url);
|
|
123269
124070
|
const prefix2 = `/v1/workspaces/${workspaceId}/codemode/sdk`;
|
|
123270
124071
|
let forwarded;
|
|
@@ -123282,7 +124083,7 @@ function createAppComposition(deps) {
|
|
|
123282
124083
|
});
|
|
123283
124084
|
app.get("/v1/workspaces/:workspaceId/codemode/catalog", async (c) => {
|
|
123284
124085
|
const workspaceId = c.req.param("workspaceId");
|
|
123285
|
-
const grant = await
|
|
124086
|
+
const grant = await requireAccessGrant53(c, routeDeps, workspaceId);
|
|
123286
124087
|
if (!isCodemodeGrant(grant)) {
|
|
123287
124088
|
throw new HTTPException118(403, { message: "Codemode access denied" });
|
|
123288
124089
|
}
|
|
@@ -123294,7 +124095,7 @@ function createAppComposition(deps) {
|
|
|
123294
124095
|
});
|
|
123295
124096
|
app.post("/v1/workspaces/:workspaceId/codemode/calls", async (c) => {
|
|
123296
124097
|
const workspaceId = c.req.param("workspaceId");
|
|
123297
|
-
const grant = await
|
|
124098
|
+
const grant = await requireAccessGrant53(c, routeDeps, workspaceId);
|
|
123298
124099
|
if (!isCodemodeGrant(grant)) {
|
|
123299
124100
|
throw new HTTPException118(403, { message: "Codemode access denied" });
|
|
123300
124101
|
}
|
|
@@ -123314,7 +124115,7 @@ function createAppComposition(deps) {
|
|
|
123314
124115
|
});
|
|
123315
124116
|
app.get("/v1/workspaces/:workspaceId/codemode/calls/:operationId", async (c) => {
|
|
123316
124117
|
const workspaceId = c.req.param("workspaceId");
|
|
123317
|
-
const grant = await
|
|
124118
|
+
const grant = await requireAccessGrant53(c, routeDeps, workspaceId);
|
|
123318
124119
|
if (!isCodemodeGrant(grant)) {
|
|
123319
124120
|
throw new HTTPException118(403, { message: "Codemode access denied" });
|
|
123320
124121
|
}
|
|
@@ -123511,7 +124312,7 @@ async function validateCodexAccountTarget(request) {
|
|
|
123511
124312
|
}
|
|
123512
124313
|
}
|
|
123513
124314
|
async function requireMcpAccessGrantAuthorization(c, deps, workspaceId) {
|
|
123514
|
-
const authorization = await
|
|
124315
|
+
const authorization = await requireAccessGrantAuthorization41(c, deps, workspaceId);
|
|
123515
124316
|
const grant = authorization.grant;
|
|
123516
124317
|
if (hasPermission34(grant.permissions, "workspace:read")) {
|
|
123517
124318
|
return authorization;
|
|
@@ -124905,4 +125706,4 @@ export {
|
|
|
124905
125706
|
withDefaultEnabledCapabilityMcpTools,
|
|
124906
125707
|
workflowIdForSession4 as workflowIdForSession
|
|
124907
125708
|
};
|
|
124908
|
-
//# sourceMappingURL=chunk-
|
|
125709
|
+
//# sourceMappingURL=chunk-KKEXIBQG.js.map
|