@opengeni/api-router 2.10.0-canary.0 → 2.10.0-canary.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.
@@ -1,5 +1,8 @@
1
1
  import { type AccessGrant, type AttemptToolCatalog, type CodemodeCallSubmission as CodemodeCallSubmissionValue, type CodemodeOperation } from "@opengeni/contracts";
2
2
  import type { ApiRouteDeps } from "@opengeni/core";
3
+ /** Reuse normal REST handlers, including their resource/command authorization.
4
+ * The exact attempt is checked before issuing this internal-only credential. */
5
+ export declare function codemodeSessionRequest(deps: ApiRouteDeps, grant: AccessGrant, request: Request, path: string): Promise<Request>;
3
6
  export type CodemodeGrantAuthority = {
4
7
  accountId: string;
5
8
  workspaceId: string;
package/dist/index.js CHANGED
@@ -7,7 +7,7 @@ import {
7
7
  createOpenGeniSlackBotInteractionClient,
8
8
  startSlackInteractionPump,
9
9
  startTemporalScheduleCleanupPump
10
- } from "./chunk-XXH7DW34.js";
10
+ } from "./chunk-BVX5ASFY.js";
11
11
 
12
12
  // src/index.ts
13
13
  import {
@@ -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_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_wait" | "company_profile_confirm" | "company_profile_propose" | "computer_act" | "computer_clipboard" | "computer_lifecycle" | "computer_observe" | "computer_open" | "computer_targets" | "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_set" | "goal_update" | "instruction_policy_propose" | "interaction_discover" | "interaction_request_human" | "knowledge_correct" | "knowledge_propose" | "memory_correct" | "memory_save" | "memory_search" | "preference_propose" | "preference_registry_get" | "preference_registry_summary" | "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_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_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_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_propose" | "interaction_discover" | "interaction_request_human" | "knowledge_correct" | "knowledge_propose" | "memory_correct" | "memory_save" | "memory_search" | "preference_propose" | "preference_registry_get" | "preference_registry_summary" | "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_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;
@@ -0,0 +1,24 @@
1
+ import type { SessionEvent } from "@opengeni/contracts";
2
+ export declare const COMMAND_READ_MAX_WAIT_SECONDS = 50;
3
+ export declare const COMMAND_WAIT_DEFAULT_SECONDS = 45;
4
+ /** Shared provider-neutral command_read/command_wait operation. Notifications
5
+ * are only wake hints. Every result, including timeout, comes from durable DB
6
+ * state. Subscribe before reading to close the finish/subscription race, and
7
+ * periodically recheck durable state because live hints may be lost. */
8
+ export declare function readCommandWithWait<T extends {
9
+ terminal: boolean;
10
+ chunks: unknown[];
11
+ hasMore?: boolean;
12
+ }>(input: {
13
+ commandId: string;
14
+ waitSeconds?: number;
15
+ maxOutputBytes?: number | undefined;
16
+ signal?: AbortSignal;
17
+ read: () => Promise<T>;
18
+ subscribe: (onEvents: (events: SessionEvent[]) => void) => Promise<() => void>;
19
+ }): Promise<T & {
20
+ waitedMs: number;
21
+ timedOut: boolean;
22
+ aborted: boolean;
23
+ liveFanout: boolean;
24
+ }>;
@@ -82,8 +82,8 @@ export declare function scheduledTaskMcpSummary(task: ScheduledTask): {
82
82
  runMode: "existing_session" | "new_session_per_run" | "reusable_session";
83
83
  overlapPolicy: "allow_concurrent" | "buffer_one" | "skip";
84
84
  action: {
85
+ readonly sourceId?: never;
85
86
  kind: "agent_turn";
86
- sourceId?: never;
87
87
  sourceGeneration?: never;
88
88
  sourceLifecycleGeneration?: never;
89
89
  destination?: never;
@@ -205,8 +205,8 @@ export declare function boundScheduledTaskDetailMcp(task: ScheduledTask): {
205
205
  runMode: "existing_session" | "new_session_per_run" | "reusable_session";
206
206
  overlapPolicy: "allow_concurrent" | "buffer_one" | "skip";
207
207
  action: {
208
+ readonly sourceId?: never;
208
209
  kind: "agent_turn";
209
- sourceId?: never;
210
210
  sourceGeneration?: never;
211
211
  sourceLifecycleGeneration?: never;
212
212
  destination?: never;
@@ -389,8 +389,8 @@ export declare function boundScheduledTaskMcpPage(input: {
389
389
  runMode: "existing_session" | "new_session_per_run" | "reusable_session";
390
390
  overlapPolicy: "allow_concurrent" | "buffer_one" | "skip";
391
391
  action: {
392
+ readonly sourceId?: never;
392
393
  kind: "agent_turn";
393
- sourceId?: never;
394
394
  sourceGeneration?: never;
395
395
  sourceLifecycleGeneration?: never;
396
396
  destination?: never;
@@ -195,6 +195,7 @@ export declare function capSessionDiscoveryCompactPage(page: Awaited<ReturnType<
195
195
  previewOmissionReason: "aggregatePreviewBudget";
196
196
  previewDrillDownTool: "session_events";
197
197
  previewDrillDownInput: {
198
+ view: "debug";
198
199
  direction: "before";
199
200
  limit: 1;
200
201
  mode: "monitoring";
@@ -309,6 +310,7 @@ export declare function capSessionDiscoveryPage(page: Awaited<ReturnType<typeof
309
310
  omissionReason: "aggregatePreviewBudget" | null;
310
311
  drillDownTool: "session_events";
311
312
  drillDownInput: {
313
+ view: "debug";
312
314
  direction: "before";
313
315
  limit: 1;
314
316
  mode: "monitoring";
@@ -0,0 +1,86 @@
1
+ import { z } from "zod";
2
+ import type { SessionEventSliceOptions, SessionEventSlicePage } from "@opengeni/db/session-event-slices";
3
+ export declare const SESSION_EVENT_VIEW_MAX_BYTES: number;
4
+ declare const selectionSchema: z.ZodObject<{
5
+ sessionId: z.ZodString;
6
+ view: z.ZodEnum<{
7
+ conversation: "conversation";
8
+ results: "results";
9
+ tools: "tools";
10
+ }>;
11
+ includeArguments: z.ZodBoolean;
12
+ includeOutput: z.ZodBoolean;
13
+ callId: z.ZodNullable<z.ZodString>;
14
+ direction: z.ZodEnum<{
15
+ after: "after";
16
+ before: "before";
17
+ }>;
18
+ after: z.ZodNumber;
19
+ before: z.ZodNullable<z.ZodNumber>;
20
+ }, z.core.$strip>;
21
+ type Selection = z.infer<typeof selectionSchema>;
22
+ export type SessionEventViewInput = {
23
+ sessionId: string;
24
+ view?: Selection["view"] | undefined;
25
+ after?: number | undefined;
26
+ before?: number | undefined;
27
+ direction?: Selection["direction"] | undefined;
28
+ limit?: number | undefined;
29
+ cursor?: string | undefined;
30
+ callId?: string | undefined;
31
+ includeArguments?: boolean | undefined;
32
+ includeOutput?: boolean | undefined;
33
+ };
34
+ type Item = {
35
+ sequence: number;
36
+ turnId?: string;
37
+ role?: string;
38
+ text?: string;
39
+ } & Record<string, unknown>;
40
+ type ReadPage = (options: SessionEventSliceOptions) => Promise<SessionEventSlicePage>;
41
+ /** A cursor is a bounded selector, never authority. The caller reauthorizes every read. */
42
+ export declare function resolveSessionEventView(input: SessionEventViewInput): {
43
+ selection: {
44
+ sessionId: string;
45
+ view: "conversation" | "results" | "tools";
46
+ includeArguments: boolean;
47
+ includeOutput: boolean;
48
+ callId: string | null;
49
+ direction: "after" | "before";
50
+ after: number;
51
+ before: number | null;
52
+ };
53
+ continuation: {
54
+ v: 1 | 2;
55
+ selection: {
56
+ sessionId: string;
57
+ view: "conversation" | "results" | "tools";
58
+ includeArguments: boolean;
59
+ includeOutput: boolean;
60
+ callId: string | null;
61
+ direction: "after" | "before";
62
+ after: number;
63
+ before: number | null;
64
+ };
65
+ sequence: number | null;
66
+ offset: number;
67
+ } | null;
68
+ };
69
+ /** Read-only projection over the existing RLS/audit query. No command observations. */
70
+ export declare function readSessionEventView(input: SessionEventViewInput, read: ReadPage): Promise<{
71
+ view: "conversation" | "results" | "tools";
72
+ direction: "after" | "before";
73
+ events: Item[];
74
+ nextAfter: number | null;
75
+ nextBefore: number | null;
76
+ hasMore: boolean;
77
+ nextCursor: string | null;
78
+ sourceExact: boolean;
79
+ sourceLoss?: {
80
+ reason: "structured_value_exceeds_budget";
81
+ completeTextAvailable: false;
82
+ message: string;
83
+ };
84
+ maxBytes: number;
85
+ }>;
86
+ export {};
@@ -3,11 +3,13 @@ import type { Session } from "@opengeni/contracts";
3
3
  import { type Database, type LeaseSnapshot } from "@opengeni/db";
4
4
  import { type EventBus } from "@opengeni/events";
5
5
  import { type Observability } from "@opengeni/observability";
6
+ import type { ObjectStorage } from "@opengeni/storage";
6
7
  import { SandboxChannelAService, type ChannelASession, type EstablishedSandboxSession, type RoutingSandboxSession } from "@opengeni/runtime/sandbox";
7
8
  export type ChannelAServices = {
8
9
  db: Database;
9
10
  settings: Settings;
10
11
  bus: EventBus;
12
+ objectStorage?: ObjectStorage | null;
11
13
  observability?: Observability | undefined;
12
14
  };
13
15
  export type ChannelAOperation = "fs.list" | "fs.list-batch" | "fs.read" | "artifact.publish" | "fs.write" | "fs.delete" | "fs.move" | "fs.mkdir" | "git.status" | "git.diff" | "git.read-batch" | "git.log" | "git.show" | "terminal.exec" | "terminal.pty.open" | "terminal.pty.write" | "terminal.pty.resize" | "terminal.pty.close" | "browser.create" | "browser.resume" | "browser.suspend" | "browser.end" | "browser.read" | "browser.action" | "browser.control" | "browser.download.save" | "browser.attach" | "computer.create" | "computer.end" | "computer.read" | "computer.action" | "computer.control" | "computer.attach";
@@ -1,6 +1,7 @@
1
1
  import type { Settings } from "@opengeni/config";
2
2
  import { type Database, type LeaseSnapshot } from "@opengeni/db";
3
3
  import { type EstablishedSandboxSession } from "@opengeni/runtime/sandbox";
4
+ import type { ObjectStorage } from "@opengeni/storage";
4
5
  /** The sole API-direct cold->warming owner path used by Channel A and viewer
5
6
  * attach. It never publishes warm until archive identity, hydrated tree, command
6
7
  * routing, provider identity, and the selected rematerialization attempt all
@@ -18,6 +19,7 @@ export declare function establishApiSandboxSpawner(input: {
18
19
  acquiredLease: LeaseSnapshot;
19
20
  fallbackEnvelope: Record<string, unknown> | null;
20
21
  dataPlaneUrl: string | null;
22
+ objectStorage?: ObjectStorage | null;
21
23
  }): Promise<{
22
24
  established: EstablishedSandboxSession;
23
25
  lease: LeaseSnapshot;
@@ -2,6 +2,7 @@ import type { Settings } from "@opengeni/config";
2
2
  import { type Session } from "@opengeni/contracts";
3
3
  import { type Database, type LeaseSnapshot, type SandboxRecord } from "@opengeni/db";
4
4
  import { type EventBus } from "@opengeni/events";
5
+ import type { ObjectStorage } from "@opengeni/storage";
5
6
  import { establishSandboxSessionFromEnvelope, type EstablishedSandboxSession } from "@opengeni/runtime/sandbox";
6
7
  /** The minimal services a viewer op needs: the DB + settings (lease cadence +
7
8
  * the sandbox client construction the leaf reads from settings). The bus is
@@ -11,6 +12,7 @@ export type ViewerServices = {
11
12
  db: Database;
12
13
  settings: Settings;
13
14
  bus?: EventBus;
15
+ objectStorage?: ObjectStorage | null;
14
16
  /** Provider-establish dependency used by API-direct readiness and stream
15
17
  * operations. Production uses the runtime leaf; isolated tests may supply a
16
18
  * deterministic provider without replacing a process-global module. */
@@ -0,0 +1,264 @@
1
+ // src/mcp/session-event-view.ts
2
+ import { z } from "zod";
3
+ var SESSION_EVENT_VIEW_MAX_BYTES = 16 * 1024;
4
+ var selectionSchema = z.object({
5
+ sessionId: z.string().uuid(),
6
+ view: z.enum(["conversation", "results", "tools"]),
7
+ includeArguments: z.boolean(),
8
+ includeOutput: z.boolean(),
9
+ callId: z.string().max(512).refine(
10
+ (value) => Buffer.byteLength(JSON.stringify(value), "utf8") <= 2048,
11
+ "callId exceeds the encoded cursor budget"
12
+ ).nullable(),
13
+ direction: z.enum(["before", "after"]),
14
+ after: z.number().int().nonnegative(),
15
+ before: z.number().int().positive().nullable()
16
+ });
17
+ var cursorSchema = z.object({
18
+ v: z.union([z.literal(1), z.literal(2)]),
19
+ selection: selectionSchema,
20
+ sequence: z.number().int().positive().nullable(),
21
+ offset: z.number().int().nonnegative().max(2147483647)
22
+ });
23
+ var types = {
24
+ conversation: ["user.message", "agent.message.completed"],
25
+ // Final turn output is authoritative: do not repeat message.completed text.
26
+ results: ["turn.completed", "turn.failed", "goal.completed", "goal.paused", "tool.auth_needed"],
27
+ tools: ["agent.toolCall.created", "agent.toolCall.output"]
28
+ };
29
+ var record = (value) => value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
30
+ var bytes = (value) => Buffer.byteLength(JSON.stringify(value, null, 2), "utf8");
31
+ var encode = (selection, sequence = null, offset = 0, v = 2) => Buffer.from(JSON.stringify({ v, selection, sequence, offset })).toString("base64url");
32
+ function resolveSessionEventView(input) {
33
+ let continuation = null;
34
+ if (input.cursor !== void 0) {
35
+ if (input.cursor.length > 4096)
36
+ throw new Error("session_events cursor exceeds 4096 characters");
37
+ try {
38
+ continuation = cursorSchema.parse(
39
+ JSON.parse(Buffer.from(input.cursor, "base64url").toString())
40
+ );
41
+ } catch {
42
+ throw new Error("Invalid session_events cursor");
43
+ }
44
+ const previous = continuation.selection;
45
+ for (const key of [
46
+ "sessionId",
47
+ "view",
48
+ "includeArguments",
49
+ "includeOutput",
50
+ "callId",
51
+ "direction",
52
+ "after",
53
+ "before"
54
+ ]) {
55
+ if (input[key] !== void 0 && input[key] !== previous[key]) {
56
+ throw new Error(`session_events cursor cannot change ${key}`);
57
+ }
58
+ }
59
+ }
60
+ const selection = continuation?.selection ?? selectionSchema.parse({
61
+ sessionId: input.sessionId,
62
+ view: input.view ?? "conversation",
63
+ includeArguments: input.includeArguments ?? false,
64
+ includeOutput: input.includeOutput ?? false,
65
+ callId: input.callId ?? null,
66
+ direction: input.direction ?? (input.before !== void 0 || input.after === void 0 ? "before" : "after"),
67
+ after: input.after ?? 0,
68
+ before: input.before ?? null
69
+ });
70
+ if (selection.view !== "tools" && (selection.callId || selection.includeArguments || selection.includeOutput)) {
71
+ throw new Error("callId/includeArguments/includeOutput require view=tools");
72
+ }
73
+ if (continuation && (continuation.sequence === null && continuation.offset !== 0 || continuation.sequence !== null && (continuation.sequence <= selection.after || selection.before !== null && continuation.sequence >= selection.before)))
74
+ throw new Error("Invalid session_events cursor position");
75
+ return { selection, continuation };
76
+ }
77
+ function project(event, selection) {
78
+ if (event.duplicateOfEventId || event.turnAssociation && event.turnAssociation !== "current")
79
+ return null;
80
+ const p = record(event.payload);
81
+ const base = { sequence: event.sequence, ...event.turnId ? { turnId: event.turnId } : {} };
82
+ if (selection.view === "conversation") {
83
+ return typeof p.text === "string" && p.text.length > 0 ? { ...base, role: event.type === "user.message" ? "user" : "assistant", text: p.text } : null;
84
+ }
85
+ if (selection.view === "results") {
86
+ if (event.type === "turn.completed" && ("maintenance" in p || "segmentLimit" in p)) return null;
87
+ const value2 = event.type === "turn.completed" ? p.output ?? p.result : p;
88
+ if (value2 === void 0 || value2 === "") return null;
89
+ return {
90
+ ...base,
91
+ type: event.type,
92
+ text: typeof value2 === "string" ? value2 : JSON.stringify(value2)
93
+ };
94
+ }
95
+ const callId = p.callId ?? p.call_id ?? p.id;
96
+ if (selection.callId !== null && selection.callId !== callId) return null;
97
+ const output = event.type === "agent.toolCall.output";
98
+ const value = output ? p.output : p.arguments ?? p.args;
99
+ return {
100
+ ...base,
101
+ callId,
102
+ kind: output ? "result" : "call",
103
+ ...typeof p.name === "string" ? { name: p.name } : {},
104
+ ...p.identityOmitted === true ? { identityOmitted: true } : {},
105
+ ...p.isError === true || record(value).isError === true ? { isError: true } : {},
106
+ ...(output ? selection.includeOutput : selection.includeArguments) && value !== void 0 ? {
107
+ text: typeof value === "string" ? value : JSON.stringify(value),
108
+ encoding: typeof value === "string" ? "text" : "json"
109
+ } : {}
110
+ };
111
+ }
112
+ async function readSessionEventView(input, read) {
113
+ const { selection, continuation } = resolveSessionEventView(input);
114
+ const limit = Math.max(1, Math.min(50, input.limit ?? 10));
115
+ let after = continuation?.sequence && selection.direction === "after" ? continuation.sequence - 1 : selection.after;
116
+ let before = continuation?.sequence && selection.direction === "before" ? continuation.sequence + 1 : selection.before;
117
+ const events = [];
118
+ let hasMore = false;
119
+ let nextCursor = null;
120
+ let sourceExact = true;
121
+ let edge = null;
122
+ let legacyActive = continuation?.v === 1 && continuation.sequence !== null;
123
+ const page = () => ({
124
+ view: selection.view,
125
+ direction: selection.direction,
126
+ events,
127
+ nextAfter: selection.direction === "after" ? edge ?? selection.after : null,
128
+ nextBefore: selection.direction === "before" ? edge ?? selection.before : null,
129
+ hasMore,
130
+ nextCursor,
131
+ sourceExact,
132
+ ...!sourceExact ? {
133
+ sourceLoss: {
134
+ reason: "structured_value_exceeds_budget",
135
+ completeTextAvailable: false,
136
+ message: "An oversized structured value was omitted. Scalar text remains retrievable through continuation; omitted structured values are not complete JSON results."
137
+ }
138
+ } : {},
139
+ maxBytes: SESSION_EVENT_VIEW_MAX_BYTES
140
+ });
141
+ const resume = (sequence, offset = 0) => {
142
+ hasMore = true;
143
+ nextCursor = encode(
144
+ { ...selection, after, before },
145
+ sequence,
146
+ offset,
147
+ legacyActive && sequence === continuation?.sequence ? 1 : 2
148
+ );
149
+ };
150
+ for (let scan = 0; scan < 64; scan += 1) {
151
+ const source = await read({
152
+ after,
153
+ ...before === null ? {} : { before },
154
+ direction: selection.direction,
155
+ limit: 8,
156
+ includeTypes: types[selection.view],
157
+ payloadMode: "full",
158
+ excludeUnclaimedHumanPrompts: true,
159
+ maxBytes: 1024 * 1024,
160
+ legacyUtf16: legacyActive,
161
+ view: selection.view,
162
+ includeArguments: selection.includeArguments,
163
+ includeOutput: selection.includeOutput,
164
+ ...continuation?.sequence ? { sourceSequence: continuation.sequence, sourceOffset: continuation.offset } : {}
165
+ });
166
+ sourceExact &&= source.fullPayloadsExact;
167
+ const ordered = selection.direction === "before" ? [...source.events].reverse() : source.events;
168
+ for (const event of ordered) {
169
+ const slice = source.slices?.[event.sequence];
170
+ const item = project(event, selection);
171
+ if (item) {
172
+ const offset = continuation?.sequence === event.sequence ? continuation.offset : 0;
173
+ if (slice?.omitted) {
174
+ delete item.text;
175
+ item.sourceOmitted = { reason: "structured_value_exceeds_budget", complete: false };
176
+ }
177
+ const advance = (text) => slice?.unit === "codepoint" ? Array.from(text).length : text.length;
178
+ if (!slice && offset > (item.text?.length ?? 0))
179
+ throw new Error("Invalid message continuation offset");
180
+ if (!slice && offset > 0 && item.text && /[\uD800-\uDBFF]/.test(item.text[offset - 1]) && /[\uDC00-\uDFFF]/.test(item.text[offset] ?? "")) {
181
+ throw new Error("Invalid message continuation offset: splits a surrogate pair");
182
+ }
183
+ if (!slice && offset > 0 && item.text) item.text = item.text.slice(offset);
184
+ events.push(item);
185
+ if (bytes(page()) > SESSION_EVENT_VIEW_MAX_BYTES - 4096) {
186
+ events.pop();
187
+ if (events.length > 0) {
188
+ resume(event.sequence, offset);
189
+ if (selection.direction === "before") events.reverse();
190
+ return page();
191
+ }
192
+ if (!item.text) throw new Error("Session event identity exceeds page budget");
193
+ const text = item.text;
194
+ let low = 0;
195
+ let high = text.length;
196
+ while (low < high) {
197
+ const mid = Math.ceil((low + high) / 2);
198
+ if (bytes({ ...item, text: text.slice(0, mid) }) <= SESSION_EVENT_VIEW_MAX_BYTES - 6e3)
199
+ low = mid;
200
+ else high = mid - 1;
201
+ }
202
+ if (low > 0 && /[\uD800-\uDBFF]/.test(text[low - 1])) low -= 1;
203
+ if (low === 0) throw new Error("Session event identity exceeds page budget");
204
+ events.push({
205
+ ...item,
206
+ text: text.slice(0, low),
207
+ fragment: {
208
+ offset,
209
+ nextOffset: offset + advance(text.slice(0, low)),
210
+ complete: false,
211
+ unit: slice?.unit ?? "utf16"
212
+ }
213
+ });
214
+ resume(event.sequence, offset + advance(text.slice(0, low)));
215
+ return page();
216
+ }
217
+ const nextOffset = offset + advance(item.text ?? "");
218
+ if (slice && !slice.omitted && nextOffset < slice.total) {
219
+ item.fragment = { offset, nextOffset, complete: false, unit: slice.unit };
220
+ resume(event.sequence, nextOffset);
221
+ if (selection.direction === "before") events.reverse();
222
+ return page();
223
+ }
224
+ if (offset > 0)
225
+ item.fragment = {
226
+ offset,
227
+ nextOffset,
228
+ complete: true,
229
+ unit: slice?.unit ?? "utf16"
230
+ };
231
+ }
232
+ edge = event.sequence;
233
+ legacyActive = false;
234
+ if (selection.direction === "after") after = event.sequence;
235
+ else before = event.sequence;
236
+ if (events.length >= limit) {
237
+ hasMore = source.hasMore || event !== ordered.at(-1);
238
+ nextCursor = hasMore ? encode({ ...selection, after, before }) : null;
239
+ if (selection.direction === "before") events.reverse();
240
+ return page();
241
+ }
242
+ }
243
+ if (source.coveredSequence) {
244
+ if (selection.direction === "after") after = Math.max(after, source.coveredSequence.last);
245
+ else before = Math.min(before ?? Infinity, source.coveredSequence.first);
246
+ }
247
+ if (!source.hasMore) {
248
+ hasMore = false;
249
+ nextCursor = null;
250
+ if (selection.direction === "before") events.reverse();
251
+ return page();
252
+ }
253
+ hasMore = true;
254
+ nextCursor = encode({ ...selection, after, before });
255
+ }
256
+ if (selection.direction === "before") events.reverse();
257
+ return page();
258
+ }
259
+ export {
260
+ SESSION_EVENT_VIEW_MAX_BYTES,
261
+ readSessionEventView,
262
+ resolveSessionEventView
263
+ };
264
+ //# sourceMappingURL=session-event-view-MU3HMNV3.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/mcp/session-event-view.ts"],"sourcesContent":["import { z } from \"zod\";\nimport type { SessionEvent, SessionEventType } from \"@opengeni/contracts\";\nimport type {\n SessionEventSliceOptions,\n SessionEventSlicePage,\n} from \"@opengeni/db/session-event-slices\";\n\nexport const SESSION_EVENT_VIEW_MAX_BYTES = 16 * 1024;\nconst selectionSchema = z.object({\n sessionId: z.string().uuid(),\n view: z.enum([\"conversation\", \"results\", \"tools\"]),\n includeArguments: z.boolean(),\n includeOutput: z.boolean(),\n callId: z\n .string()\n .max(512)\n .refine(\n (value) => Buffer.byteLength(JSON.stringify(value), \"utf8\") <= 2048,\n \"callId exceeds the encoded cursor budget\",\n )\n .nullable(),\n direction: z.enum([\"before\", \"after\"]),\n after: z.number().int().nonnegative(),\n before: z.number().int().positive().nullable(),\n});\nconst cursorSchema = z.object({\n v: z.union([z.literal(1), z.literal(2)]),\n selection: selectionSchema,\n sequence: z.number().int().positive().nullable(),\n offset: z.number().int().nonnegative().max(2_147_483_647),\n});\ntype Selection = z.infer<typeof selectionSchema>;\nexport type SessionEventViewInput = {\n sessionId: string;\n view?: Selection[\"view\"] | undefined;\n after?: number | undefined;\n before?: number | undefined;\n direction?: Selection[\"direction\"] | undefined;\n limit?: number | undefined;\n cursor?: string | undefined;\n callId?: string | undefined;\n includeArguments?: boolean | undefined;\n includeOutput?: boolean | undefined;\n};\ntype Item = { sequence: number; turnId?: string; role?: string; text?: string } & Record<\n string,\n unknown\n>;\ntype ReadPage = (options: SessionEventSliceOptions) => Promise<SessionEventSlicePage>;\nconst types: Record<Selection[\"view\"], SessionEventType[]> = {\n conversation: [\"user.message\", \"agent.message.completed\"],\n // Final turn output is authoritative: do not repeat message.completed text.\n results: [\"turn.completed\", \"turn.failed\", \"goal.completed\", \"goal.paused\", \"tool.auth_needed\"],\n tools: [\"agent.toolCall.created\", \"agent.toolCall.output\"],\n};\nconst record = (value: unknown): Record<string, unknown> =>\n value !== null && typeof value === \"object\" && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : {};\nconst bytes = (value: unknown) => Buffer.byteLength(JSON.stringify(value, null, 2), \"utf8\");\nconst encode = (selection: Selection, sequence: number | null = null, offset = 0, v = 2) =>\n Buffer.from(JSON.stringify({ v, selection, sequence, offset })).toString(\"base64url\");\n\n/** A cursor is a bounded selector, never authority. The caller reauthorizes every read. */\nexport function resolveSessionEventView(input: SessionEventViewInput) {\n let continuation: z.infer<typeof cursorSchema> | null = null;\n if (input.cursor !== undefined) {\n if (input.cursor.length > 4096)\n throw new Error(\"session_events cursor exceeds 4096 characters\");\n try {\n continuation = cursorSchema.parse(\n JSON.parse(Buffer.from(input.cursor, \"base64url\").toString()),\n );\n } catch {\n throw new Error(\"Invalid session_events cursor\");\n }\n const previous = continuation.selection;\n for (const key of [\n \"sessionId\",\n \"view\",\n \"includeArguments\",\n \"includeOutput\",\n \"callId\",\n \"direction\",\n \"after\",\n \"before\",\n ] as const) {\n if (input[key] !== undefined && input[key] !== previous[key]) {\n throw new Error(`session_events cursor cannot change ${key}`);\n }\n }\n }\n const selection =\n continuation?.selection ??\n selectionSchema.parse({\n sessionId: input.sessionId,\n view: input.view ?? \"conversation\",\n includeArguments: input.includeArguments ?? false,\n includeOutput: input.includeOutput ?? false,\n callId: input.callId ?? null,\n direction:\n input.direction ??\n (input.before !== undefined || input.after === undefined ? \"before\" : \"after\"),\n after: input.after ?? 0,\n before: input.before ?? null,\n });\n if (\n selection.view !== \"tools\" &&\n (selection.callId || selection.includeArguments || selection.includeOutput)\n ) {\n throw new Error(\"callId/includeArguments/includeOutput require view=tools\");\n }\n if (\n continuation &&\n ((continuation.sequence === null && continuation.offset !== 0) ||\n (continuation.sequence !== null &&\n (continuation.sequence <= selection.after ||\n (selection.before !== null && continuation.sequence >= selection.before))))\n )\n throw new Error(\"Invalid session_events cursor position\");\n return { selection, continuation };\n}\n\nfunction project(event: SessionEvent, selection: Selection): Item | null {\n if (event.duplicateOfEventId || (event.turnAssociation && event.turnAssociation !== \"current\"))\n return null;\n const p = record(event.payload);\n const base = { sequence: event.sequence, ...(event.turnId ? { turnId: event.turnId } : {}) };\n if (selection.view === \"conversation\") {\n return typeof p.text === \"string\" && p.text.length > 0\n ? { ...base, role: event.type === \"user.message\" ? \"user\" : \"assistant\", text: p.text }\n : null;\n }\n if (selection.view === \"results\") {\n if (event.type === \"turn.completed\" && (\"maintenance\" in p || \"segmentLimit\" in p)) return null;\n const value = event.type === \"turn.completed\" ? (p.output ?? p.result) : p;\n if (value === undefined || value === \"\") return null;\n return {\n ...base,\n type: event.type,\n text: typeof value === \"string\" ? value : JSON.stringify(value),\n };\n }\n const callId = p.callId ?? p.call_id ?? p.id;\n if (selection.callId !== null && selection.callId !== callId) return null;\n const output = event.type === \"agent.toolCall.output\";\n // Choose one canonical slot. Never ship the raw receipt beside its parsed copy.\n const value = output ? p.output : (p.arguments ?? p.args);\n return {\n ...base,\n callId,\n kind: output ? \"result\" : \"call\",\n ...(typeof p.name === \"string\" ? { name: p.name } : {}),\n ...(p.identityOmitted === true ? { identityOmitted: true } : {}),\n ...(p.isError === true || record(value).isError === true ? { isError: true } : {}),\n ...((output ? selection.includeOutput : selection.includeArguments) && value !== undefined\n ? {\n text: typeof value === \"string\" ? value : JSON.stringify(value),\n encoding: typeof value === \"string\" ? \"text\" : \"json\",\n }\n : {}),\n };\n}\n\n/** Read-only projection over the existing RLS/audit query. No command observations. */\nexport async function readSessionEventView(input: SessionEventViewInput, read: ReadPage) {\n const { selection, continuation } = resolveSessionEventView(input);\n const limit = Math.max(1, Math.min(50, input.limit ?? 10));\n let after =\n continuation?.sequence && selection.direction === \"after\"\n ? continuation.sequence - 1\n : selection.after;\n let before =\n continuation?.sequence && selection.direction === \"before\"\n ? continuation.sequence + 1\n : selection.before;\n const events: Item[] = [];\n let hasMore = false;\n let nextCursor: string | null = null;\n let sourceExact = true;\n let edge: number | null = null;\n let legacyActive = continuation?.v === 1 && continuation.sequence !== null;\n const page = () => ({\n view: selection.view,\n direction: selection.direction,\n events,\n nextAfter: selection.direction === \"after\" ? (edge ?? selection.after) : null,\n nextBefore: selection.direction === \"before\" ? (edge ?? selection.before) : null,\n hasMore,\n nextCursor,\n sourceExact,\n ...(!sourceExact\n ? {\n sourceLoss: {\n reason: \"structured_value_exceeds_budget\" as const,\n completeTextAvailable: false as const,\n message:\n \"An oversized structured value was omitted. Scalar text remains retrievable through continuation; omitted structured values are not complete JSON results.\",\n },\n }\n : {}),\n maxBytes: SESSION_EVENT_VIEW_MAX_BYTES,\n });\n const resume = (sequence: number, offset = 0) => {\n hasMore = true;\n nextCursor = encode(\n { ...selection, after, before },\n sequence,\n offset,\n legacyActive && sequence === continuation?.sequence ? 1 : 2,\n );\n };\n // Bound work even when a sparse exact callId lookup matches nothing. The\n // returned cursor advances the scan without claiming the lookup is exhausted.\n for (let scan = 0; scan < 64; scan += 1) {\n const source = await read({\n after,\n ...(before === null ? {} : { before }),\n direction: selection.direction,\n limit: 8,\n includeTypes: types[selection.view],\n payloadMode: \"full\",\n excludeUnclaimedHumanPrompts: true,\n maxBytes: 1024 * 1024,\n legacyUtf16: legacyActive,\n view: selection.view,\n includeArguments: selection.includeArguments,\n includeOutput: selection.includeOutput,\n ...(continuation?.sequence\n ? { sourceSequence: continuation.sequence, sourceOffset: continuation.offset }\n : {}),\n });\n sourceExact &&= source.fullPayloadsExact;\n const ordered = selection.direction === \"before\" ? [...source.events].reverse() : source.events;\n for (const event of ordered) {\n const slice = source.slices?.[event.sequence];\n const item = project(event, selection);\n if (item) {\n const offset = continuation?.sequence === event.sequence ? continuation.offset : 0;\n if (slice?.omitted) {\n delete item.text;\n item.sourceOmitted = { reason: \"structured_value_exceeds_budget\", complete: false };\n }\n const advance = (text: string) =>\n slice?.unit === \"codepoint\" ? Array.from(text).length : text.length;\n if (!slice && offset > (item.text?.length ?? 0))\n throw new Error(\"Invalid message continuation offset\");\n if (\n !slice &&\n offset > 0 &&\n item.text &&\n /[\\uD800-\\uDBFF]/.test(item.text[offset - 1]!) &&\n /[\\uDC00-\\uDFFF]/.test(item.text[offset] ?? \"\")\n ) {\n throw new Error(\"Invalid message continuation offset: splits a surrogate pair\");\n }\n if (!slice && offset > 0 && item.text) item.text = item.text.slice(offset);\n events.push(item);\n // Reserve enough for the bounded cursor and fragment facts.\n if (bytes(page()) > SESSION_EVENT_VIEW_MAX_BYTES - 4096) {\n events.pop();\n if (events.length > 0) {\n resume(event.sequence, offset);\n if (selection.direction === \"before\") events.reverse();\n return page();\n }\n if (!item.text) throw new Error(\"Session event identity exceeds page budget\");\n const text = item.text;\n let low = 0;\n let high = text.length;\n while (low < high) {\n const mid = Math.ceil((low + high) / 2);\n if (bytes({ ...item, text: text.slice(0, mid) }) <= SESSION_EVENT_VIEW_MAX_BYTES - 6000)\n low = mid;\n else high = mid - 1;\n }\n // Do not split a UTF-16 surrogate pair (UTF-8 remains lossless).\n if (low > 0 && /[\\uD800-\\uDBFF]/.test(text[low - 1]!)) low -= 1;\n if (low === 0) throw new Error(\"Session event identity exceeds page budget\");\n events.push({\n ...item,\n text: text.slice(0, low),\n fragment: {\n offset,\n nextOffset: offset + advance(text.slice(0, low)),\n complete: false,\n unit: slice?.unit ?? \"utf16\",\n },\n });\n resume(event.sequence, offset + advance(text.slice(0, low)));\n return page();\n }\n const nextOffset = offset + advance(item.text ?? \"\");\n if (slice && !slice.omitted && nextOffset < slice.total) {\n item.fragment = { offset, nextOffset, complete: false, unit: slice.unit };\n resume(event.sequence, nextOffset);\n if (selection.direction === \"before\") events.reverse();\n return page();\n }\n if (offset > 0)\n item.fragment = {\n offset,\n nextOffset,\n complete: true,\n unit: slice?.unit ?? \"utf16\",\n };\n }\n edge = event.sequence;\n legacyActive = false;\n if (selection.direction === \"after\") after = event.sequence;\n else before = event.sequence;\n if (events.length >= limit) {\n hasMore = source.hasMore || event !== ordered.at(-1);\n nextCursor = hasMore ? encode({ ...selection, after, before }) : null;\n if (selection.direction === \"before\") events.reverse();\n return page();\n }\n }\n // Metadata selection can advance over stale/duplicate rows without a\n // projected payload. Use its covered edge rather than repeating the page.\n if (source.coveredSequence) {\n if (selection.direction === \"after\") after = Math.max(after, source.coveredSequence.last);\n else before = Math.min(before ?? Infinity, source.coveredSequence.first);\n }\n if (!source.hasMore) {\n hasMore = false;\n nextCursor = null;\n if (selection.direction === \"before\") events.reverse();\n return page();\n }\n hasMore = true;\n nextCursor = encode({ ...selection, after, before });\n }\n if (selection.direction === \"before\") events.reverse();\n return page();\n}\n"],"mappings":"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","names":["value"]}
@@ -0,0 +1,41 @@
1
+ import { type AccessGrant } from "@opengeni/contracts";
2
+ import type { ApiRouteDeps, ObjectStorageDependency } from "@opengeni/core";
3
+ import { prepareWorkspaceArtifactContent, type PreparedWorkspaceArtifactContent } from "./workspace-artifact-content.js";
4
+ type Storage = NonNullable<ObjectStorageDependency>;
5
+ export declare const MAX_SITE_SOURCE_BYTES: number;
6
+ export declare function validateSiteSource(storage: Storage, key: string, sizeBytes: number): Promise<void>;
7
+ export declare function prepareWorkspaceArtifactUpload(deps: ApiRouteDeps, grant: AccessGrant): Promise<{
8
+ uploadId: string;
9
+ html: {
10
+ putUrl: string;
11
+ requiredHeaders: Record<string, string>;
12
+ };
13
+ source: {
14
+ putUrl: string;
15
+ requiredHeaders: Record<string, string>;
16
+ };
17
+ expiresAt: string;
18
+ }>;
19
+ /** Provider version-pinned reads, without Site SHA checks or full HTML buffering. */
20
+ export declare function readArtifactObject(storage: Storage, key: string, expectedHead?: Awaited<ReturnType<NonNullable<Storage["headObject"]>>>): AsyncGenerator<Uint8Array<ArrayBufferLike>, void, unknown>;
21
+ type Input = Partial<Parameters<typeof prepareWorkspaceArtifactContent>[2]> & {
22
+ uploadId?: string;
23
+ };
24
+ export declare function prepareWorkspaceArtifactPublication(deps: ApiRouteDeps, grant: AccessGrant, input: Input): Promise<PreparedWorkspaceArtifactContent & {
25
+ uploadId?: string;
26
+ }>;
27
+ export declare function workspaceArtifactDownloads(storage: Storage, ref: {
28
+ contentKey: string;
29
+ sourceKey: string | null;
30
+ }, audience: "sandbox" | "public"): Promise<{
31
+ html: {
32
+ url: string;
33
+ expiresAt: Date;
34
+ };
35
+ source: {
36
+ url: string;
37
+ expiresAt: Date;
38
+ format: string;
39
+ } | null;
40
+ }>;
41
+ export {};
@@ -2,11 +2,11 @@ import { type ToolGatewayIdentity, type WorkspaceArtifactSourceBundle as Workspa
2
2
  import type { ObjectStorageDependency } from "@opengeni/core";
3
3
  export type PreparedWorkspaceArtifactContent = {
4
4
  contentKey: string;
5
- contentSha256: string;
5
+ contentSha256: null;
6
6
  sizeBytes: number;
7
- sourceKey: string;
8
- sourceSha256: string;
9
- sourceSizeBytes: number;
7
+ sourceKey: string | null;
8
+ sourceSha256: null;
9
+ sourceSizeBytes: number | null;
10
10
  requestedTools?: ToolGatewayIdentity[];
11
11
  persistContent: () => Promise<void>;
12
12
  discardContent: () => Promise<void>;