@opengeni/api-router 0.26.1 → 0.30.1
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/http/sse.ts
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
WorkspaceInteractionRevisionEvent,
|
|
3
|
+
type SessionEvent,
|
|
4
|
+
type WorkspaceControlEvent,
|
|
5
|
+
} from "@opengeni/contracts";
|
|
6
|
+
import {
|
|
7
|
+
getWorkspaceInteractionRevisionState,
|
|
8
|
+
listSessionEvents,
|
|
9
|
+
listWorkspaceControlEvents,
|
|
10
|
+
type Database,
|
|
11
|
+
} from "@opengeni/db";
|
|
3
12
|
import {
|
|
4
13
|
formatSessionEventSse,
|
|
5
14
|
formatWorkspaceControlEventSse,
|
|
@@ -13,9 +22,11 @@ const WORKSPACE_CONTROL_REPLAY_PAGE_SIZE = 100;
|
|
|
13
22
|
export const SSE_QUEUED_FRAME_MAX_COUNT = 1;
|
|
14
23
|
export const SSE_WRITE_STALL_TIMEOUT_MS = 30_000;
|
|
15
24
|
export const SSE_HEARTBEAT_INTERVAL_MS = 15_000;
|
|
16
|
-
|
|
25
|
+
type SseStreamKind = "session" | "workspace_control" | "workspace_interaction";
|
|
26
|
+
const activeSseStreams: Record<SseStreamKind, number> = {
|
|
17
27
|
session: 0,
|
|
18
28
|
workspace_control: 0,
|
|
29
|
+
workspace_interaction: 0,
|
|
19
30
|
};
|
|
20
31
|
|
|
21
32
|
export type SseDeliveryBoundObservation = {
|
|
@@ -586,8 +597,196 @@ export async function sseWorkspaceControlStream(
|
|
|
586
597
|
});
|
|
587
598
|
}
|
|
588
599
|
|
|
600
|
+
export type WorkspaceInteractionSseOptions = SseDeliveryOptions & {
|
|
601
|
+
pollIntervalMs?: number | undefined;
|
|
602
|
+
};
|
|
603
|
+
|
|
604
|
+
/**
|
|
605
|
+
* One HTTP connection for the two workspace-wide invalidation domains used by
|
|
606
|
+
* every visible OpenGeni surface. Keeping these as separate HTTP/1 streams
|
|
607
|
+
* consumes all six per-origin browser connections with only two windows and
|
|
608
|
+
* starves ordinary mutations/terminal grants. The durable cursors remain
|
|
609
|
+
* independent; this function only multiplexes their already-bounded SSE frames.
|
|
610
|
+
*/
|
|
611
|
+
export async function sseWorkspaceLiveStream(
|
|
612
|
+
db: Database,
|
|
613
|
+
bus: EventBus,
|
|
614
|
+
accountId: string,
|
|
615
|
+
workspaceId: string,
|
|
616
|
+
controlAfter: number,
|
|
617
|
+
interactionAfter: number,
|
|
618
|
+
signal: AbortSignal,
|
|
619
|
+
options: WorkspaceInteractionSseOptions = {},
|
|
620
|
+
): Promise<Response> {
|
|
621
|
+
const upstream = new AbortController();
|
|
622
|
+
const channel = createByteBoundedSseStream({
|
|
623
|
+
maxQueuedBytes: options.maxQueuedBytes ?? SESSION_EVENT_SSE_FRAME_MAX_BYTES,
|
|
624
|
+
...(options.stallTimeoutMs === undefined ? {} : { stallTimeoutMs: options.stallTimeoutMs }),
|
|
625
|
+
onObservation: sseObservationReporter("workspace_interaction", options),
|
|
626
|
+
onStop: () => {
|
|
627
|
+
signal.removeEventListener("abort", abort);
|
|
628
|
+
upstream.abort();
|
|
629
|
+
},
|
|
630
|
+
});
|
|
631
|
+
const abort = () => {
|
|
632
|
+
upstream.abort();
|
|
633
|
+
channel.close();
|
|
634
|
+
};
|
|
635
|
+
if (signal.aborted) abort();
|
|
636
|
+
else signal.addEventListener("abort", abort, { once: true });
|
|
637
|
+
|
|
638
|
+
let writeTail = Promise.resolve(true);
|
|
639
|
+
const write = (frame: string): Promise<boolean> => {
|
|
640
|
+
const pending = writeTail.then(() => channel.write(frame));
|
|
641
|
+
writeTail = pending.catch(() => false);
|
|
642
|
+
return pending;
|
|
643
|
+
};
|
|
644
|
+
const pump = async (response: Response): Promise<void> => {
|
|
645
|
+
if (!response.body) throw new TypeError("workspace live upstream omitted its body");
|
|
646
|
+
const reader = response.body.getReader();
|
|
647
|
+
const decoder = new TextDecoder();
|
|
648
|
+
try {
|
|
649
|
+
for (;;) {
|
|
650
|
+
const next = await reader.read();
|
|
651
|
+
if (next.done) {
|
|
652
|
+
const tail = decoder.decode();
|
|
653
|
+
if (tail) await write(tail);
|
|
654
|
+
return;
|
|
655
|
+
}
|
|
656
|
+
const frame = decoder.decode(next.value, { stream: true });
|
|
657
|
+
if (frame && !(await write(frame))) return;
|
|
658
|
+
}
|
|
659
|
+
} finally {
|
|
660
|
+
await reader.cancel().catch(() => undefined);
|
|
661
|
+
reader.releaseLock();
|
|
662
|
+
}
|
|
663
|
+
};
|
|
664
|
+
|
|
665
|
+
void (async () => {
|
|
666
|
+
try {
|
|
667
|
+
const [control, interaction] = await Promise.all([
|
|
668
|
+
sseWorkspaceControlStream(db, bus, workspaceId, controlAfter, upstream.signal, options),
|
|
669
|
+
sseWorkspaceInteractionRevisionStream(
|
|
670
|
+
db,
|
|
671
|
+
accountId,
|
|
672
|
+
workspaceId,
|
|
673
|
+
interactionAfter,
|
|
674
|
+
upstream.signal,
|
|
675
|
+
options,
|
|
676
|
+
),
|
|
677
|
+
]);
|
|
678
|
+
const pumps = [pump(control), pump(interaction)];
|
|
679
|
+
let termination: unknown = new TypeError("workspace live upstream ended unexpectedly");
|
|
680
|
+
try {
|
|
681
|
+
await Promise.race(pumps);
|
|
682
|
+
} catch (error) {
|
|
683
|
+
termination = error;
|
|
684
|
+
} finally {
|
|
685
|
+
// Either durable domain ending makes the multiplexed connection stale.
|
|
686
|
+
// Stop its sibling immediately so the SDK reconnects both independent
|
|
687
|
+
// cursors instead of silently losing one domain forever.
|
|
688
|
+
upstream.abort();
|
|
689
|
+
await Promise.allSettled(pumps);
|
|
690
|
+
}
|
|
691
|
+
if (!signal.aborted && !channel.stopped()) channel.fail(termination);
|
|
692
|
+
} catch (error) {
|
|
693
|
+
if (!upstream.signal.aborted && !channel.stopped()) channel.fail(error);
|
|
694
|
+
}
|
|
695
|
+
})();
|
|
696
|
+
|
|
697
|
+
return new Response(channel.stream, {
|
|
698
|
+
headers: {
|
|
699
|
+
"Content-Type": "text/event-stream; charset=utf-8",
|
|
700
|
+
"Cache-Control": "no-cache, no-transform",
|
|
701
|
+
Connection: "keep-alive",
|
|
702
|
+
},
|
|
703
|
+
});
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
/**
|
|
707
|
+
* Latest-wins interaction invalidation stream. The durable truth is one
|
|
708
|
+
* monotonic workspace row, not an ever-growing event log. Each poll reads only
|
|
709
|
+
* that row; reconnect immediately projects the newest revision after `after`.
|
|
710
|
+
*/
|
|
711
|
+
export async function sseWorkspaceInteractionRevisionStream(
|
|
712
|
+
db: Database,
|
|
713
|
+
accountId: string,
|
|
714
|
+
workspaceId: string,
|
|
715
|
+
after: number,
|
|
716
|
+
signal: AbortSignal,
|
|
717
|
+
options: WorkspaceInteractionSseOptions = {},
|
|
718
|
+
): Promise<Response> {
|
|
719
|
+
const heartbeatIntervalMs = resolveHeartbeatInterval(options.heartbeatIntervalMs);
|
|
720
|
+
const pollIntervalMs = resolveInteractionPollInterval(options.pollIntervalMs);
|
|
721
|
+
let lastSent = after;
|
|
722
|
+
let lastWriteAt = Date.now();
|
|
723
|
+
let stopRequested = false;
|
|
724
|
+
let detachAbortListener = () => {};
|
|
725
|
+
let closeMetrics = () => {};
|
|
726
|
+
const channel = createByteBoundedSseStream({
|
|
727
|
+
maxQueuedBytes: options.maxQueuedBytes ?? SESSION_EVENT_SSE_FRAME_MAX_BYTES,
|
|
728
|
+
...(options.stallTimeoutMs === undefined ? {} : { stallTimeoutMs: options.stallTimeoutMs }),
|
|
729
|
+
onObservation: sseObservationReporter("workspace_interaction", options),
|
|
730
|
+
onStop: () => {
|
|
731
|
+
stopRequested = true;
|
|
732
|
+
closeMetrics();
|
|
733
|
+
detachAbortListener();
|
|
734
|
+
},
|
|
735
|
+
});
|
|
736
|
+
closeMetrics = observeSseConnection("workspace_interaction", after, options.observability);
|
|
737
|
+
|
|
738
|
+
const write = async (frame: string): Promise<boolean> => {
|
|
739
|
+
const accepted = await channel.write(frame);
|
|
740
|
+
if (accepted) lastWriteAt = Date.now();
|
|
741
|
+
return accepted;
|
|
742
|
+
};
|
|
743
|
+
|
|
744
|
+
void (async () => {
|
|
745
|
+
try {
|
|
746
|
+
if (!(await write(": connected\n\n"))) return;
|
|
747
|
+
for (;;) {
|
|
748
|
+
if (stopRequested || signal.aborted || channel.stopped()) return;
|
|
749
|
+
const state = await getWorkspaceInteractionRevisionState(db, { accountId, workspaceId });
|
|
750
|
+
if (state.revision > lastSent) {
|
|
751
|
+
const event = WorkspaceInteractionRevisionEvent.parse({
|
|
752
|
+
workspaceId,
|
|
753
|
+
sequence: state.revision,
|
|
754
|
+
revision: state.revision,
|
|
755
|
+
type: "workspace.interaction.changed",
|
|
756
|
+
occurredAt: (state.updatedAt ?? new Date()).toISOString(),
|
|
757
|
+
});
|
|
758
|
+
if (!(await write(formatWorkspaceInteractionRevisionSse(event)))) return;
|
|
759
|
+
lastSent = event.revision;
|
|
760
|
+
} else if (Date.now() - lastWriteAt >= heartbeatIntervalMs) {
|
|
761
|
+
if (!(await write(": heartbeat\n\n"))) return;
|
|
762
|
+
}
|
|
763
|
+
await abortableDelay(pollIntervalMs, signal);
|
|
764
|
+
}
|
|
765
|
+
} catch (error) {
|
|
766
|
+
if (!signal.aborted && !channel.stopped()) {
|
|
767
|
+
channel.fail(retryableSseFailure("workspace interaction stream delivery failed", error));
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
})();
|
|
771
|
+
|
|
772
|
+
const abort = () => channel.close();
|
|
773
|
+
if (signal.aborted) abort();
|
|
774
|
+
else {
|
|
775
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
776
|
+
detachAbortListener = () => signal.removeEventListener("abort", abort);
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
return new Response(channel.stream, {
|
|
780
|
+
headers: {
|
|
781
|
+
"Content-Type": "text/event-stream; charset=utf-8",
|
|
782
|
+
"Cache-Control": "no-cache, no-transform",
|
|
783
|
+
Connection: "keep-alive",
|
|
784
|
+
},
|
|
785
|
+
});
|
|
786
|
+
}
|
|
787
|
+
|
|
589
788
|
function observeSseConnection(
|
|
590
|
-
stream:
|
|
789
|
+
stream: SseStreamKind,
|
|
591
790
|
after: number,
|
|
592
791
|
observability: Observability | undefined,
|
|
593
792
|
): () => void {
|
|
@@ -659,7 +858,7 @@ export type SessionSseDeliveryOptions = SseDeliveryOptions & {
|
|
|
659
858
|
};
|
|
660
859
|
|
|
661
860
|
function sseObservationReporter(
|
|
662
|
-
stream:
|
|
861
|
+
stream: SseStreamKind,
|
|
663
862
|
options: SseDeliveryOptions,
|
|
664
863
|
): (observation: SseDeliveryBoundObservation) => void {
|
|
665
864
|
return (observation) => {
|
|
@@ -692,3 +891,28 @@ function resolveHeartbeatInterval(value: number | undefined): number {
|
|
|
692
891
|
}
|
|
693
892
|
return interval;
|
|
694
893
|
}
|
|
894
|
+
|
|
895
|
+
function resolveInteractionPollInterval(value: number | undefined): number {
|
|
896
|
+
const interval = value ?? 1_000;
|
|
897
|
+
if (!Number.isSafeInteger(interval) || interval < 100 || interval > 60_000) {
|
|
898
|
+
throw new RangeError("interaction revision poll interval must be between 100 and 60000ms");
|
|
899
|
+
}
|
|
900
|
+
return interval;
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
function formatWorkspaceInteractionRevisionSse(event: WorkspaceInteractionRevisionEvent): string {
|
|
904
|
+
return `id: ${event.sequence}\nevent: ${event.type}\ndata: ${JSON.stringify(event)}\n\n`;
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
async function abortableDelay(delayMs: number, signal: AbortSignal): Promise<void> {
|
|
908
|
+
if (signal.aborted) return;
|
|
909
|
+
await new Promise<void>((resolve) => {
|
|
910
|
+
const timer = setTimeout(done, delayMs);
|
|
911
|
+
function done() {
|
|
912
|
+
clearTimeout(timer);
|
|
913
|
+
signal.removeEventListener("abort", done);
|
|
914
|
+
resolve();
|
|
915
|
+
}
|
|
916
|
+
signal.addEventListener("abort", done, { once: true });
|
|
917
|
+
});
|
|
918
|
+
}
|
|
@@ -6,10 +6,10 @@ import {
|
|
|
6
6
|
discoverOpenApiAuth,
|
|
7
7
|
fetchGraphqlIntrospection,
|
|
8
8
|
fetchIntegrationSourceDocument,
|
|
9
|
-
|
|
9
|
+
filterOpenApiDocumentForDefinition,
|
|
10
10
|
googleDiscoveryToOpenApi,
|
|
11
|
+
integrationDefinitionById,
|
|
11
12
|
parseOpenApiDocument,
|
|
12
|
-
providerPresetById,
|
|
13
13
|
type IntegrationCredentialResolver,
|
|
14
14
|
type IntegrationInvocationAuthority,
|
|
15
15
|
type IntegrationTransport,
|
|
@@ -18,11 +18,11 @@ import {
|
|
|
18
18
|
import {
|
|
19
19
|
ApiIntegrationPreview,
|
|
20
20
|
type ApiIntegrationAuthPreview,
|
|
21
|
-
type
|
|
21
|
+
type IntegrationSource,
|
|
22
22
|
} from "@opengeni/contracts";
|
|
23
23
|
import type { StoredApiIntegrationRevision } from "@opengeni/db";
|
|
24
24
|
|
|
25
|
-
const
|
|
25
|
+
const MAX_CURATED_DEFINITION_SOURCE_BYTES = 64 * 1024 * 1024;
|
|
26
26
|
|
|
27
27
|
export type ApiIntegrationConnectionDescriptor = {
|
|
28
28
|
id: string;
|
|
@@ -41,14 +41,14 @@ export type ResolvedApiIntegrationPreview = {
|
|
|
41
41
|
};
|
|
42
42
|
|
|
43
43
|
export async function resolveApiIntegrationPreview(input: {
|
|
44
|
-
source:
|
|
44
|
+
source: IntegrationSource;
|
|
45
45
|
connection: ApiIntegrationConnectionDescriptor | null;
|
|
46
46
|
transport: IntegrationTransport;
|
|
47
47
|
credentialResolver?: IntegrationCredentialResolver;
|
|
48
48
|
authority: IntegrationInvocationAuthority;
|
|
49
49
|
}): Promise<ResolvedApiIntegrationPreview> {
|
|
50
|
-
if (input.source.kind === "
|
|
51
|
-
return await
|
|
50
|
+
if (input.source.kind === "definition") {
|
|
51
|
+
return await resolveDefinition(input);
|
|
52
52
|
}
|
|
53
53
|
if (input.source.kind === "graphql") {
|
|
54
54
|
return await resolveGraphql(input, input.source.endpoint, input.source.name);
|
|
@@ -77,52 +77,56 @@ export async function resolveApiIntegrationPreview(input: {
|
|
|
77
77
|
}
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
-
async function
|
|
80
|
+
async function resolveDefinition(
|
|
81
81
|
input: Parameters<typeof resolveApiIntegrationPreview>[0],
|
|
82
82
|
): Promise<ResolvedApiIntegrationPreview> {
|
|
83
|
-
if (input.source.kind !== "
|
|
84
|
-
const
|
|
85
|
-
if (!
|
|
83
|
+
if (input.source.kind !== "definition") throw new Error("Expected a provider definition source");
|
|
84
|
+
const definition = integrationDefinitionById(input.source.definitionId);
|
|
85
|
+
if (!definition) throw new Error(`Unknown Integration definition: ${input.source.definitionId}`);
|
|
86
86
|
const bytes = await fetchIntegrationSourceDocument(
|
|
87
87
|
input.transport,
|
|
88
|
-
|
|
89
|
-
|
|
88
|
+
definition.source.url,
|
|
89
|
+
MAX_CURATED_DEFINITION_SOURCE_BYTES,
|
|
90
90
|
);
|
|
91
91
|
let document: Record<string, unknown>;
|
|
92
|
-
if (
|
|
92
|
+
if (definition.source.kind === "google_discovery") {
|
|
93
93
|
document = googleDiscoveryToOpenApi(JSON.parse(new TextDecoder().decode(bytes)));
|
|
94
94
|
} else {
|
|
95
|
-
document =
|
|
95
|
+
document = filterOpenApiDocumentForDefinition(parseOpenApiDocument(bytes), definition);
|
|
96
96
|
}
|
|
97
|
-
const identity =
|
|
97
|
+
const identity = integrationInstallationIdentity("openapi", definition.id, definition.source.url);
|
|
98
98
|
const revision = compileOpenApiRevision(document, {
|
|
99
|
-
|
|
100
|
-
sourceUrl:
|
|
101
|
-
|
|
102
|
-
provider:
|
|
99
|
+
definitionId: definition.id,
|
|
100
|
+
sourceUrl: definition.source.url,
|
|
101
|
+
baseUrl: definition.baseUrl,
|
|
102
|
+
provider: definition.provider.id,
|
|
103
103
|
});
|
|
104
104
|
const baseUrl = firstOpenApiServerUrl(revision);
|
|
105
|
-
const providerDomain =
|
|
105
|
+
const providerDomain = definition.provider.domain;
|
|
106
106
|
const auth: ApiIntegrationAuthPreview = {
|
|
107
107
|
kind: "oauth2",
|
|
108
108
|
providerDomain,
|
|
109
|
-
scopes: [...
|
|
109
|
+
scopes: [...definition.authentication.scopes],
|
|
110
110
|
};
|
|
111
111
|
return resolvedPreview({
|
|
112
112
|
source: input.source,
|
|
113
|
-
|
|
113
|
+
definitionId: definition.id,
|
|
114
|
+
definitionProvenance: "curated",
|
|
114
115
|
identity,
|
|
115
116
|
revision,
|
|
116
|
-
provider:
|
|
117
|
+
provider: definition.provider.id,
|
|
117
118
|
providerDomain,
|
|
118
119
|
baseUrl,
|
|
119
|
-
sourceUrl:
|
|
120
|
-
name:
|
|
121
|
-
description:
|
|
120
|
+
sourceUrl: definition.source.url,
|
|
121
|
+
name: definition.name,
|
|
122
|
+
description: definition.summary,
|
|
122
123
|
auth,
|
|
123
124
|
connection: input.connection,
|
|
124
|
-
requiredScopes: [...
|
|
125
|
-
authScheme: {
|
|
125
|
+
requiredScopes: [...definition.authentication.scopes],
|
|
126
|
+
authScheme: {
|
|
127
|
+
kind: definition.authentication.kind,
|
|
128
|
+
placement: definition.authentication.tokenPlacement,
|
|
129
|
+
},
|
|
126
130
|
});
|
|
127
131
|
}
|
|
128
132
|
|
|
@@ -134,9 +138,13 @@ async function resolveOpenApi(
|
|
|
134
138
|
const document = parseOpenApiDocument(
|
|
135
139
|
await fetchIntegrationSourceDocument(input.transport, sourceUrl),
|
|
136
140
|
);
|
|
137
|
-
const identity =
|
|
141
|
+
const identity = integrationInstallationIdentity(
|
|
142
|
+
"openapi",
|
|
143
|
+
new URL(sourceUrl).hostname,
|
|
144
|
+
sourceUrl,
|
|
145
|
+
);
|
|
138
146
|
const revision = compileOpenApiRevision(document, {
|
|
139
|
-
|
|
147
|
+
definitionId: identity.sourceDerivedDefinitionId,
|
|
140
148
|
sourceUrl,
|
|
141
149
|
...(baseUrl ? { baseUrl } : {}),
|
|
142
150
|
});
|
|
@@ -146,7 +154,8 @@ async function resolveOpenApi(
|
|
|
146
154
|
const auth = authPreview(discovered, providerDomain, input.connection);
|
|
147
155
|
return resolvedPreview({
|
|
148
156
|
source: input.source,
|
|
149
|
-
|
|
157
|
+
definitionId: identity.sourceDerivedDefinitionId,
|
|
158
|
+
definitionProvenance: "workspace",
|
|
150
159
|
identity,
|
|
151
160
|
revision,
|
|
152
161
|
provider: null,
|
|
@@ -168,7 +177,7 @@ async function resolveGraphql(
|
|
|
168
177
|
name?: string,
|
|
169
178
|
): Promise<ResolvedApiIntegrationPreview> {
|
|
170
179
|
const providerDomain = new URL(endpoint).hostname.toLowerCase();
|
|
171
|
-
const identity =
|
|
180
|
+
const identity = integrationInstallationIdentity("graphql", providerDomain, endpoint);
|
|
172
181
|
const introspection = await fetchGraphqlIntrospection({
|
|
173
182
|
endpoint,
|
|
174
183
|
transport: input.transport,
|
|
@@ -176,7 +185,7 @@ async function resolveGraphql(
|
|
|
176
185
|
...(input.credentialResolver ? { credentialResolver: input.credentialResolver } : {}),
|
|
177
186
|
});
|
|
178
187
|
const revision = compileGraphqlRevision(introspection, {
|
|
179
|
-
|
|
188
|
+
definitionId: identity.sourceDerivedDefinitionId,
|
|
180
189
|
endpoint,
|
|
181
190
|
sourceUrl: endpoint,
|
|
182
191
|
...(name ? { name } : {}),
|
|
@@ -186,7 +195,8 @@ async function resolveGraphql(
|
|
|
186
195
|
: ({ kind: "none" } as const);
|
|
187
196
|
return resolvedPreview({
|
|
188
197
|
source: input.source,
|
|
189
|
-
|
|
198
|
+
definitionId: identity.sourceDerivedDefinitionId,
|
|
199
|
+
definitionProvenance: "workspace",
|
|
190
200
|
identity,
|
|
191
201
|
revision,
|
|
192
202
|
provider: null,
|
|
@@ -203,9 +213,10 @@ async function resolveGraphql(
|
|
|
203
213
|
}
|
|
204
214
|
|
|
205
215
|
function resolvedPreview(input: {
|
|
206
|
-
source:
|
|
207
|
-
|
|
208
|
-
|
|
216
|
+
source: IntegrationSource;
|
|
217
|
+
definitionId: string;
|
|
218
|
+
definitionProvenance: "curated" | "workspace";
|
|
219
|
+
identity: ReturnType<typeof integrationInstallationIdentity>;
|
|
209
220
|
revision: StoredApiIntegrationRevision;
|
|
210
221
|
provider: string | null;
|
|
211
222
|
providerDomain: string;
|
|
@@ -230,9 +241,9 @@ function resolvedPreview(input: {
|
|
|
230
241
|
return {
|
|
231
242
|
preview: ApiIntegrationPreview.parse({
|
|
232
243
|
source: input.source,
|
|
233
|
-
|
|
244
|
+
definitionId: input.definitionId,
|
|
245
|
+
definitionProvenance: input.definitionProvenance,
|
|
234
246
|
protocol: input.revision.protocol,
|
|
235
|
-
integrationId: input.identity.integrationId,
|
|
236
247
|
capabilityId: input.identity.capabilityId,
|
|
237
248
|
pluginKey: input.identity.pluginKey,
|
|
238
249
|
serverId: input.identity.serverId,
|
|
@@ -265,7 +276,11 @@ function resolvedPreview(input: {
|
|
|
265
276
|
};
|
|
266
277
|
}
|
|
267
278
|
|
|
268
|
-
function
|
|
279
|
+
function integrationInstallationIdentity(
|
|
280
|
+
protocol: "openapi" | "graphql",
|
|
281
|
+
label: string,
|
|
282
|
+
locator: string,
|
|
283
|
+
) {
|
|
269
284
|
const hash = createHash("sha256").update(`${protocol}\0${locator}`).digest("hex").slice(0, 12);
|
|
270
285
|
const slug =
|
|
271
286
|
label
|
|
@@ -274,7 +289,7 @@ function integrationIdentity(protocol: "openapi" | "graphql", label: string, loc
|
|
|
274
289
|
.replace(/^-+|-+$/g, "")
|
|
275
290
|
.slice(0, 48) || "integration";
|
|
276
291
|
return {
|
|
277
|
-
|
|
292
|
+
sourceDerivedDefinitionId: `${slug}-${hash}`,
|
|
278
293
|
pluginKey: `integration/${protocol}/${slug}-${hash}`,
|
|
279
294
|
capabilityId: `api:${protocol}:${slug}-${hash}`,
|
|
280
295
|
serverId: `api_${protocol}_${slug.replaceAll("-", "_").slice(0, 48)}_${hash}`,
|