@opengeni/api-router 0.26.1 → 0.30.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/app.d.ts +1 -0
- package/dist/app.js +5 -3
- package/dist/auth/canonical-human-session-admission.d.ts +27 -0
- package/dist/browser-auth-broker.d.ts +20 -0
- package/dist/browser-controller-authority.d.ts +19 -1
- package/dist/browser-network-route.d.ts +8 -0
- package/dist/{chunk-JIKNR5YL.js → chunk-2PQMSRCB.js} +15518 -6863
- package/dist/chunk-2PQMSRCB.js.map +1 -0
- package/dist/controller-data-plane.d.ts +12 -0
- package/dist/http/auth.d.ts +1 -1
- package/dist/http/sse.d.ts +18 -1
- package/dist/index.js +1 -1
- package/dist/integrations/api-integrations.d.ts +2 -2
- package/dist/integrations/fiken.d.ts +233 -0
- package/dist/integrations/google-drive.d.ts +6 -6
- package/dist/integrations/provider-oauth.d.ts +0 -2
- package/dist/integrations/slack-bot.d.ts +171 -1
- package/dist/integrations/slack-interactions.d.ts +17 -5
- package/dist/interaction-holder-heartbeat.d.ts +17 -0
- package/dist/interaction-metrics.d.ts +11 -0
- package/dist/mcp/server.d.ts +3 -2
- package/dist/model-catalog.d.ts +1 -0
- package/dist/routes/canonical-human-identities.d.ts +3 -0
- package/dist/routes/channels.d.ts +3 -0
- package/dist/routes/integration-facets.d.ts +3 -0
- package/dist/routes/interaction-resources.d.ts +5 -0
- package/dist/routes/organization-memberships.d.ts +3 -0
- package/dist/routes/sessions.d.ts +2 -1
- package/dist/routes/slack-task-policy.d.ts +3 -0
- package/dist/routes/supergrok.d.ts +3 -0
- package/dist/sandbox/channel-a.d.ts +8 -1
- package/dist/sandbox/machines.d.ts +2 -2
- package/dist/sandbox/viewer.d.ts +3 -1
- package/dist/slack-reaction-files.d.ts +27 -0
- package/dist/transcription/providers/xai-subscription.d.ts +8 -0
- package/dist/xai-realtime.d.ts +26 -0
- package/dist/xai-subscription-auth.d.ts +23 -0
- package/package.json +17 -16
- package/src/app.ts +352 -49
- package/src/auth/canonical-human-session-admission.ts +116 -0
- package/src/auth/managed-auth.ts +101 -0
- package/src/browser-auth-broker.ts +155 -0
- package/src/browser-controller-authority.ts +50 -1
- package/src/browser-network-route.ts +34 -0
- package/src/controller-data-plane.ts +35 -0
- package/src/http/auth.ts +27 -9
- package/src/http/sse.ts +229 -5
- package/src/integrations/api-integrations.ts +57 -42
- package/src/integrations/fiken.ts +1230 -0
- package/src/integrations/google-drive.ts +31 -26
- package/src/integrations/provider-oauth.ts +70 -91
- package/src/integrations/slack-bot.ts +638 -9
- package/src/integrations/slack-interactions.ts +1583 -73
- package/src/interaction-holder-heartbeat.ts +95 -0
- package/src/interaction-metrics.ts +159 -0
- package/src/mcp/documents.ts +115 -6
- package/src/mcp/server.ts +865 -242
- package/src/model-catalog.ts +19 -6
- package/src/routes/api-integrations.ts +32 -29
- package/src/routes/browser-identities.ts +24 -0
- package/src/routes/browser-sessions.ts +2122 -183
- package/src/routes/canonical-human-identities.ts +156 -0
- package/src/routes/channels.ts +90 -0
- package/src/routes/computer-sessions.ts +441 -86
- package/src/routes/connections.ts +141 -4
- package/src/routes/{integration-features.ts → integration-facets.ts} +63 -63
- package/src/routes/interaction-resources.ts +595 -0
- package/src/routes/organization-memberships.ts +53 -0
- package/src/routes/plugins.ts +2 -1
- package/src/routes/sessions.ts +419 -43
- package/src/routes/skills.ts +92 -10
- package/src/routes/slack-task-policy.ts +115 -0
- package/src/routes/supergrok.ts +717 -0
- package/src/routes/transcription-recordings.ts +9 -2
- package/src/routes/transcriptions.ts +2 -1
- package/src/routes/video-generation.ts +34 -4
- package/src/routes/workspaces.ts +22 -4
- package/src/sandbox/channel-a.ts +29 -2
- package/src/sandbox/machines.ts +5 -5
- package/src/sandbox/viewer.ts +10 -3
- package/src/slack-reaction-files.ts +181 -0
- package/src/transcription/providers/xai-subscription.ts +110 -0
- package/src/transcription/service.ts +17 -2
- package/src/xai-realtime.ts +216 -0
- package/src/xai-subscription-auth.ts +132 -0
- package/dist/chunk-JIKNR5YL.js.map +0 -1
- package/dist/routes/integration-features.d.ts +0 -3
package/src/app.ts
CHANGED
|
@@ -3,7 +3,9 @@ import {
|
|
|
3
3
|
configuredAllowedModels,
|
|
4
4
|
configuredAllowedReasoningEfforts,
|
|
5
5
|
configuredModels,
|
|
6
|
+
resolveFirstPartyMcpToolPolicy,
|
|
6
7
|
withCodexCatalogProvider,
|
|
8
|
+
withXaiSubscriptionCatalogProvider,
|
|
7
9
|
} from "@opengeni/config";
|
|
8
10
|
import {
|
|
9
11
|
ClientConfig,
|
|
@@ -13,6 +15,7 @@ import {
|
|
|
13
15
|
OPENGENI_API_CONTRACT_REVISION,
|
|
14
16
|
OPENGENI_CORRELATION_HEADER,
|
|
15
17
|
resolveWorkspaceMemoryEnabled,
|
|
18
|
+
resolveWorkspaceMemoryPromptMode,
|
|
16
19
|
VOICE_INPUT_ACCEPTED_MIME_TYPES,
|
|
17
20
|
TRANSCRIPTION_RECORDING_PROVIDER_SEGMENT_SECONDS,
|
|
18
21
|
type AccessGrant,
|
|
@@ -34,6 +37,7 @@ import {
|
|
|
34
37
|
dbSql,
|
|
35
38
|
getWorkspace,
|
|
36
39
|
rlsContextForWorkspace,
|
|
40
|
+
withSessionRlsActorContext,
|
|
37
41
|
} from "@opengeni/db";
|
|
38
42
|
import { createObservability } from "@opengeni/observability";
|
|
39
43
|
import { createObjectStorage } from "@opengeni/storage";
|
|
@@ -50,6 +54,7 @@ import {
|
|
|
50
54
|
CodexCompactionV2ProviderLockedError,
|
|
51
55
|
hasPermission,
|
|
52
56
|
requireAccessGrant,
|
|
57
|
+
requireLiveAgentAttemptAuthorization,
|
|
53
58
|
requirePermission,
|
|
54
59
|
requireSessionAuthorization,
|
|
55
60
|
SessionAuthorizationDeniedError,
|
|
@@ -67,11 +72,16 @@ import {
|
|
|
67
72
|
submitAndDispatchCodemodeCall,
|
|
68
73
|
} from "./codemode";
|
|
69
74
|
import { boundedMcpRequest, McpPayloadTooLargeError } from "@opengeni/runtime/mcp-network";
|
|
75
|
+
import {
|
|
76
|
+
BrowserControlProtocolError,
|
|
77
|
+
BrowserControlTransportError,
|
|
78
|
+
} from "@opengeni/runtime/sandbox";
|
|
70
79
|
import { requireAccessKey } from "./http/auth";
|
|
71
80
|
import { allowedCorsOrigin } from "./http/cors";
|
|
72
81
|
import { registerCapabilityRoutes } from "./routes/capabilities";
|
|
73
82
|
import { registerCatalogAssetRoutes } from "./routes/catalog-assets";
|
|
74
83
|
import { registerCodexRoutes } from "./routes/codex";
|
|
84
|
+
import { registerSuperGrokRoutes } from "./routes/supergrok";
|
|
75
85
|
import { registerConnectionRoutes } from "./routes/connections";
|
|
76
86
|
import { registerDocumentRoutes } from "./routes/documents";
|
|
77
87
|
import { registerEnrollmentRoutes } from "./routes/enrollments";
|
|
@@ -87,10 +97,12 @@ import { registerComputerSessionRoutes } from "./routes/computer-sessions";
|
|
|
87
97
|
import { registerGitHubRoutes } from "./routes/github";
|
|
88
98
|
import { registerInstallRoutes } from "./routes/install";
|
|
89
99
|
import { registerApiIntegrationRoutes } from "./routes/api-integrations";
|
|
90
|
-
import {
|
|
100
|
+
import { registerIntegrationFacetRoutes } from "./routes/integration-facets";
|
|
101
|
+
import { registerInteractionResourceRoutes } from "./routes/interaction-resources";
|
|
91
102
|
import { registerPackRoutes } from "./routes/packs";
|
|
92
103
|
import { registerPluginRoutes } from "./routes/plugins";
|
|
93
104
|
import { registerSkillRoutes } from "./routes/skills";
|
|
105
|
+
import { registerChannelRoutes } from "./routes/channels";
|
|
94
106
|
import { registerRigRoutes } from "./routes/rigs";
|
|
95
107
|
import { registerScheduledTaskRoutes } from "./routes/scheduled-tasks";
|
|
96
108
|
import { registerSessionRoutes } from "./routes/sessions";
|
|
@@ -98,6 +110,7 @@ import { registerSocialRoutes } from "./routes/social";
|
|
|
98
110
|
import { registerWorkspaceRoutes } from "./routes/workspaces";
|
|
99
111
|
import { registerWorkspaceInstructionPolicyRoutes } from "./routes/workspace-instruction-policies";
|
|
100
112
|
import { registerCompanyProfileRoutes } from "./routes/company-profile";
|
|
113
|
+
import { registerSlackTaskPolicyRoutes } from "./routes/slack-task-policy";
|
|
101
114
|
import { registerWorkspaceStateRoutes } from "./routes/workspace-state";
|
|
102
115
|
import { registerWorkspaceArtifactRoutes } from "./routes/workspace-artifacts";
|
|
103
116
|
import { registerPreferenceRegistryRoutes } from "./routes/preference-registry";
|
|
@@ -105,6 +118,8 @@ import { registerInsightsRoutes } from "./routes/insights";
|
|
|
105
118
|
import { registerTranscriptionRoutes } from "./routes/transcriptions";
|
|
106
119
|
import { registerEditableArtifactRoutes } from "./routes/editable-artifacts";
|
|
107
120
|
import { registerVideoGenerationRoutes } from "./routes/video-generation";
|
|
121
|
+
import { registerCanonicalHumanIdentityRoutes } from "./routes/canonical-human-identities";
|
|
122
|
+
import { registerOrganizationMembershipRoutes } from "./routes/organization-memberships";
|
|
108
123
|
import { projectClientModel } from "./model-catalog";
|
|
109
124
|
import { createTranscriptionService } from "./transcription/service";
|
|
110
125
|
import { createFfmpegTranscriptionSegmenter } from "./transcription/segmenter";
|
|
@@ -133,7 +148,7 @@ export {
|
|
|
133
148
|
export { workflowIdForSession } from "@opengeni/core";
|
|
134
149
|
export { replaySessionEvents, sseSessionStream, sseWorkspaceControlStream } from "./http/sse";
|
|
135
150
|
|
|
136
|
-
export const API_MAX_REQUEST_BODY_BYTES =
|
|
151
|
+
export const API_MAX_REQUEST_BODY_BYTES = 32 * 1024 * 1024;
|
|
137
152
|
|
|
138
153
|
/** Effective Hono bodyLimit — API JSON ceiling or voice multipart + multipart overhead. */
|
|
139
154
|
export function apiRequestBodyLimitBytes(settings: {
|
|
@@ -462,9 +477,12 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
462
477
|
|
|
463
478
|
app.get("/v1/config/client", async (c) => {
|
|
464
479
|
c.header("cache-control", "no-store");
|
|
465
|
-
const
|
|
480
|
+
const codexCatalogSettings = deps.settings.codexSubscriptionEnabled
|
|
466
481
|
? withCodexCatalogProvider(deps.settings)
|
|
467
482
|
: deps.settings;
|
|
483
|
+
const catalogSettings = deps.settings.supergrokSubscriptionEnabled
|
|
484
|
+
? withXaiSubscriptionCatalogProvider(codexCatalogSettings)
|
|
485
|
+
: codexCatalogSettings;
|
|
468
486
|
return c.json(
|
|
469
487
|
ClientConfig.parse({
|
|
470
488
|
deploymentRevision: deps.settings.deploymentRevision,
|
|
@@ -483,6 +501,7 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
483
501
|
id: server.id,
|
|
484
502
|
name: server.name ?? server.id,
|
|
485
503
|
})),
|
|
504
|
+
firstPartyMcpTools: resolveFirstPartyMcpToolPolicy(deps.settings),
|
|
486
505
|
fileUploads: {
|
|
487
506
|
enabled: objectStorage !== null,
|
|
488
507
|
maxSizeBytes: objectStorage?.maxSinglePutSizeBytes ?? 5_000_000_000,
|
|
@@ -522,6 +541,22 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
522
541
|
);
|
|
523
542
|
});
|
|
524
543
|
|
|
544
|
+
app.use("/v1/workspaces/:workspaceId/*", async (c, next) => {
|
|
545
|
+
const workspaceId = c.req.param("workspaceId");
|
|
546
|
+
if (workspaceRequestRequiresCodexAccountPrevalidation(c.req.raw)) {
|
|
547
|
+
const grant = await requireAccessGrant(c, routeDeps, workspaceId);
|
|
548
|
+
await validateCodexAccountTarget(c.req.raw);
|
|
549
|
+
await withAccessGrantSessionRlsContext(routeDeps, grant, next);
|
|
550
|
+
return;
|
|
551
|
+
}
|
|
552
|
+
if (workspaceActorContextExempt(c.req.method, new URL(c.req.url).pathname)) {
|
|
553
|
+
await next();
|
|
554
|
+
return;
|
|
555
|
+
}
|
|
556
|
+
const grant = await requireAccessGrant(c, routeDeps, workspaceId);
|
|
557
|
+
await withAccessGrantSessionRlsContext(routeDeps, grant, next);
|
|
558
|
+
});
|
|
559
|
+
|
|
525
560
|
app.all("/v1/workspaces/:workspaceId/mcp", async (c) => {
|
|
526
561
|
const workspaceId = c.req.param("workspaceId");
|
|
527
562
|
let boundedRequest: Request;
|
|
@@ -536,37 +571,41 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
536
571
|
throw error;
|
|
537
572
|
}
|
|
538
573
|
const grant = await requireMcpAccessGrant(c, routeDeps, workspaceId);
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
} catch (error) {
|
|
548
|
-
if (error instanceof SessionAuthorizationDeniedError) {
|
|
549
|
-
throw new HTTPException(404, { message: "session not found" });
|
|
550
|
-
}
|
|
551
|
-
if (error instanceof SessionAuthorizationUnavailableError) {
|
|
552
|
-
throw new HTTPException(503, {
|
|
553
|
-
message: "session authorization is unavailable",
|
|
574
|
+
return await withAccessGrantSessionRlsContext(routeDeps, grant, async () => {
|
|
575
|
+
const boundSessionId = grant.metadata?.sessionId;
|
|
576
|
+
if (typeof boundSessionId === "string") {
|
|
577
|
+
try {
|
|
578
|
+
await requireSessionAuthorization(routeDeps, grant, {
|
|
579
|
+
sessionId: boundSessionId,
|
|
580
|
+
operation: "session.first_party_mcp.call",
|
|
581
|
+
surface: "first_party_mcp",
|
|
554
582
|
});
|
|
583
|
+
} catch (error) {
|
|
584
|
+
if (error instanceof SessionAuthorizationDeniedError) {
|
|
585
|
+
throw new HTTPException(404, { message: "session not found" });
|
|
586
|
+
}
|
|
587
|
+
if (error instanceof SessionAuthorizationUnavailableError) {
|
|
588
|
+
throw new HTTPException(503, {
|
|
589
|
+
message: "session authorization is unavailable",
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
throw error;
|
|
555
593
|
}
|
|
556
|
-
throw error;
|
|
557
594
|
}
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
595
|
+
const workspace = await getWorkspace(routeDeps.db, workspaceId);
|
|
596
|
+
const workspaceMemoryEnabled = resolveWorkspaceMemoryEnabled(workspace?.settings);
|
|
597
|
+
const workspaceMemoryPromptMode = resolveWorkspaceMemoryPromptMode(workspace?.settings);
|
|
598
|
+
const transport = new WebStandardStreamableHTTPServerTransport({
|
|
599
|
+
enableJsonResponse: true,
|
|
600
|
+
});
|
|
601
|
+
const mcp = buildOpenGeniMcpServer(routeDeps, grant, {
|
|
602
|
+
requestOrigin: new URL(c.req.url).origin,
|
|
603
|
+
workspaceMemoryEnabled,
|
|
604
|
+
workspaceMemoryPromptMode,
|
|
605
|
+
});
|
|
606
|
+
await mcp.connect(transport);
|
|
607
|
+
return await transport.handleRequest(boundedRequest);
|
|
567
608
|
});
|
|
568
|
-
await mcp.connect(transport);
|
|
569
|
-
return await transport.handleRequest(boundedRequest);
|
|
570
609
|
});
|
|
571
610
|
|
|
572
611
|
app.get("/v1/workspaces/:workspaceId/codemode/catalog", async (c) => {
|
|
@@ -631,10 +670,12 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
631
670
|
registerDocumentRoutes(app, routeDeps);
|
|
632
671
|
registerGitHubRoutes(app, routeDeps);
|
|
633
672
|
registerInstallRoutes(app, routeDeps);
|
|
673
|
+
registerInteractionResourceRoutes(app, routeDeps);
|
|
634
674
|
registerWorkspaceRoutes(app, routeDeps);
|
|
635
675
|
registerInsightsRoutes(app, routeDeps);
|
|
636
676
|
registerWorkspaceInstructionPolicyRoutes(app, routeDeps);
|
|
637
677
|
registerCompanyProfileRoutes(app, routeDeps);
|
|
678
|
+
registerSlackTaskPolicyRoutes(app, routeDeps);
|
|
638
679
|
registerWorkspaceStateRoutes(app, routeDeps);
|
|
639
680
|
registerMemorySlackPublicationRoutes(app, routeDeps);
|
|
640
681
|
registerWorkspaceArtifactRoutes(app, routeDeps);
|
|
@@ -643,11 +684,12 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
643
684
|
registerConnectionRoutes(app, routeDeps);
|
|
644
685
|
registerCapabilityRoutes(app, routeDeps);
|
|
645
686
|
registerApiIntegrationRoutes(app, routeDeps);
|
|
646
|
-
|
|
687
|
+
registerIntegrationFacetRoutes(app, routeDeps);
|
|
647
688
|
registerCatalogAssetRoutes(app, routeDeps);
|
|
648
689
|
registerEnrollmentRoutes(app, routeDeps);
|
|
649
690
|
registerMachineRoutes(app, routeDeps);
|
|
650
691
|
registerEnvironmentRoutes(app, routeDeps);
|
|
692
|
+
registerChannelRoutes(app, routeDeps);
|
|
651
693
|
registerRigRoutes(app, routeDeps);
|
|
652
694
|
registerPackRoutes(app, routeDeps);
|
|
653
695
|
registerPluginRoutes(app, routeDeps);
|
|
@@ -655,9 +697,12 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
655
697
|
registerSessionRoutes(app, routeDeps);
|
|
656
698
|
registerScheduledTaskRoutes(app, routeDeps);
|
|
657
699
|
registerCodexRoutes(app, routeDeps);
|
|
700
|
+
registerSuperGrokRoutes(app, routeDeps);
|
|
658
701
|
registerTranscriptionRoutes(app, routeDeps);
|
|
659
702
|
registerEditableArtifactRoutes(app, routeDeps);
|
|
660
703
|
registerVideoGenerationRoutes(app, routeDeps);
|
|
704
|
+
registerCanonicalHumanIdentityRoutes(app, routeDeps);
|
|
705
|
+
registerOrganizationMembershipRoutes(app, routeDeps);
|
|
661
706
|
registerSlackInteractionRoutes(app, routeDeps);
|
|
662
707
|
|
|
663
708
|
app.notFound((c) => {
|
|
@@ -699,6 +744,7 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
699
744
|
? (boundedPublicMessage(apiError.message) ?? "Request failed.")
|
|
700
745
|
: publicErrorMessage(error, status),
|
|
701
746
|
retryable: apiError?.retryable ?? retryableHttpStatus(status),
|
|
747
|
+
...(mutationOutcomeUnknown(error, c.req.method) ? { outcomeUnknown: true } : {}),
|
|
702
748
|
requestId,
|
|
703
749
|
...(apiError?.details ? { details: apiError.details } : {}),
|
|
704
750
|
},
|
|
@@ -709,6 +755,14 @@ export function createAppComposition(deps: AppDependencies): {
|
|
|
709
755
|
return { app, routeDeps };
|
|
710
756
|
}
|
|
711
757
|
|
|
758
|
+
function mutationOutcomeUnknown(error: unknown, method: string): boolean {
|
|
759
|
+
if (method === "GET" || method === "HEAD" || method === "OPTIONS") return false;
|
|
760
|
+
const cause = error instanceof HTTPException ? error.cause : error;
|
|
761
|
+
return (
|
|
762
|
+
cause instanceof BrowserControlTransportError || cause instanceof BrowserControlProtocolError
|
|
763
|
+
);
|
|
764
|
+
}
|
|
765
|
+
|
|
712
766
|
export function appendVary(current: string | null, value: string): string {
|
|
713
767
|
const values = (current ?? "")
|
|
714
768
|
.split(",")
|
|
@@ -720,6 +774,74 @@ export function appendVary(current: string | null, value: string): string {
|
|
|
720
774
|
return values.join(", ");
|
|
721
775
|
}
|
|
722
776
|
|
|
777
|
+
const publicWorkspaceBrowserRoutePatterns = [
|
|
778
|
+
/^\/v1\/workspaces\/[^/]+\/github\/connect$/,
|
|
779
|
+
/^\/v1\/workspaces\/[^/]+\/github\/installations\/[^/]+\/configure$/,
|
|
780
|
+
/^\/v1\/workspaces\/[^/]+\/github\/installations\/select$/,
|
|
781
|
+
] as const;
|
|
782
|
+
|
|
783
|
+
export function workspaceActorContextExempt(method: string, pathname: string): boolean {
|
|
784
|
+
if (/^\/v1\/workspaces\/[^/]+\/mcp$/.test(pathname)) return true;
|
|
785
|
+
if (
|
|
786
|
+
method === "GET" &&
|
|
787
|
+
publicWorkspaceBrowserRoutePatterns.some((route) => route.test(pathname))
|
|
788
|
+
) {
|
|
789
|
+
return true;
|
|
790
|
+
}
|
|
791
|
+
return method === "POST" && /^\/v1\/workspaces\/[^/]+\/github\/installations$/.test(pathname);
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
function workspaceRequestRequiresCodexAccountPrevalidation(request: Request): boolean {
|
|
795
|
+
return (
|
|
796
|
+
request.method === "POST" &&
|
|
797
|
+
/^\/v1\/workspaces\/[^/]+\/sessions\/[^/]+\/codex-account$/.test(new URL(request.url).pathname)
|
|
798
|
+
);
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
async function validateCodexAccountTarget(request: Request): Promise<void> {
|
|
802
|
+
const body = (await request
|
|
803
|
+
.clone()
|
|
804
|
+
.json()
|
|
805
|
+
.catch(() => null)) as { target?: unknown } | null;
|
|
806
|
+
if (typeof body?.target !== "string" || body.target.length === 0) {
|
|
807
|
+
throw new HTTPException(400, {
|
|
808
|
+
message: 'target is required ("auto" or an account id)',
|
|
809
|
+
});
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
async function withAccessGrantSessionRlsContext<T>(
|
|
814
|
+
deps: ApiRouteDeps,
|
|
815
|
+
grant: AccessGrant,
|
|
816
|
+
fn: () => Promise<T>,
|
|
817
|
+
): Promise<T> {
|
|
818
|
+
if (grant.principalKind !== "agent_attempt") {
|
|
819
|
+
return await withSessionRlsActorContext({ subjectId: grant.subjectId }, fn);
|
|
820
|
+
}
|
|
821
|
+
const callerSessionId = grant.metadata?.sessionId;
|
|
822
|
+
if (typeof callerSessionId !== "string") {
|
|
823
|
+
throw new HTTPException(403, { message: "agent attempt authority is invalid" });
|
|
824
|
+
}
|
|
825
|
+
try {
|
|
826
|
+
const actor = await requireLiveAgentAttemptAuthorization(deps.db, grant, callerSessionId);
|
|
827
|
+
return await withSessionRlsActorContext(
|
|
828
|
+
{
|
|
829
|
+
subjectId: actor.subjectId,
|
|
830
|
+
initiatingHumanSubjectId: actor.initiatingHumanSubjectId,
|
|
831
|
+
},
|
|
832
|
+
fn,
|
|
833
|
+
);
|
|
834
|
+
} catch (error) {
|
|
835
|
+
if (error instanceof SessionAuthorizationDeniedError) {
|
|
836
|
+
throw new HTTPException(403, {
|
|
837
|
+
message: "agent attempt authority is invalid",
|
|
838
|
+
cause: error,
|
|
839
|
+
});
|
|
840
|
+
}
|
|
841
|
+
throw error;
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
|
|
723
845
|
async function requireMcpAccessGrant(
|
|
724
846
|
c: Parameters<typeof requireAccessGrant>[0],
|
|
725
847
|
deps: ApiRouteDeps,
|
|
@@ -989,7 +1111,10 @@ async function withTimeout<T>(promise: Promise<T>, timeoutMs: number): Promise<T
|
|
|
989
1111
|
}
|
|
990
1112
|
}
|
|
991
1113
|
|
|
992
|
-
const routeLabelPatterns: Array<{
|
|
1114
|
+
const routeLabelPatterns: Array<{
|
|
1115
|
+
pattern: RegExp;
|
|
1116
|
+
label: string | ((match: RegExpMatchArray) => string);
|
|
1117
|
+
}> = [
|
|
993
1118
|
{ pattern: /^\/healthz$/, label: "/healthz" },
|
|
994
1119
|
{ pattern: /^\/readyz$/, label: "/readyz" },
|
|
995
1120
|
{ pattern: /^\/traffic-readyz$/, label: "/traffic-readyz" },
|
|
@@ -1005,6 +1130,22 @@ const routeLabelPatterns: Array<{ pattern: RegExp; label: string }> = [
|
|
|
1005
1130
|
pattern: /^\/v1\/workspaces\/[^/]+\/codex\/status$/,
|
|
1006
1131
|
label: "/v1/workspaces/:workspaceId/codex/status",
|
|
1007
1132
|
},
|
|
1133
|
+
{
|
|
1134
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/supergrok\/connect\/(start|poll)$/,
|
|
1135
|
+
label: (match) => `/v1/workspaces/:workspaceId/supergrok/connect/${match[1]}`,
|
|
1136
|
+
},
|
|
1137
|
+
{
|
|
1138
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/supergrok\/(status|accounts|settings)$/,
|
|
1139
|
+
label: (match) => `/v1/workspaces/:workspaceId/supergrok/${match[1]}`,
|
|
1140
|
+
},
|
|
1141
|
+
{
|
|
1142
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/supergrok\/accounts\/[^/]+\/(activate|allocator)$/,
|
|
1143
|
+
label: (match) => `/v1/workspaces/:workspaceId/supergrok/accounts/:accountId/${match[1]}`,
|
|
1144
|
+
},
|
|
1145
|
+
{
|
|
1146
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/supergrok\/accounts\/[^/]+$/,
|
|
1147
|
+
label: "/v1/workspaces/:workspaceId/supergrok/accounts/:accountId",
|
|
1148
|
+
},
|
|
1008
1149
|
{
|
|
1009
1150
|
pattern: /^\/v1\/workspaces\/[^/]+\/codex\/usage$/,
|
|
1010
1151
|
label: "/v1/workspaces/:workspaceId/codex/usage",
|
|
@@ -1051,6 +1192,154 @@ const routeLabelPatterns: Array<{ pattern: RegExp; label: string }> = [
|
|
|
1051
1192
|
pattern: /^\/v1\/workspaces\/[^/]+\/control-events\/stream$/,
|
|
1052
1193
|
label: "/v1/workspaces/:workspaceId/control-events/stream",
|
|
1053
1194
|
},
|
|
1195
|
+
{
|
|
1196
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/live-events\/stream$/,
|
|
1197
|
+
label: "/v1/workspaces/:workspaceId/live-events/stream",
|
|
1198
|
+
},
|
|
1199
|
+
{
|
|
1200
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/interaction-events\/stream$/,
|
|
1201
|
+
label: "/v1/workspaces/:workspaceId/interaction-events/stream",
|
|
1202
|
+
},
|
|
1203
|
+
{
|
|
1204
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/attached-browsers\/[^/]+$/,
|
|
1205
|
+
label: "/v1/workspaces/:workspaceId/attached-browsers/:deviceId",
|
|
1206
|
+
},
|
|
1207
|
+
{
|
|
1208
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/attached-browsers$/,
|
|
1209
|
+
label: "/v1/workspaces/:workspaceId/attached-browsers",
|
|
1210
|
+
},
|
|
1211
|
+
{
|
|
1212
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/browser-identities\/[^/]+\/revisions$/,
|
|
1213
|
+
label: "/v1/workspaces/:workspaceId/browser-identities/:identityId/revisions",
|
|
1214
|
+
},
|
|
1215
|
+
{
|
|
1216
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/browser-identities\/[^/]+$/,
|
|
1217
|
+
label: "/v1/workspaces/:workspaceId/browser-identities/:identityId",
|
|
1218
|
+
},
|
|
1219
|
+
{
|
|
1220
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/browser-identities$/,
|
|
1221
|
+
label: "/v1/workspaces/:workspaceId/browser-identities",
|
|
1222
|
+
},
|
|
1223
|
+
{
|
|
1224
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/browser-sessions\/[^/]+\/downloads\/[^/]+\/save$/,
|
|
1225
|
+
label:
|
|
1226
|
+
"/v1/workspaces/:workspaceId/browser-sessions/:browserSessionId/downloads/:downloadId/save",
|
|
1227
|
+
},
|
|
1228
|
+
{
|
|
1229
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/browser-sessions\/[^/]+\/downloads\/[^/]+$/,
|
|
1230
|
+
label: "/v1/workspaces/:workspaceId/browser-sessions/:browserSessionId/downloads/:downloadId",
|
|
1231
|
+
},
|
|
1232
|
+
{
|
|
1233
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/browser-sessions\/[^/]+\/downloads$/,
|
|
1234
|
+
label: "/v1/workspaces/:workspaceId/browser-sessions/:browserSessionId/downloads",
|
|
1235
|
+
},
|
|
1236
|
+
{
|
|
1237
|
+
pattern:
|
|
1238
|
+
/^\/v1\/workspaces\/[^/]+\/browser-sessions\/[^/]+\/auth-runs\/[^/]+\/external-auth\/interactive$/,
|
|
1239
|
+
label:
|
|
1240
|
+
"/v1/workspaces/:workspaceId/browser-sessions/:browserSessionId/auth-runs/:authRunId/external-auth/interactive",
|
|
1241
|
+
},
|
|
1242
|
+
{
|
|
1243
|
+
pattern:
|
|
1244
|
+
/^\/v1\/workspaces\/[^/]+\/browser-sessions\/[^/]+\/auth-runs\/[^/]+\/(external-auth|protected-fill|report|verify)$/,
|
|
1245
|
+
label: (match) =>
|
|
1246
|
+
`/v1/workspaces/:workspaceId/browser-sessions/:browserSessionId/auth-runs/:authRunId/${match[1]}`,
|
|
1247
|
+
},
|
|
1248
|
+
{
|
|
1249
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/browser-sessions\/[^/]+\/auth-runs\/[^/]+$/,
|
|
1250
|
+
label: "/v1/workspaces/:workspaceId/browser-sessions/:browserSessionId/auth-runs/:authRunId",
|
|
1251
|
+
},
|
|
1252
|
+
{
|
|
1253
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/browser-sessions\/[^/]+\/auth-runs$/,
|
|
1254
|
+
label: "/v1/workspaces/:workspaceId/browser-sessions/:browserSessionId/auth-runs",
|
|
1255
|
+
},
|
|
1256
|
+
{
|
|
1257
|
+
pattern:
|
|
1258
|
+
/^\/v1\/workspaces\/[^/]+\/browser-sessions\/[^/]+\/targets\/[^/]+\/(diagnostics|observation|select)$/,
|
|
1259
|
+
label: (match) =>
|
|
1260
|
+
`/v1/workspaces/:workspaceId/browser-sessions/:browserSessionId/targets/:targetId/${match[1]}`,
|
|
1261
|
+
},
|
|
1262
|
+
{
|
|
1263
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/browser-sessions\/[^/]+\/targets\/[^/]+$/,
|
|
1264
|
+
label: "/v1/workspaces/:workspaceId/browser-sessions/:browserSessionId/targets/:targetId",
|
|
1265
|
+
},
|
|
1266
|
+
{
|
|
1267
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/browser-sessions\/[^/]+\/operations\/[^/]+$/,
|
|
1268
|
+
label: "/v1/workspaces/:workspaceId/browser-sessions/:browserSessionId/operations/:operationId",
|
|
1269
|
+
},
|
|
1270
|
+
{
|
|
1271
|
+
pattern:
|
|
1272
|
+
/^\/v1\/workspaces\/[^/]+\/browser-sessions\/[^/]+\/(actions|attachments|clipboard|end|heartbeat|resume|revisions|suspend|targets)$/,
|
|
1273
|
+
label: (match) => `/v1/workspaces/:workspaceId/browser-sessions/:browserSessionId/${match[1]}`,
|
|
1274
|
+
},
|
|
1275
|
+
{
|
|
1276
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/browser-sessions\/[^/]+$/,
|
|
1277
|
+
label: "/v1/workspaces/:workspaceId/browser-sessions/:browserSessionId",
|
|
1278
|
+
},
|
|
1279
|
+
{
|
|
1280
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/browser-sessions$/,
|
|
1281
|
+
label: "/v1/workspaces/:workspaceId/browser-sessions",
|
|
1282
|
+
},
|
|
1283
|
+
{
|
|
1284
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/computer-sessions\/[^/]+\/targets\/[^/]+\/observation$/,
|
|
1285
|
+
label:
|
|
1286
|
+
"/v1/workspaces/:workspaceId/computer-sessions/:computerSessionId/targets/:targetId/observation",
|
|
1287
|
+
},
|
|
1288
|
+
{
|
|
1289
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/computer-sessions\/[^/]+\/operations\/[^/]+$/,
|
|
1290
|
+
label:
|
|
1291
|
+
"/v1/workspaces/:workspaceId/computer-sessions/:computerSessionId/operations/:operationId",
|
|
1292
|
+
},
|
|
1293
|
+
{
|
|
1294
|
+
pattern:
|
|
1295
|
+
/^\/v1\/workspaces\/[^/]+\/computer-sessions\/[^/]+\/(actions|attachments|clipboard|end|heartbeat|targets)$/,
|
|
1296
|
+
label: (match) =>
|
|
1297
|
+
`/v1/workspaces/:workspaceId/computer-sessions/:computerSessionId/${match[1]}`,
|
|
1298
|
+
},
|
|
1299
|
+
{
|
|
1300
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/computer-sessions\/[^/]+$/,
|
|
1301
|
+
label: "/v1/workspaces/:workspaceId/computer-sessions/:computerSessionId",
|
|
1302
|
+
},
|
|
1303
|
+
{
|
|
1304
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/computer-sessions$/,
|
|
1305
|
+
label: "/v1/workspaces/:workspaceId/computer-sessions",
|
|
1306
|
+
},
|
|
1307
|
+
{
|
|
1308
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/network-routes\/[^/]+$/,
|
|
1309
|
+
label: "/v1/workspaces/:workspaceId/network-routes/:networkRouteId",
|
|
1310
|
+
},
|
|
1311
|
+
{
|
|
1312
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/network-routes$/,
|
|
1313
|
+
label: "/v1/workspaces/:workspaceId/network-routes",
|
|
1314
|
+
},
|
|
1315
|
+
{
|
|
1316
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/site-auth-connections\/[^/]+$/,
|
|
1317
|
+
label: "/v1/workspaces/:workspaceId/site-auth-connections/:siteAuthConnectionId",
|
|
1318
|
+
},
|
|
1319
|
+
{
|
|
1320
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/site-auth-connections$/,
|
|
1321
|
+
label: "/v1/workspaces/:workspaceId/site-auth-connections",
|
|
1322
|
+
},
|
|
1323
|
+
{
|
|
1324
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/auth-runs\/[^/]+$/,
|
|
1325
|
+
label: "/v1/workspaces/:workspaceId/auth-runs/:authRunId",
|
|
1326
|
+
},
|
|
1327
|
+
{
|
|
1328
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/auth-runs$/,
|
|
1329
|
+
label: "/v1/workspaces/:workspaceId/auth-runs",
|
|
1330
|
+
},
|
|
1331
|
+
{
|
|
1332
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/interaction-interventions\/[^/]+\/resolve$/,
|
|
1333
|
+
label: "/v1/workspaces/:workspaceId/interaction-interventions/:interventionId/resolve",
|
|
1334
|
+
},
|
|
1335
|
+
{
|
|
1336
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/interaction-interventions\/[^/]+$/,
|
|
1337
|
+
label: "/v1/workspaces/:workspaceId/interaction-interventions/:interventionId",
|
|
1338
|
+
},
|
|
1339
|
+
{
|
|
1340
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/interaction-interventions$/,
|
|
1341
|
+
label: "/v1/workspaces/:workspaceId/interaction-interventions",
|
|
1342
|
+
},
|
|
1054
1343
|
{
|
|
1055
1344
|
pattern: /^\/v1\/workspaces\/[^/]+\/control-events$/,
|
|
1056
1345
|
label: "/v1/workspaces/:workspaceId/control-events",
|
|
@@ -1281,8 +1570,8 @@ const routeLabelPatterns: Array<{ pattern: RegExp; label: string }> = [
|
|
|
1281
1570
|
label: "/v1/workspaces/:workspaceId/integrations/install",
|
|
1282
1571
|
},
|
|
1283
1572
|
{
|
|
1284
|
-
pattern: /^\/v1\/workspaces\/[^/]+\/integrations\/
|
|
1285
|
-
label: "/v1/workspaces/:workspaceId/integrations/
|
|
1573
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/integrations\/definitions$/,
|
|
1574
|
+
label: "/v1/workspaces/:workspaceId/integrations/definitions",
|
|
1286
1575
|
},
|
|
1287
1576
|
{
|
|
1288
1577
|
pattern: /^\/v1\/workspaces\/[^/]+\/integrations\/[^/]+\/instances\/[^/]+\/uninstall-preview$/,
|
|
@@ -1291,36 +1580,36 @@ const routeLabelPatterns: Array<{ pattern: RegExp; label: string }> = [
|
|
|
1291
1580
|
},
|
|
1292
1581
|
{
|
|
1293
1582
|
pattern:
|
|
1294
|
-
/^\/v1\/workspaces\/[^/]+\/integrations\/[^/]+\/instances\/[^/]+\/
|
|
1583
|
+
/^\/v1\/workspaces\/[^/]+\/integrations\/[^/]+\/instances\/[^/]+\/facets\/[^/]+\/browse$/,
|
|
1295
1584
|
label:
|
|
1296
|
-
"/v1/workspaces/:workspaceId/integrations/:capabilityId/instances/:instanceKey/
|
|
1585
|
+
"/v1/workspaces/:workspaceId/integrations/:capabilityId/instances/:instanceKey/facets/:facetKey/browse",
|
|
1297
1586
|
},
|
|
1298
1587
|
{
|
|
1299
1588
|
pattern:
|
|
1300
|
-
/^\/v1\/workspaces\/[^/]+\/integrations\/[^/]+\/instances\/[^/]+\/
|
|
1589
|
+
/^\/v1\/workspaces\/[^/]+\/integrations\/[^/]+\/instances\/[^/]+\/facets\/[^/]+\/source$/,
|
|
1301
1590
|
label:
|
|
1302
|
-
"/v1/workspaces/:workspaceId/integrations/:capabilityId/instances/:instanceKey/
|
|
1591
|
+
"/v1/workspaces/:workspaceId/integrations/:capabilityId/instances/:instanceKey/facets/:facetKey/source",
|
|
1303
1592
|
},
|
|
1304
1593
|
{
|
|
1305
1594
|
pattern:
|
|
1306
|
-
/^\/v1\/workspaces\/[^/]+\/integrations\/[^/]+\/instances\/[^/]+\/
|
|
1595
|
+
/^\/v1\/workspaces\/[^/]+\/integrations\/[^/]+\/instances\/[^/]+\/facets\/[^/]+\/pause$/,
|
|
1307
1596
|
label:
|
|
1308
|
-
"/v1/workspaces/:workspaceId/integrations/:capabilityId/instances/:instanceKey/
|
|
1597
|
+
"/v1/workspaces/:workspaceId/integrations/:capabilityId/instances/:instanceKey/facets/:facetKey/pause",
|
|
1309
1598
|
},
|
|
1310
1599
|
{
|
|
1311
1600
|
pattern:
|
|
1312
|
-
/^\/v1\/workspaces\/[^/]+\/integrations\/[^/]+\/instances\/[^/]+\/
|
|
1601
|
+
/^\/v1\/workspaces\/[^/]+\/integrations\/[^/]+\/instances\/[^/]+\/facets\/[^/]+\/resume$/,
|
|
1313
1602
|
label:
|
|
1314
|
-
"/v1/workspaces/:workspaceId/integrations/:capabilityId/instances/:instanceKey/
|
|
1603
|
+
"/v1/workspaces/:workspaceId/integrations/:capabilityId/instances/:instanceKey/facets/:facetKey/resume",
|
|
1315
1604
|
},
|
|
1316
1605
|
{
|
|
1317
|
-
pattern: /^\/v1\/workspaces\/[^/]+\/integrations\/[^/]+\/instances\/[^/]+\/
|
|
1606
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/integrations\/[^/]+\/instances\/[^/]+\/facets\/[^/]+$/,
|
|
1318
1607
|
label:
|
|
1319
|
-
"/v1/workspaces/:workspaceId/integrations/:capabilityId/instances/:instanceKey/
|
|
1608
|
+
"/v1/workspaces/:workspaceId/integrations/:capabilityId/instances/:instanceKey/facets/:facetKey",
|
|
1320
1609
|
},
|
|
1321
1610
|
{
|
|
1322
|
-
pattern: /^\/v1\/workspaces\/[^/]+\/integrations\/[^/]+\/instances\/[^/]+\/
|
|
1323
|
-
label: "/v1/workspaces/:workspaceId/integrations/:capabilityId/instances/:instanceKey/
|
|
1611
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/integrations\/[^/]+\/instances\/[^/]+\/facets$/,
|
|
1612
|
+
label: "/v1/workspaces/:workspaceId/integrations/:capabilityId/instances/:instanceKey/facets",
|
|
1324
1613
|
},
|
|
1325
1614
|
{
|
|
1326
1615
|
pattern: /^\/v1\/workspaces\/[^/]+\/integrations\/[^/]+\/instances\/[^/]+$/,
|
|
@@ -1422,6 +1711,18 @@ const routeLabelPatterns: Array<{ pattern: RegExp; label: string }> = [
|
|
|
1422
1711
|
pattern: /^\/v1\/workspaces\/[^/]+\/connections\/slack-bot\/install$/,
|
|
1423
1712
|
label: "/v1/workspaces/:workspaceId/connections/slack-bot/install",
|
|
1424
1713
|
},
|
|
1714
|
+
{
|
|
1715
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/connections\/fiken\/install$/,
|
|
1716
|
+
label: "/v1/workspaces/:workspaceId/connections/fiken/install",
|
|
1717
|
+
},
|
|
1718
|
+
{
|
|
1719
|
+
pattern: /^\/v1\/workspaces\/[^/]+\/connections\/fiken\/oauth\/start$/,
|
|
1720
|
+
label: "/v1/workspaces/:workspaceId/connections/fiken/oauth/start",
|
|
1721
|
+
},
|
|
1722
|
+
{
|
|
1723
|
+
pattern: /^\/v1\/integrations\/fiken\/callback$/,
|
|
1724
|
+
label: "/v1/integrations/fiken/callback",
|
|
1725
|
+
},
|
|
1425
1726
|
{
|
|
1426
1727
|
pattern: /^\/v1\/workspaces\/[^/]+\/connections\/[^/]+$/,
|
|
1427
1728
|
label: "/v1/workspaces/:workspaceId/connections/:connectionId",
|
|
@@ -1495,9 +1796,11 @@ const routeLabelPatterns: Array<{ pattern: RegExp; label: string }> = [
|
|
|
1495
1796
|
];
|
|
1496
1797
|
|
|
1497
1798
|
export function routeLabel(pathname: string): string {
|
|
1498
|
-
const
|
|
1499
|
-
|
|
1500
|
-
|
|
1799
|
+
for (const candidate of routeLabelPatterns) {
|
|
1800
|
+
const match = pathname.match(candidate.pattern);
|
|
1801
|
+
if (match) {
|
|
1802
|
+
return typeof candidate.label === "string" ? candidate.label : candidate.label(match);
|
|
1803
|
+
}
|
|
1501
1804
|
}
|
|
1502
1805
|
return pathname.startsWith("/v1/") ? "/v1/unknown" : "/unknown";
|
|
1503
1806
|
}
|