@opengeni/api-router 0.23.1 → 0.26.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app.d.ts +2 -2
- package/dist/app.js +1 -1
- package/dist/browser-controller-authority.d.ts +43 -0
- package/dist/browser-state-authority.d.ts +27 -0
- package/dist/{chunk-T4T2PGU4.js → chunk-JIKNR5YL.js} +26181 -14408
- package/dist/chunk-JIKNR5YL.js.map +1 -0
- package/dist/codemode.d.ts +23 -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/cors.d.ts +1 -0
- 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/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 +19 -0
- package/dist/integrations/slack-bot.d.ts +4 -0
- package/dist/integrations/slack-interactions.d.ts +8 -2
- package/dist/integrations/social-api.d.ts +2 -1
- 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 +14 -3
- package/dist/memory-slack-delivery.d.ts +9 -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/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-features.d.ts +3 -0
- package/dist/routes/memory-slack-publications.d.ts +6 -0
- package/dist/routes/plugins.d.ts +8 -0
- package/dist/routes/sessions.d.ts +17 -2
- package/dist/routes/skills.d.ts +6 -0
- package/dist/routes/video-generation.d.ts +3 -0
- package/dist/sandbox/channel-a.d.ts +55 -2
- package/dist/sandbox/metrics-ingestion.d.ts +6 -1
- package/dist/sandbox/viewer.d.ts +4 -2
- package/dist/temporal-schedule-cleanup.d.ts +26 -0
- package/package.json +19 -14
- package/src/app.ts +274 -37
- package/src/browser-controller-authority.ts +137 -0
- package/src/browser-state-authority.ts +236 -0
- package/src/codemode.ts +186 -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 +2 -0
- package/src/http/cors.ts +3 -0
- package/src/index.ts +119 -20
- package/src/integrations/api-integrations.ts +350 -0
- package/src/integrations/atlassian.ts +1621 -0
- package/src/integrations/github-skill-source.ts +142 -0
- package/src/integrations/google-drive.ts +1000 -64
- package/src/integrations/oauth-client.ts +123 -33
- package/src/integrations/provider-oauth.ts +777 -0
- package/src/integrations/slack-bot.ts +31 -2
- package/src/integrations/slack-interactions.ts +487 -27
- package/src/integrations/social-api.ts +11 -0
- package/src/mcp/documents.ts +32 -1
- 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 +923 -145
- package/src/memory-slack-delivery.ts +209 -0
- package/src/routes/api-integrations.ts +407 -0
- package/src/routes/browser-identities.ts +136 -0
- package/src/routes/browser-sessions.ts +2543 -0
- package/src/routes/company-profile.ts +255 -0
- package/src/routes/computer-sessions.ts +1247 -0
- package/src/routes/connections.ts +284 -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-features.ts +258 -0
- package/src/routes/memory-slack-publications.ts +216 -0
- package/src/routes/packs.ts +437 -7
- package/src/routes/plugins.ts +751 -0
- package/src/routes/rigs.ts +77 -20
- package/src/routes/scheduled-tasks.ts +67 -40
- package/src/routes/sessions.ts +421 -245
- package/src/routes/skills.ts +174 -0
- package/src/routes/video-generation.ts +132 -0
- package/src/routes/workspaces.ts +46 -24
- package/src/sandbox/channel-a.ts +723 -116
- package/src/sandbox/metrics-ingestion.ts +121 -3
- package/src/sandbox/rematerialize.ts +35 -47
- package/src/sandbox/viewer.ts +29 -9
- package/src/temporal-schedule-cleanup.ts +135 -0
- package/dist/chunk-T4T2PGU4.js.map +0 -1
- package/dist/mcp/toolspace.d.ts +0 -71
- package/src/mcp/toolspace.ts +0 -1190
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
import type { AccessGrant } from "@opengeni/contracts";
|
|
4
|
+
import type {
|
|
5
|
+
EditableArtifactAgentApplication,
|
|
6
|
+
EditableArtifactAgentCommandBatch,
|
|
7
|
+
EditableArtifactAgentContext,
|
|
8
|
+
EditableArtifactAgentQuery,
|
|
9
|
+
} from "@opengeni/core/editable-artifacts";
|
|
10
|
+
import {
|
|
11
|
+
editableArtifactClientTransactionId,
|
|
12
|
+
editableArtifactId,
|
|
13
|
+
} from "@opengeni/core/editable-artifacts";
|
|
14
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
15
|
+
import type { RequestHandlerExtra } from "@modelcontextprotocol/sdk/shared/protocol.js";
|
|
16
|
+
import type { ServerNotification, ServerRequest } from "@modelcontextprotocol/sdk/types.js";
|
|
17
|
+
import * as z from "zod/v4";
|
|
18
|
+
|
|
19
|
+
import type { ApiRouteDeps } from "@opengeni/core";
|
|
20
|
+
import { editableArtifactActorForGrant } from "../routes/editable-artifacts";
|
|
21
|
+
import { EditableArtifactQueryRequestSchema } from "./editable-artifact-query-schema";
|
|
22
|
+
|
|
23
|
+
const ArtifactId = z
|
|
24
|
+
.string()
|
|
25
|
+
.regex(/^[0-9a-f]{32}$/u)
|
|
26
|
+
.refine((value) => value !== "0".repeat(32));
|
|
27
|
+
const StateHash = z.string().regex(/^sha256:[0-9a-f]{64}$/u);
|
|
28
|
+
const Modality = z.enum(["spreadsheet", "document", "presentation"]);
|
|
29
|
+
const Format = z.enum(["xlsx", "pptx", "docx", "pdf", "png", "webp"]);
|
|
30
|
+
const JsonRecord = z.record(z.string(), z.unknown());
|
|
31
|
+
const ArtifactCommand = z
|
|
32
|
+
.object({ kind: z.string().min(1).max(128) })
|
|
33
|
+
.passthrough()
|
|
34
|
+
.describe(
|
|
35
|
+
"One canonical modality command. Use the exact OpenGeni document, spreadsheet, or presentation skill command contract; never send local-file facade calls here.",
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
const ArtifactMetadata = z
|
|
39
|
+
.object({
|
|
40
|
+
id: ArtifactId,
|
|
41
|
+
modality: Modality,
|
|
42
|
+
title: z.string(),
|
|
43
|
+
lifecycle: z.enum(["active", "archived"]),
|
|
44
|
+
headSequence: z.number().int().nonnegative(),
|
|
45
|
+
stateHash: StateHash,
|
|
46
|
+
createdAt: z.string(),
|
|
47
|
+
updatedAt: z.string(),
|
|
48
|
+
})
|
|
49
|
+
.strict();
|
|
50
|
+
const MutationReceipt = z
|
|
51
|
+
.object({
|
|
52
|
+
artifact: ArtifactMetadata,
|
|
53
|
+
transaction: z
|
|
54
|
+
.object({
|
|
55
|
+
id: ArtifactId,
|
|
56
|
+
clientTransactionId: z.string(),
|
|
57
|
+
sequenceStart: z.number().int().positive(),
|
|
58
|
+
sequenceEnd: z.number().int().positive(),
|
|
59
|
+
stateHash: StateHash,
|
|
60
|
+
committedAt: z.string(),
|
|
61
|
+
replayed: z.boolean(),
|
|
62
|
+
})
|
|
63
|
+
.strict(),
|
|
64
|
+
})
|
|
65
|
+
.strict();
|
|
66
|
+
const ExportFile = z
|
|
67
|
+
.object({
|
|
68
|
+
fileId: z.string().uuid(),
|
|
69
|
+
filename: z.string(),
|
|
70
|
+
contentType: z.string(),
|
|
71
|
+
sizeBytes: z.number().int().positive(),
|
|
72
|
+
sha256: z.string().regex(/^[0-9a-f]{64}$/u),
|
|
73
|
+
artifactId: ArtifactId,
|
|
74
|
+
versionId: ArtifactId,
|
|
75
|
+
materializationJobId: ArtifactId,
|
|
76
|
+
sourceHeadSequence: z.number().int().nonnegative(),
|
|
77
|
+
sourceStateHash: StateHash,
|
|
78
|
+
})
|
|
79
|
+
.strict();
|
|
80
|
+
|
|
81
|
+
type ToolExtra = RequestHandlerExtra<ServerRequest, ServerNotification>;
|
|
82
|
+
type RegisterEditableArtifactToolsInput = Readonly<{
|
|
83
|
+
server: McpServer;
|
|
84
|
+
deps: ApiRouteDeps;
|
|
85
|
+
grant: AccessGrant;
|
|
86
|
+
sessionId: string;
|
|
87
|
+
authorize(): Promise<void>;
|
|
88
|
+
}>;
|
|
89
|
+
|
|
90
|
+
/** Register the one canonical agent artifact surface used directly and by CodeMode. */
|
|
91
|
+
export function registerEditableArtifactAgentTools(
|
|
92
|
+
input: RegisterEditableArtifactToolsInput,
|
|
93
|
+
): void {
|
|
94
|
+
const application = () => requireApplication(input.deps);
|
|
95
|
+
const context = (toolName: string, extra: ToolExtra) =>
|
|
96
|
+
artifactAttemptContext(input.grant, input.sessionId, toolName, extra);
|
|
97
|
+
const execute = async <T>(
|
|
98
|
+
operation: () => Promise<T>,
|
|
99
|
+
): Promise<ReturnType<typeof jsonResult>> => {
|
|
100
|
+
await input.authorize();
|
|
101
|
+
return jsonResult(await operation());
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
input.server.registerTool(
|
|
105
|
+
"editable_artifact_list",
|
|
106
|
+
{
|
|
107
|
+
title: "List session artifacts",
|
|
108
|
+
description:
|
|
109
|
+
"List the durable editable workbooks, documents, and presentations used in this session. These are the same live artifacts visible to the user.",
|
|
110
|
+
inputSchema: { limit: z.number().int().min(1).max(64).optional() },
|
|
111
|
+
outputSchema: { artifacts: z.array(ArtifactMetadata) },
|
|
112
|
+
annotations: readOnlyAnnotations("List session artifacts"),
|
|
113
|
+
},
|
|
114
|
+
async ({ limit }, extra) =>
|
|
115
|
+
await execute(async () => ({
|
|
116
|
+
artifacts: await application().list({
|
|
117
|
+
...context("editable_artifact_list", extra),
|
|
118
|
+
...(limit === undefined ? {} : { limit }),
|
|
119
|
+
}),
|
|
120
|
+
})),
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
input.server.registerTool(
|
|
124
|
+
"editable_artifact_create",
|
|
125
|
+
{
|
|
126
|
+
title: "Create editable artifact",
|
|
127
|
+
description:
|
|
128
|
+
"Create an empty durable workbook, document, or presentation and associate it with this session. Continue editing it with opengeni__editable_artifact_apply; do not create a shadow Office file.",
|
|
129
|
+
inputSchema: {
|
|
130
|
+
modality: Modality,
|
|
131
|
+
title: z.string().trim().min(1).max(512),
|
|
132
|
+
},
|
|
133
|
+
outputSchema: ArtifactMetadata,
|
|
134
|
+
annotations: mutationAnnotations("Create editable artifact", { idempotent: true }),
|
|
135
|
+
},
|
|
136
|
+
async ({ modality, title }, extra) => {
|
|
137
|
+
const invocation = context("editable_artifact_create", extra);
|
|
138
|
+
return await execute(
|
|
139
|
+
async () =>
|
|
140
|
+
await application().create({
|
|
141
|
+
...invocation,
|
|
142
|
+
idempotencyKey: editableArtifactClientTransactionId(invocation.operationKey),
|
|
143
|
+
modality,
|
|
144
|
+
title,
|
|
145
|
+
signal: extra.signal,
|
|
146
|
+
}),
|
|
147
|
+
);
|
|
148
|
+
},
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
input.server.registerTool(
|
|
152
|
+
"editable_artifact_import",
|
|
153
|
+
{
|
|
154
|
+
title: "Import Office file",
|
|
155
|
+
description:
|
|
156
|
+
"Import one ready workspace DOCX, XLSX, or PPTX file into a new durable editable artifact. The imported artifact becomes the shared working state; the source file remains immutable provenance.",
|
|
157
|
+
inputSchema: {
|
|
158
|
+
fileId: z.string().uuid(),
|
|
159
|
+
modality: Modality,
|
|
160
|
+
title: z.string().trim().min(1).max(512),
|
|
161
|
+
},
|
|
162
|
+
outputSchema: ArtifactMetadata,
|
|
163
|
+
annotations: mutationAnnotations("Import Office file", { idempotent: true }),
|
|
164
|
+
},
|
|
165
|
+
async ({ fileId, modality, title }, extra) => {
|
|
166
|
+
const invocation = context("editable_artifact_import", extra);
|
|
167
|
+
return await execute(
|
|
168
|
+
async () =>
|
|
169
|
+
await application().import({
|
|
170
|
+
...invocation,
|
|
171
|
+
idempotencyKey: editableArtifactClientTransactionId(invocation.operationKey),
|
|
172
|
+
fileId,
|
|
173
|
+
modality,
|
|
174
|
+
title,
|
|
175
|
+
signal: extra.signal,
|
|
176
|
+
}),
|
|
177
|
+
);
|
|
178
|
+
},
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
input.server.registerTool(
|
|
182
|
+
"editable_artifact_get",
|
|
183
|
+
{
|
|
184
|
+
title: "Get editable artifact",
|
|
185
|
+
description:
|
|
186
|
+
"Read current durable metadata for one editable artifact and associate it with this session.",
|
|
187
|
+
inputSchema: { artifactId: ArtifactId },
|
|
188
|
+
outputSchema: ArtifactMetadata,
|
|
189
|
+
annotations: readOnlyAnnotations("Get editable artifact"),
|
|
190
|
+
},
|
|
191
|
+
async ({ artifactId }, extra) =>
|
|
192
|
+
await execute(
|
|
193
|
+
async () =>
|
|
194
|
+
await application().get({
|
|
195
|
+
...context("editable_artifact_get", extra),
|
|
196
|
+
artifactId: editableArtifactId(artifactId),
|
|
197
|
+
}),
|
|
198
|
+
),
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
input.server.registerTool(
|
|
202
|
+
"editable_artifact_inspect",
|
|
203
|
+
{
|
|
204
|
+
title: "Inspect editable artifact",
|
|
205
|
+
description:
|
|
206
|
+
"Query the artifact's current canonical head through the same native kernel used by the browser. Use bounded modality queries; results are current, not a sandbox-file snapshot.",
|
|
207
|
+
inputSchema: {
|
|
208
|
+
artifactId: ArtifactId,
|
|
209
|
+
modality: Modality,
|
|
210
|
+
request: EditableArtifactQueryRequestSchema,
|
|
211
|
+
},
|
|
212
|
+
outputSchema: { artifact: ArtifactMetadata, projection: z.unknown() },
|
|
213
|
+
annotations: readOnlyAnnotations("Inspect editable artifact"),
|
|
214
|
+
},
|
|
215
|
+
async ({ artifactId, modality, request }, extra) =>
|
|
216
|
+
await execute(
|
|
217
|
+
async () =>
|
|
218
|
+
await application().inspect({
|
|
219
|
+
...context("editable_artifact_inspect", extra),
|
|
220
|
+
artifactId: editableArtifactId(artifactId),
|
|
221
|
+
request: { modality, query: request } as EditableArtifactAgentQuery,
|
|
222
|
+
}),
|
|
223
|
+
),
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
input.server.registerTool(
|
|
227
|
+
"editable_artifact_apply",
|
|
228
|
+
{
|
|
229
|
+
title: "Edit editable artifact",
|
|
230
|
+
description:
|
|
231
|
+
"Atomically apply a bounded modality command batch to the current durable artifact head. Inspect first, use stable object IDs from that projection, and verify the result afterward.",
|
|
232
|
+
inputSchema: {
|
|
233
|
+
artifactId: ArtifactId,
|
|
234
|
+
modality: Modality,
|
|
235
|
+
expectedHeadSequence: z.number().int().nonnegative(),
|
|
236
|
+
expectedStateHash: StateHash,
|
|
237
|
+
commands: z.array(ArtifactCommand).min(1).max(4_096),
|
|
238
|
+
},
|
|
239
|
+
outputSchema: MutationReceipt,
|
|
240
|
+
annotations: mutationAnnotations("Edit editable artifact", {
|
|
241
|
+
destructive: true,
|
|
242
|
+
idempotent: true,
|
|
243
|
+
}),
|
|
244
|
+
},
|
|
245
|
+
async ({ artifactId, modality, expectedHeadSequence, expectedStateHash, commands }, extra) => {
|
|
246
|
+
const invocation = context("editable_artifact_apply", extra);
|
|
247
|
+
return await execute(
|
|
248
|
+
async () =>
|
|
249
|
+
await application().apply({
|
|
250
|
+
...invocation,
|
|
251
|
+
artifactId: editableArtifactId(artifactId),
|
|
252
|
+
clientTransactionId: editableArtifactClientTransactionId(invocation.operationKey),
|
|
253
|
+
expectedHeadSequence,
|
|
254
|
+
expectedStateHash,
|
|
255
|
+
batch: { modality, commands } as EditableArtifactAgentCommandBatch,
|
|
256
|
+
}),
|
|
257
|
+
);
|
|
258
|
+
},
|
|
259
|
+
);
|
|
260
|
+
|
|
261
|
+
input.server.registerTool(
|
|
262
|
+
"editable_artifact_export",
|
|
263
|
+
{
|
|
264
|
+
title: "Export editable artifact",
|
|
265
|
+
description:
|
|
266
|
+
"Pin the current artifact head and start an immutable Office/PDF/image export. This does not write into the sandbox. Poll opengeni__editable_artifact_export_status for the resulting workspace file ID.",
|
|
267
|
+
inputSchema: {
|
|
268
|
+
artifactId: ArtifactId,
|
|
269
|
+
format: Format,
|
|
270
|
+
options: JsonRecord.optional(),
|
|
271
|
+
},
|
|
272
|
+
outputSchema: {
|
|
273
|
+
artifact: ArtifactMetadata,
|
|
274
|
+
versionId: ArtifactId,
|
|
275
|
+
jobId: ArtifactId,
|
|
276
|
+
sourceHeadSequence: z.number().int().nonnegative(),
|
|
277
|
+
sourceStateHash: StateHash,
|
|
278
|
+
state: z.enum(["pending", "running", "succeeded", "failed"]),
|
|
279
|
+
},
|
|
280
|
+
annotations: mutationAnnotations("Export editable artifact", { idempotent: true }),
|
|
281
|
+
},
|
|
282
|
+
async ({ artifactId, format, options }, extra) => {
|
|
283
|
+
const invocation = context("editable_artifact_export", extra);
|
|
284
|
+
return await execute(
|
|
285
|
+
async () =>
|
|
286
|
+
await application().startExport({
|
|
287
|
+
...invocation,
|
|
288
|
+
artifactId: editableArtifactId(artifactId),
|
|
289
|
+
idempotencyKey: invocation.operationKey,
|
|
290
|
+
format,
|
|
291
|
+
...(options ? { options } : {}),
|
|
292
|
+
signal: extra.signal,
|
|
293
|
+
}),
|
|
294
|
+
);
|
|
295
|
+
},
|
|
296
|
+
);
|
|
297
|
+
|
|
298
|
+
input.server.registerTool(
|
|
299
|
+
"editable_artifact_export_status",
|
|
300
|
+
{
|
|
301
|
+
title: "Get artifact export",
|
|
302
|
+
description:
|
|
303
|
+
"Read export status. When complete, atomically promote the immutable result into the workspace file domain and return its file ID; download it only when sandbox-local bytes are actually needed.",
|
|
304
|
+
inputSchema: {
|
|
305
|
+
artifactId: ArtifactId,
|
|
306
|
+
versionId: ArtifactId,
|
|
307
|
+
jobId: ArtifactId,
|
|
308
|
+
},
|
|
309
|
+
outputSchema: {
|
|
310
|
+
artifact: ArtifactMetadata,
|
|
311
|
+
versionId: ArtifactId,
|
|
312
|
+
jobId: ArtifactId,
|
|
313
|
+
sourceHeadSequence: z.number().int().nonnegative(),
|
|
314
|
+
sourceStateHash: StateHash,
|
|
315
|
+
state: z.enum(["pending", "running", "succeeded", "failed"]),
|
|
316
|
+
errorCode: z.string().nullable(),
|
|
317
|
+
file: ExportFile.nullable(),
|
|
318
|
+
},
|
|
319
|
+
annotations: mutationAnnotations("Get artifact export", { idempotent: true }),
|
|
320
|
+
},
|
|
321
|
+
async ({ artifactId, versionId, jobId }, extra) =>
|
|
322
|
+
await execute(
|
|
323
|
+
async () =>
|
|
324
|
+
await application().exportStatus({
|
|
325
|
+
...context("editable_artifact_export_status", extra),
|
|
326
|
+
artifactId: editableArtifactId(artifactId),
|
|
327
|
+
versionId,
|
|
328
|
+
jobId,
|
|
329
|
+
signal: extra.signal,
|
|
330
|
+
}),
|
|
331
|
+
),
|
|
332
|
+
);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function artifactAttemptContext(
|
|
336
|
+
grant: AccessGrant,
|
|
337
|
+
sessionId: string,
|
|
338
|
+
toolName: string,
|
|
339
|
+
extra: ToolExtra,
|
|
340
|
+
): EditableArtifactAgentContext & Readonly<{ operationKey: string }> {
|
|
341
|
+
const operationId = trustedOperationId(extra);
|
|
342
|
+
const digest = createHash("sha256")
|
|
343
|
+
.update("opengeni:editable-artifact-agent-call:v1\0", "utf8")
|
|
344
|
+
.update(grant.accountId, "utf8")
|
|
345
|
+
.update("\0", "utf8")
|
|
346
|
+
.update(grant.workspaceId, "utf8")
|
|
347
|
+
.update("\0", "utf8")
|
|
348
|
+
.update(sessionId, "utf8")
|
|
349
|
+
.update("\0", "utf8")
|
|
350
|
+
.update(String(grant.metadata?.["turnId"] ?? ""), "utf8")
|
|
351
|
+
.update("\0", "utf8")
|
|
352
|
+
.update(String(grant.metadata?.["attemptId"] ?? ""), "utf8")
|
|
353
|
+
.update("\0", "utf8")
|
|
354
|
+
.update(String(grant.metadata?.["executionGeneration"] ?? ""), "utf8")
|
|
355
|
+
.update("\0", "utf8")
|
|
356
|
+
.update(toolName, "utf8")
|
|
357
|
+
.update("\0", "utf8")
|
|
358
|
+
.update(operationId, "utf8")
|
|
359
|
+
.digest("hex");
|
|
360
|
+
const replicaId =
|
|
361
|
+
digest.slice(0, 16) === "0".repeat(16) ? `1${digest.slice(1, 16)}` : digest.slice(0, 16);
|
|
362
|
+
const actor = editableArtifactActorForGrant(grant, replicaId);
|
|
363
|
+
if (actor.kind !== "agent" || actor.sessionId !== sessionId) {
|
|
364
|
+
throw new Error("Editable artifact tools require exact signed attempt authority");
|
|
365
|
+
}
|
|
366
|
+
return Object.freeze({
|
|
367
|
+
scope: Object.freeze({ accountId: grant.accountId, workspaceId: grant.workspaceId }),
|
|
368
|
+
actor,
|
|
369
|
+
sessionId,
|
|
370
|
+
operationKey: `artifact:${toolName}:${digest}`,
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
function trustedOperationId(extra: ToolExtra): string {
|
|
375
|
+
const value = extra._meta?.["opengeniOperationId"];
|
|
376
|
+
if (typeof value === "string" && /^[A-Za-z0-9._:-]{1,200}$/u.test(value)) return value;
|
|
377
|
+
const requestId = String(extra.requestId);
|
|
378
|
+
if (requestId.length < 1 || requestId.length > 256) {
|
|
379
|
+
throw new Error("Editable artifact tool request identity is invalid");
|
|
380
|
+
}
|
|
381
|
+
return requestId;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function requireApplication(deps: ApiRouteDeps): EditableArtifactAgentApplication {
|
|
385
|
+
if (!deps.editableArtifactAgent) {
|
|
386
|
+
throw new Error("Editable artifact agent service is unavailable");
|
|
387
|
+
}
|
|
388
|
+
return deps.editableArtifactAgent;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function jsonResult(value: unknown) {
|
|
392
|
+
const structuredContent = jsonValue(value);
|
|
393
|
+
if (!isRecord(structuredContent)) {
|
|
394
|
+
throw new Error("Editable artifact tool result must be an object");
|
|
395
|
+
}
|
|
396
|
+
return {
|
|
397
|
+
content: [{ type: "text" as const, text: JSON.stringify(structuredContent) }],
|
|
398
|
+
structuredContent,
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
function jsonValue(value: unknown): unknown {
|
|
403
|
+
if (value === null || typeof value === "string" || typeof value === "boolean") return value;
|
|
404
|
+
if (typeof value === "number") {
|
|
405
|
+
if (!Number.isFinite(value)) throw new Error("Artifact result contains a non-finite number");
|
|
406
|
+
return value;
|
|
407
|
+
}
|
|
408
|
+
if (typeof value === "bigint") return value.toString(10);
|
|
409
|
+
if (value instanceof Uint8Array) {
|
|
410
|
+
return Object.freeze({ encoding: "base64", data: Buffer.from(value).toString("base64") });
|
|
411
|
+
}
|
|
412
|
+
if (Array.isArray(value)) return value.map(jsonValue);
|
|
413
|
+
if (isRecord(value)) {
|
|
414
|
+
const output: Record<string, unknown> = {};
|
|
415
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
416
|
+
if (entry !== undefined) output[key] = jsonValue(entry);
|
|
417
|
+
}
|
|
418
|
+
return output;
|
|
419
|
+
}
|
|
420
|
+
throw new Error("Artifact result contains unsupported data");
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
424
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
function readOnlyAnnotations(title: string) {
|
|
428
|
+
return {
|
|
429
|
+
title,
|
|
430
|
+
readOnlyHint: true,
|
|
431
|
+
destructiveHint: false,
|
|
432
|
+
idempotentHint: true,
|
|
433
|
+
openWorldHint: false,
|
|
434
|
+
} as const;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
function mutationAnnotations(
|
|
438
|
+
title: string,
|
|
439
|
+
options: Readonly<{ destructive?: boolean; idempotent: boolean }>,
|
|
440
|
+
) {
|
|
441
|
+
return {
|
|
442
|
+
title,
|
|
443
|
+
readOnlyHint: false,
|
|
444
|
+
destructiveHint: options.destructive === true,
|
|
445
|
+
idempotentHint: options.idempotent,
|
|
446
|
+
openWorldHint: false,
|
|
447
|
+
} as const;
|
|
448
|
+
}
|
|
@@ -24,6 +24,14 @@ type Utf8Projection = {
|
|
|
24
24
|
export type ScheduledTaskUtf8ProjectionFact = Omit<Utf8Projection, "value">;
|
|
25
25
|
|
|
26
26
|
type ScheduledTaskScheduleProjection =
|
|
27
|
+
| {
|
|
28
|
+
schedule: Extract<ScheduledTaskScheduleSpec, { type: "manual" }>;
|
|
29
|
+
projection: {
|
|
30
|
+
type: "manual";
|
|
31
|
+
truncated: false;
|
|
32
|
+
fields: Record<string, never>;
|
|
33
|
+
};
|
|
34
|
+
}
|
|
27
35
|
| {
|
|
28
36
|
schedule: Extract<ScheduledTaskScheduleSpec, { type: "once" }>;
|
|
29
37
|
projection: {
|
|
@@ -171,6 +179,11 @@ export function projectScheduledTaskSchedule(
|
|
|
171
179
|
schedule: ScheduledTaskScheduleSpec,
|
|
172
180
|
): ScheduledTaskScheduleProjection {
|
|
173
181
|
switch (schedule.type) {
|
|
182
|
+
case "manual":
|
|
183
|
+
return {
|
|
184
|
+
schedule: { type: "manual" },
|
|
185
|
+
projection: { type: "manual", truncated: false, fields: {} },
|
|
186
|
+
};
|
|
174
187
|
case "once": {
|
|
175
188
|
const runAt = projectScheduledTaskUtf8(
|
|
176
189
|
schedule.runAt,
|
|
@@ -294,6 +307,7 @@ function toolIdentity(
|
|
|
294
307
|
}
|
|
295
308
|
|
|
296
309
|
export function scheduledTaskMcpSummary(task: ScheduledTask) {
|
|
310
|
+
const action = task.action ?? ({ kind: "agent_turn" } as const);
|
|
297
311
|
const name = projectScheduledTaskUtf8(task.name, SCHEDULED_TASK_NAME_MAX_BYTES);
|
|
298
312
|
const schedule = projectScheduledTaskSchedule(task.schedule);
|
|
299
313
|
const model = task.agentConfig.model
|
|
@@ -308,6 +322,26 @@ export function scheduledTaskMcpSummary(task: ScheduledTask) {
|
|
|
308
322
|
schedule: schedule.schedule,
|
|
309
323
|
runMode: task.runMode,
|
|
310
324
|
overlapPolicy: task.overlapPolicy,
|
|
325
|
+
action:
|
|
326
|
+
action.kind === "agent_turn"
|
|
327
|
+
? { kind: "agent_turn" as const }
|
|
328
|
+
: {
|
|
329
|
+
kind: action.kind,
|
|
330
|
+
sourceId: action.sourceId,
|
|
331
|
+
sourceGeneration: action.sourceGeneration,
|
|
332
|
+
sourceLifecycleGeneration: action.sourceLifecycleGeneration,
|
|
333
|
+
destination: action.destination,
|
|
334
|
+
initiatingSubjectId: action.initiatingSubjectId,
|
|
335
|
+
allDescendants: action.allDescendants,
|
|
336
|
+
connection: {
|
|
337
|
+
connectionId: action.connection.connectionId,
|
|
338
|
+
connectionVersion: action.connection.connectionVersion,
|
|
339
|
+
providerDomain: action.connection.providerDomain,
|
|
340
|
+
kind: action.connection.kind,
|
|
341
|
+
ownerSubjectId: action.connection.ownerSubjectId,
|
|
342
|
+
},
|
|
343
|
+
limits: action.limits,
|
|
344
|
+
},
|
|
311
345
|
targetSessionId: task.targetSessionId,
|
|
312
346
|
reusableSessionId: task.reusableSessionId,
|
|
313
347
|
variableSetId: task.variableSetId,
|