@opengeni/api-router 0.22.2 → 0.23.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.js +1 -1
- package/dist/auth/managed-auth.d.ts +0 -30
- package/dist/{chunk-HWXJW5C7.js → chunk-T4T2PGU4.js} +3036 -1362
- package/dist/chunk-T4T2PGU4.js.map +1 -0
- package/dist/http/sse.d.ts +2 -0
- package/dist/index.js +29 -9
- package/dist/index.js.map +1 -1
- package/dist/integrations/oauth-client.d.ts +8 -0
- package/dist/integrations/slack-interactions.d.ts +7 -1
- package/dist/mcp/receipts.d.ts +28 -0
- package/dist/mcp/scheduled-task-view.d.ts +350 -0
- package/dist/mcp/toolspace.d.ts +9 -0
- package/dist/sandbox/auth-callout.d.ts +2 -0
- package/dist/sandbox/channel-a.d.ts +5 -1
- package/package.json +12 -12
- package/src/app.ts +3 -4
- package/src/auth/managed-auth.ts +0 -16
- package/src/http/sse.ts +101 -6
- package/src/index.ts +28 -3
- package/src/integrations/oauth-client.ts +36 -56
- package/src/integrations/slack-interactions.ts +123 -15
- package/src/mcp/documents.ts +42 -25
- package/src/mcp/receipts.ts +95 -0
- package/src/mcp/scheduled-task-view.ts +608 -0
- package/src/mcp/server.ts +812 -182
- package/src/mcp/toolspace.ts +75 -71
- package/src/observability.ts +3 -3
- package/src/routes/api-keys.ts +7 -1
- package/src/routes/codex.ts +7 -4
- package/src/routes/connections.ts +74 -3
- package/src/routes/enrollments.ts +54 -12
- package/src/routes/environments.ts +60 -11
- package/src/routes/files.ts +175 -65
- package/src/routes/install.ts +31 -1
- package/src/routes/machines.ts +1 -1
- package/src/routes/scheduled-tasks.ts +39 -14
- package/src/routes/sessions.ts +77 -12
- package/src/routes/transcription-recordings.ts +65 -33
- package/src/sandbox/auth-callout.ts +16 -4
- package/src/sandbox/channel-a.ts +124 -7
- package/src/sandbox/enrollment.ts +13 -3
- package/src/sandbox/machines.ts +1 -1
- package/src/sandbox/viewer.ts +29 -20
- package/dist/chunk-HWXJW5C7.js.map +0 -1
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import {
|
|
2
|
+
MCP_MUTATION_RECEIPT_VERSION,
|
|
3
|
+
McpMutationReceipt,
|
|
4
|
+
type McpMutationReceiptType,
|
|
5
|
+
} from "@opengeni/contracts";
|
|
6
|
+
|
|
7
|
+
export type McpMutationReceiptInput = Omit<
|
|
8
|
+
McpMutationReceiptType,
|
|
9
|
+
"receiptVersion" | "timestamp" | "warnings"
|
|
10
|
+
> & {
|
|
11
|
+
timestamp?: string;
|
|
12
|
+
warnings?: string[];
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Build and validate a compact first-party MCP mutation receipt at the API
|
|
17
|
+
* boundary. Contract parsing is intentional: it prevents a handler from
|
|
18
|
+
* accidentally adding an unbounded entity or a copy of request fields.
|
|
19
|
+
*/
|
|
20
|
+
export function mcpMutationReceipt(input: McpMutationReceiptInput): McpMutationReceiptType {
|
|
21
|
+
return McpMutationReceipt.parse({
|
|
22
|
+
receiptVersion: MCP_MUTATION_RECEIPT_VERSION,
|
|
23
|
+
...input,
|
|
24
|
+
timestamp: input.timestamp ?? new Date().toISOString(),
|
|
25
|
+
warnings: input.warnings ?? [],
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export type SessionCreateReceiptResult = {
|
|
30
|
+
session: {
|
|
31
|
+
id: string;
|
|
32
|
+
queueVersion: number;
|
|
33
|
+
status: string;
|
|
34
|
+
sandboxGroupId: string;
|
|
35
|
+
parentSessionId: string | null;
|
|
36
|
+
rootSessionId: string;
|
|
37
|
+
nestedAgentDepth: number;
|
|
38
|
+
effectiveMaxNestedAgentDepth: number;
|
|
39
|
+
};
|
|
40
|
+
outcome: "created" | "repaired" | "replayed";
|
|
41
|
+
changed: boolean;
|
|
42
|
+
usageRecording: "recorded" | "failed";
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/** Project committed session-create truth without copying request fields. */
|
|
46
|
+
export function sessionCreateMutationReceipt(
|
|
47
|
+
result: SessionCreateReceiptResult,
|
|
48
|
+
idempotencyKeyRequested: boolean,
|
|
49
|
+
): McpMutationReceiptType {
|
|
50
|
+
const usageRecordingFailed = result.usageRecording === "failed";
|
|
51
|
+
const retryable = usageRecordingFailed && idempotencyKeyRequested;
|
|
52
|
+
return mcpMutationReceipt({
|
|
53
|
+
operation: "session_create",
|
|
54
|
+
committed: true,
|
|
55
|
+
outcome: usageRecordingFailed ? "partial_failure" : result.outcome,
|
|
56
|
+
changed: result.changed,
|
|
57
|
+
resource: {
|
|
58
|
+
type: "session",
|
|
59
|
+
id: result.session.id,
|
|
60
|
+
version: result.session.queueVersion,
|
|
61
|
+
state: result.session.status,
|
|
62
|
+
},
|
|
63
|
+
idempotency: {
|
|
64
|
+
status:
|
|
65
|
+
result.outcome === "replayed"
|
|
66
|
+
? "replayed"
|
|
67
|
+
: idempotencyKeyRequested
|
|
68
|
+
? "applied"
|
|
69
|
+
: "not_requested",
|
|
70
|
+
},
|
|
71
|
+
...(usageRecordingFailed
|
|
72
|
+
? {
|
|
73
|
+
partialFailure: { stage: "usage_recording", retryable },
|
|
74
|
+
warnings: [
|
|
75
|
+
retryable
|
|
76
|
+
? "The session committed, but usage recording failed. Retry only with the same idempotency key."
|
|
77
|
+
: "The session committed, but usage recording failed. Do not retry this keyless request; inspect the returned session.",
|
|
78
|
+
],
|
|
79
|
+
}
|
|
80
|
+
: {}),
|
|
81
|
+
facts: {
|
|
82
|
+
sandboxGroupId: result.session.sandboxGroupId,
|
|
83
|
+
parentSessionId: result.session.parentSessionId,
|
|
84
|
+
sessionCreateOutcome: result.outcome,
|
|
85
|
+
},
|
|
86
|
+
id: result.session.id,
|
|
87
|
+
rootSessionId: result.session.rootSessionId,
|
|
88
|
+
nestedAgentDepth: result.session.nestedAgentDepth,
|
|
89
|
+
effectiveMaxNestedAgentDepth: result.session.effectiveMaxNestedAgentDepth,
|
|
90
|
+
nextAction: {
|
|
91
|
+
tool: "session_get",
|
|
92
|
+
arguments: { sessionId: result.session.id },
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
}
|
|
@@ -0,0 +1,608 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ResourceRef,
|
|
3
|
+
ScheduledTask,
|
|
4
|
+
ScheduledTaskScheduleSpec,
|
|
5
|
+
ToolRef,
|
|
6
|
+
} from "@opengeni/contracts";
|
|
7
|
+
|
|
8
|
+
export const SCHEDULED_TASK_MCP_MAX_BYTES = 64 * 1024;
|
|
9
|
+
export const SCHEDULED_TASK_NAME_MAX_BYTES = 512;
|
|
10
|
+
export const SCHEDULED_TASK_PROMPT_MAX_BYTES = 8 * 1024;
|
|
11
|
+
export const SCHEDULED_TASK_SCHEDULE_FIELD_MAX_BYTES = 256;
|
|
12
|
+
const SCHEDULED_TASK_GOAL_FIELD_MAX_BYTES = 2 * 1024;
|
|
13
|
+
const SCHEDULED_TASK_MODEL_MAX_BYTES = 512;
|
|
14
|
+
const SCHEDULED_TASK_TIMESTAMP_MAX_BYTES = 128;
|
|
15
|
+
const SCHEDULED_TASK_IDENTITY_PREVIEW_LIMIT = 20;
|
|
16
|
+
|
|
17
|
+
type Utf8Projection = {
|
|
18
|
+
value: string;
|
|
19
|
+
originalBytes: number;
|
|
20
|
+
deliveredBytes: number;
|
|
21
|
+
truncated: boolean;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export type ScheduledTaskUtf8ProjectionFact = Omit<Utf8Projection, "value">;
|
|
25
|
+
|
|
26
|
+
type ScheduledTaskScheduleProjection =
|
|
27
|
+
| {
|
|
28
|
+
schedule: Extract<ScheduledTaskScheduleSpec, { type: "once" }>;
|
|
29
|
+
projection: {
|
|
30
|
+
type: "once";
|
|
31
|
+
truncated: boolean;
|
|
32
|
+
fields: {
|
|
33
|
+
runAt: ScheduledTaskUtf8ProjectionFact;
|
|
34
|
+
timeZone: ScheduledTaskUtf8ProjectionFact;
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
| {
|
|
39
|
+
schedule: Extract<ScheduledTaskScheduleSpec, { type: "interval" }>;
|
|
40
|
+
projection: {
|
|
41
|
+
type: "interval";
|
|
42
|
+
truncated: boolean;
|
|
43
|
+
fields: {
|
|
44
|
+
startAt: ScheduledTaskUtf8ProjectionFact | null;
|
|
45
|
+
endAt: ScheduledTaskUtf8ProjectionFact | null;
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
| {
|
|
50
|
+
schedule: Extract<ScheduledTaskScheduleSpec, { type: "calendar" }>;
|
|
51
|
+
projection: {
|
|
52
|
+
type: "calendar";
|
|
53
|
+
truncated: boolean;
|
|
54
|
+
fields: {
|
|
55
|
+
timeZone: ScheduledTaskUtf8ProjectionFact;
|
|
56
|
+
};
|
|
57
|
+
};
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
type DetailBudget = {
|
|
61
|
+
promptBytes: number;
|
|
62
|
+
goalFieldBytes: number;
|
|
63
|
+
identityPreviewLimit: number;
|
|
64
|
+
resourceUriBytes: number;
|
|
65
|
+
resourceRefBytes: number;
|
|
66
|
+
toolIdBytes: number;
|
|
67
|
+
metadataKeyBytes: number;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const DETAIL_BUDGETS: readonly DetailBudget[] = [
|
|
71
|
+
{
|
|
72
|
+
promptBytes: SCHEDULED_TASK_PROMPT_MAX_BYTES,
|
|
73
|
+
goalFieldBytes: SCHEDULED_TASK_GOAL_FIELD_MAX_BYTES,
|
|
74
|
+
identityPreviewLimit: SCHEDULED_TASK_IDENTITY_PREVIEW_LIMIT,
|
|
75
|
+
resourceUriBytes: 512,
|
|
76
|
+
resourceRefBytes: 256,
|
|
77
|
+
toolIdBytes: 256,
|
|
78
|
+
metadataKeyBytes: 256,
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
promptBytes: 4 * 1024,
|
|
82
|
+
goalFieldBytes: 1024,
|
|
83
|
+
identityPreviewLimit: 10,
|
|
84
|
+
resourceUriBytes: 256,
|
|
85
|
+
resourceRefBytes: 128,
|
|
86
|
+
toolIdBytes: 128,
|
|
87
|
+
metadataKeyBytes: 128,
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
promptBytes: 2 * 1024,
|
|
91
|
+
goalFieldBytes: 512,
|
|
92
|
+
identityPreviewLimit: 5,
|
|
93
|
+
resourceUriBytes: 128,
|
|
94
|
+
resourceRefBytes: 96,
|
|
95
|
+
toolIdBytes: 96,
|
|
96
|
+
metadataKeyBytes: 96,
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
promptBytes: 1024,
|
|
100
|
+
goalFieldBytes: 256,
|
|
101
|
+
identityPreviewLimit: 2,
|
|
102
|
+
resourceUriBytes: 96,
|
|
103
|
+
resourceRefBytes: 64,
|
|
104
|
+
toolIdBytes: 64,
|
|
105
|
+
metadataKeyBytes: 64,
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
promptBytes: 512,
|
|
109
|
+
goalFieldBytes: 128,
|
|
110
|
+
identityPreviewLimit: 0,
|
|
111
|
+
resourceUriBytes: 64,
|
|
112
|
+
resourceRefBytes: 64,
|
|
113
|
+
toolIdBytes: 64,
|
|
114
|
+
metadataKeyBytes: 64,
|
|
115
|
+
},
|
|
116
|
+
];
|
|
117
|
+
|
|
118
|
+
function utf8Prefix(value: string, maxBytes: number): string {
|
|
119
|
+
let index = 0;
|
|
120
|
+
let bytes = 0;
|
|
121
|
+
while (index < value.length) {
|
|
122
|
+
const codePoint = value.codePointAt(index)!;
|
|
123
|
+
const character = String.fromCodePoint(codePoint);
|
|
124
|
+
const characterBytes = Buffer.byteLength(character, "utf8");
|
|
125
|
+
if (bytes + characterBytes > maxBytes) break;
|
|
126
|
+
bytes += characterBytes;
|
|
127
|
+
index += character.length;
|
|
128
|
+
}
|
|
129
|
+
return value.slice(0, index);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export function projectScheduledTaskUtf8(value: string, maxBytes: number): Utf8Projection {
|
|
133
|
+
const byteLimit = Math.max(0, Math.floor(maxBytes));
|
|
134
|
+
const originalBytes = Buffer.byteLength(value, "utf8");
|
|
135
|
+
if (originalBytes <= byteLimit) {
|
|
136
|
+
return { value, originalBytes, deliveredBytes: originalBytes, truncated: false };
|
|
137
|
+
}
|
|
138
|
+
let marker = "";
|
|
139
|
+
let prefix = "";
|
|
140
|
+
let omittedBytes = originalBytes;
|
|
141
|
+
for (let attempt = 0; attempt < 4; attempt += 1) {
|
|
142
|
+
marker = `…[${omittedBytes} UTF-8 bytes omitted]`;
|
|
143
|
+
if (Buffer.byteLength(marker, "utf8") > byteLimit) {
|
|
144
|
+
marker = utf8Prefix(marker, byteLimit);
|
|
145
|
+
prefix = "";
|
|
146
|
+
break;
|
|
147
|
+
}
|
|
148
|
+
prefix = utf8Prefix(value, byteLimit - Buffer.byteLength(marker, "utf8"));
|
|
149
|
+
const nextOmitted = originalBytes - Buffer.byteLength(prefix, "utf8");
|
|
150
|
+
if (nextOmitted === omittedBytes) break;
|
|
151
|
+
omittedBytes = nextOmitted;
|
|
152
|
+
}
|
|
153
|
+
const projected = `${prefix}${marker}`;
|
|
154
|
+
return {
|
|
155
|
+
value: projected,
|
|
156
|
+
originalBytes,
|
|
157
|
+
deliveredBytes: Buffer.byteLength(projected, "utf8"),
|
|
158
|
+
truncated: true,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function projectionFact(projection: Utf8Projection): ScheduledTaskUtf8ProjectionFact {
|
|
163
|
+
return {
|
|
164
|
+
originalBytes: projection.originalBytes,
|
|
165
|
+
deliveredBytes: projection.deliveredBytes,
|
|
166
|
+
truncated: projection.truncated,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export function projectScheduledTaskSchedule(
|
|
171
|
+
schedule: ScheduledTaskScheduleSpec,
|
|
172
|
+
): ScheduledTaskScheduleProjection {
|
|
173
|
+
switch (schedule.type) {
|
|
174
|
+
case "once": {
|
|
175
|
+
const runAt = projectScheduledTaskUtf8(
|
|
176
|
+
schedule.runAt,
|
|
177
|
+
SCHEDULED_TASK_SCHEDULE_FIELD_MAX_BYTES,
|
|
178
|
+
);
|
|
179
|
+
const timeZone = projectScheduledTaskUtf8(
|
|
180
|
+
schedule.timeZone,
|
|
181
|
+
SCHEDULED_TASK_SCHEDULE_FIELD_MAX_BYTES,
|
|
182
|
+
);
|
|
183
|
+
return {
|
|
184
|
+
schedule: { type: "once", runAt: runAt.value, timeZone: timeZone.value },
|
|
185
|
+
projection: {
|
|
186
|
+
type: "once",
|
|
187
|
+
truncated: runAt.truncated || timeZone.truncated,
|
|
188
|
+
fields: { runAt: projectionFact(runAt), timeZone: projectionFact(timeZone) },
|
|
189
|
+
},
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
case "interval": {
|
|
193
|
+
const startAt = schedule.startAt
|
|
194
|
+
? projectScheduledTaskUtf8(schedule.startAt, SCHEDULED_TASK_SCHEDULE_FIELD_MAX_BYTES)
|
|
195
|
+
: null;
|
|
196
|
+
const endAt = schedule.endAt
|
|
197
|
+
? projectScheduledTaskUtf8(schedule.endAt, SCHEDULED_TASK_SCHEDULE_FIELD_MAX_BYTES)
|
|
198
|
+
: null;
|
|
199
|
+
return {
|
|
200
|
+
schedule: {
|
|
201
|
+
type: "interval",
|
|
202
|
+
everySeconds: schedule.everySeconds,
|
|
203
|
+
...(startAt ? { startAt: startAt.value } : {}),
|
|
204
|
+
...(endAt ? { endAt: endAt.value } : {}),
|
|
205
|
+
},
|
|
206
|
+
projection: {
|
|
207
|
+
type: "interval",
|
|
208
|
+
truncated: Boolean(startAt?.truncated || endAt?.truncated),
|
|
209
|
+
fields: {
|
|
210
|
+
startAt: startAt ? projectionFact(startAt) : null,
|
|
211
|
+
endAt: endAt ? projectionFact(endAt) : null,
|
|
212
|
+
},
|
|
213
|
+
},
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
case "calendar": {
|
|
217
|
+
const timeZone = projectScheduledTaskUtf8(
|
|
218
|
+
schedule.timeZone,
|
|
219
|
+
SCHEDULED_TASK_SCHEDULE_FIELD_MAX_BYTES,
|
|
220
|
+
);
|
|
221
|
+
return {
|
|
222
|
+
schedule: {
|
|
223
|
+
type: "calendar",
|
|
224
|
+
timeZone: timeZone.value,
|
|
225
|
+
hour: schedule.hour,
|
|
226
|
+
minute: schedule.minute,
|
|
227
|
+
...(schedule.daysOfWeek ? { daysOfWeek: schedule.daysOfWeek } : {}),
|
|
228
|
+
},
|
|
229
|
+
projection: {
|
|
230
|
+
type: "calendar",
|
|
231
|
+
truncated: timeZone.truncated,
|
|
232
|
+
fields: { timeZone: projectionFact(timeZone) },
|
|
233
|
+
},
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function jsonBytes(value: unknown): number {
|
|
240
|
+
return Buffer.byteLength(JSON.stringify(value), "utf8");
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function mcpJsonBytes(value: unknown): number {
|
|
244
|
+
return Buffer.byteLength(JSON.stringify(value, null, 2), "utf8");
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function settleMeasuredBytes(
|
|
248
|
+
value: unknown,
|
|
249
|
+
read: () => number,
|
|
250
|
+
write: (bytes: number) => void,
|
|
251
|
+
): number {
|
|
252
|
+
for (let attempt = 0; attempt < 16; attempt += 1) {
|
|
253
|
+
const bytes = mcpJsonBytes(value);
|
|
254
|
+
if (bytes === read()) return bytes;
|
|
255
|
+
write(bytes);
|
|
256
|
+
}
|
|
257
|
+
return mcpJsonBytes(value);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function resourceIdentity(
|
|
261
|
+
resource: ResourceRef,
|
|
262
|
+
budget: DetailBudget,
|
|
263
|
+
): { value: Record<string, unknown>; truncatedFieldCount: number } {
|
|
264
|
+
if (resource.kind === "repository") {
|
|
265
|
+
const uri = projectScheduledTaskUtf8(resource.uri, budget.resourceUriBytes);
|
|
266
|
+
const ref = resource.ref
|
|
267
|
+
? projectScheduledTaskUtf8(resource.ref, budget.resourceRefBytes)
|
|
268
|
+
: null;
|
|
269
|
+
return {
|
|
270
|
+
value: {
|
|
271
|
+
kind: resource.kind,
|
|
272
|
+
uri: uri.value,
|
|
273
|
+
...(ref ? { ref: ref.value } : {}),
|
|
274
|
+
},
|
|
275
|
+
truncatedFieldCount: Number(uri.truncated) + Number(ref?.truncated ?? false),
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
return { value: { kind: resource.kind, fileId: resource.fileId }, truncatedFieldCount: 0 };
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function toolIdentity(
|
|
282
|
+
tool: ToolRef,
|
|
283
|
+
budget: DetailBudget,
|
|
284
|
+
): { value: Record<string, unknown>; truncatedFieldCount: number } {
|
|
285
|
+
const id = projectScheduledTaskUtf8(tool.id, budget.toolIdBytes);
|
|
286
|
+
return {
|
|
287
|
+
value: {
|
|
288
|
+
kind: tool.kind,
|
|
289
|
+
id: id.value,
|
|
290
|
+
...(tool.optional !== undefined ? { optional: tool.optional } : {}),
|
|
291
|
+
},
|
|
292
|
+
truncatedFieldCount: Number(id.truncated),
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
export function scheduledTaskMcpSummary(task: ScheduledTask) {
|
|
297
|
+
const name = projectScheduledTaskUtf8(task.name, SCHEDULED_TASK_NAME_MAX_BYTES);
|
|
298
|
+
const schedule = projectScheduledTaskSchedule(task.schedule);
|
|
299
|
+
const model = task.agentConfig.model
|
|
300
|
+
? projectScheduledTaskUtf8(task.agentConfig.model, SCHEDULED_TASK_MODEL_MAX_BYTES)
|
|
301
|
+
: null;
|
|
302
|
+
const createdAt = projectScheduledTaskUtf8(task.createdAt, SCHEDULED_TASK_TIMESTAMP_MAX_BYTES);
|
|
303
|
+
const updatedAt = projectScheduledTaskUtf8(task.updatedAt, SCHEDULED_TASK_TIMESTAMP_MAX_BYTES);
|
|
304
|
+
const result = {
|
|
305
|
+
id: task.id,
|
|
306
|
+
name: name.value,
|
|
307
|
+
status: task.status,
|
|
308
|
+
schedule: schedule.schedule,
|
|
309
|
+
runMode: task.runMode,
|
|
310
|
+
overlapPolicy: task.overlapPolicy,
|
|
311
|
+
targetSessionId: task.targetSessionId,
|
|
312
|
+
reusableSessionId: task.reusableSessionId,
|
|
313
|
+
variableSetId: task.variableSetId,
|
|
314
|
+
rigId: task.rigId,
|
|
315
|
+
createdAt: createdAt.value,
|
|
316
|
+
updatedAt: updatedAt.value,
|
|
317
|
+
configuration: {
|
|
318
|
+
model: model?.value ?? null,
|
|
319
|
+
reasoningEffort: task.agentConfig.reasoningEffort ?? null,
|
|
320
|
+
sandboxBackend: task.agentConfig.sandboxBackend ?? null,
|
|
321
|
+
hasGoal: task.agentConfig.goal !== undefined,
|
|
322
|
+
promptBytes: Buffer.byteLength(task.agentConfig.prompt, "utf8"),
|
|
323
|
+
resourceCount: task.agentConfig.resources.length,
|
|
324
|
+
toolCount: task.agentConfig.tools.length,
|
|
325
|
+
metadataKeyCount: Object.keys(task.agentConfig.metadata).length,
|
|
326
|
+
taskMetadataKeyCount: Object.keys(task.metadata).length,
|
|
327
|
+
},
|
|
328
|
+
projection: {
|
|
329
|
+
bounded: true,
|
|
330
|
+
name: projectionFact(name),
|
|
331
|
+
schedule: schedule.projection,
|
|
332
|
+
model: model ? projectionFact(model) : null,
|
|
333
|
+
createdAt: projectionFact(createdAt),
|
|
334
|
+
updatedAt: projectionFact(updatedAt),
|
|
335
|
+
bytes: 0,
|
|
336
|
+
maxBytes: SCHEDULED_TASK_MCP_MAX_BYTES,
|
|
337
|
+
},
|
|
338
|
+
};
|
|
339
|
+
settleMeasuredBytes(
|
|
340
|
+
result,
|
|
341
|
+
() => result.projection.bytes,
|
|
342
|
+
(bytes) => {
|
|
343
|
+
result.projection.bytes = bytes;
|
|
344
|
+
},
|
|
345
|
+
);
|
|
346
|
+
return result;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function buildScheduledTaskDetailMcp(
|
|
350
|
+
task: ScheduledTask,
|
|
351
|
+
budget: DetailBudget,
|
|
352
|
+
reducedForBytes: boolean,
|
|
353
|
+
) {
|
|
354
|
+
const prompt = projectScheduledTaskUtf8(task.agentConfig.prompt, budget.promptBytes);
|
|
355
|
+
const goalText = task.agentConfig.goal
|
|
356
|
+
? projectScheduledTaskUtf8(task.agentConfig.goal.text, budget.goalFieldBytes)
|
|
357
|
+
: null;
|
|
358
|
+
const goalCriteria = task.agentConfig.goal?.successCriteria
|
|
359
|
+
? projectScheduledTaskUtf8(task.agentConfig.goal.successCriteria, budget.goalFieldBytes)
|
|
360
|
+
: null;
|
|
361
|
+
const resourceProjections = task.agentConfig.resources
|
|
362
|
+
.slice(0, budget.identityPreviewLimit)
|
|
363
|
+
.map((resource) => resourceIdentity(resource, budget));
|
|
364
|
+
const toolProjections = task.agentConfig.tools
|
|
365
|
+
.slice(0, budget.identityPreviewLimit)
|
|
366
|
+
.map((tool) => toolIdentity(tool, budget));
|
|
367
|
+
const resources = resourceProjections.map((projection) => projection.value);
|
|
368
|
+
const tools = toolProjections.map((projection) => projection.value);
|
|
369
|
+
const metadataKeyProjections = Object.keys(task.agentConfig.metadata)
|
|
370
|
+
.sort()
|
|
371
|
+
.slice(0, budget.identityPreviewLimit)
|
|
372
|
+
.map((key) => projectScheduledTaskUtf8(key, budget.metadataKeyBytes));
|
|
373
|
+
const taskMetadataKeyProjections = Object.keys(task.metadata)
|
|
374
|
+
.sort()
|
|
375
|
+
.slice(0, budget.identityPreviewLimit)
|
|
376
|
+
.map((key) => projectScheduledTaskUtf8(key, budget.metadataKeyBytes));
|
|
377
|
+
const metadataKeys = metadataKeyProjections.map((projection) => projection.value);
|
|
378
|
+
const taskMetadataKeys = taskMetadataKeyProjections.map((projection) => projection.value);
|
|
379
|
+
|
|
380
|
+
const result = {
|
|
381
|
+
...scheduledTaskMcpSummary(task),
|
|
382
|
+
entity: {
|
|
383
|
+
agentConfig: {
|
|
384
|
+
prompt: prompt.value,
|
|
385
|
+
resources,
|
|
386
|
+
tools,
|
|
387
|
+
metadataKeys,
|
|
388
|
+
model: task.agentConfig.model
|
|
389
|
+
? projectScheduledTaskUtf8(task.agentConfig.model, SCHEDULED_TASK_MODEL_MAX_BYTES).value
|
|
390
|
+
: null,
|
|
391
|
+
reasoningEffort: task.agentConfig.reasoningEffort ?? null,
|
|
392
|
+
sandboxBackend: task.agentConfig.sandboxBackend ?? null,
|
|
393
|
+
goal: goalText
|
|
394
|
+
? {
|
|
395
|
+
text: goalText.value,
|
|
396
|
+
successCriteria: goalCriteria?.value ?? null,
|
|
397
|
+
maxAutoContinuations: task.agentConfig.goal?.maxAutoContinuations ?? null,
|
|
398
|
+
}
|
|
399
|
+
: null,
|
|
400
|
+
},
|
|
401
|
+
taskMetadataKeys,
|
|
402
|
+
},
|
|
403
|
+
detailProjection: {
|
|
404
|
+
bounded: true,
|
|
405
|
+
fullEntityAvailableViaRest: true,
|
|
406
|
+
reducedForBytes,
|
|
407
|
+
terminal: false,
|
|
408
|
+
prompt: projectionFact(prompt),
|
|
409
|
+
goalText: goalText ? projectionFact(goalText) : null,
|
|
410
|
+
goalSuccessCriteria: goalCriteria ? projectionFact(goalCriteria) : null,
|
|
411
|
+
resources: {
|
|
412
|
+
originalCount: task.agentConfig.resources.length,
|
|
413
|
+
deliveredCount: resources.length,
|
|
414
|
+
originalBytes: jsonBytes(task.agentConfig.resources),
|
|
415
|
+
truncatedIdentityFieldCount: resourceProjections.reduce(
|
|
416
|
+
(count, projection) => count + projection.truncatedFieldCount,
|
|
417
|
+
0,
|
|
418
|
+
),
|
|
419
|
+
},
|
|
420
|
+
tools: {
|
|
421
|
+
originalCount: task.agentConfig.tools.length,
|
|
422
|
+
deliveredCount: tools.length,
|
|
423
|
+
originalBytes: jsonBytes(task.agentConfig.tools),
|
|
424
|
+
truncatedIdentityFieldCount: toolProjections.reduce(
|
|
425
|
+
(count, projection) => count + projection.truncatedFieldCount,
|
|
426
|
+
0,
|
|
427
|
+
),
|
|
428
|
+
},
|
|
429
|
+
metadata: {
|
|
430
|
+
originalKeyCount: Object.keys(task.agentConfig.metadata).length,
|
|
431
|
+
deliveredKeyCount: metadataKeys.length,
|
|
432
|
+
originalBytes: jsonBytes(task.agentConfig.metadata),
|
|
433
|
+
truncatedKeyCount: metadataKeyProjections.filter((projection) => projection.truncated)
|
|
434
|
+
.length,
|
|
435
|
+
valuesIncluded: false,
|
|
436
|
+
},
|
|
437
|
+
taskMetadata: {
|
|
438
|
+
originalKeyCount: Object.keys(task.metadata).length,
|
|
439
|
+
deliveredKeyCount: taskMetadataKeys.length,
|
|
440
|
+
originalBytes: jsonBytes(task.metadata),
|
|
441
|
+
truncatedKeyCount: taskMetadataKeyProjections.filter((projection) => projection.truncated)
|
|
442
|
+
.length,
|
|
443
|
+
valuesIncluded: false,
|
|
444
|
+
},
|
|
445
|
+
bytes: 0,
|
|
446
|
+
maxBytes: SCHEDULED_TASK_MCP_MAX_BYTES,
|
|
447
|
+
},
|
|
448
|
+
};
|
|
449
|
+
settleMeasuredBytes(
|
|
450
|
+
result,
|
|
451
|
+
() => result.detailProjection.bytes,
|
|
452
|
+
(bytes) => {
|
|
453
|
+
result.detailProjection.bytes = bytes;
|
|
454
|
+
},
|
|
455
|
+
);
|
|
456
|
+
return result;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
export function boundScheduledTaskDetailMcp(task: ScheduledTask) {
|
|
460
|
+
for (const [index, budget] of DETAIL_BUDGETS.entries()) {
|
|
461
|
+
const result = buildScheduledTaskDetailMcp(task, budget, index > 0);
|
|
462
|
+
if (result.detailProjection.bytes <= SCHEDULED_TASK_MCP_MAX_BYTES) return result;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
const summary = scheduledTaskMcpSummary(task);
|
|
466
|
+
const prompt = projectScheduledTaskUtf8(task.agentConfig.prompt, 128);
|
|
467
|
+
const goalText = task.agentConfig.goal
|
|
468
|
+
? projectScheduledTaskUtf8(task.agentConfig.goal.text, 64)
|
|
469
|
+
: null;
|
|
470
|
+
const goalCriteria = task.agentConfig.goal?.successCriteria
|
|
471
|
+
? projectScheduledTaskUtf8(task.agentConfig.goal.successCriteria, 64)
|
|
472
|
+
: null;
|
|
473
|
+
const terminal = {
|
|
474
|
+
...summary,
|
|
475
|
+
entity: {
|
|
476
|
+
agentConfig: {
|
|
477
|
+
prompt: prompt.value,
|
|
478
|
+
resources: [],
|
|
479
|
+
tools: [],
|
|
480
|
+
metadataKeys: [],
|
|
481
|
+
model: null,
|
|
482
|
+
reasoningEffort: task.agentConfig.reasoningEffort ?? null,
|
|
483
|
+
sandboxBackend: task.agentConfig.sandboxBackend ?? null,
|
|
484
|
+
goal: goalText
|
|
485
|
+
? {
|
|
486
|
+
text: goalText.value,
|
|
487
|
+
successCriteria: goalCriteria?.value ?? null,
|
|
488
|
+
maxAutoContinuations: task.agentConfig.goal?.maxAutoContinuations ?? null,
|
|
489
|
+
}
|
|
490
|
+
: null,
|
|
491
|
+
},
|
|
492
|
+
taskMetadataKeys: [],
|
|
493
|
+
},
|
|
494
|
+
detailProjection: {
|
|
495
|
+
bounded: true,
|
|
496
|
+
fullEntityAvailableViaRest: true,
|
|
497
|
+
reducedForBytes: true,
|
|
498
|
+
terminal: true,
|
|
499
|
+
reason: "detail_projection_exceeded_model_envelope",
|
|
500
|
+
nextAction: {
|
|
501
|
+
surface: "REST",
|
|
502
|
+
resourceType: "scheduled_task",
|
|
503
|
+
resourceId: task.id,
|
|
504
|
+
},
|
|
505
|
+
prompt: projectionFact(prompt),
|
|
506
|
+
goalText: goalText ? projectionFact(goalText) : null,
|
|
507
|
+
goalSuccessCriteria: goalCriteria ? projectionFact(goalCriteria) : null,
|
|
508
|
+
resources: {
|
|
509
|
+
originalCount: task.agentConfig.resources.length,
|
|
510
|
+
deliveredCount: 0,
|
|
511
|
+
originalBytes: jsonBytes(task.agentConfig.resources),
|
|
512
|
+
truncatedIdentityFieldCount: 0,
|
|
513
|
+
},
|
|
514
|
+
tools: {
|
|
515
|
+
originalCount: task.agentConfig.tools.length,
|
|
516
|
+
deliveredCount: 0,
|
|
517
|
+
originalBytes: jsonBytes(task.agentConfig.tools),
|
|
518
|
+
truncatedIdentityFieldCount: 0,
|
|
519
|
+
},
|
|
520
|
+
metadata: {
|
|
521
|
+
originalKeyCount: Object.keys(task.agentConfig.metadata).length,
|
|
522
|
+
deliveredKeyCount: 0,
|
|
523
|
+
originalBytes: jsonBytes(task.agentConfig.metadata),
|
|
524
|
+
truncatedKeyCount: 0,
|
|
525
|
+
valuesIncluded: false,
|
|
526
|
+
},
|
|
527
|
+
taskMetadata: {
|
|
528
|
+
originalKeyCount: Object.keys(task.metadata).length,
|
|
529
|
+
deliveredKeyCount: 0,
|
|
530
|
+
originalBytes: jsonBytes(task.metadata),
|
|
531
|
+
truncatedKeyCount: 0,
|
|
532
|
+
valuesIncluded: false,
|
|
533
|
+
},
|
|
534
|
+
bytes: 0,
|
|
535
|
+
maxBytes: SCHEDULED_TASK_MCP_MAX_BYTES,
|
|
536
|
+
},
|
|
537
|
+
};
|
|
538
|
+
settleMeasuredBytes(
|
|
539
|
+
terminal,
|
|
540
|
+
() => terminal.detailProjection.bytes,
|
|
541
|
+
(bytes) => {
|
|
542
|
+
terminal.detailProjection.bytes = bytes;
|
|
543
|
+
},
|
|
544
|
+
);
|
|
545
|
+
return terminal;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
export function boundScheduledTaskMcpPage(input: {
|
|
549
|
+
tasks: ScheduledTask[];
|
|
550
|
+
limit: number;
|
|
551
|
+
offset: number;
|
|
552
|
+
sourceHasMore: boolean;
|
|
553
|
+
maxBytes?: number;
|
|
554
|
+
}) {
|
|
555
|
+
const maxBytes = Math.max(8 * 1024, input.maxBytes ?? SCHEDULED_TASK_MCP_MAX_BYTES);
|
|
556
|
+
let summaries = input.tasks.map(scheduledTaskMcpSummary);
|
|
557
|
+
|
|
558
|
+
const build = () => {
|
|
559
|
+
const droppedTasks = input.tasks.slice(summaries.length);
|
|
560
|
+
const rowsDroppedForBytes = droppedTasks.length > 0;
|
|
561
|
+
const hasMore = input.sourceHasMore || rowsDroppedForBytes;
|
|
562
|
+
const nextOffset = hasMore && input.tasks.length > 0 ? input.offset + input.tasks.length : null;
|
|
563
|
+
const page = {
|
|
564
|
+
tasks: summaries,
|
|
565
|
+
page: {
|
|
566
|
+
limit: input.limit,
|
|
567
|
+
offset: input.offset,
|
|
568
|
+
hasMore,
|
|
569
|
+
nextOffset,
|
|
570
|
+
},
|
|
571
|
+
projection: {
|
|
572
|
+
bounded: true,
|
|
573
|
+
rowsDroppedForBytes,
|
|
574
|
+
droppedRowCount: droppedTasks.length,
|
|
575
|
+
droppedRows: droppedTasks.map((task) => ({
|
|
576
|
+
id: task.id,
|
|
577
|
+
nextAction: {
|
|
578
|
+
tool: "scheduled_tasks_get",
|
|
579
|
+
arguments: { id: task.id, includeEntity: false },
|
|
580
|
+
},
|
|
581
|
+
})),
|
|
582
|
+
sourceRowsConsumed: input.tasks.length,
|
|
583
|
+
rowsReturned: summaries.length,
|
|
584
|
+
terminal: hasMore && nextOffset === null,
|
|
585
|
+
...(hasMore && nextOffset === null
|
|
586
|
+
? { reason: "source_reported_more_rows_without_a_consumed_row" }
|
|
587
|
+
: {}),
|
|
588
|
+
bytes: 0,
|
|
589
|
+
maxBytes,
|
|
590
|
+
},
|
|
591
|
+
};
|
|
592
|
+
settleMeasuredBytes(
|
|
593
|
+
page,
|
|
594
|
+
() => page.projection.bytes,
|
|
595
|
+
(bytes) => {
|
|
596
|
+
page.projection.bytes = bytes;
|
|
597
|
+
},
|
|
598
|
+
);
|
|
599
|
+
return page;
|
|
600
|
+
};
|
|
601
|
+
|
|
602
|
+
let page = build();
|
|
603
|
+
while (page.projection.bytes > maxBytes && summaries.length > 0) {
|
|
604
|
+
summaries = summaries.slice(0, -1);
|
|
605
|
+
page = build();
|
|
606
|
+
}
|
|
607
|
+
return page;
|
|
608
|
+
}
|