@opengeni/api-router 0.12.7 → 0.14.3
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 +18 -38
- package/dist/app.js +3 -1
- package/dist/auth/managed-auth.d.ts +35 -0
- package/dist/{chunk-SD5GJTZ4.js → chunk-36PW33ND.js} +3744 -638
- package/dist/chunk-36PW33ND.js.map +1 -0
- package/dist/codex-redemption-security.d.ts +15 -0
- package/dist/github-access.d.ts +12 -0
- package/dist/github-browser-flow.d.ts +14 -0
- package/dist/http/auth.d.ts +3 -0
- package/dist/http/common.d.ts +3 -0
- package/dist/http/sse.d.ts +70 -0
- package/dist/index.d.ts +11 -13
- package/dist/index.js +1 -1
- package/dist/integrations/google-drive.d.ts +76 -0
- package/dist/integrations/oauth-client.d.ts +42 -0
- package/dist/integrations/provider-domain.d.ts +7 -0
- package/dist/integrations/slack-bot.d.ts +343 -0
- package/dist/mcp/documents.d.ts +8 -0
- package/dist/mcp/files.d.ts +5 -0
- package/dist/mcp/server.d.ts +107 -0
- package/dist/mcp/session-view.d.ts +191 -0
- package/dist/mcp/toolspace.d.ts +61 -0
- package/dist/model-catalog.d.ts +39 -0
- package/dist/observability.d.ts +3 -0
- package/dist/routes/api-keys.d.ts +3 -0
- package/dist/routes/billing.d.ts +16 -0
- package/dist/routes/capabilities.d.ts +3 -0
- package/dist/routes/catalog-assets.d.ts +5 -0
- package/dist/routes/codex.d.ts +10 -0
- package/dist/routes/connections.d.ts +3 -0
- package/dist/routes/documents.d.ts +3 -0
- package/dist/routes/enrollments.d.ts +3 -0
- package/dist/routes/environments.d.ts +5 -0
- package/dist/routes/files.d.ts +4 -0
- package/dist/routes/github.d.ts +3 -0
- package/dist/routes/insights.d.ts +3 -0
- package/dist/routes/install.d.ts +5 -0
- package/dist/routes/machines.d.ts +3 -0
- package/dist/routes/packs.d.ts +3 -0
- package/dist/routes/preference-registry.d.ts +5 -0
- package/dist/routes/rigs.d.ts +3 -0
- package/dist/routes/scheduled-tasks.d.ts +3 -0
- package/dist/routes/sessions.d.ts +15 -0
- package/dist/routes/social.d.ts +3 -0
- package/dist/routes/transcriptions.d.ts +3 -0
- package/dist/routes/workspace-capture.d.ts +59 -0
- package/dist/routes/workspace-instruction-policies.d.ts +3 -0
- package/dist/routes/workspace-state.d.ts +3 -0
- package/dist/routes/workspaces.d.ts +5 -0
- package/dist/sandbox/access.d.ts +21 -0
- package/dist/sandbox/auth-callout.d.ts +30 -0
- package/dist/sandbox/channel-a.d.ts +37 -0
- package/dist/sandbox/enrollment.d.ts +120 -0
- package/dist/sandbox/machines.d.ts +29 -0
- package/dist/sandbox/metrics-ingestion.d.ts +128 -0
- package/dist/sandbox/rematerialize.d.ts +24 -0
- package/dist/sandbox/viewer.d.ts +251 -0
- package/dist/transcription/providers/azure-openai.d.ts +9 -0
- package/dist/transcription/providers/codex-subscription.d.ts +9 -0
- package/dist/transcription/providers/openai.d.ts +9 -0
- package/dist/transcription/service.d.ts +10 -0
- package/dist/workspace-state-projection.d.ts +16 -0
- package/package.json +13 -13
- package/src/app.ts +75 -5
- package/src/github-access.ts +1 -0
- package/src/integrations/google-drive.ts +869 -0
- package/src/integrations/oauth-client.ts +41 -6
- package/src/integrations/slack-bot.ts +756 -26
- package/src/mcp/server.ts +226 -42
- package/src/mcp/session-view.ts +1 -0
- package/src/mcp/toolspace.ts +17 -9
- package/src/routes/connections.ts +137 -7
- package/src/routes/documents.ts +13 -2
- package/src/routes/github.ts +327 -20
- package/src/routes/insights.ts +30 -0
- package/src/routes/preference-registry.ts +482 -0
- package/src/routes/sessions.ts +22 -23
- package/src/routes/transcriptions.ts +155 -0
- package/src/routes/workspace-capture.ts +108 -8
- package/src/routes/workspace-state.ts +61 -0
- package/src/sandbox/channel-a.ts +2 -0
- package/src/sandbox/rematerialize.ts +111 -4
- package/src/sandbox/viewer.ts +29 -12
- package/src/transcription/providers/azure-openai.ts +45 -0
- package/src/transcription/providers/codex-subscription.ts +100 -0
- package/src/transcription/providers/openai.ts +93 -0
- package/src/transcription/service.ts +121 -0
- package/src/workspace-state-projection.ts +244 -0
- package/dist/chunk-SD5GJTZ4.js.map +0 -1
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Settings } from "@opengeni/config";
|
|
2
|
+
export type { ApiSandboxSession, ApiSandboxClient, ResumeBoxByIdInput, ResumedSandboxSession, } from "@opengeni/core";
|
|
3
|
+
import type { ApiSandboxClient, ResumeBoxByIdInput, ResumedSandboxSession } from "@opengeni/core";
|
|
4
|
+
export declare class SandboxResumeError extends Error {
|
|
5
|
+
readonly cause?: unknown;
|
|
6
|
+
constructor(message: string, cause?: unknown);
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Construct the API process's own sandbox client from settings, agent-loop-free.
|
|
10
|
+
* Returns undefined when `sandboxBackend=none` (no box to touch). The Modal
|
|
11
|
+
* token + app name are read from settings (already parsed by getSettings and
|
|
12
|
+
* present in the API runtime env), so the client can resume Modal boxes by id.
|
|
13
|
+
*/
|
|
14
|
+
export declare function createApiSandboxClient(settings: Settings): ApiSandboxClient | undefined;
|
|
15
|
+
/**
|
|
16
|
+
* Build the `resumeBoxById` helper bound to the API's sandbox client. Given a
|
|
17
|
+
* backend + a serialized resume_state envelope, it resumes the box and returns
|
|
18
|
+
* a live session for one in-process op. The caller drives exec/readFile and then
|
|
19
|
+
* drops the handle (resume → use → drop); it does NOT own the box.
|
|
20
|
+
*/
|
|
21
|
+
export declare function makeResumeBoxById(client: ApiSandboxClient | undefined): (input: ResumeBoxByIdInput) => Promise<ResumedSandboxSession>;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { type NatsCalloutConfig, type Settings } from "@opengeni/config";
|
|
2
|
+
import { type Database } from "@opengeni/db";
|
|
3
|
+
import { type ResponderConnection } from "@opengeni/events";
|
|
4
|
+
import type { Observability } from "@opengeni/observability";
|
|
5
|
+
/** The NATS subject nats-server publishes authorization requests on (ADR-26). */
|
|
6
|
+
export declare const AUTH_CALLOUT_SUBJECT = "$SYS.REQ.USER.AUTH";
|
|
7
|
+
export interface AuthCalloutDeps {
|
|
8
|
+
db: Database;
|
|
9
|
+
settings: Settings;
|
|
10
|
+
callout: NatsCalloutConfig;
|
|
11
|
+
observability?: Observability;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* The pure validate→scoped-JWT decision, isolated from the NATS transport so it is
|
|
15
|
+
* unit-testable. Given the raw authorization-request JWT bytes, returns the signed
|
|
16
|
+
* authorization-response JWT bytes to reply with — a GRANT (embedding a scoped user
|
|
17
|
+
* JWT) on success, a DENIAL (carrying `nats.error`, no user JWT) otherwise. NEVER
|
|
18
|
+
* throws on a bad/invalid request: every failure becomes a signed denial (the
|
|
19
|
+
* server then refuses the connection cleanly).
|
|
20
|
+
*/
|
|
21
|
+
export declare function handleAuthorizationRequest(deps: AuthCalloutDeps, requestBytes: Uint8Array): Promise<Uint8Array>;
|
|
22
|
+
/**
|
|
23
|
+
* Start the auth-callout responder: open a SEPARATE NATS connection authenticated
|
|
24
|
+
* as the callout `auth_users` user, subscribe $SYS.REQ.USER.AUTH, and answer every
|
|
25
|
+
* authorization request via {@link handleAuthorizationRequest}. Returns a handle
|
|
26
|
+
* whose `close()` drains the connection. Gated by the caller (sandboxSelfhostedEnabled
|
|
27
|
+
* + a resolvable callout config); a deployment without the callout plane never starts
|
|
28
|
+
* it.
|
|
29
|
+
*/
|
|
30
|
+
export declare function startAuthCalloutResponder(deps: AuthCalloutDeps, natsUrl: string): Promise<ResponderConnection>;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { type Settings } from "@opengeni/config";
|
|
2
|
+
import type { Session } from "@opengeni/contracts";
|
|
3
|
+
import { type Database, type LeaseSnapshot } from "@opengeni/db";
|
|
4
|
+
import { type EventBus } from "@opengeni/events";
|
|
5
|
+
import { SandboxChannelAService, type RoutingSandboxSession } from "@opengeni/runtime/sandbox";
|
|
6
|
+
export type ChannelAServices = {
|
|
7
|
+
db: Database;
|
|
8
|
+
settings: Settings;
|
|
9
|
+
bus: EventBus;
|
|
10
|
+
};
|
|
11
|
+
export type ChannelAContext = {
|
|
12
|
+
accountId: string;
|
|
13
|
+
workspaceId: string;
|
|
14
|
+
session: Session;
|
|
15
|
+
subjectId: string;
|
|
16
|
+
};
|
|
17
|
+
export type ChannelAHandle = {
|
|
18
|
+
service: SandboxChannelAService;
|
|
19
|
+
/** Connected Machine homes deliberately have no cloud lease. Durable PTYs
|
|
20
|
+
* require a real home-provider lease and reject this null case. */
|
|
21
|
+
lease: LeaseSnapshot | null;
|
|
22
|
+
routingSession: RoutingSandboxSession;
|
|
23
|
+
requestId: string;
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Run a Channel-A op against a live box, API-direct. Acquires an exact direct holder
|
|
27
|
+
* (warming the box when cold), resumes by id, builds the service, runs `fn`, and
|
|
28
|
+
* ALWAYS releases the holder + drops the handle in `finally`. Maps the service's
|
|
29
|
+
* typed errors to HTTP status (the route never sees a raw ChannelA*Error).
|
|
30
|
+
*
|
|
31
|
+
* Gated behind sandboxOwnershipEnabled at the route (the lease is dormant
|
|
32
|
+
* otherwise). A `backend:none` session has no box -> 409 before touching it.
|
|
33
|
+
*/
|
|
34
|
+
export declare function withChannelA<T>(services: ChannelAServices, ctx: ChannelAContext, fn: (handle: ChannelAHandle) => Promise<T>): Promise<T>;
|
|
35
|
+
/** Map the service's typed errors to HTTP status (the §5.3 matrix). Re-throws an
|
|
36
|
+
* already-HTTPException unchanged. */
|
|
37
|
+
export declare function mapChannelAError(error: unknown): unknown;
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { type Settings } from "@opengeni/config";
|
|
2
|
+
import { type DeviceEnrollmentLookupResponse, type DeviceEnrollmentPollResponse, type DeviceEnrollmentStartResponse, type EnrollTokenExchangeResponse, type MintEnrollTokenResponse } from "@opengeni/contracts";
|
|
3
|
+
import { type Database, type DeviceEnrollmentRequestRecord, type EnrollmentOs } from "@opengeni/db";
|
|
4
|
+
export declare const DEVICE_CODE_TTL_SECONDS = 600;
|
|
5
|
+
export declare const DEVICE_POLL_INTERVAL_SECONDS = 5;
|
|
6
|
+
export declare const ENROLLMENT_BEARER_TTL_SECONDS: number;
|
|
7
|
+
export declare const RELAY_TOKEN_TTL_SECONDS: number;
|
|
8
|
+
export declare const ENROLL_TOKEN_TTL_SECONDS = 3600;
|
|
9
|
+
export type EnrollmentServices = {
|
|
10
|
+
db: Database;
|
|
11
|
+
settings: Settings;
|
|
12
|
+
};
|
|
13
|
+
/** A workspace-scoped flow START context (the route resolves the workspace the
|
|
14
|
+
* agent's flow binds to from the deployment edge / a workspace hint). */
|
|
15
|
+
export type DeviceStartInput = {
|
|
16
|
+
accountId: string;
|
|
17
|
+
workspaceId: string;
|
|
18
|
+
publicKey: string;
|
|
19
|
+
os: EnrollmentOs;
|
|
20
|
+
arch: string;
|
|
21
|
+
machineName?: string | null;
|
|
22
|
+
canOfferDisplay: boolean;
|
|
23
|
+
requestsScreenControl: boolean;
|
|
24
|
+
verificationOrigin: string;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* START a device-flow: persist a short-TTL single-use pending request + return the
|
|
28
|
+
* DeviceAuthStart. Retries the user_code mint on the (astronomically rare) partial-
|
|
29
|
+
* unique collision among live pending rows.
|
|
30
|
+
*/
|
|
31
|
+
export declare function startDeviceEnrollment(services: EnrollmentServices, input: DeviceStartInput): Promise<DeviceEnrollmentStartResponse>;
|
|
32
|
+
/** APPROVE a flow by user_code (the LOUD consent step). Returns the resulting
|
|
33
|
+
* enrollment + sandbox ids, or null when no LIVE pending request matches the code
|
|
34
|
+
* in this workspace (an unknown/expired/already-terminal code). */
|
|
35
|
+
export declare function approveDeviceEnrollment(services: EnrollmentServices, input: {
|
|
36
|
+
accountId: string;
|
|
37
|
+
workspaceId: string;
|
|
38
|
+
userCode: string;
|
|
39
|
+
allowScreenControl: boolean;
|
|
40
|
+
approvedBySubjectId: string;
|
|
41
|
+
approvedBySubjectLabel?: string | null;
|
|
42
|
+
}): Promise<{
|
|
43
|
+
enrollmentId: string;
|
|
44
|
+
sandboxId: string;
|
|
45
|
+
allowScreenControl: boolean;
|
|
46
|
+
} | null>;
|
|
47
|
+
/** LOOK UP a pending flow by user_code GLOBALLY (the click-Grant approve page;
|
|
48
|
+
* design 11 §B.1). Returns the resolved pending record (carrying its workspaceId)
|
|
49
|
+
* or null when no live pending row matches the code. The ROUTE authorizes the
|
|
50
|
+
* caller against the resolved workspaceId (enrollments:read) BEFORE exposing
|
|
51
|
+
* anything — a failed grant OR a null here both surface as 404 so cross-workspace
|
|
52
|
+
* existence is never revealed. This does NOT consume the request. */
|
|
53
|
+
export declare function lookupDeviceEnrollment(services: EnrollmentServices, input: {
|
|
54
|
+
userCode: string;
|
|
55
|
+
}): Promise<DeviceEnrollmentRequestRecord | null>;
|
|
56
|
+
/** Project a resolved pending record to the presentational lookup response (no
|
|
57
|
+
* secrets, no device_code) the approve screen (EnrollmentConsent) renders. */
|
|
58
|
+
export declare function toLookupResponse(record: DeviceEnrollmentRequestRecord): DeviceEnrollmentLookupResponse;
|
|
59
|
+
/** DENY a flow by user_code (the explicit "no" at the approve page; design 11
|
|
60
|
+
* §B.2). Workspace-scoped (the route asserts the grant). Returns whether a pending
|
|
61
|
+
* row was flipped to denied (false for an unknown / already-terminal code). */
|
|
62
|
+
export declare function denyDeviceEnrollment(services: EnrollmentServices, input: {
|
|
63
|
+
accountId: string;
|
|
64
|
+
workspaceId: string;
|
|
65
|
+
userCode: string;
|
|
66
|
+
}): Promise<{
|
|
67
|
+
denied: boolean;
|
|
68
|
+
}>;
|
|
69
|
+
/** MINT a headless enroll token (design 11 §A2.2). Signs an `oget_` token bound to
|
|
70
|
+
* the workspace + account + the screen-control consent, with a 1h TTL. Returns
|
|
71
|
+
* null when the credential plane is disabled (no signing secret) so the route can
|
|
72
|
+
* mirror poll's "disabled" handling. The token value is NEVER logged. */
|
|
73
|
+
export declare function mintEnrollToken(services: EnrollmentServices, input: {
|
|
74
|
+
accountId: string;
|
|
75
|
+
workspaceId: string;
|
|
76
|
+
allowScreenControl: boolean;
|
|
77
|
+
}): Promise<MintEnrollTokenResponse | null>;
|
|
78
|
+
/** Distinguishes the two exchange failure modes for the route. */
|
|
79
|
+
export type ExchangeEnrollTokenResult = {
|
|
80
|
+
ok: true;
|
|
81
|
+
credentials: EnrollTokenExchangeResponse["credentials"];
|
|
82
|
+
} | {
|
|
83
|
+
ok: false;
|
|
84
|
+
reason: "disabled";
|
|
85
|
+
} | {
|
|
86
|
+
ok: false;
|
|
87
|
+
reason: "invalid";
|
|
88
|
+
};
|
|
89
|
+
/** EXCHANGE a headless enroll token (design 11 §A2.3) — the UNAUTHENTICATED path
|
|
90
|
+
* where the token IS the auth. Verifies the `oget_` token, then performs the SAME
|
|
91
|
+
* finalize as approve (upsert enrollment + ensure selfhosted sandbox,
|
|
92
|
+
* consentedWholeMachine=true, consentedScreenControl=token.allowScreenControl) and
|
|
93
|
+
* builds the IDENTICAL EnrollmentCredentials the poll authorized branch returns.
|
|
94
|
+
* Returns reason "disabled" when no signing secret (mirror poll), "invalid" when
|
|
95
|
+
* the token fails verification (the route 401s). */
|
|
96
|
+
export declare function exchangeEnrollToken(services: EnrollmentServices, input: {
|
|
97
|
+
token: string;
|
|
98
|
+
publicKey: string;
|
|
99
|
+
os: EnrollmentOs;
|
|
100
|
+
arch: string;
|
|
101
|
+
machineName?: string | null;
|
|
102
|
+
canOfferDisplay: boolean;
|
|
103
|
+
}): Promise<ExchangeEnrollTokenResult>;
|
|
104
|
+
/**
|
|
105
|
+
* POLL a flow by device_code. Resolves the state machine:
|
|
106
|
+
* - unknown code → "expired" (do not leak existence; an unknown code
|
|
107
|
+
* behaves like an expired one to the agent).
|
|
108
|
+
* - pending + within TTL → "pending".
|
|
109
|
+
* - pending + past TTL → "expired".
|
|
110
|
+
* - denied → "denied".
|
|
111
|
+
* - approved | consumed → "authorized" + the EnrollmentCredentials (the
|
|
112
|
+
* approved row is flipped to consumed; a legitimate
|
|
113
|
+
* re-poll of a consumed row still returns the creds).
|
|
114
|
+
* When the credential plane is disabled (no resolvable signing secret), an
|
|
115
|
+
* otherwise-authorized poll returns "disabled" so the agent surfaces a clear reason
|
|
116
|
+
* rather than half-enrolling.
|
|
117
|
+
*/
|
|
118
|
+
export declare function pollDeviceEnrollment(services: EnrollmentServices, input: {
|
|
119
|
+
deviceCode: string;
|
|
120
|
+
}): Promise<DeviceEnrollmentPollResponse>;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { Settings } from "@opengeni/config";
|
|
2
|
+
import { type Database, type MachineMetricsRow } from "@opengeni/db";
|
|
3
|
+
import type { EventBus } from "@opengeni/events";
|
|
4
|
+
import { MetricSample, type MachinesResponse } from "@opengeni/contracts";
|
|
5
|
+
export type MachinesServices = {
|
|
6
|
+
db: Database;
|
|
7
|
+
settings: Settings;
|
|
8
|
+
bus?: EventBus;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Project a stored `machine_metrics_latest` row to the contract `MetricSample`.
|
|
12
|
+
* The DB carries `gpuUtilPercent` + `gpuMemUsedBytes`/`gpuMemTotalBytes`; the wire
|
|
13
|
+
* `MetricSample` exposes the single `gpuUtilPct` + `gpuMemBytes` (USED bytes — the
|
|
14
|
+
* "how much VRAM is in use" the dashboard reads). A null any-numeric stays null
|
|
15
|
+
* (the not-reported contract); the byte/load fields default to 0 when a sample
|
|
16
|
+
* carried no value (the agent reports 0 == not-reported for those).
|
|
17
|
+
*/
|
|
18
|
+
export declare function metricRowToSample(row: MachineMetricsRow): MetricSample;
|
|
19
|
+
/**
|
|
20
|
+
* Build the Machines dashboard response for a workspace. When `sessionId` is
|
|
21
|
+
* supplied (an in-session view) the session's synthetic Modal group box is
|
|
22
|
+
* prepended (`isSessionGroup:true`) and the active-sandbox pointer is echoed;
|
|
23
|
+
* without it (the pure workspace dashboard) `activeSandboxId` is null and only
|
|
24
|
+
* the enrolled machines are listed.
|
|
25
|
+
*/
|
|
26
|
+
export declare function listMachines(services: MachinesServices, input: {
|
|
27
|
+
workspaceId: string;
|
|
28
|
+
sessionId?: string | null;
|
|
29
|
+
}): Promise<MachinesResponse>;
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { type Database, type MachineMetricsSample } from "@opengeni/db";
|
|
2
|
+
import { type EventBus } from "@opengeni/events";
|
|
3
|
+
import type { Observability } from "@opengeni/observability";
|
|
4
|
+
import { Hello, type MetricsSample } from "@opengeni/agent-proto";
|
|
5
|
+
/** The wildcard subject the agent event plane publishes heartbeats on. */
|
|
6
|
+
export declare const AGENT_EVENTS_SUBJECT = "agent.*.*.events";
|
|
7
|
+
/** The wildcard subject the agent publishes its connect Hello on. */
|
|
8
|
+
export declare const AGENT_HELLO_SUBJECT = "agent.*.*.hello";
|
|
9
|
+
/** Parse `agent.<ws>.<id>.events` → `{ workspaceId, agentId }` (heartbeat plane). */
|
|
10
|
+
export declare function parseAgentEventSubject(subject: string): {
|
|
11
|
+
workspaceId: string;
|
|
12
|
+
agentId: string;
|
|
13
|
+
} | null;
|
|
14
|
+
/** Parse `agent.<ws>.<id>.hello` → `{ workspaceId, agentId }` (connect plane). */
|
|
15
|
+
export declare function parseAgentHelloSubject(subject: string): {
|
|
16
|
+
workspaceId: string;
|
|
17
|
+
agentId: string;
|
|
18
|
+
} | null;
|
|
19
|
+
/**
|
|
20
|
+
* Project a wire `MetricsSample` (proto, ms-stamped, GPU as a repeated list) to
|
|
21
|
+
* the DB `MachineMetricsSample`. The proto byte/count fields are protobuf-encoded
|
|
22
|
+
* as decimal strings (uint64) on the TS side (ts-proto `string`); coerce to
|
|
23
|
+
* numbers. The DB carries a single `gpuUtilPercent` + `gpuMemUsedBytes`/Total —
|
|
24
|
+
* we take the FIRST GPU (the dashboard surfaces the primary accelerator); absent
|
|
25
|
+
* GPUs stay null (the not-reported contract). A zero on a non-GPU field is the
|
|
26
|
+
* agent's "not reported" (we keep it null-friendly via `nullIfZero` only for the
|
|
27
|
+
* GPU plane; cpu/mem/disk 0 is a legitimate reading the dashboard shows as 0).
|
|
28
|
+
*/
|
|
29
|
+
export declare function wireSampleToDbSample(wire: MetricsSample): MachineMetricsSample;
|
|
30
|
+
/**
|
|
31
|
+
* Ingest ONE decoded heartbeat for an enrolled machine. Resolves the enrollment's
|
|
32
|
+
* accountId (needed for the RLS-scoped writes) from the enrollment row; an
|
|
33
|
+
* unknown/cross-workspace agentId is ignored (no row → no write). Touches
|
|
34
|
+
* last-seen + upserts latest + downsamples the series.
|
|
35
|
+
*/
|
|
36
|
+
export declare function ingestHeartbeat(db: Database, input: {
|
|
37
|
+
workspaceId: string;
|
|
38
|
+
agentId: string;
|
|
39
|
+
sample: MetricsSample;
|
|
40
|
+
}): Promise<{
|
|
41
|
+
ingested: boolean;
|
|
42
|
+
seriesAppended: boolean;
|
|
43
|
+
}>;
|
|
44
|
+
/**
|
|
45
|
+
* Decode a raw `AgentEvent` payload + ingest it (the per-message handler). A
|
|
46
|
+
* heartbeat carrying a metrics sample is ingested; a going-offline records the
|
|
47
|
+
* machine-plane marker + fans out the link-plane session events. Decode failures
|
|
48
|
+
* are reported + swallowed. `bus` (when present) enables the session-event
|
|
49
|
+
* fan-out; the live consumer always supplies it, pure unit tests may omit it.
|
|
50
|
+
*/
|
|
51
|
+
export declare function handleAgentEventPayload(db: Database, observability: Observability | undefined, payload: Uint8Array, subject: string, bus?: EventBus): Promise<void>;
|
|
52
|
+
/**
|
|
53
|
+
* Start the metrics-ingestion consumer: subscribe `agent.*.*.events` and ingest
|
|
54
|
+
* every heartbeat. Gated by sandboxSelfhostedEnabled (the caller checks the flag;
|
|
55
|
+
* a disabled deployment never starts the consumer). Returns the unsubscribe fn.
|
|
56
|
+
*/
|
|
57
|
+
export declare function startMetricsIngestion(deps: {
|
|
58
|
+
db: Database;
|
|
59
|
+
bus: EventBus;
|
|
60
|
+
observability?: Observability;
|
|
61
|
+
}): () => void;
|
|
62
|
+
/**
|
|
63
|
+
* The LIVE display presence the agent's Hello reports: a desktop framebuffer is
|
|
64
|
+
* available (`capabilities.desktop`, which the agent sets true only when a display
|
|
65
|
+
* probes AND it can stream it) OR a `Display` detail is present. An unset
|
|
66
|
+
* Capabilities (or a headless machine) → false. This is what `has_display` should
|
|
67
|
+
* track, replacing the enroll-time snapshot.
|
|
68
|
+
*/
|
|
69
|
+
export declare function helloReportsDisplay(hello: Hello): boolean;
|
|
70
|
+
/**
|
|
71
|
+
* The human, actionable reason a display is present but UNUSABLE (macOS Screen
|
|
72
|
+
* Recording / TCC not granted), or null when capture is permitted / the machine is
|
|
73
|
+
* headless. Normalizes the proto's non-optional "" empty string to null so the DB
|
|
74
|
+
* carries a clean tri-state (a real reason vs. no reason) — the Machines dashboard
|
|
75
|
+
* shows "display: capture not granted" only when this is non-null.
|
|
76
|
+
*/
|
|
77
|
+
export declare function helloDesktopUnavailableReason(hello: Hello): string | null;
|
|
78
|
+
/** Whether the runner's current Hello advertises the op-stream engine. */
|
|
79
|
+
export declare function helloReportsOpStream(hello: Hello): boolean;
|
|
80
|
+
/**
|
|
81
|
+
* Reconcile `enrollments.has_display` (+ the capture-blocked reason) to what a Hello
|
|
82
|
+
* reports. Resolves the enrollment (the accountId is the RLS principal + the
|
|
83
|
+
* existence check + the current values). A no-change Hello short-circuits BEFORE
|
|
84
|
+
* issuing any write (and the DB writer is itself change-guarded on BOTH fields as a
|
|
85
|
+
* backstop), so a steady state never churns. An unknown/cross-workspace agentId is a
|
|
86
|
+
* no-op.
|
|
87
|
+
*/
|
|
88
|
+
export declare function refreshEnrollmentDisplay(db: Database, input: {
|
|
89
|
+
workspaceId: string;
|
|
90
|
+
agentId: string;
|
|
91
|
+
hasDisplay: boolean;
|
|
92
|
+
desktopUnavailableReason?: string | null;
|
|
93
|
+
}): Promise<{
|
|
94
|
+
updated: boolean;
|
|
95
|
+
}>;
|
|
96
|
+
/**
|
|
97
|
+
* Reconcile `enrollments.op_stream` to what a Hello reports. Resolves the
|
|
98
|
+
* enrollment first so the accountId remains the RLS principal and so a no-change
|
|
99
|
+
* Hello short-circuits BEFORE issuing any write (the DB writer is itself
|
|
100
|
+
* change-guarded as a backstop). An unknown/cross-workspace agentId is a no-op.
|
|
101
|
+
*/
|
|
102
|
+
export declare function refreshEnrollmentOpStream(db: Database, input: {
|
|
103
|
+
workspaceId: string;
|
|
104
|
+
agentId: string;
|
|
105
|
+
opStream: boolean;
|
|
106
|
+
}): Promise<{
|
|
107
|
+
updated: boolean;
|
|
108
|
+
}>;
|
|
109
|
+
/**
|
|
110
|
+
* Decode a raw `Hello` payload + refresh the enrollment's display cursor + clear
|
|
111
|
+
* any pending clean going-offline marker and, when the reconnect actually cleared
|
|
112
|
+
* one, fan out machine.link.restored to the sessions with an active op on the
|
|
113
|
+
* machine (the per-message handler for the hello plane). Decode failures + write
|
|
114
|
+
* failures are reported + swallowed — a Hello must NEVER break the agent's connect.
|
|
115
|
+
* `bus` (when present) enables the link.restored fan-out.
|
|
116
|
+
*/
|
|
117
|
+
export declare function handleHelloPayload(db: Database, observability: Observability | undefined, payload: Uint8Array, subject: string, bus?: EventBus): Promise<void>;
|
|
118
|
+
/**
|
|
119
|
+
* Start the Hello display-refresh consumer: subscribe `agent.*.*.hello` and
|
|
120
|
+
* reconcile `has_display` to the live capability the agent reports on every
|
|
121
|
+
* connect. Gated by sandboxSelfhostedEnabled (the caller checks the flag). Returns
|
|
122
|
+
* the unsubscribe fn.
|
|
123
|
+
*/
|
|
124
|
+
export declare function startHelloIngestion(deps: {
|
|
125
|
+
db: Database;
|
|
126
|
+
bus: EventBus;
|
|
127
|
+
observability?: Observability;
|
|
128
|
+
}): () => void;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Settings } from "@opengeni/config";
|
|
2
|
+
import { type Database, type LeaseSnapshot } from "@opengeni/db";
|
|
3
|
+
import { type EstablishedSandboxSession } from "@opengeni/runtime/sandbox";
|
|
4
|
+
/** The sole API-direct cold->warming owner path used by Channel A and viewer
|
|
5
|
+
* attach. It never publishes warm until archive identity, hydrated tree, command
|
|
6
|
+
* routing, provider identity, and the selected rematerialization attempt all
|
|
7
|
+
* agree under one lease epoch. */
|
|
8
|
+
export declare function establishApiSandboxSpawner(input: {
|
|
9
|
+
db: Database;
|
|
10
|
+
settings: Settings;
|
|
11
|
+
accountId: string;
|
|
12
|
+
workspaceId: string;
|
|
13
|
+
sandboxGroupId: string;
|
|
14
|
+
sessionId: string;
|
|
15
|
+
backend: string;
|
|
16
|
+
environment: Record<string, string>;
|
|
17
|
+
expectedEpoch: number;
|
|
18
|
+
acquiredLease: LeaseSnapshot;
|
|
19
|
+
fallbackEnvelope: Record<string, unknown> | null;
|
|
20
|
+
dataPlaneUrl: string | null;
|
|
21
|
+
}): Promise<{
|
|
22
|
+
established: EstablishedSandboxSession;
|
|
23
|
+
lease: LeaseSnapshot;
|
|
24
|
+
}>;
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import type { Settings } from "@opengeni/config";
|
|
2
|
+
import { type Session } from "@opengeni/contracts";
|
|
3
|
+
import { type Database, type LeaseSnapshot, type SandboxRecord } from "@opengeni/db";
|
|
4
|
+
import { type EventBus } from "@opengeni/events";
|
|
5
|
+
import { establishSandboxSessionFromEnvelope, type EstablishedSandboxSession } from "@opengeni/runtime/sandbox";
|
|
6
|
+
/** The minimal services a viewer op needs: the DB + settings (lease cadence +
|
|
7
|
+
* the sandbox client construction the leaf reads from settings). The bus is
|
|
8
|
+
* optional — only the rotation path (emitting stream.url.rotated to OTHER
|
|
9
|
+
* viewers) needs it. */
|
|
10
|
+
export type ViewerServices = {
|
|
11
|
+
db: Database;
|
|
12
|
+
settings: Settings;
|
|
13
|
+
bus?: EventBus;
|
|
14
|
+
/** Provider-establish dependency used by API-direct readiness and stream
|
|
15
|
+
* operations. Production uses the runtime leaf; isolated tests may supply a
|
|
16
|
+
* deterministic provider without replacing a process-global module. */
|
|
17
|
+
establishSandboxSession?: typeof establishSandboxSessionFromEnvelope;
|
|
18
|
+
};
|
|
19
|
+
/** A coherent snapshot the routes echo back: the holder id (the viewer's fence-
|
|
20
|
+
* carrying handle), the lease liveness/epoch, and the recorded data-plane URL
|
|
21
|
+
* (null until P4 mints the desktop tunnel). */
|
|
22
|
+
export type ViewerAttachResult = {
|
|
23
|
+
viewerId: string;
|
|
24
|
+
liveness: LeaseSnapshot["liveness"];
|
|
25
|
+
leaseEpoch: number;
|
|
26
|
+
workspaceGeneration: number | null;
|
|
27
|
+
archiveGeneration: number | null;
|
|
28
|
+
archiveComplete: boolean;
|
|
29
|
+
sandboxGroupId: string;
|
|
30
|
+
viewerHeartbeatIntervalMs: number;
|
|
31
|
+
dataPlaneUrl: string | null;
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* The STABLE run-scoped sandbox environment a COLD box must be created with so
|
|
35
|
+
* that — whether the box is first warmed by an API-direct ATTACH (here) or by the
|
|
36
|
+
* worker TURN — its manifest environment matches the environment the agent later
|
|
37
|
+
* declares for a turn. Without this, an attach-warmed box was created with the
|
|
38
|
+
* BASE allowlist env only (establishSandboxSessionFromEnvelope's
|
|
39
|
+
* collectSandboxEnvironment default), so the next turn's fuller env (git identity
|
|
40
|
+
* + workspace environment + HOME) introduced a delta and the SDK's
|
|
41
|
+
* `validateNoEnvironmentDelta` threw "Live sandbox sessions cannot change manifest
|
|
42
|
+
* environment variables" — the BLOCKING error this fixes.
|
|
43
|
+
*
|
|
44
|
+
* Mirrors the worker turn's STABLE env (config.stableSandboxEnvironmentForRun +
|
|
45
|
+
* the session's attached, decrypted workspace environment + — for a repo-attached
|
|
46
|
+
* session — the stable git-auth POINTERS the turn declares since the token-broker:
|
|
47
|
+
* GIT_ASKPASS / GIT_TERMINAL_PROMPT / bot identity). The pointers carry NO rotating
|
|
48
|
+
* value (the token lives in the box FILE the clone hook seeds), so they are
|
|
49
|
+
* attach-reproducible; omitting them cold-created a box whose env lacked keys the
|
|
50
|
+
* next repo turn's manifest declares → the SDK guard threw "Live sandbox sessions
|
|
51
|
+
* cannot change manifest environment variables" whenever a viewer attach (an open
|
|
52
|
+
* session page) won the cold-create race against the first turn.
|
|
53
|
+
*/
|
|
54
|
+
export declare function sessionAttachEnvironment(services: ViewerServices, workspaceId: string, session: Session): Promise<Record<string, string>>;
|
|
55
|
+
/**
|
|
56
|
+
* Acquire a `viewer` holder on the group lease, spinning up the box IN-PROCESS
|
|
57
|
+
* when cold. Mirrors the worker's resumeBoxForTurn spawner/attached branches,
|
|
58
|
+
* but with kind:'viewer' and run by the API process — no Temporal, no worker.
|
|
59
|
+
*
|
|
60
|
+
* `viewerId` is the unique-per-connection holder id (a uuid the client carries
|
|
61
|
+
* through heartbeats + detach); generated when absent.
|
|
62
|
+
*/
|
|
63
|
+
export declare function attachViewer(services: ViewerServices, input: {
|
|
64
|
+
accountId: string;
|
|
65
|
+
workspaceId: string;
|
|
66
|
+
session: Session;
|
|
67
|
+
viewerId?: string;
|
|
68
|
+
}): Promise<ViewerAttachResult>;
|
|
69
|
+
export type SessionGroupReadinessHold = {
|
|
70
|
+
lease: LeaseSnapshot;
|
|
71
|
+
/** Idempotently release the viewer holder after route publication settles. */
|
|
72
|
+
release: () => Promise<void>;
|
|
73
|
+
};
|
|
74
|
+
/** Readiness callback for fleet attach/swap. It acquires one disposable viewer
|
|
75
|
+
* holder through the same provider-verifying path as the UI and RETURNS that
|
|
76
|
+
* holder to the swap owner. The holder must remain live until route publication
|
|
77
|
+
* settles, otherwise the reaper can drain the just-verified target between the
|
|
78
|
+
* readiness probe and the route CAS. A first 409 may have fenced a missing warm
|
|
79
|
+
* provider; one bounded retry lets the normal cold->warming election rematerialize
|
|
80
|
+
* it. */
|
|
81
|
+
export declare function ensureSessionGroupReady(services: ViewerServices, input: {
|
|
82
|
+
accountId: string;
|
|
83
|
+
workspaceId: string;
|
|
84
|
+
session: Session;
|
|
85
|
+
}): Promise<SessionGroupReadinessHold>;
|
|
86
|
+
/**
|
|
87
|
+
* Refresh a viewer holder's TTL (the app-level viewer heartbeat). Epoch-fenced:
|
|
88
|
+
* a stale-epoch heartbeat (a box re-established under a newer epoch) returns
|
|
89
|
+
* false and the client must re-attach. Returns whether the holder is still live.
|
|
90
|
+
*/
|
|
91
|
+
export declare function heartbeatViewer(services: ViewerServices, input: {
|
|
92
|
+
accountId: string;
|
|
93
|
+
workspaceId: string;
|
|
94
|
+
sandboxGroupId: string;
|
|
95
|
+
viewerId: string;
|
|
96
|
+
expectedEpoch: number;
|
|
97
|
+
}): Promise<boolean>;
|
|
98
|
+
/**
|
|
99
|
+
* Release a viewer holder (the client disconnected). Idempotent: a double
|
|
100
|
+
* detach (or a detach after the reaper already TTL-reaped the holder) is a
|
|
101
|
+
* no-op. The box drains/stops only when no turn AND no viewer holds it.
|
|
102
|
+
*/
|
|
103
|
+
export declare function detachViewer(services: ViewerServices, input: {
|
|
104
|
+
accountId: string;
|
|
105
|
+
workspaceId: string;
|
|
106
|
+
sandboxGroupId: string;
|
|
107
|
+
viewerId: string;
|
|
108
|
+
}): Promise<{
|
|
109
|
+
liveness: LeaseSnapshot["liveness"];
|
|
110
|
+
refcount: number;
|
|
111
|
+
} | null>;
|
|
112
|
+
/** Non-locking lease snapshot for the capability-negotiation read. */
|
|
113
|
+
export declare function readGroupLease(services: ViewerServices, input: {
|
|
114
|
+
workspaceId: string;
|
|
115
|
+
sandboxGroupId: string;
|
|
116
|
+
}): Promise<LeaseSnapshot | null>;
|
|
117
|
+
export declare function viewerHeartbeatIntervalMs(settings: Settings): number;
|
|
118
|
+
/**
|
|
119
|
+
* The desktop WIRE transport for the session's ACTIVE sandbox — the single
|
|
120
|
+
* invariant that keeps the advertised transport in lockstep with where
|
|
121
|
+
* mintDesktopStream routed the pixels. It MUST be derived from the ACTIVE sandbox
|
|
122
|
+
* (not the session's HOME backend that negotiateCapabilities keys on): a selfhosted
|
|
123
|
+
* machine serves the RELAY framebuffer (PNG-per-frame → "relay-frames"/"frames",
|
|
124
|
+
* view-only in v1), and a Modal group box serves noVNC/RFB over the 6080 tunnel
|
|
125
|
+
* (→ "vnc-ws"/"novnc", take-control unless the deployment disabled it). Advertising
|
|
126
|
+
* relay-frames for a Modal box (the swap-away case) hands the client a dead relay
|
|
127
|
+
* socket → "desktop stream closed before it opened"; the reverse hands a machine's
|
|
128
|
+
* relay URL to the noVNC renderer. `selfhostedActive` == (activeSandboxId set AND the
|
|
129
|
+
* active sandbox kind is "selfhosted") — EXACTLY mintDesktopStream's routing predicate.
|
|
130
|
+
*/
|
|
131
|
+
export declare function resolveActiveDesktopTransport(selfhostedActive: boolean, interactive: boolean): {
|
|
132
|
+
transport: "relay-frames" | "vnc-ws";
|
|
133
|
+
client: "frames" | "novnc";
|
|
134
|
+
mode: "read-only" | "interactive";
|
|
135
|
+
};
|
|
136
|
+
/** The minted pixel cell the handshake/attach folds into the DesktopStream
|
|
137
|
+
* capability. Null when degraded (no secret, headless backend, display-stack
|
|
138
|
+
* failure, provider tunnel failure) — degradation is a value, never a throw. */
|
|
139
|
+
export type DesktopStreamMint = {
|
|
140
|
+
url: string;
|
|
141
|
+
token: string;
|
|
142
|
+
expiresAt: string;
|
|
143
|
+
resolution: [number, number];
|
|
144
|
+
leaseEpoch: number;
|
|
145
|
+
};
|
|
146
|
+
export type MintDesktopStreamInput = {
|
|
147
|
+
accountId: string;
|
|
148
|
+
workspaceId: string;
|
|
149
|
+
session: Session;
|
|
150
|
+
/** The viewer holder id the scoped token is minted for. */
|
|
151
|
+
viewerId: string;
|
|
152
|
+
/** The live lease (must be warm/draining — the box is up). A selfhosted-active
|
|
153
|
+
* session may have no Modal group lease; omit and the selfhosted branch handles it. */
|
|
154
|
+
lease?: LeaseSnapshot;
|
|
155
|
+
/** The epoch the CALLER last observed the URL minted under. When the live
|
|
156
|
+
* lease epoch is greater, the box rolled over → emit stream.url.rotated to the
|
|
157
|
+
* other viewers. Omit on a first mint (no prior URL to rotate from). */
|
|
158
|
+
previousEpoch?: number;
|
|
159
|
+
/** Test seam: override how the box is re-established by id. Defaults to the
|
|
160
|
+
* real leaf `establishSandboxSessionFromEnvelope`. Production NEVER passes
|
|
161
|
+
* this; it exists so a real-lease integration test can inject a fake provider
|
|
162
|
+
* session carrying `resolveExposedPort` without a live cloud box. */
|
|
163
|
+
establish?: (envelope: Record<string, unknown> | null) => Promise<EstablishedSandboxSession>;
|
|
164
|
+
/** Test seam: inject a fake relay-resolving session for the selfhosted-active
|
|
165
|
+
* branch. Production NEVER passes this. */
|
|
166
|
+
resolveSelfhostedSession?: (sandbox: SandboxRecord) => Promise<{
|
|
167
|
+
resolveExposedPort?: (port: number) => Promise<unknown>;
|
|
168
|
+
}>;
|
|
169
|
+
};
|
|
170
|
+
/**
|
|
171
|
+
* Mint (or re-mint) the desktop pixel cell for a viewer against a WARM box,
|
|
172
|
+
* IN-PROCESS. Returns the minted cell, or null when the desktop tier degrades
|
|
173
|
+
* (no resolvable stream-token secret, a headless backend, a display-stack
|
|
174
|
+
* failure, or a provider-tunnel failure) — the caller surfaces transport:null,
|
|
175
|
+
* never an exception to the user.
|
|
176
|
+
*
|
|
177
|
+
* Idempotent display-stack + resolveExposedPort are safe to call N times. The
|
|
178
|
+
* resolved URL is recorded on the lease (data_plane_url) under the epoch fence; a
|
|
179
|
+
* stale-epoch write (the box re-established under a newer epoch mid-call) is a
|
|
180
|
+
* no-op and the mint returns null rather than disclosing a capability for the
|
|
181
|
+
* superseded provider epoch.
|
|
182
|
+
*/
|
|
183
|
+
export declare function mintDesktopStream(services: ViewerServices, input: MintDesktopStreamInput): Promise<DesktopStreamMint | null>;
|
|
184
|
+
/** The minted terminal cell the handshake/attach folds into the Terminal
|
|
185
|
+
* capability (pty-ws). Null when degraded — the caller surfaces transport
|
|
186
|
+
* "sse-events" (the read-only firehose), never an exception. */
|
|
187
|
+
export type TerminalStreamMint = {
|
|
188
|
+
url: string;
|
|
189
|
+
token: string;
|
|
190
|
+
expiresAt: string;
|
|
191
|
+
leaseEpoch: number;
|
|
192
|
+
};
|
|
193
|
+
export type MintTerminalStreamInput = {
|
|
194
|
+
accountId: string;
|
|
195
|
+
workspaceId: string;
|
|
196
|
+
session: Session;
|
|
197
|
+
/** The viewer holder / principal id the scoped token is minted for. */
|
|
198
|
+
viewerId: string;
|
|
199
|
+
/** The live lease (must be warm/draining — the box is up). A selfhosted-active
|
|
200
|
+
* session may have no Modal group lease; omit and the selfhosted branch handles it. */
|
|
201
|
+
lease?: LeaseSnapshot;
|
|
202
|
+
/** Test seam: override how the box is re-established by id (see
|
|
203
|
+
* MintDesktopStreamInput.establish). Production NEVER passes this. */
|
|
204
|
+
establish?: (envelope: Record<string, unknown> | null) => Promise<EstablishedSandboxSession>;
|
|
205
|
+
/** Test seam: inject a fake relay-resolving session for the selfhosted-active
|
|
206
|
+
* branch. Production NEVER passes this. */
|
|
207
|
+
resolveSelfhostedSession?: (sandbox: SandboxRecord) => Promise<{
|
|
208
|
+
resolveExposedPort?: (port: number) => Promise<unknown>;
|
|
209
|
+
}>;
|
|
210
|
+
};
|
|
211
|
+
/**
|
|
212
|
+
* Mint (or re-mint) the REAL PTY (ttyd pty-ws) terminal cell for a viewer against
|
|
213
|
+
* a WARM box, IN-PROCESS. Returns the minted cell, or null when the terminal tier
|
|
214
|
+
* degrades (terminal off, no resolvable stream-token secret, a headless backend,
|
|
215
|
+
* a ttyd-launch failure, or a provider-tunnel failure) — the caller surfaces the
|
|
216
|
+
* sse-events firehose, never an exception to the user. Mirrors mintDesktopStream.
|
|
217
|
+
*/
|
|
218
|
+
export declare function mintTerminalStream(services: ViewerServices, input: MintTerminalStreamInput): Promise<TerminalStreamMint | null>;
|
|
219
|
+
/** The structural slice of a selfhosted session the relay stream mint needs. */
|
|
220
|
+
type RelayResolvableSession = {
|
|
221
|
+
resolveExposedPort?: (port: number) => Promise<unknown>;
|
|
222
|
+
};
|
|
223
|
+
export type MintSelfhostedStreamInput = {
|
|
224
|
+
workspaceId: string;
|
|
225
|
+
sessionId: string;
|
|
226
|
+
/** The viewer holder / principal id the scoped token is minted for. */
|
|
227
|
+
viewerId: string;
|
|
228
|
+
/** The swap fence: the session's `active_epoch`. The minted `ogs_` token carries
|
|
229
|
+
* THIS as its leaseEpoch claim so the relay rejects a stale-epoch (swapped-away)
|
|
230
|
+
* viewer. */
|
|
231
|
+
activeEpoch: number;
|
|
232
|
+
/** The exposed stream port (6080 desktop / 7681 terminal). */
|
|
233
|
+
port: number;
|
|
234
|
+
/** The resolvable selfhosted session (the routing proxy resolves the active
|
|
235
|
+
* selfhosted backend; its `resolveExposedPort` returns the relay endpoint). */
|
|
236
|
+
session: RelayResolvableSession;
|
|
237
|
+
};
|
|
238
|
+
/**
|
|
239
|
+
* Mint the selfhosted relay stream cell for a viewer against the session's ACTIVE
|
|
240
|
+
* selfhosted machine, IN-PROCESS. Resolves the relay endpoint via the selfhosted
|
|
241
|
+
* session's `resolveExposedPort` and mints the scoped `ogs_` token FENCED BY THE
|
|
242
|
+
* SWAP `active_epoch`. Returns null when the stream tier degrades (no stream-token
|
|
243
|
+
* secret, the agent is offline / cannot ensure a channel) — the caller surfaces
|
|
244
|
+
* transport:null, never an exception.
|
|
245
|
+
*
|
|
246
|
+
* The token is RECORDED against the viewer holder by the caller and is NEVER a URL
|
|
247
|
+
* query param (the relay validates the in-band token); the relay's stale-viewer
|
|
248
|
+
* fence uses the token's leaseEpoch claim (== activeEpoch here).
|
|
249
|
+
*/
|
|
250
|
+
export declare function mintSelfhostedStream(services: ViewerServices, input: MintSelfhostedStreamInput): Promise<TerminalStreamMint | null>;
|
|
251
|
+
export {};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { TranscriptionProvider } from "@opengeni/core";
|
|
2
|
+
export declare function createAzureOpenAiTranscriptionProvider(input: {
|
|
3
|
+
endpoint: string;
|
|
4
|
+
deployment: string;
|
|
5
|
+
apiVersion: string;
|
|
6
|
+
apiKey: string | null;
|
|
7
|
+
adToken: string | null;
|
|
8
|
+
fetch?: typeof fetch;
|
|
9
|
+
}): TranscriptionProvider;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Settings } from "@opengeni/config";
|
|
2
|
+
import { type TranscriptionAvailabilityContext, type TranscriptionProvider } from "@opengeni/core";
|
|
3
|
+
import { type Database } from "@opengeni/db";
|
|
4
|
+
export declare function createCodexSubscriptionTranscriptionProvider(input: {
|
|
5
|
+
settings: Settings;
|
|
6
|
+
db: Database;
|
|
7
|
+
fetch?: typeof fetch;
|
|
8
|
+
probe?: (context?: TranscriptionAvailabilityContext) => boolean | Promise<boolean>;
|
|
9
|
+
}): TranscriptionProvider;
|