@opengeni/api-router 4.1.0 → 4.1.2
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.js +1 -1
- package/dist/{chunk-24C5LPNE.js → chunk-2SSU5KWV.js} +715 -279
- package/dist/{chunk-24C5LPNE.js.map → chunk-2SSU5KWV.js.map} +1 -1
- package/dist/index.js +1 -1
- package/dist/integrations/github-lens-connect.d.ts +1 -1
- package/dist/integrations/slack-interactions.d.ts +1 -1
- package/dist/mcp/documents.d.ts +2 -0
- package/dist/mcp/first-party-tool-permissions.d.ts +8 -0
- package/dist/routes/browser-sessions.d.ts +3 -0
- package/dist/routes/github-browser-pages.d.ts +5 -0
- package/dist/routes/insights.d.ts +26 -0
- package/dist/sandbox-file-artifacts.d.ts +17 -1
- package/package.json +15 -15
- package/src/mcp/documents.ts +9 -2
- package/src/mcp/first-party-tool-permissions.ts +2 -0
- package/src/mcp/knowledge-entries.ts +15 -2
- package/src/mcp/server.ts +51 -2
- package/src/routes/browser-sessions.ts +140 -0
- package/src/routes/computer-sessions.ts +18 -1
- package/src/routes/documents.ts +1 -0
- package/src/routes/files.ts +4 -0
- package/src/routes/github-browser-pages.ts +102 -0
- package/src/routes/github.ts +6 -47
- package/src/routes/insights.ts +70 -6
- package/src/routes/knowledge.ts +19 -0
- package/src/routes/managed-onboarding.ts +3 -0
- package/src/routes/organization-memberships.ts +3 -0
- package/src/routes/sessions.ts +89 -0
- package/src/sandbox/channel-a.ts +8 -0
- package/src/sandbox/viewer.ts +16 -3
- package/src/sandbox-file-artifacts.ts +37 -26
- package/src/workspace-tool-gateway.ts +1 -0
package/dist/index.js
CHANGED
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
createOpenGeniSlackBotInteractionClient,
|
|
8
8
|
startSlackInteractionPump,
|
|
9
9
|
startTemporalScheduleCleanupPump
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-2SSU5KWV.js";
|
|
11
11
|
|
|
12
12
|
// src/index.ts
|
|
13
13
|
import { scheduledTaskKnowledgeSource } from "@opengeni/contracts";
|
|
@@ -46,7 +46,7 @@ export declare function requireGitHubLensConnect(deps: ApiRouteDeps): Promise<{
|
|
|
46
46
|
eager?: boolean | undefined;
|
|
47
47
|
optional?: boolean | undefined;
|
|
48
48
|
}[];
|
|
49
|
-
firstPartyMcpTools: ("artifacts_archive" | "artifacts_create" | "artifacts_get_source" | "artifacts_list" | "artifacts_prepare_upload" | "artifacts_publish" | "artifacts_restore" | "artifacts_rollback" | "atlassian_get" | "atlassian_search" | "atlassian_sources_list" | "browser_act" | "browser_auth" | "browser_clipboard" | "browser_debug" | "browser_identity" | "browser_lifecycle" | "browser_observe" | "browser_open" | "browser_publish" | "browser_tabs" | "capability_authorization_request" | "capability_catalog_search" | "command_read" | "command_wait" | "company_profile_confirm" | "company_profile_propose" | "computer_act" | "computer_clipboard" | "computer_lifecycle" | "computer_observe" | "computer_open" | "computer_targets" | "connected_machine_enroll_token" | "connected_machine_remove" | "editable_artifact_apply" | "editable_artifact_create" | "editable_artifact_export" | "editable_artifact_export_status" | "editable_artifact_get" | "editable_artifact_import" | "editable_artifact_inspect" | "editable_artifact_list" | "environment_list" | "environment_set_variable" | "fiken_bank_accounts_list" | "fiken_companies_list" | "fiken_contact_create" | "fiken_contacts_list" | "fiken_invoice_draft_create" | "fiken_invoice_get" | "fiken_invoices_list" | "fiken_products_list" | "fiken_purchases_list" | "fiken_sales_list" | "github_connect_link" | "github_repositories_list" | "goal_complete" | "goal_pause" | "goal_progress" | "goal_resume" | "goal_set" | "goal_update" | "instruction_policy_get" | "instruction_policy_propose" | "instruction_policy_save" | "interaction_discover" | "interaction_request_human" | "knowledge_archive" | "knowledge_browse" | "knowledge_correct" | "knowledge_get" | "knowledge_prepare_save" | "knowledge_propose" | "knowledge_retain_file" | "knowledge_retain_message" | "knowledge_save" | "knowledge_search" | "memory_correct" | "memory_save" | "memory_search" | "preference_propose" | "preference_registry_get" | "preference_registry_summary" | "project_create" | "project_delete" | "project_get" | "project_list" | "project_reorder" | "project_update" | "reddit_accounts_list" | "reddit_mentions_live" | "reddit_post_reply" | "reddit_posts_sync" | "reddit_search_live" | "reddit_thread_fetch" | "remember" | "remember_confirm" | "rig_get" | "rig_list" | "rig_promote" | "rig_propose_change" | "rig_verify" | "run_on" | "sandbox_attach" | "sandbox_file_publish" | "sandbox_provision" | "sandbox_swap" | "sandboxes_list" | "scheduled_task_runs_list" | "scheduled_tasks_create" | "scheduled_tasks_delete" | "scheduled_tasks_get" | "scheduled_tasks_list" | "scheduled_tasks_pause" | "scheduled_tasks_resume" | "scheduled_tasks_trigger" | "scheduled_tasks_update" | "session_create" | "session_events" | "session_get" | "session_human_input_respond" | "session_pause" | "session_resume" | "session_send_message" | "session_set_project" | "session_steer" | "session_wait" | "sessions_list" | "set_other_session_title" | "set_session_title" | "slack_bot_channel_history" | "slack_bot_delete_message" | "slack_bot_file_content" | "slack_bot_file_info" | "slack_bot_list_channels" | "slack_bot_list_files" | "slack_bot_list_users" | "slack_bot_post_message" | "slack_bot_search" | "slack_bot_thread_replies" | "social_connections_list" | "social_daily_analysis_context" | "social_mentions_live" | "social_post_reply" | "social_posts_recent" | "social_posts_sync" | "social_search_live" | "social_thread_fetch" | "task_note_archive" | "task_note_promote_instruction_policy" | "task_note_promote_knowledge" | "task_note_promote_preference" | "task_note_replace" | "task_note_save" | "task_notes_list" | "variable_set_get_variable" | "variable_set_list" | "variable_set_set_variable" | "wait_for_input" | "work_claim_release" | "work_claim_upsert" | "x_accounts_list" | "x_mentions_live" | "x_post_reply" | "x_posts_sync" | "x_search_live" | "x_thread_fetch")[];
|
|
49
|
+
firstPartyMcpTools: ("artifacts_archive" | "artifacts_create" | "artifacts_get_source" | "artifacts_list" | "artifacts_prepare_upload" | "artifacts_publish" | "artifacts_restore" | "artifacts_rollback" | "atlassian_get" | "atlassian_search" | "atlassian_sources_list" | "browser_act" | "browser_auth" | "browser_clipboard" | "browser_debug" | "browser_identity" | "browser_lifecycle" | "browser_observe" | "browser_open" | "browser_publish" | "browser_read" | "browser_screenshot" | "browser_tabs" | "capability_authorization_request" | "capability_catalog_search" | "command_read" | "command_wait" | "company_profile_confirm" | "company_profile_propose" | "computer_act" | "computer_clipboard" | "computer_lifecycle" | "computer_observe" | "computer_open" | "computer_targets" | "connected_machine_enroll_token" | "connected_machine_remove" | "editable_artifact_apply" | "editable_artifact_create" | "editable_artifact_export" | "editable_artifact_export_status" | "editable_artifact_get" | "editable_artifact_import" | "editable_artifact_inspect" | "editable_artifact_list" | "environment_list" | "environment_set_variable" | "fiken_bank_accounts_list" | "fiken_companies_list" | "fiken_contact_create" | "fiken_contacts_list" | "fiken_invoice_draft_create" | "fiken_invoice_get" | "fiken_invoices_list" | "fiken_products_list" | "fiken_purchases_list" | "fiken_sales_list" | "github_connect_link" | "github_repositories_list" | "goal_complete" | "goal_pause" | "goal_progress" | "goal_resume" | "goal_set" | "goal_update" | "instruction_policy_get" | "instruction_policy_propose" | "instruction_policy_save" | "interaction_discover" | "interaction_request_human" | "knowledge_archive" | "knowledge_browse" | "knowledge_correct" | "knowledge_get" | "knowledge_prepare_save" | "knowledge_propose" | "knowledge_retain_file" | "knowledge_retain_message" | "knowledge_save" | "knowledge_search" | "memory_correct" | "memory_save" | "memory_search" | "preference_propose" | "preference_registry_get" | "preference_registry_summary" | "project_create" | "project_delete" | "project_get" | "project_list" | "project_reorder" | "project_update" | "reddit_accounts_list" | "reddit_mentions_live" | "reddit_post_reply" | "reddit_posts_sync" | "reddit_search_live" | "reddit_thread_fetch" | "remember" | "remember_confirm" | "rig_get" | "rig_list" | "rig_promote" | "rig_propose_change" | "rig_verify" | "run_on" | "sandbox_attach" | "sandbox_file_publish" | "sandbox_provision" | "sandbox_swap" | "sandboxes_list" | "scheduled_task_runs_list" | "scheduled_tasks_create" | "scheduled_tasks_delete" | "scheduled_tasks_get" | "scheduled_tasks_list" | "scheduled_tasks_pause" | "scheduled_tasks_resume" | "scheduled_tasks_trigger" | "scheduled_tasks_update" | "session_create" | "session_events" | "session_get" | "session_human_input_respond" | "session_pause" | "session_resume" | "session_send_message" | "session_set_project" | "session_steer" | "session_wait" | "sessions_list" | "set_other_session_title" | "set_session_title" | "slack_bot_channel_history" | "slack_bot_delete_message" | "slack_bot_file_content" | "slack_bot_file_info" | "slack_bot_list_channels" | "slack_bot_list_files" | "slack_bot_list_users" | "slack_bot_post_message" | "slack_bot_search" | "slack_bot_thread_replies" | "social_connections_list" | "social_daily_analysis_context" | "social_mentions_live" | "social_post_reply" | "social_posts_recent" | "social_posts_sync" | "social_search_live" | "social_thread_fetch" | "task_note_archive" | "task_note_promote_instruction_policy" | "task_note_promote_knowledge" | "task_note_promote_preference" | "task_note_replace" | "task_note_save" | "task_notes_list" | "variable_set_get_variable" | "variable_set_list" | "variable_set_set_variable" | "wait_for_input" | "work_claim_release" | "work_claim_upsert" | "x_accounts_list" | "x_mentions_live" | "x_post_reply" | "x_posts_sync" | "x_search_live" | "x_thread_fetch")[];
|
|
50
50
|
firstPartyMcpPermissions: ("account:admin" | "account:read" | "api_keys:manage" | "artifacts:publish" | "artifacts:read" | "billing:manage" | "billing:read" | "capabilities:manage" | "codemode:call" | "connections:read" | "connections:write" | "documents:manage" | "documents:search" | "enrollments:manage" | "enrollments:read" | "environments:manage" | "environments:use" | "files:read" | "files:upload" | "files:write" | "github:manage" | "github:use" | "goals:manage" | "mcp_servers:attach" | "members:manage" | "rigs:manage" | "rigs:use" | "scheduled_tasks:manage" | "scheduled_tasks:run" | "secrets:list" | "secrets:read" | "secrets:write" | "sessions:control" | "sessions:create" | "sessions:read" | "stream:acknowledge" | "stream:control" | "stream:view" | "terminal:attach" | "variable-sets:attach" | "variable-sets:list" | "variable-sets:manage" | "variable-sets:read" | "variable-sets:use" | "variable-sets:write" | "workspace:admin" | "workspace:create" | "workspace:read")[];
|
|
51
51
|
model: string | null;
|
|
52
52
|
reasoningEffort: "high" | "low" | "max" | "medium" | "minimal" | "none" | "xhigh" | null;
|
|
@@ -19,7 +19,7 @@ export declare const SLACK_SESSION_INSTRUCTIONS: string;
|
|
|
19
19
|
* selection at session creation while keeping Slack mutations out of the model
|
|
20
20
|
* surface; interaction delivery remains owned by the durable delivery pump.
|
|
21
21
|
*/
|
|
22
|
-
export declare const SLACK_TASK_FIRST_PARTY_MCP_TOOLS: ("artifacts_archive" | "artifacts_create" | "artifacts_get_source" | "artifacts_list" | "artifacts_prepare_upload" | "artifacts_publish" | "artifacts_restore" | "artifacts_rollback" | "atlassian_get" | "atlassian_search" | "atlassian_sources_list" | "browser_act" | "browser_auth" | "browser_clipboard" | "browser_debug" | "browser_identity" | "browser_lifecycle" | "browser_observe" | "browser_open" | "browser_publish" | "browser_tabs" | "capability_authorization_request" | "capability_catalog_search" | "command_read" | "command_wait" | "company_profile_confirm" | "company_profile_propose" | "computer_act" | "computer_clipboard" | "computer_lifecycle" | "computer_observe" | "computer_open" | "computer_targets" | "connected_machine_enroll_token" | "connected_machine_remove" | "editable_artifact_apply" | "editable_artifact_create" | "editable_artifact_export" | "editable_artifact_export_status" | "editable_artifact_get" | "editable_artifact_import" | "editable_artifact_inspect" | "editable_artifact_list" | "environment_list" | "environment_set_variable" | "fiken_bank_accounts_list" | "fiken_companies_list" | "fiken_contact_create" | "fiken_contacts_list" | "fiken_invoice_draft_create" | "fiken_invoice_get" | "fiken_invoices_list" | "fiken_products_list" | "fiken_purchases_list" | "fiken_sales_list" | "github_connect_link" | "github_repositories_list" | "goal_complete" | "goal_pause" | "goal_progress" | "goal_resume" | "goal_set" | "goal_update" | "instruction_policy_get" | "instruction_policy_propose" | "instruction_policy_save" | "interaction_discover" | "interaction_request_human" | "knowledge_archive" | "knowledge_browse" | "knowledge_correct" | "knowledge_get" | "knowledge_prepare_save" | "knowledge_propose" | "knowledge_retain_file" | "knowledge_retain_message" | "knowledge_save" | "knowledge_search" | "memory_correct" | "memory_save" | "memory_search" | "preference_propose" | "preference_registry_get" | "preference_registry_summary" | "project_create" | "project_delete" | "project_get" | "project_list" | "project_reorder" | "project_update" | "reddit_accounts_list" | "reddit_mentions_live" | "reddit_post_reply" | "reddit_posts_sync" | "reddit_search_live" | "reddit_thread_fetch" | "remember" | "remember_confirm" | "rig_get" | "rig_list" | "rig_promote" | "rig_propose_change" | "rig_verify" | "run_on" | "sandbox_attach" | "sandbox_file_publish" | "sandbox_provision" | "sandbox_swap" | "sandboxes_list" | "scheduled_task_runs_list" | "scheduled_tasks_create" | "scheduled_tasks_delete" | "scheduled_tasks_get" | "scheduled_tasks_list" | "scheduled_tasks_pause" | "scheduled_tasks_resume" | "scheduled_tasks_trigger" | "scheduled_tasks_update" | "session_create" | "session_events" | "session_get" | "session_human_input_respond" | "session_pause" | "session_resume" | "session_send_message" | "session_set_project" | "session_steer" | "session_wait" | "sessions_list" | "set_other_session_title" | "set_session_title" | "slack_bot_channel_history" | "slack_bot_delete_message" | "slack_bot_file_content" | "slack_bot_file_info" | "slack_bot_list_channels" | "slack_bot_list_files" | "slack_bot_list_users" | "slack_bot_post_message" | "slack_bot_search" | "slack_bot_thread_replies" | "social_connections_list" | "social_daily_analysis_context" | "social_mentions_live" | "social_post_reply" | "social_posts_recent" | "social_posts_sync" | "social_search_live" | "social_thread_fetch" | "task_note_archive" | "task_note_promote_instruction_policy" | "task_note_promote_knowledge" | "task_note_promote_preference" | "task_note_replace" | "task_note_save" | "task_notes_list" | "variable_set_get_variable" | "variable_set_list" | "variable_set_set_variable" | "wait_for_input" | "work_claim_release" | "work_claim_upsert" | "x_accounts_list" | "x_mentions_live" | "x_post_reply" | "x_posts_sync" | "x_search_live" | "x_thread_fetch")[];
|
|
22
|
+
export declare const SLACK_TASK_FIRST_PARTY_MCP_TOOLS: ("artifacts_archive" | "artifacts_create" | "artifacts_get_source" | "artifacts_list" | "artifacts_prepare_upload" | "artifacts_publish" | "artifacts_restore" | "artifacts_rollback" | "atlassian_get" | "atlassian_search" | "atlassian_sources_list" | "browser_act" | "browser_auth" | "browser_clipboard" | "browser_debug" | "browser_identity" | "browser_lifecycle" | "browser_observe" | "browser_open" | "browser_publish" | "browser_read" | "browser_screenshot" | "browser_tabs" | "capability_authorization_request" | "capability_catalog_search" | "command_read" | "command_wait" | "company_profile_confirm" | "company_profile_propose" | "computer_act" | "computer_clipboard" | "computer_lifecycle" | "computer_observe" | "computer_open" | "computer_targets" | "connected_machine_enroll_token" | "connected_machine_remove" | "editable_artifact_apply" | "editable_artifact_create" | "editable_artifact_export" | "editable_artifact_export_status" | "editable_artifact_get" | "editable_artifact_import" | "editable_artifact_inspect" | "editable_artifact_list" | "environment_list" | "environment_set_variable" | "fiken_bank_accounts_list" | "fiken_companies_list" | "fiken_contact_create" | "fiken_contacts_list" | "fiken_invoice_draft_create" | "fiken_invoice_get" | "fiken_invoices_list" | "fiken_products_list" | "fiken_purchases_list" | "fiken_sales_list" | "github_connect_link" | "github_repositories_list" | "goal_complete" | "goal_pause" | "goal_progress" | "goal_resume" | "goal_set" | "goal_update" | "instruction_policy_get" | "instruction_policy_propose" | "instruction_policy_save" | "interaction_discover" | "interaction_request_human" | "knowledge_archive" | "knowledge_browse" | "knowledge_correct" | "knowledge_get" | "knowledge_prepare_save" | "knowledge_propose" | "knowledge_retain_file" | "knowledge_retain_message" | "knowledge_save" | "knowledge_search" | "memory_correct" | "memory_save" | "memory_search" | "preference_propose" | "preference_registry_get" | "preference_registry_summary" | "project_create" | "project_delete" | "project_get" | "project_list" | "project_reorder" | "project_update" | "reddit_accounts_list" | "reddit_mentions_live" | "reddit_post_reply" | "reddit_posts_sync" | "reddit_search_live" | "reddit_thread_fetch" | "remember" | "remember_confirm" | "rig_get" | "rig_list" | "rig_promote" | "rig_propose_change" | "rig_verify" | "run_on" | "sandbox_attach" | "sandbox_file_publish" | "sandbox_provision" | "sandbox_swap" | "sandboxes_list" | "scheduled_task_runs_list" | "scheduled_tasks_create" | "scheduled_tasks_delete" | "scheduled_tasks_get" | "scheduled_tasks_list" | "scheduled_tasks_pause" | "scheduled_tasks_resume" | "scheduled_tasks_trigger" | "scheduled_tasks_update" | "session_create" | "session_events" | "session_get" | "session_human_input_respond" | "session_pause" | "session_resume" | "session_send_message" | "session_set_project" | "session_steer" | "session_wait" | "sessions_list" | "set_other_session_title" | "set_session_title" | "slack_bot_channel_history" | "slack_bot_delete_message" | "slack_bot_file_content" | "slack_bot_file_info" | "slack_bot_list_channels" | "slack_bot_list_files" | "slack_bot_list_users" | "slack_bot_post_message" | "slack_bot_search" | "slack_bot_thread_replies" | "social_connections_list" | "social_daily_analysis_context" | "social_mentions_live" | "social_post_reply" | "social_posts_recent" | "social_posts_sync" | "social_search_live" | "social_thread_fetch" | "task_note_archive" | "task_note_promote_instruction_policy" | "task_note_promote_knowledge" | "task_note_promote_preference" | "task_note_replace" | "task_note_save" | "task_notes_list" | "variable_set_get_variable" | "variable_set_list" | "variable_set_set_variable" | "wait_for_input" | "work_claim_release" | "work_claim_upsert" | "x_accounts_list" | "x_mentions_live" | "x_post_reply" | "x_posts_sync" | "x_search_live" | "x_thread_fetch")[];
|
|
23
23
|
export type NormalizedSlackInteraction = {
|
|
24
24
|
providerEventId: string;
|
|
25
25
|
providerMessageId: string;
|
package/dist/mcp/documents.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { Settings } from "@opengeni/config";
|
|
1
2
|
import type { DocumentServices } from "@opengeni/documents";
|
|
2
3
|
import { type KnowledgeContext, type Database } from "@opengeni/db";
|
|
3
4
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
@@ -5,4 +6,5 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
5
6
|
* chunk/base retrieval is retired: it cannot bypass pending Knowledge review. */
|
|
6
7
|
export declare function buildDocumentsMcpServer(db: Database, accountId: string, workspaceId: string, documentServices: DocumentServices, options: {
|
|
7
8
|
knowledge: KnowledgeContext;
|
|
9
|
+
settings?: Settings;
|
|
8
10
|
}): McpServer;
|
|
@@ -271,6 +271,14 @@ export declare const FIRST_PARTY_TOOL_AUTHORIZATION: {
|
|
|
271
271
|
sessionRequired: true;
|
|
272
272
|
allOf: "sessions:read"[];
|
|
273
273
|
};
|
|
274
|
+
browser_read: {
|
|
275
|
+
sessionRequired: true;
|
|
276
|
+
allOf: "sessions:read"[];
|
|
277
|
+
};
|
|
278
|
+
browser_screenshot: {
|
|
279
|
+
sessionRequired: true;
|
|
280
|
+
allOf: "sessions:read"[];
|
|
281
|
+
};
|
|
274
282
|
browser_act: {
|
|
275
283
|
sessionRequired: true;
|
|
276
284
|
allOf: "sessions:control"[];
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { InteractionActor, type AccessGrant, type FileAsset } from "@opengeni/contracts";
|
|
2
2
|
import { type ApiRouteDeps, type ResolvedSessionAuthorization } from "@opengeni/core";
|
|
3
|
+
import { type BrowserControlScreenshotOptions, type BrowserControlFrame } from "@opengeni/runtime/sandbox";
|
|
3
4
|
import type { Hono } from "hono";
|
|
4
5
|
export declare function registerBrowserSessionRoutes(app: Hono, deps: ApiRouteDeps): void;
|
|
5
6
|
export declare function browserNeedsStandaloneDisplayStack(input: {
|
|
@@ -7,6 +8,8 @@ export declare function browserNeedsStandaloneDisplayStack(input: {
|
|
|
7
8
|
linkedComputer: boolean;
|
|
8
9
|
nativeBrowserControl: boolean;
|
|
9
10
|
}): boolean;
|
|
11
|
+
export declare function parseBrowserScreenshotOptions(query: URLSearchParams): BrowserControlScreenshotOptions;
|
|
12
|
+
export declare function browserScreenshotResponse(frame: Pick<BrowserControlFrame, "data" | "mediaType" | "metadataHeader">): Response;
|
|
10
13
|
export declare function interactionActorForGrant(grant: AccessGrant): ReturnType<typeof InteractionActor.parse>;
|
|
11
14
|
/** Resolve Drive authority from the same immutable session authorization that
|
|
12
15
|
* admitted the browser action. Agent-attempt subjects are technical worker
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { GitHubInstallationBindingCandidate } from "@opengeni/contracts";
|
|
2
|
+
export declare function githubInstallationChooserHtml(candidates: GitHubInstallationBindingCandidate[], state: string, workspaceId: string, baseUrl: string): string;
|
|
3
|
+
export declare function githubSetupSuccessHtml(account: string, returnUrl: string): string;
|
|
4
|
+
export declare function githubSetupPendingHtml(): string;
|
|
5
|
+
export declare function githubSuccessHtml(envLines: string[]): string;
|
|
@@ -1,4 +1,30 @@
|
|
|
1
1
|
import { type ApiRouteDeps, type WorkspaceInsightsFilterField } from "@opengeni/core";
|
|
2
2
|
import type { Hono } from "hono";
|
|
3
3
|
export declare function normalizeWorkspaceInsightsQueryFilter(value: string | null | undefined, field: WorkspaceInsightsFilterField): string | null;
|
|
4
|
+
/**
|
|
5
|
+
* Share one in-flight computation between identical concurrent callers.
|
|
6
|
+
*
|
|
7
|
+
* A reload, a second tab, or a retried fetch joins the rollup already running
|
|
8
|
+
* instead of starting another multi-second aggregate on the database.
|
|
9
|
+
* Authorization and filter validation still run per request before a caller
|
|
10
|
+
* may join; the map only ever holds running work. The caller supplies the
|
|
11
|
+
* complete sharing key, which must identify everything the result depends on.
|
|
12
|
+
*/
|
|
13
|
+
export declare function createInFlightCoalescer<T>(): {
|
|
14
|
+
run: (key: string, work: () => Promise<T>) => Promise<T>;
|
|
15
|
+
readonly size: number;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Insights rows are filtered by the database RLS actor (private sessions are
|
|
19
|
+
* visible only to their owner), so two administrators of one workspace can
|
|
20
|
+
* legitimately receive different responses. The actor identity is part of
|
|
21
|
+
* the key; requests share work only when every visibility input is equal.
|
|
22
|
+
*/
|
|
23
|
+
export declare function workspaceInsightsCoalesceKey(input: {
|
|
24
|
+
workspaceId: string;
|
|
25
|
+
range: string;
|
|
26
|
+
provider: string | null;
|
|
27
|
+
model: string | null;
|
|
28
|
+
rlsActor: string | null;
|
|
29
|
+
}): string;
|
|
4
30
|
export declare function registerInsightsRoutes(app: Hono, deps: ApiRouteDeps): void;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { SandboxFileArtifactReceipt, type AccessGrant, type Session } from "@opengeni/contracts";
|
|
2
2
|
import { type ApiRouteDeps, type AccessGrantAuthorization } from "@opengeni/core";
|
|
3
3
|
import { getFile } from "@opengeni/db";
|
|
4
|
+
import { type SandboxChannelAService } from "@opengeni/runtime/sandbox";
|
|
4
5
|
/** Original bytes inherit the already-authorized source session's ownership. */
|
|
5
6
|
export declare function publishSandboxFileArtifact(deps: ApiRouteDeps, input: Parameters<typeof publishSandboxFileArtifactInScope>[1]): Promise<SandboxFileArtifactReceipt>;
|
|
6
7
|
declare function publishSandboxFileArtifactInScope(deps: ApiRouteDeps, input: {
|
|
@@ -10,7 +11,22 @@ declare function publishSandboxFileArtifactInScope(deps: ApiRouteDeps, input: {
|
|
|
10
11
|
path: string;
|
|
11
12
|
signal?: AbortSignal | undefined;
|
|
12
13
|
}): Promise<SandboxFileArtifactReceipt>;
|
|
13
|
-
|
|
14
|
+
/** Use the root captured by the same fenced Channel-A service that reads bytes,
|
|
15
|
+
* not the session's placement-home label or a caller-supplied root. */
|
|
16
|
+
export declare function readSandboxArtifactFile(service: Pick<SandboxChannelAService, "capabilities" | "fsRead">, value: string, maxBytes: number): Promise<{
|
|
17
|
+
path: string;
|
|
18
|
+
sandboxPath: string;
|
|
19
|
+
read: {
|
|
20
|
+
path: string;
|
|
21
|
+
encoding: "base64" | "utf8";
|
|
22
|
+
content: string;
|
|
23
|
+
sizeBytes: number;
|
|
24
|
+
truncated: boolean;
|
|
25
|
+
isBinary: boolean;
|
|
26
|
+
revision: number;
|
|
27
|
+
};
|
|
28
|
+
}>;
|
|
29
|
+
export declare function sandboxArtifactRelativePath(value: string, workspaceRoot?: string): string;
|
|
14
30
|
export declare function sandboxArtifactSafeFilename(filename: string): string;
|
|
15
31
|
export declare function sandboxFileContentType(filename: string): string;
|
|
16
32
|
export declare function sandboxArtifactIdentity(input: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opengeni/api-router",
|
|
3
|
-
"version": "4.1.
|
|
3
|
+
"version": "4.1.2",
|
|
4
4
|
"description": "OpenGeni HTTP surface: the Hono adapter/router (createApp), routes, MCP HTTP transport, and HTTP access adapters over @opengeni/core. An engine-distribution surface — its runtime closure includes engine-internal packages.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -44,22 +44,22 @@
|
|
|
44
44
|
"@llamaindex/liteparse": "2.14.2",
|
|
45
45
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
46
46
|
"@opengeni/agent-proto": "^0.6.0",
|
|
47
|
-
"@opengeni/artifact-tool": "^0.3.
|
|
47
|
+
"@opengeni/artifact-tool": "^0.3.32",
|
|
48
48
|
"@opengeni/capabilities": "^0.3.4",
|
|
49
|
-
"@opengeni/codemode": "^0.6.
|
|
50
|
-
"@opengeni/codex": "^0.2.
|
|
51
|
-
"@opengeni/config": "^2.1.
|
|
52
|
-
"@opengeni/contracts": "^5.1.
|
|
53
|
-
"@opengeni/core": "^4.0.
|
|
54
|
-
"@opengeni/db": "^6.0.
|
|
55
|
-
"@opengeni/documents": "^0.8.
|
|
56
|
-
"@opengeni/events": "^0.4.
|
|
57
|
-
"@opengeni/github": "^0.7.
|
|
49
|
+
"@opengeni/codemode": "^0.6.2",
|
|
50
|
+
"@opengeni/codex": "^0.2.26",
|
|
51
|
+
"@opengeni/config": "^2.1.1",
|
|
52
|
+
"@opengeni/contracts": "^5.1.1",
|
|
53
|
+
"@opengeni/core": "^4.0.3",
|
|
54
|
+
"@opengeni/db": "^6.0.3",
|
|
55
|
+
"@opengeni/documents": "^0.8.34",
|
|
56
|
+
"@opengeni/events": "^0.4.32",
|
|
57
|
+
"@opengeni/github": "^0.7.16",
|
|
58
58
|
"@opengeni/network": "^0.3.1",
|
|
59
|
-
"@opengeni/observability": "^0.8.
|
|
60
|
-
"@opengeni/runtime": "^4.0.
|
|
61
|
-
"@opengeni/storage": "^0.2.
|
|
62
|
-
"@opengeni/tool-gateway": "^0.1.
|
|
59
|
+
"@opengeni/observability": "^0.8.32",
|
|
60
|
+
"@opengeni/runtime": "^4.0.3",
|
|
61
|
+
"@opengeni/storage": "^0.2.133",
|
|
62
|
+
"@opengeni/tool-gateway": "^0.1.13",
|
|
63
63
|
"@opengeni/xai-subscription": "^0.1.4",
|
|
64
64
|
"@temporalio/client": "^1.17.0",
|
|
65
65
|
"better-auth": "^1.6.14",
|
package/src/mcp/documents.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { searchKnowledgeEntries } from "@opengeni/core";
|
|
2
|
+
import type { Settings } from "@opengeni/config";
|
|
2
3
|
import { KnowledgeEntryListRequest } from "@opengeni/contracts";
|
|
3
4
|
import type { DocumentServices } from "@opengeni/documents";
|
|
4
5
|
import {
|
|
@@ -17,7 +18,7 @@ export function buildDocumentsMcpServer(
|
|
|
17
18
|
accountId: string,
|
|
18
19
|
workspaceId: string,
|
|
19
20
|
documentServices: DocumentServices,
|
|
20
|
-
options: { knowledge: KnowledgeContext },
|
|
21
|
+
options: { knowledge: KnowledgeContext; settings?: Settings },
|
|
21
22
|
): McpServer {
|
|
22
23
|
const context = options.knowledge;
|
|
23
24
|
if (context.accountId !== accountId || context.workspaceId !== workspaceId)
|
|
@@ -39,7 +40,13 @@ export function buildDocumentsMcpServer(
|
|
|
39
40
|
{
|
|
40
41
|
type: "text",
|
|
41
42
|
text: JSON.stringify(
|
|
42
|
-
await searchKnowledgeEntries(
|
|
43
|
+
await searchKnowledgeEntries(
|
|
44
|
+
db,
|
|
45
|
+
context,
|
|
46
|
+
input,
|
|
47
|
+
() => documentServices.embedder,
|
|
48
|
+
options.settings,
|
|
49
|
+
),
|
|
43
50
|
),
|
|
44
51
|
},
|
|
45
52
|
],
|
|
@@ -117,6 +117,8 @@ export const FIRST_PARTY_TOOL_AUTHORIZATION = {
|
|
|
117
117
|
browser_open: { sessionRequired: true, allOf: ["sessions:control"] },
|
|
118
118
|
browser_tabs: { sessionRequired: true, allOf: ["sessions:control"] },
|
|
119
119
|
browser_observe: { sessionRequired: true, allOf: ["sessions:read"] },
|
|
120
|
+
browser_read: { sessionRequired: true, allOf: ["sessions:read"] },
|
|
121
|
+
browser_screenshot: { sessionRequired: true, allOf: ["sessions:read"] },
|
|
120
122
|
browser_act: { sessionRequired: true, allOf: ["sessions:control"] },
|
|
121
123
|
browser_clipboard: { sessionRequired: true, allOf: ["sessions:read"] },
|
|
122
124
|
browser_debug: { sessionRequired: true, allOf: ["sessions:read"] },
|
|
@@ -142,7 +142,14 @@ export function registerKnowledgeEntryTools(
|
|
|
142
142
|
},
|
|
143
143
|
(input) =>
|
|
144
144
|
run((context) =>
|
|
145
|
-
prepareKnowledgeSave(
|
|
145
|
+
prepareKnowledgeSave(
|
|
146
|
+
deps.db,
|
|
147
|
+
context,
|
|
148
|
+
input,
|
|
149
|
+
() => deps.getDocumentServices().embedder,
|
|
150
|
+
undefined,
|
|
151
|
+
deps.settings,
|
|
152
|
+
),
|
|
146
153
|
),
|
|
147
154
|
);
|
|
148
155
|
server.registerTool(
|
|
@@ -159,7 +166,13 @@ export function registerKnowledgeEntryTools(
|
|
|
159
166
|
},
|
|
160
167
|
(input) =>
|
|
161
168
|
run((context) =>
|
|
162
|
-
searchKnowledgeEntries(
|
|
169
|
+
searchKnowledgeEntries(
|
|
170
|
+
deps.db,
|
|
171
|
+
context,
|
|
172
|
+
input,
|
|
173
|
+
() => deps.getDocumentServices().embedder,
|
|
174
|
+
deps.settings,
|
|
175
|
+
),
|
|
163
176
|
),
|
|
164
177
|
);
|
|
165
178
|
server.registerTool(
|
package/src/mcp/server.ts
CHANGED
|
@@ -5854,9 +5854,14 @@ function registerCapabilityDiscoveryTools(
|
|
|
5854
5854
|
// This is a readiness projection, never credential resolution or a new grant.
|
|
5855
5855
|
const setupProjections = async (items: CapabilityCatalogItem[]) => {
|
|
5856
5856
|
const availableServerIds = new Set<string>();
|
|
5857
|
+
const availableFirstPartyTools = new Set<string>();
|
|
5857
5858
|
const acceptedServerIds = new Set<string>();
|
|
5858
5859
|
let accountSelectionKnown = false;
|
|
5859
|
-
if (
|
|
5860
|
+
if (
|
|
5861
|
+
items.some(
|
|
5862
|
+
(item) => item.enabled && (item.connectionRef || item.surfaceType === "first_party_fiken"),
|
|
5863
|
+
)
|
|
5864
|
+
) {
|
|
5860
5865
|
const claims = exactAgentCommandContext(grant, sessionId);
|
|
5861
5866
|
const attemptCatalog = await getAttemptToolCatalog(deps.db, {
|
|
5862
5867
|
accountId: grant.accountId,
|
|
@@ -5868,7 +5873,11 @@ function registerCapabilityDiscoveryTools(
|
|
|
5868
5873
|
attemptCatalog.turnId === claims.callerTurnId &&
|
|
5869
5874
|
attemptCatalog.executionGeneration === claims.callerExecutionGeneration
|
|
5870
5875
|
) {
|
|
5871
|
-
for (const entry of attemptCatalog.entries)
|
|
5876
|
+
for (const entry of attemptCatalog.entries) {
|
|
5877
|
+
availableServerIds.add(entry.identity.serverId);
|
|
5878
|
+
if (entry.identity.serverId === "opengeni")
|
|
5879
|
+
availableFirstPartyTools.add(entry.identity.toolName);
|
|
5880
|
+
}
|
|
5872
5881
|
const bindings = await getSessionTurnMcpAccountBindings(
|
|
5873
5882
|
deps.db,
|
|
5874
5883
|
grant.workspaceId,
|
|
@@ -5890,6 +5899,8 @@ function registerCapabilityDiscoveryTools(
|
|
|
5890
5899
|
availableServerIds,
|
|
5891
5900
|
acceptedServerIds,
|
|
5892
5901
|
accountSelectionKnown,
|
|
5902
|
+
availableFirstPartyTools,
|
|
5903
|
+
new Set((grant.metadata?.firstPartyMcpTools as string[] | undefined) ?? []),
|
|
5893
5904
|
),
|
|
5894
5905
|
),
|
|
5895
5906
|
);
|
|
@@ -6024,7 +6035,45 @@ async function capabilitySetupProjection(
|
|
|
6024
6035
|
availableServerIds: ReadonlySet<string>,
|
|
6025
6036
|
acceptedServerIds: ReadonlySet<string>,
|
|
6026
6037
|
accountSelectionKnown: boolean,
|
|
6038
|
+
availableFirstPartyTools: ReadonlySet<string>,
|
|
6039
|
+
selectedFirstPartyTools: ReadonlySet<string>,
|
|
6027
6040
|
): Promise<CapabilitySetupProjection> {
|
|
6041
|
+
if (item.surfaceType === "first_party_fiken") {
|
|
6042
|
+
if (!item.enabled || item.metadata.connectionStatus !== "active") {
|
|
6043
|
+
return {
|
|
6044
|
+
status: "authorization_required",
|
|
6045
|
+
action: "connect",
|
|
6046
|
+
detail:
|
|
6047
|
+
item.metadata.connectionStatus === "needs_reauth"
|
|
6048
|
+
? "The workspace Fiken connection needs reconnection."
|
|
6049
|
+
: "Connect Fiken through the protected setup form.",
|
|
6050
|
+
};
|
|
6051
|
+
}
|
|
6052
|
+
const selected = FIRST_PARTY_MCP_TOOL_NAMES.filter(
|
|
6053
|
+
(name) => name.startsWith("fiken_") && selectedFirstPartyTools.has(name),
|
|
6054
|
+
);
|
|
6055
|
+
if (selected.length === 0) {
|
|
6056
|
+
return {
|
|
6057
|
+
status: "authorization_required",
|
|
6058
|
+
action: "enable",
|
|
6059
|
+
detail:
|
|
6060
|
+
"Fiken is connected, but its tools are not selected for this conversation. Ask the user to add its tools through the setup card, then send a new message.",
|
|
6061
|
+
};
|
|
6062
|
+
}
|
|
6063
|
+
if (selected.some((name) => availableFirstPartyTools.has(name))) {
|
|
6064
|
+
return {
|
|
6065
|
+
status: "ready",
|
|
6066
|
+
action: null,
|
|
6067
|
+
detail: "Fiken is connected and has tools available in this turn.",
|
|
6068
|
+
};
|
|
6069
|
+
}
|
|
6070
|
+
return {
|
|
6071
|
+
status: "unavailable",
|
|
6072
|
+
action: null,
|
|
6073
|
+
detail:
|
|
6074
|
+
"Fiken is connected, but its selected tools are unavailable in this execution. Check tool permissions and setup before retrying; reconnection is not required by this status.",
|
|
6075
|
+
};
|
|
6076
|
+
}
|
|
6028
6077
|
if (item.id === "api:github-app" || item.surfaceType === "first_party_github") {
|
|
6029
6078
|
const missing = githubAppMissingSettings(deps.settings);
|
|
6030
6079
|
if (missing.length > 0) {
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
environmentsEncryptionKeyBytes,
|
|
4
4
|
resolveFirstPartyDelegationSecret,
|
|
5
5
|
resolveStreamTokenSecret,
|
|
6
|
+
sandboxWarmRateMicrosPerSecond,
|
|
6
7
|
} from "@opengeni/config";
|
|
7
8
|
import {
|
|
8
9
|
BROWSER_CONTROL_WEBSOCKET_BEARER_PREFIX,
|
|
@@ -20,6 +21,8 @@ import {
|
|
|
20
21
|
BrowserDownloadSaveRequest,
|
|
21
22
|
BrowserDownloadSaveResponse,
|
|
22
23
|
BrowserDownloadListResponse,
|
|
24
|
+
BrowserDomReadRequest,
|
|
25
|
+
BrowserDomReadResponse,
|
|
23
26
|
BrowserExternalAuthCommand,
|
|
24
27
|
BrowserOpenTargetRequest,
|
|
25
28
|
BrowserObservation,
|
|
@@ -31,6 +34,7 @@ import {
|
|
|
31
34
|
BrowserSessionListResponse,
|
|
32
35
|
BrowserSessionMutationResponse,
|
|
33
36
|
BrowserTargetListResponse,
|
|
37
|
+
BrowserTargetState,
|
|
34
38
|
CreateBrowserSessionRequest,
|
|
35
39
|
ExternalAuthInteractiveRequest,
|
|
36
40
|
ExternalAuthInteractiveResponse,
|
|
@@ -75,6 +79,7 @@ import {
|
|
|
75
79
|
BrowserSessionNotFoundError,
|
|
76
80
|
BrowserSessionOperationConflictError,
|
|
77
81
|
BrowserSessionStateError,
|
|
82
|
+
SandboxPaidComputeAdmissionError,
|
|
78
83
|
completeBrowserSessionEnd,
|
|
79
84
|
completeExternalAuth,
|
|
80
85
|
completeBrowserDownloadSave,
|
|
@@ -88,6 +93,7 @@ import {
|
|
|
88
93
|
dispatchExternalAuth,
|
|
89
94
|
dispatchProtectedAuthFill,
|
|
90
95
|
failBrowserSessionOperation,
|
|
96
|
+
failPreparedBrowserSessionSuspend,
|
|
91
97
|
failBrowserSessionResume,
|
|
92
98
|
failBrowserSessionResumePreparation,
|
|
93
99
|
failBrowserSessionSuspension,
|
|
@@ -169,6 +175,8 @@ import {
|
|
|
169
175
|
provisionBrowserControlClient,
|
|
170
176
|
renewSandboxProviderExpiration,
|
|
171
177
|
type BrowserControlPlacementSession,
|
|
178
|
+
type BrowserControlScreenshotOptions,
|
|
179
|
+
type BrowserControlFrame,
|
|
172
180
|
type PlacementBrowserStateCaptureReceipt,
|
|
173
181
|
type PlacementBrowserNetworkRoute,
|
|
174
182
|
type PlacementBrowserTransport,
|
|
@@ -952,6 +960,71 @@ export function registerBrowserSessionRoutes(app: Hono, deps: ApiRouteDeps): voi
|
|
|
952
960
|
},
|
|
953
961
|
);
|
|
954
962
|
|
|
963
|
+
app.get(
|
|
964
|
+
"/v1/workspaces/:workspaceId/browser-sessions/:browserSessionId/targets/:targetId/state",
|
|
965
|
+
async (context) => {
|
|
966
|
+
const { workspaceId, grant, browserSessionId } = await browserRoutePreamble(
|
|
967
|
+
context,
|
|
968
|
+
"sessions:read",
|
|
969
|
+
);
|
|
970
|
+
const targetId = requireOpaqueParam(context, "targetId");
|
|
971
|
+
const result = await withActiveBrowserController(
|
|
972
|
+
context,
|
|
973
|
+
grant,
|
|
974
|
+
workspaceId,
|
|
975
|
+
browserSessionId,
|
|
976
|
+
"session.read",
|
|
977
|
+
"browser.read",
|
|
978
|
+
async ({ sessionClient }) => await sessionClient.targetState(targetId),
|
|
979
|
+
);
|
|
980
|
+
return context.json(BrowserTargetState.parse(result));
|
|
981
|
+
},
|
|
982
|
+
);
|
|
983
|
+
|
|
984
|
+
app.post(
|
|
985
|
+
"/v1/workspaces/:workspaceId/browser-sessions/:browserSessionId/targets/:targetId/dom-read",
|
|
986
|
+
async (context) => {
|
|
987
|
+
const { workspaceId, grant, browserSessionId } = await browserRoutePreamble(
|
|
988
|
+
context,
|
|
989
|
+
"sessions:read",
|
|
990
|
+
);
|
|
991
|
+
const targetId = requireOpaqueParam(context, "targetId");
|
|
992
|
+
const request = await parseJsonBody(context, BrowserDomReadRequest);
|
|
993
|
+
const result = await withActiveBrowserController(
|
|
994
|
+
context,
|
|
995
|
+
grant,
|
|
996
|
+
workspaceId,
|
|
997
|
+
browserSessionId,
|
|
998
|
+
"session.read",
|
|
999
|
+
"browser.read",
|
|
1000
|
+
async ({ sessionClient }) => await sessionClient.readDom(targetId, request),
|
|
1001
|
+
);
|
|
1002
|
+
return context.json(BrowserDomReadResponse.parse(result));
|
|
1003
|
+
},
|
|
1004
|
+
);
|
|
1005
|
+
|
|
1006
|
+
app.get(
|
|
1007
|
+
"/v1/workspaces/:workspaceId/browser-sessions/:browserSessionId/targets/:targetId/screenshot",
|
|
1008
|
+
async (context) => {
|
|
1009
|
+
const { workspaceId, grant, browserSessionId } = await browserRoutePreamble(
|
|
1010
|
+
context,
|
|
1011
|
+
"sessions:read",
|
|
1012
|
+
);
|
|
1013
|
+
const targetId = requireOpaqueParam(context, "targetId");
|
|
1014
|
+
const captureOptions = parseBrowserScreenshotOptions(new URL(context.req.url).searchParams);
|
|
1015
|
+
const frame = await withActiveBrowserController(
|
|
1016
|
+
context,
|
|
1017
|
+
grant,
|
|
1018
|
+
workspaceId,
|
|
1019
|
+
browserSessionId,
|
|
1020
|
+
"session.read",
|
|
1021
|
+
"browser.read",
|
|
1022
|
+
async ({ sessionClient }) => await sessionClient.capture(targetId, captureOptions),
|
|
1023
|
+
);
|
|
1024
|
+
return browserScreenshotResponse(frame);
|
|
1025
|
+
},
|
|
1026
|
+
);
|
|
1027
|
+
|
|
955
1028
|
app.post(
|
|
956
1029
|
"/v1/workspaces/:workspaceId/browser-sessions/:browserSessionId/actions",
|
|
957
1030
|
async (context) => {
|
|
@@ -2047,6 +2120,7 @@ export function registerBrowserSessionRoutes(app: Hono, deps: ApiRouteDeps): voi
|
|
|
2047
2120
|
const request = await parseJsonBody(context, BrowserSessionLifecycleRequest);
|
|
2048
2121
|
const startedAtMs = performance.now();
|
|
2049
2122
|
const origin = requestOrigin(context, deps.settings);
|
|
2123
|
+
let preparedForDispatch = false;
|
|
2050
2124
|
try {
|
|
2051
2125
|
const before = await getBrowserSessionControlRecord(deps.db, {
|
|
2052
2126
|
accountId: grant.accountId,
|
|
@@ -2098,6 +2172,8 @@ export function registerBrowserSessionRoutes(app: Hono, deps: ApiRouteDeps): voi
|
|
|
2098
2172
|
return context.json(parsed, 200);
|
|
2099
2173
|
}
|
|
2100
2174
|
|
|
2175
|
+
preparedForDispatch = true;
|
|
2176
|
+
|
|
2101
2177
|
const record = await getBrowserSessionControlRecord(deps.db, {
|
|
2102
2178
|
accountId: grant.accountId,
|
|
2103
2179
|
workspaceId,
|
|
@@ -2259,6 +2335,15 @@ export function registerBrowserSessionRoutes(app: Hono, deps: ApiRouteDeps): voi
|
|
|
2259
2335
|
observeLifecycleResult(deps.observability, startedAtMs, parsed);
|
|
2260
2336
|
return context.json(parsed, 200);
|
|
2261
2337
|
} catch (error) {
|
|
2338
|
+
if (preparedForDispatch) {
|
|
2339
|
+
await failPreparedBrowserSessionSuspend(deps.db, {
|
|
2340
|
+
accountId: grant.accountId,
|
|
2341
|
+
workspaceId,
|
|
2342
|
+
browserSessionId,
|
|
2343
|
+
operationId: request.operationId,
|
|
2344
|
+
error: interactionFailure(error),
|
|
2345
|
+
});
|
|
2346
|
+
}
|
|
2262
2347
|
throw browserRouteError(error);
|
|
2263
2348
|
}
|
|
2264
2349
|
},
|
|
@@ -2792,6 +2877,12 @@ export function registerBrowserSessionRoutes(app: Hono, deps: ApiRouteDeps): voi
|
|
|
2792
2877
|
resolved.kind !== "selfhosted" ||
|
|
2793
2878
|
resolved.sandboxId !== expectedPlacement.sandboxId
|
|
2794
2879
|
) {
|
|
2880
|
+
if (operation === "browser.create") {
|
|
2881
|
+
throw new HTTPException(422, {
|
|
2882
|
+
message:
|
|
2883
|
+
"The requested Connected Machine is not this session's current placement. Move the session to that machine before creating an interaction resource.",
|
|
2884
|
+
});
|
|
2885
|
+
}
|
|
2795
2886
|
return await throwBrowserSourcePlacementChanged(
|
|
2796
2887
|
grant,
|
|
2797
2888
|
sourceSession.id,
|
|
@@ -3748,6 +3839,10 @@ async function ensureInteractionHolder(
|
|
|
3748
3839
|
holderId: interactionHolderId(browserSessionId),
|
|
3749
3840
|
subjectId: sourceSession.id,
|
|
3750
3841
|
backend: placement.lease.backend,
|
|
3842
|
+
warmBilling: {
|
|
3843
|
+
mode: deps.settings.sandboxWarmBillingMode,
|
|
3844
|
+
rateMicrosPerSecond: sandboxWarmRateMicrosPerSecond(deps.settings, placement.lease.backend),
|
|
3845
|
+
},
|
|
3751
3846
|
os: placement.lease.os,
|
|
3752
3847
|
image: sandboxRuntime.image,
|
|
3753
3848
|
rigVersionId: sourceSession.rigVersionId,
|
|
@@ -4160,6 +4255,49 @@ function optionalBoundedInteger(
|
|
|
4160
4255
|
return parsed;
|
|
4161
4256
|
}
|
|
4162
4257
|
|
|
4258
|
+
export function parseBrowserScreenshotOptions(
|
|
4259
|
+
query: URLSearchParams,
|
|
4260
|
+
): BrowserControlScreenshotOptions {
|
|
4261
|
+
for (const key of ["fullPage", "format", "quality"]) {
|
|
4262
|
+
if (query.getAll(key).length > 1) {
|
|
4263
|
+
throw new HTTPException(400, { message: `duplicate browser screenshot ${key}` });
|
|
4264
|
+
}
|
|
4265
|
+
}
|
|
4266
|
+
const fullPage = query.get("fullPage");
|
|
4267
|
+
if (fullPage !== null && fullPage !== "true" && fullPage !== "false") {
|
|
4268
|
+
throw new HTTPException(400, { message: "invalid browser screenshot fullPage" });
|
|
4269
|
+
}
|
|
4270
|
+
const format = query.get("format");
|
|
4271
|
+
if (format !== null && format !== "jpeg" && format !== "png") {
|
|
4272
|
+
throw new HTTPException(400, { message: "invalid browser screenshot format" });
|
|
4273
|
+
}
|
|
4274
|
+
const quality = query.get("quality");
|
|
4275
|
+
const parsedQuality = quality === null ? null : optionalBoundedInteger(quality, 1, 100);
|
|
4276
|
+
if (quality !== null && parsedQuality === null) {
|
|
4277
|
+
throw new HTTPException(400, { message: "invalid browser screenshot quality" });
|
|
4278
|
+
}
|
|
4279
|
+
return {
|
|
4280
|
+
...(fullPage === null ? {} : { fullPage: fullPage === "true" }),
|
|
4281
|
+
...(format === null ? {} : { format }),
|
|
4282
|
+
...(parsedQuality === null ? {} : { quality: parsedQuality }),
|
|
4283
|
+
};
|
|
4284
|
+
}
|
|
4285
|
+
|
|
4286
|
+
export function browserScreenshotResponse(
|
|
4287
|
+
frame: Pick<BrowserControlFrame, "data" | "mediaType" | "metadataHeader">,
|
|
4288
|
+
): Response {
|
|
4289
|
+
// Node Buffers can be views into a larger backing allocation. Copy exactly
|
|
4290
|
+
// the image bytes before constructing the Response.
|
|
4291
|
+
return new Response(Uint8Array.from(frame.data).buffer, {
|
|
4292
|
+
status: 200,
|
|
4293
|
+
headers: {
|
|
4294
|
+
"cache-control": "no-store",
|
|
4295
|
+
"content-type": frame.mediaType,
|
|
4296
|
+
"x-opengeni-browser-frame": frame.metadataHeader,
|
|
4297
|
+
},
|
|
4298
|
+
});
|
|
4299
|
+
}
|
|
4300
|
+
|
|
4163
4301
|
function isUuid(value: unknown): value is string {
|
|
4164
4302
|
return (
|
|
4165
4303
|
typeof value === "string" &&
|
|
@@ -4716,6 +4854,8 @@ function browserRouteError(error: unknown): HTTPException {
|
|
|
4716
4854
|
const connectedMachineError = interactionControlApiError(error, "browser");
|
|
4717
4855
|
if (connectedMachineError) return connectedMachineError;
|
|
4718
4856
|
if (error instanceof HTTPException) return error;
|
|
4857
|
+
if (error instanceof SandboxPaidComputeAdmissionError)
|
|
4858
|
+
return new HTTPException(402, { message: error.message, cause: error });
|
|
4719
4859
|
if (error instanceof BrowserSessionNotFoundError) {
|
|
4720
4860
|
return new HTTPException(404, { message: error.message, cause: error });
|
|
4721
4861
|
}
|