@opengeni/api-router 0.23.1 → 0.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app.d.ts +3 -2
- package/dist/app.js +5 -3
- package/dist/auth/canonical-human-session-admission.d.ts +27 -0
- package/dist/browser-auth-broker.d.ts +20 -0
- package/dist/browser-controller-authority.d.ts +61 -0
- package/dist/browser-network-route.d.ts +8 -0
- package/dist/browser-state-authority.d.ts +27 -0
- package/dist/{chunk-T4T2PGU4.js → chunk-2PQMSRCB.js} +37891 -17463
- package/dist/chunk-2PQMSRCB.js.map +1 -0
- package/dist/codemode.d.ts +23 -0
- package/dist/controller-data-plane.d.ts +12 -0
- package/dist/editable-artifact-live-hints.d.ts +11 -0
- package/dist/editable-artifact-native-kernel.d.ts +37 -0
- package/dist/editable-artifact-office-import.d.ts +22 -0
- package/dist/editable-artifact-production.d.ts +29 -0
- package/dist/editable-artifact-websocket.d.ts +49 -0
- package/dist/editable-artifact-workspace-files.d.ts +23 -0
- package/dist/github-browser-flow.d.ts +6 -0
- package/dist/http/auth.d.ts +1 -1
- package/dist/http/cors.d.ts +1 -0
- package/dist/http/sse.d.ts +18 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.js +1796 -22
- package/dist/index.js.map +1 -1
- package/dist/integrations/api-integrations.d.ts +24 -0
- package/dist/integrations/atlassian.d.ts +176 -0
- package/dist/integrations/fiken.d.ts +233 -0
- package/dist/integrations/github-skill-source.d.ts +5 -0
- package/dist/integrations/google-drive.d.ts +85 -0
- package/dist/integrations/oauth-client.d.ts +22 -1
- package/dist/integrations/provider-oauth.d.ts +17 -0
- package/dist/integrations/slack-bot.d.ts +175 -1
- package/dist/integrations/slack-interactions.d.ts +24 -6
- package/dist/integrations/social-api.d.ts +2 -1
- package/dist/interaction-holder-heartbeat.d.ts +17 -0
- package/dist/interaction-metrics.d.ts +11 -0
- package/dist/mcp/editable-artifact-query-schema.d.ts +4 -0
- package/dist/mcp/editable-artifacts.d.ts +13 -0
- package/dist/mcp/scheduled-task-view.d.ts +168 -0
- package/dist/mcp/server.d.ts +16 -4
- package/dist/memory-slack-delivery.d.ts +9 -0
- package/dist/model-catalog.d.ts +1 -0
- package/dist/routes/api-integrations.d.ts +8 -0
- package/dist/routes/browser-identities.d.ts +5 -0
- package/dist/routes/browser-sessions.d.ts +6 -0
- package/dist/routes/canonical-human-identities.d.ts +3 -0
- package/dist/routes/channels.d.ts +3 -0
- package/dist/routes/company-profile.d.ts +3 -0
- package/dist/routes/computer-sessions.d.ts +6 -0
- package/dist/routes/editable-artifacts.d.ts +44 -0
- package/dist/routes/integration-facets.d.ts +3 -0
- package/dist/routes/interaction-resources.d.ts +5 -0
- package/dist/routes/memory-slack-publications.d.ts +6 -0
- package/dist/routes/organization-memberships.d.ts +3 -0
- package/dist/routes/plugins.d.ts +8 -0
- package/dist/routes/sessions.d.ts +18 -2
- package/dist/routes/skills.d.ts +6 -0
- package/dist/routes/slack-task-policy.d.ts +3 -0
- package/dist/routes/supergrok.d.ts +3 -0
- package/dist/routes/video-generation.d.ts +3 -0
- package/dist/sandbox/channel-a.d.ts +62 -2
- package/dist/sandbox/machines.d.ts +2 -2
- package/dist/sandbox/metrics-ingestion.d.ts +6 -1
- package/dist/sandbox/viewer.d.ts +7 -3
- package/dist/slack-reaction-files.d.ts +27 -0
- package/dist/temporal-schedule-cleanup.d.ts +26 -0
- package/dist/transcription/providers/xai-subscription.d.ts +8 -0
- package/dist/xai-realtime.d.ts +26 -0
- package/dist/xai-subscription-auth.d.ts +23 -0
- package/package.json +20 -14
- package/src/app.ts +604 -64
- package/src/auth/canonical-human-session-admission.ts +116 -0
- package/src/auth/managed-auth.ts +101 -0
- package/src/browser-auth-broker.ts +155 -0
- package/src/browser-controller-authority.ts +186 -0
- package/src/browser-network-route.ts +34 -0
- package/src/browser-state-authority.ts +236 -0
- package/src/codemode.ts +186 -0
- package/src/controller-data-plane.ts +35 -0
- package/src/editable-artifact-live-hints.ts +64 -0
- package/src/editable-artifact-native-kernel.ts +659 -0
- package/src/editable-artifact-office-import.ts +230 -0
- package/src/editable-artifact-production.ts +419 -0
- package/src/editable-artifact-websocket.ts +311 -0
- package/src/editable-artifact-workspace-files.ts +186 -0
- package/src/github-browser-flow.ts +35 -6
- package/src/http/auth.ts +29 -9
- package/src/http/cors.ts +3 -0
- package/src/http/sse.ts +229 -5
- package/src/index.ts +119 -20
- package/src/integrations/api-integrations.ts +365 -0
- package/src/integrations/atlassian.ts +1621 -0
- package/src/integrations/fiken.ts +1230 -0
- package/src/integrations/github-skill-source.ts +142 -0
- package/src/integrations/google-drive.ts +1005 -64
- package/src/integrations/oauth-client.ts +123 -33
- package/src/integrations/provider-oauth.ts +756 -0
- package/src/integrations/slack-bot.ts +669 -11
- package/src/integrations/slack-interactions.ts +2063 -93
- package/src/integrations/social-api.ts +11 -0
- package/src/interaction-holder-heartbeat.ts +95 -0
- package/src/interaction-metrics.ts +159 -0
- package/src/mcp/documents.ts +146 -6
- package/src/mcp/editable-artifact-query-schema.ts +236 -0
- package/src/mcp/editable-artifacts.ts +448 -0
- package/src/mcp/scheduled-task-view.ts +34 -0
- package/src/mcp/server.ts +1756 -355
- package/src/memory-slack-delivery.ts +209 -0
- package/src/model-catalog.ts +19 -6
- package/src/routes/api-integrations.ts +410 -0
- package/src/routes/browser-identities.ts +160 -0
- package/src/routes/browser-sessions.ts +4482 -0
- package/src/routes/canonical-human-identities.ts +156 -0
- package/src/routes/channels.ts +90 -0
- package/src/routes/company-profile.ts +255 -0
- package/src/routes/computer-sessions.ts +1602 -0
- package/src/routes/connections.ts +421 -99
- package/src/routes/documents.ts +22 -3
- package/src/routes/editable-artifacts.ts +1159 -0
- package/src/routes/files.ts +106 -4
- package/src/routes/github.ts +18 -2
- package/src/routes/install.ts +7 -1
- package/src/routes/integration-facets.ts +258 -0
- package/src/routes/interaction-resources.ts +595 -0
- package/src/routes/memory-slack-publications.ts +216 -0
- package/src/routes/organization-memberships.ts +53 -0
- package/src/routes/packs.ts +437 -7
- package/src/routes/plugins.ts +752 -0
- package/src/routes/rigs.ts +77 -20
- package/src/routes/scheduled-tasks.ts +67 -40
- package/src/routes/sessions.ts +830 -278
- package/src/routes/skills.ts +256 -0
- package/src/routes/slack-task-policy.ts +115 -0
- package/src/routes/supergrok.ts +717 -0
- package/src/routes/transcription-recordings.ts +9 -2
- package/src/routes/transcriptions.ts +2 -1
- package/src/routes/video-generation.ts +162 -0
- package/src/routes/workspaces.ts +68 -28
- package/src/sandbox/channel-a.ts +750 -116
- package/src/sandbox/machines.ts +5 -5
- package/src/sandbox/metrics-ingestion.ts +121 -3
- package/src/sandbox/rematerialize.ts +35 -47
- package/src/sandbox/viewer.ts +39 -12
- package/src/slack-reaction-files.ts +181 -0
- package/src/temporal-schedule-cleanup.ts +135 -0
- package/src/transcription/providers/xai-subscription.ts +110 -0
- package/src/transcription/service.ts +17 -2
- package/src/xai-realtime.ts +216 -0
- package/src/xai-subscription-auth.ts +132 -0
- package/dist/chunk-T4T2PGU4.js.map +0 -1
- package/dist/mcp/toolspace.d.ts +0 -71
- package/src/mcp/toolspace.ts +0 -1190
|
@@ -39,6 +39,17 @@ type ConnectionRef = { workspaceId: string; connectionId: string; subjectId?: st
|
|
|
39
39
|
|
|
40
40
|
const MAX_LIVE_RESULTS = 50;
|
|
41
41
|
|
|
42
|
+
export function assertSocialConnectionProvider(
|
|
43
|
+
connection: Pick<SocialConnection, "provider">,
|
|
44
|
+
expected: "x" | "reddit",
|
|
45
|
+
): void {
|
|
46
|
+
if (connection.provider !== expected) {
|
|
47
|
+
throw new Error(
|
|
48
|
+
`Selected account belongs to ${connection.provider}, not the ${expected} integration`,
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
42
53
|
export async function socialSearchLive(
|
|
43
54
|
deps: SocialApiDeps,
|
|
44
55
|
ref: ConnectionRef,
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import type { AccessGrant } from "@opengeni/contracts";
|
|
2
|
+
import { touchInteractionOperation, touchLeaseHolder } from "@opengeni/db";
|
|
3
|
+
import type { ApiRouteDeps } from "@opengeni/core";
|
|
4
|
+
|
|
5
|
+
const MAX_HEARTBEAT_INTERVAL_MS = 10_000;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Keep one durable interaction operation—and its sandbox holder when present—
|
|
9
|
+
* live while a bounded controller mutation is in flight. Browser profile
|
|
10
|
+
* capture/restore can legitimately take minutes; without this pulse a lifecycle
|
|
11
|
+
* reaper cannot distinguish that request from a caller that disappeared.
|
|
12
|
+
*/
|
|
13
|
+
export async function withInteractionHolderHeartbeat<T>(
|
|
14
|
+
deps: ApiRouteDeps,
|
|
15
|
+
input: {
|
|
16
|
+
grant: AccessGrant;
|
|
17
|
+
workspaceId: string;
|
|
18
|
+
sandboxGroupId: string | null;
|
|
19
|
+
holderId: string;
|
|
20
|
+
operationId: string;
|
|
21
|
+
resourceId: string;
|
|
22
|
+
controllerGeneration: string;
|
|
23
|
+
},
|
|
24
|
+
run: () => Promise<T>,
|
|
25
|
+
): Promise<T> {
|
|
26
|
+
const intervalMs = Math.max(
|
|
27
|
+
1_000,
|
|
28
|
+
Math.min(
|
|
29
|
+
MAX_HEARTBEAT_INTERVAL_MS,
|
|
30
|
+
Math.floor(deps.settings.sandboxInteractionHolderTtlMs / 3),
|
|
31
|
+
),
|
|
32
|
+
);
|
|
33
|
+
let stopped = false;
|
|
34
|
+
let authorityLost = false;
|
|
35
|
+
let timer: ReturnType<typeof setTimeout> | null = null;
|
|
36
|
+
let pending: Promise<void> = Promise.resolve();
|
|
37
|
+
|
|
38
|
+
const pulse = async (): Promise<void> => {
|
|
39
|
+
try {
|
|
40
|
+
const [operationAlive, holderAlive] = await Promise.all([
|
|
41
|
+
touchInteractionOperation(deps.db, {
|
|
42
|
+
accountId: input.grant.accountId,
|
|
43
|
+
workspaceId: input.workspaceId,
|
|
44
|
+
operationId: input.operationId,
|
|
45
|
+
resourceId: input.resourceId,
|
|
46
|
+
controllerGeneration: input.controllerGeneration,
|
|
47
|
+
}),
|
|
48
|
+
input.sandboxGroupId
|
|
49
|
+
? touchLeaseHolder(deps.db, {
|
|
50
|
+
accountId: input.grant.accountId,
|
|
51
|
+
workspaceId: input.workspaceId,
|
|
52
|
+
sandboxGroupId: input.sandboxGroupId,
|
|
53
|
+
kind: "interaction",
|
|
54
|
+
holderId: input.holderId,
|
|
55
|
+
})
|
|
56
|
+
: Promise.resolve(true),
|
|
57
|
+
]);
|
|
58
|
+
if (!operationAlive || !holderAlive) authorityLost = true;
|
|
59
|
+
} catch (error) {
|
|
60
|
+
// A transient database error does not surrender controller authority. The
|
|
61
|
+
// next pulse can recover, and the operation's authoritative settlement
|
|
62
|
+
// still goes through the database before it is exposed as complete.
|
|
63
|
+
deps.observability?.warn("interaction holder heartbeat failed", {
|
|
64
|
+
workspaceId: input.workspaceId,
|
|
65
|
+
sandboxGroupId: input.sandboxGroupId,
|
|
66
|
+
holderId: input.holderId,
|
|
67
|
+
error: error instanceof Error ? error.message : String(error),
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
const schedule = (): void => {
|
|
72
|
+
if (stopped) return;
|
|
73
|
+
timer = setTimeout(() => {
|
|
74
|
+
pending = pulse().finally(schedule);
|
|
75
|
+
}, intervalMs);
|
|
76
|
+
timer.unref?.();
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
await pulse();
|
|
80
|
+
if (authorityLost) {
|
|
81
|
+
throw new Error("Interaction controller placement authority was lost");
|
|
82
|
+
}
|
|
83
|
+
schedule();
|
|
84
|
+
try {
|
|
85
|
+
const result = await run();
|
|
86
|
+
if (authorityLost) {
|
|
87
|
+
throw new Error("Interaction controller placement authority was lost");
|
|
88
|
+
}
|
|
89
|
+
return result;
|
|
90
|
+
} finally {
|
|
91
|
+
stopped = true;
|
|
92
|
+
if (timer) clearTimeout(timer);
|
|
93
|
+
await pending;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AuthRunMutationResponse,
|
|
3
|
+
BrowserActionReceipt,
|
|
4
|
+
BrowserActionRequest,
|
|
5
|
+
PublishBrowserRevisionResponse,
|
|
6
|
+
BrowserSessionMutationResponse,
|
|
7
|
+
ComputerActionReceipt,
|
|
8
|
+
ComputerActionRequest,
|
|
9
|
+
ComputerSessionMutationResponse,
|
|
10
|
+
InteractionInterventionMutationResponse,
|
|
11
|
+
ProtectedAuthFillResponse,
|
|
12
|
+
} from "@opengeni/contracts";
|
|
13
|
+
import {
|
|
14
|
+
interactionAuthMetricObserver,
|
|
15
|
+
interactionInterventionMetricObserver,
|
|
16
|
+
interactionOperationMetricObserver,
|
|
17
|
+
type Observability,
|
|
18
|
+
} from "@opengeni/observability";
|
|
19
|
+
|
|
20
|
+
type InteractionActionReceipt = BrowserActionReceipt | ComputerActionReceipt;
|
|
21
|
+
type InteractionLifecycleMutation =
|
|
22
|
+
| BrowserSessionMutationResponse
|
|
23
|
+
| ComputerSessionMutationResponse;
|
|
24
|
+
type AuthMutation = AuthRunMutationResponse | ProtectedAuthFillResponse;
|
|
25
|
+
|
|
26
|
+
const STALE_INTERACTION_ERROR_CODES = new Set([
|
|
27
|
+
"controller_stale",
|
|
28
|
+
"target_stale",
|
|
29
|
+
"observation_stale",
|
|
30
|
+
"document_stale",
|
|
31
|
+
"frame_stale",
|
|
32
|
+
"attempt_stale",
|
|
33
|
+
]);
|
|
34
|
+
|
|
35
|
+
export function observeBrowserActionResult(
|
|
36
|
+
observability: Observability | null | undefined,
|
|
37
|
+
startedAtMs: number,
|
|
38
|
+
request: BrowserActionRequest,
|
|
39
|
+
receipt: BrowserActionReceipt,
|
|
40
|
+
): void {
|
|
41
|
+
interactionOperationMetricObserver(observability)({
|
|
42
|
+
resource: "browser",
|
|
43
|
+
operation: "act",
|
|
44
|
+
mode: browserActionMode(request.action),
|
|
45
|
+
outcome: actionReceiptOutcome(receipt),
|
|
46
|
+
durationMs: elapsedMs(startedAtMs),
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function observeComputerActionResult(
|
|
51
|
+
observability: Observability | null | undefined,
|
|
52
|
+
startedAtMs: number,
|
|
53
|
+
request: ComputerActionRequest,
|
|
54
|
+
receipt: ComputerActionReceipt,
|
|
55
|
+
): void {
|
|
56
|
+
interactionOperationMetricObserver(observability)({
|
|
57
|
+
resource: "computer",
|
|
58
|
+
operation: "act",
|
|
59
|
+
mode: computerActionMode(request.action.type),
|
|
60
|
+
outcome: actionReceiptOutcome(receipt),
|
|
61
|
+
durationMs: elapsedMs(startedAtMs),
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function observeLifecycleResult(
|
|
66
|
+
observability: Observability | null | undefined,
|
|
67
|
+
startedAtMs: number,
|
|
68
|
+
response: InteractionLifecycleMutation,
|
|
69
|
+
): void {
|
|
70
|
+
interactionOperationMetricObserver(observability)({
|
|
71
|
+
resource: response.operation.resourceKind === "browser_session" ? "browser" : "computer",
|
|
72
|
+
operation: response.operation.kind,
|
|
73
|
+
mode: "lifecycle",
|
|
74
|
+
outcome: actionReceiptOutcome(response.operation),
|
|
75
|
+
durationMs: elapsedMs(startedAtMs),
|
|
76
|
+
replayed: response.operation.replayed,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function observeBrowserRevisionPublication(
|
|
81
|
+
observability: Observability | null | undefined,
|
|
82
|
+
startedAtMs: number,
|
|
83
|
+
response: PublishBrowserRevisionResponse,
|
|
84
|
+
): void {
|
|
85
|
+
interactionOperationMetricObserver(observability)({
|
|
86
|
+
resource: "browser",
|
|
87
|
+
operation: "publish",
|
|
88
|
+
mode: "lifecycle",
|
|
89
|
+
outcome: "completed",
|
|
90
|
+
durationMs: elapsedMs(startedAtMs),
|
|
91
|
+
replayed: response.replayed,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function observeAuthMutation(
|
|
96
|
+
observability: Observability | null | undefined,
|
|
97
|
+
startedAtMs: number,
|
|
98
|
+
response: AuthMutation,
|
|
99
|
+
): void {
|
|
100
|
+
interactionAuthMetricObserver(observability)({
|
|
101
|
+
state: response.run.state,
|
|
102
|
+
durationMs: elapsedMs(startedAtMs),
|
|
103
|
+
replayed: response.replayed,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function observeInterventionMutation(
|
|
108
|
+
observability: Observability | null | undefined,
|
|
109
|
+
response: InteractionInterventionMutationResponse,
|
|
110
|
+
): void {
|
|
111
|
+
const { intervention } = response;
|
|
112
|
+
interactionInterventionMetricObserver(observability)({
|
|
113
|
+
kind: intervention.kind,
|
|
114
|
+
outcome: intervention.status === "open" ? "opened" : intervention.status,
|
|
115
|
+
...(intervention.settledAt
|
|
116
|
+
? {
|
|
117
|
+
waitMs: Math.max(
|
|
118
|
+
0,
|
|
119
|
+
Date.parse(intervention.settledAt) - Date.parse(intervention.createdAt),
|
|
120
|
+
),
|
|
121
|
+
}
|
|
122
|
+
: {}),
|
|
123
|
+
replayed: response.replayed,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function browserActionMode(action: BrowserActionRequest["action"]): string {
|
|
128
|
+
const actions = action.type === "batch" ? action.actions : [action];
|
|
129
|
+
if (actions.some((candidate) => candidate.type === "pointer")) return "coordinate";
|
|
130
|
+
if (actions.some((candidate) => candidate.type === "type" || candidate.type === "press")) {
|
|
131
|
+
return "keyboard";
|
|
132
|
+
}
|
|
133
|
+
if (actions.some((candidate) => candidate.type === "clipboard")) return "clipboard";
|
|
134
|
+
if (actions.some((candidate) => candidate.type === "permission")) return "permission";
|
|
135
|
+
return "semantic";
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function computerActionMode(type: ComputerActionRequest["action"]["type"]): string {
|
|
139
|
+
if (type === "pointer") return "coordinate";
|
|
140
|
+
if (type === "keyboard") return "keyboard";
|
|
141
|
+
if (type === "clipboard") return "clipboard";
|
|
142
|
+
if (type === "launch") return "lifecycle";
|
|
143
|
+
return "semantic";
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function actionReceiptOutcome(receipt: Pick<InteractionActionReceipt, "state" | "error">): string {
|
|
147
|
+
if (
|
|
148
|
+
receipt.state === "failed" &&
|
|
149
|
+
receipt.error &&
|
|
150
|
+
STALE_INTERACTION_ERROR_CODES.has(receipt.error.code)
|
|
151
|
+
) {
|
|
152
|
+
return "stale";
|
|
153
|
+
}
|
|
154
|
+
return receipt.state;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function elapsedMs(startedAtMs: number): number {
|
|
158
|
+
return Math.max(0, performance.now() - startedAtMs);
|
|
159
|
+
}
|
package/src/mcp/documents.ts
CHANGED
|
@@ -1,7 +1,18 @@
|
|
|
1
|
-
import { DocumentSearchResponse } from "@opengeni/contracts";
|
|
2
1
|
import {
|
|
2
|
+
KNOWLEDGE_BROWSE_CURSOR_MAX_CHARS,
|
|
3
|
+
KnowledgeBrowseResponse,
|
|
4
|
+
KnowledgeGetResponse,
|
|
5
|
+
KnowledgeRecordId,
|
|
6
|
+
KnowledgeSearchResponse,
|
|
7
|
+
ListIndexedDocumentsResponse,
|
|
8
|
+
} from "@opengeni/contracts";
|
|
9
|
+
import {
|
|
10
|
+
browseEffectiveKnowledge,
|
|
11
|
+
getEffectiveKnowledgeRecord,
|
|
3
12
|
getDocumentChunk,
|
|
4
13
|
listDocumentBases,
|
|
14
|
+
listEffectiveIndexedDocuments,
|
|
15
|
+
searchEffectiveKnowledge,
|
|
5
16
|
searchEffectiveDocuments,
|
|
6
17
|
type DocumentAccessFilter,
|
|
7
18
|
type DocumentServices,
|
|
@@ -90,7 +101,6 @@ export function buildDocumentsMcpServer(
|
|
|
90
101
|
documentServices,
|
|
91
102
|
input,
|
|
92
103
|
options.initiatingSubjectId,
|
|
93
|
-
false,
|
|
94
104
|
),
|
|
95
105
|
);
|
|
96
106
|
|
|
@@ -102,17 +112,110 @@ export function buildDocumentsMcpServer(
|
|
|
102
112
|
inputSchema: SearchInputSchema,
|
|
103
113
|
},
|
|
104
114
|
async (input) =>
|
|
105
|
-
|
|
115
|
+
searchKnowledge(
|
|
106
116
|
db,
|
|
107
117
|
accountId,
|
|
108
118
|
workspaceId,
|
|
109
119
|
documentServices,
|
|
110
120
|
input,
|
|
111
121
|
options.initiatingSubjectId,
|
|
112
|
-
true,
|
|
113
122
|
),
|
|
114
123
|
);
|
|
115
124
|
|
|
125
|
+
server.registerTool(
|
|
126
|
+
"knowledge_get",
|
|
127
|
+
{
|
|
128
|
+
description:
|
|
129
|
+
"Fetch one stable Knowledge record by the id returned from knowledge_search or knowledge_browse. Authorization and agent access are rechecked before content or links are returned.",
|
|
130
|
+
inputSchema: { id: KnowledgeRecordId },
|
|
131
|
+
},
|
|
132
|
+
async ({ id }) => {
|
|
133
|
+
const record = await getEffectiveKnowledgeRecord(db, {
|
|
134
|
+
accountId,
|
|
135
|
+
workspaceId,
|
|
136
|
+
initiatingSubjectId: options.initiatingSubjectId,
|
|
137
|
+
id,
|
|
138
|
+
});
|
|
139
|
+
return {
|
|
140
|
+
content: [
|
|
141
|
+
{
|
|
142
|
+
type: "text",
|
|
143
|
+
text: record
|
|
144
|
+
? JSON.stringify(KnowledgeGetResponse.parse({ record }))
|
|
145
|
+
: `knowledge record not found: ${id}`,
|
|
146
|
+
},
|
|
147
|
+
],
|
|
148
|
+
isError: !record,
|
|
149
|
+
};
|
|
150
|
+
},
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
server.registerTool(
|
|
154
|
+
"knowledge_browse",
|
|
155
|
+
{
|
|
156
|
+
description:
|
|
157
|
+
"Browse authorized Knowledge without crawling folders. Omit parentId to list source documents; pass a document record id to inspect its chunks. Opaque cursors are bound to this user, workspace, parent, and filters.",
|
|
158
|
+
inputSchema: {
|
|
159
|
+
parentId: KnowledgeRecordId.optional(),
|
|
160
|
+
topic: z.string().min(1).max(256).optional(),
|
|
161
|
+
sourceKinds: SearchInputSchema.sourceKinds,
|
|
162
|
+
cursor: z.string().min(1).max(KNOWLEDGE_BROWSE_CURSOR_MAX_CHARS).optional(),
|
|
163
|
+
limit: z.number().int().positive().max(50).optional(),
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
async ({ parentId, topic, sourceKinds, cursor, limit }) => ({
|
|
167
|
+
content: [
|
|
168
|
+
{
|
|
169
|
+
type: "text",
|
|
170
|
+
text: JSON.stringify(
|
|
171
|
+
KnowledgeBrowseResponse.parse(
|
|
172
|
+
await browseEffectiveKnowledge(db, {
|
|
173
|
+
accountId,
|
|
174
|
+
workspaceId,
|
|
175
|
+
initiatingSubjectId: options.initiatingSubjectId,
|
|
176
|
+
...(parentId ? { parentId } : {}),
|
|
177
|
+
...(topic ? { topic } : {}),
|
|
178
|
+
...(sourceKinds ? { sourceKinds } : {}),
|
|
179
|
+
...(cursor ? { cursor } : {}),
|
|
180
|
+
...(limit ? { limit } : {}),
|
|
181
|
+
}),
|
|
182
|
+
),
|
|
183
|
+
),
|
|
184
|
+
},
|
|
185
|
+
],
|
|
186
|
+
}),
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
server.registerTool(
|
|
190
|
+
"list_indexed_documents",
|
|
191
|
+
{
|
|
192
|
+
description:
|
|
193
|
+
"List agent-authorized documents that became ready after an opaque checkpoint, ordered by durable indexing completion. Results include source and authority/ingestion provenance. Persist nextCheckpoint only after successfully processing the returned page, then pass it on the next scheduled run to avoid repeats.",
|
|
194
|
+
inputSchema: {
|
|
195
|
+
checkpoint: z.string().min(1).max(1_024).optional(),
|
|
196
|
+
limit: z.number().int().positive().max(100).optional(),
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
async ({ checkpoint, limit }) => ({
|
|
200
|
+
content: [
|
|
201
|
+
{
|
|
202
|
+
type: "text",
|
|
203
|
+
text: JSON.stringify(
|
|
204
|
+
ListIndexedDocumentsResponse.parse(
|
|
205
|
+
await listEffectiveIndexedDocuments(db, {
|
|
206
|
+
accountId,
|
|
207
|
+
workspaceId,
|
|
208
|
+
initiatingSubjectId: options.initiatingSubjectId,
|
|
209
|
+
...(checkpoint ? { checkpoint } : {}),
|
|
210
|
+
...(limit ? { limit } : {}),
|
|
211
|
+
}),
|
|
212
|
+
),
|
|
213
|
+
),
|
|
214
|
+
},
|
|
215
|
+
],
|
|
216
|
+
}),
|
|
217
|
+
);
|
|
218
|
+
|
|
116
219
|
server.registerTool(
|
|
117
220
|
"fetch_document_chunk",
|
|
118
221
|
{
|
|
@@ -264,7 +367,6 @@ async function searchContent(
|
|
|
264
367
|
aclTags?: string[] | undefined;
|
|
265
368
|
},
|
|
266
369
|
initiatingSubjectId: string,
|
|
267
|
-
wrapResponse: boolean,
|
|
268
370
|
) {
|
|
269
371
|
const results = await searchEffectiveDocuments(
|
|
270
372
|
db,
|
|
@@ -286,8 +388,46 @@ async function searchContent(
|
|
|
286
388
|
content: [
|
|
287
389
|
{
|
|
288
390
|
type: "text" as const,
|
|
289
|
-
text: JSON.stringify(
|
|
391
|
+
text: JSON.stringify(results),
|
|
290
392
|
},
|
|
291
393
|
],
|
|
292
394
|
};
|
|
293
395
|
}
|
|
396
|
+
|
|
397
|
+
async function searchKnowledge(
|
|
398
|
+
db: Database,
|
|
399
|
+
accountId: string,
|
|
400
|
+
workspaceId: string,
|
|
401
|
+
documentServices: DocumentServices,
|
|
402
|
+
input: {
|
|
403
|
+
query: string;
|
|
404
|
+
baseIds?: string[] | undefined;
|
|
405
|
+
limit?: number | undefined;
|
|
406
|
+
mode?: "hybrid" | "vector" | "keyword" | undefined;
|
|
407
|
+
sourceKinds?: Parameters<typeof searchContent>[4]["sourceKinds"];
|
|
408
|
+
aclTags?: string[] | undefined;
|
|
409
|
+
},
|
|
410
|
+
initiatingSubjectId: string,
|
|
411
|
+
) {
|
|
412
|
+
const response = await searchEffectiveKnowledge(
|
|
413
|
+
db,
|
|
414
|
+
{
|
|
415
|
+
accountId,
|
|
416
|
+
workspaceId,
|
|
417
|
+
query: input.query,
|
|
418
|
+
...(input.baseIds ? { baseIds: input.baseIds } : {}),
|
|
419
|
+
...(input.limit ? { limit: input.limit } : {}),
|
|
420
|
+
...(input.mode ? { mode: input.mode } : {}),
|
|
421
|
+
...(input.sourceKinds ? { sourceKinds: input.sourceKinds } : {}),
|
|
422
|
+
...(input.aclTags ? { aclTags: input.aclTags } : {}),
|
|
423
|
+
initiatingSubjectId,
|
|
424
|
+
surface: "agent",
|
|
425
|
+
},
|
|
426
|
+
documentServices,
|
|
427
|
+
);
|
|
428
|
+
return {
|
|
429
|
+
content: [
|
|
430
|
+
{ type: "text" as const, text: JSON.stringify(KnowledgeSearchResponse.parse(response)) },
|
|
431
|
+
],
|
|
432
|
+
};
|
|
433
|
+
}
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DOCUMENT_ARTIFACT_QUERY_MAX_ITEMS,
|
|
3
|
+
DOCUMENT_ARTIFACT_QUERY_MAX_TABLE_CELLS,
|
|
4
|
+
DOCUMENT_ARTIFACT_QUERY_MAX_TEXT_UTF16,
|
|
5
|
+
DOCUMENT_ARTIFACT_MAX_STRUCTURAL_COUNTER,
|
|
6
|
+
PRESENTATION_ARTIFACT_MAX_COORDINATE,
|
|
7
|
+
PRESENTATION_ARTIFACT_QUERY_MAX_NODES,
|
|
8
|
+
PRESENTATION_ARTIFACT_QUERY_MAX_SLIDES,
|
|
9
|
+
PRESENTATION_ARTIFACT_QUERY_MAX_TEXT_BYTES,
|
|
10
|
+
PRESENTATION_ARTIFACT_QUERY_RESPONSE_MAX_BYTES,
|
|
11
|
+
SPREADSHEET_ARTIFACT_METADATA_MAX_SHEETS,
|
|
12
|
+
SPREADSHEET_ARTIFACT_METADATA_MIN_RESPONSE_BYTES,
|
|
13
|
+
SPREADSHEET_ARTIFACT_PROJECTION_MAX_BYTES,
|
|
14
|
+
SPREADSHEET_ARTIFACT_VIEWPORT_MAX_AREA,
|
|
15
|
+
SPREADSHEET_ARTIFACT_VIEWPORT_MAX_CELLS,
|
|
16
|
+
SPREADSHEET_ARTIFACT_VIEWPORT_MIN_RESPONSE_BYTES,
|
|
17
|
+
type EditableArtifactStableId,
|
|
18
|
+
} from "@opengeni/contracts/editable-artifacts";
|
|
19
|
+
import type { EditableArtifactAgentQuery } from "@opengeni/core/editable-artifacts";
|
|
20
|
+
import * as z from "zod/v4";
|
|
21
|
+
|
|
22
|
+
const UINT32_MAX = 0xffff_ffff;
|
|
23
|
+
|
|
24
|
+
const Uint32 = z.number().int().min(0).max(UINT32_MAX);
|
|
25
|
+
const PositiveUint32 = z.number().int().min(1).max(UINT32_MAX);
|
|
26
|
+
const SafeCursor = z.number().int().min(0).max(Number.MAX_SAFE_INTEGER);
|
|
27
|
+
const CanonicalStableId = z
|
|
28
|
+
.string()
|
|
29
|
+
.regex(/^[0-9a-f]{32}$/u)
|
|
30
|
+
.transform((value): EditableArtifactStableId => value as EditableArtifactStableId);
|
|
31
|
+
const SpreadsheetSheetId = z
|
|
32
|
+
.string()
|
|
33
|
+
.regex(/^[0-9a-f]{32}$/u)
|
|
34
|
+
.refine(
|
|
35
|
+
(value) => value.slice(0, 16) !== "0000000000000000" && value.slice(16) !== "0000000000000000",
|
|
36
|
+
"Object id must have allocated namespace and counter halves",
|
|
37
|
+
)
|
|
38
|
+
.transform((value): EditableArtifactStableId => value as EditableArtifactStableId);
|
|
39
|
+
const DocumentSectionId = z
|
|
40
|
+
.string()
|
|
41
|
+
.regex(/^sec\/[0-9a-f]{32}$/u)
|
|
42
|
+
.refine((value) => {
|
|
43
|
+
const counter = BigInt(`0x${value.slice(-16)}`);
|
|
44
|
+
return counter > 0n && counter <= BigInt(DOCUMENT_ARTIFACT_MAX_STRUCTURAL_COUNTER);
|
|
45
|
+
}, "Section id counter must be allocated and within the document limit");
|
|
46
|
+
|
|
47
|
+
const SpreadsheetMetadataRequest = z
|
|
48
|
+
.object({
|
|
49
|
+
kind: z.literal("workbook-metadata"),
|
|
50
|
+
query: z
|
|
51
|
+
.object({
|
|
52
|
+
maxSheets: z.number().int().min(1).max(SPREADSHEET_ARTIFACT_METADATA_MAX_SHEETS),
|
|
53
|
+
maxBytes: z
|
|
54
|
+
.number()
|
|
55
|
+
.int()
|
|
56
|
+
.min(SPREADSHEET_ARTIFACT_METADATA_MIN_RESPONSE_BYTES)
|
|
57
|
+
.max(SPREADSHEET_ARTIFACT_PROJECTION_MAX_BYTES),
|
|
58
|
+
})
|
|
59
|
+
.strict(),
|
|
60
|
+
})
|
|
61
|
+
.strict();
|
|
62
|
+
|
|
63
|
+
const SpreadsheetViewportRequest = z
|
|
64
|
+
.object({
|
|
65
|
+
kind: z.literal("viewport"),
|
|
66
|
+
query: z
|
|
67
|
+
.object({
|
|
68
|
+
sheetId: SpreadsheetSheetId,
|
|
69
|
+
startRow: Uint32,
|
|
70
|
+
startColumn: Uint32,
|
|
71
|
+
rowCount: PositiveUint32,
|
|
72
|
+
columnCount: PositiveUint32,
|
|
73
|
+
maxCells: z.number().int().min(1).max(SPREADSHEET_ARTIFACT_VIEWPORT_MAX_CELLS),
|
|
74
|
+
maxBytes: z
|
|
75
|
+
.number()
|
|
76
|
+
.int()
|
|
77
|
+
.min(SPREADSHEET_ARTIFACT_VIEWPORT_MIN_RESPONSE_BYTES)
|
|
78
|
+
.max(SPREADSHEET_ARTIFACT_PROJECTION_MAX_BYTES),
|
|
79
|
+
})
|
|
80
|
+
.strict()
|
|
81
|
+
.superRefine((query, context) => {
|
|
82
|
+
const area = query.rowCount * query.columnCount;
|
|
83
|
+
if (!Number.isSafeInteger(area) || area > SPREADSHEET_ARTIFACT_VIEWPORT_MAX_AREA) {
|
|
84
|
+
context.addIssue({
|
|
85
|
+
code: "custom",
|
|
86
|
+
message: `Viewport area must not exceed ${SPREADSHEET_ARTIFACT_VIEWPORT_MAX_AREA}`,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
if (
|
|
90
|
+
query.rowCount - 1 > UINT32_MAX - query.startRow ||
|
|
91
|
+
query.columnCount - 1 > UINT32_MAX - query.startColumn
|
|
92
|
+
) {
|
|
93
|
+
context.addIssue({
|
|
94
|
+
code: "custom",
|
|
95
|
+
message: "Viewport extent exceeds spreadsheet coordinates",
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}),
|
|
99
|
+
})
|
|
100
|
+
.strict();
|
|
101
|
+
|
|
102
|
+
const DocumentLimits = z
|
|
103
|
+
.object({
|
|
104
|
+
maxItems: z.number().int().min(1).max(DOCUMENT_ARTIFACT_QUERY_MAX_ITEMS),
|
|
105
|
+
maxTextUtf16: z.number().int().min(1).max(DOCUMENT_ARTIFACT_QUERY_MAX_TEXT_UTF16),
|
|
106
|
+
maxTableCells: z.number().int().min(1).max(DOCUMENT_ARTIFACT_QUERY_MAX_TABLE_CELLS),
|
|
107
|
+
})
|
|
108
|
+
.strict();
|
|
109
|
+
|
|
110
|
+
const DocumentSummaryRequest = z.object({ kind: z.literal("summary") }).strict();
|
|
111
|
+
const DocumentBodyRequest = z
|
|
112
|
+
.object({ kind: z.literal("body"), startBlock: SafeCursor, limits: DocumentLimits })
|
|
113
|
+
.strict();
|
|
114
|
+
const DocumentStoryRequest = z
|
|
115
|
+
.object({
|
|
116
|
+
kind: z.literal("story"),
|
|
117
|
+
sectionId: DocumentSectionId,
|
|
118
|
+
storyKind: z.enum(["header", "footer"]),
|
|
119
|
+
variant: z.enum(["default", "first", "even"]),
|
|
120
|
+
startBlock: SafeCursor,
|
|
121
|
+
limits: DocumentLimits,
|
|
122
|
+
})
|
|
123
|
+
.strict();
|
|
124
|
+
const DocumentSectionsRequest = z
|
|
125
|
+
.object({ kind: z.literal("sections"), startSection: SafeCursor, limits: DocumentLimits })
|
|
126
|
+
.strict();
|
|
127
|
+
const DocumentReviewRequest = z
|
|
128
|
+
.object({ kind: z.literal("review"), startItem: SafeCursor, limits: DocumentLimits })
|
|
129
|
+
.strict();
|
|
130
|
+
|
|
131
|
+
const PresentationOwner = z
|
|
132
|
+
.object({ kind: z.enum(["master", "layout", "slide"]), id: CanonicalStableId })
|
|
133
|
+
.strict();
|
|
134
|
+
const PresentationCoordinate = z
|
|
135
|
+
.number()
|
|
136
|
+
.int()
|
|
137
|
+
.min(-PRESENTATION_ARTIFACT_MAX_COORDINATE)
|
|
138
|
+
.max(PRESENTATION_ARTIFACT_MAX_COORDINATE);
|
|
139
|
+
const PresentationExtent = z.number().int().min(1).max(PRESENTATION_ARTIFACT_MAX_COORDINATE);
|
|
140
|
+
const PresentationRect = z
|
|
141
|
+
.object({
|
|
142
|
+
x: PresentationCoordinate,
|
|
143
|
+
y: PresentationCoordinate,
|
|
144
|
+
width: PresentationExtent,
|
|
145
|
+
height: PresentationExtent,
|
|
146
|
+
})
|
|
147
|
+
.strict()
|
|
148
|
+
.superRefine((rect, context) => {
|
|
149
|
+
if (
|
|
150
|
+
Math.abs(rect.x + rect.width) > PRESENTATION_ARTIFACT_MAX_COORDINATE ||
|
|
151
|
+
Math.abs(rect.y + rect.height) > PRESENTATION_ARTIFACT_MAX_COORDINATE
|
|
152
|
+
) {
|
|
153
|
+
context.addIssue({
|
|
154
|
+
code: "custom",
|
|
155
|
+
message: "Viewport extent exceeds presentation coordinates",
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
const PresentationNodeLimit = z.number().int().min(1).max(PRESENTATION_ARTIFACT_QUERY_MAX_NODES);
|
|
160
|
+
const PresentationTextLimit = z
|
|
161
|
+
.number()
|
|
162
|
+
.int()
|
|
163
|
+
.min(1)
|
|
164
|
+
.max(PRESENTATION_ARTIFACT_QUERY_MAX_TEXT_BYTES);
|
|
165
|
+
const presentationResponseBytes = (minimum: number) =>
|
|
166
|
+
z.number().int().min(minimum).max(PRESENTATION_ARTIFACT_QUERY_RESPONSE_MAX_BYTES);
|
|
167
|
+
|
|
168
|
+
const PresentationViewportRequest = z
|
|
169
|
+
.object({
|
|
170
|
+
kind: z.literal("viewport"),
|
|
171
|
+
owner: PresentationOwner,
|
|
172
|
+
viewport: PresentationRect,
|
|
173
|
+
maxNodes: PresentationNodeLimit,
|
|
174
|
+
maxBytes: presentationResponseBytes(89),
|
|
175
|
+
})
|
|
176
|
+
.strict();
|
|
177
|
+
const PresentationHitTestRequest = z
|
|
178
|
+
.object({
|
|
179
|
+
kind: z.literal("hit-test"),
|
|
180
|
+
owner: PresentationOwner,
|
|
181
|
+
x: PresentationCoordinate,
|
|
182
|
+
y: PresentationCoordinate,
|
|
183
|
+
maxNodes: PresentationNodeLimit,
|
|
184
|
+
maxBytes: presentationResponseBytes(89),
|
|
185
|
+
})
|
|
186
|
+
.strict();
|
|
187
|
+
const PresentationResolvedSlideRequest = z
|
|
188
|
+
.object({
|
|
189
|
+
kind: z.literal("resolved-slide"),
|
|
190
|
+
slideId: CanonicalStableId,
|
|
191
|
+
maxNodes: PresentationNodeLimit,
|
|
192
|
+
maxBytes: presentationResponseBytes(56),
|
|
193
|
+
})
|
|
194
|
+
.strict();
|
|
195
|
+
const PresentationMetadataRequest = z
|
|
196
|
+
.object({ kind: z.literal("metadata"), maxBytes: presentationResponseBytes(84) })
|
|
197
|
+
.strict();
|
|
198
|
+
const PresentationSlideCatalogRequest = z
|
|
199
|
+
.object({
|
|
200
|
+
kind: z.literal("slide-catalog"),
|
|
201
|
+
startSlide: Uint32,
|
|
202
|
+
maxSlides: z.number().int().min(1).max(PRESENTATION_ARTIFACT_QUERY_MAX_SLIDES),
|
|
203
|
+
maxTextBytes: PresentationTextLimit,
|
|
204
|
+
maxBytes: presentationResponseBytes(53),
|
|
205
|
+
})
|
|
206
|
+
.strict();
|
|
207
|
+
const PresentationEditorSlideRequest = z
|
|
208
|
+
.object({
|
|
209
|
+
kind: z.literal("editor-slide"),
|
|
210
|
+
slideId: CanonicalStableId,
|
|
211
|
+
maxNodes: PresentationNodeLimit,
|
|
212
|
+
maxTextBytes: PresentationTextLimit,
|
|
213
|
+
maxBytes: presentationResponseBytes(75),
|
|
214
|
+
})
|
|
215
|
+
.strict();
|
|
216
|
+
|
|
217
|
+
/** Exact bounded query union shared by the model-facing direct inspect tool. */
|
|
218
|
+
export const EditableArtifactQueryRequestSchema: z.ZodType<EditableArtifactAgentQuery["query"]> = z
|
|
219
|
+
.union([
|
|
220
|
+
SpreadsheetMetadataRequest,
|
|
221
|
+
SpreadsheetViewportRequest,
|
|
222
|
+
DocumentSummaryRequest,
|
|
223
|
+
DocumentBodyRequest,
|
|
224
|
+
DocumentStoryRequest,
|
|
225
|
+
DocumentSectionsRequest,
|
|
226
|
+
DocumentReviewRequest,
|
|
227
|
+
PresentationViewportRequest,
|
|
228
|
+
PresentationHitTestRequest,
|
|
229
|
+
PresentationResolvedSlideRequest,
|
|
230
|
+
PresentationMetadataRequest,
|
|
231
|
+
PresentationSlideCatalogRequest,
|
|
232
|
+
PresentationEditorSlideRequest,
|
|
233
|
+
])
|
|
234
|
+
.describe(
|
|
235
|
+
"Exact bounded query for the selected modality. Spreadsheet requests retain their required nested query envelope; document and presentation requests use the fields shown by this schema.",
|
|
236
|
+
);
|