@omercnet/paseo-omp 0.3.0-next.97.1 → 0.3.0-next.99.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/package.json
CHANGED
|
@@ -58,6 +58,7 @@ const MAX_CONNECTION_SESSIONS = 32;
|
|
|
58
58
|
const MAX_ACTIVE_OPERATIONS = 128;
|
|
59
59
|
const MAX_PROVIDER_INPUT_BYTES = 2 * 1024 * 1024;
|
|
60
60
|
const MAX_NESTED_OPTION_BYTES = 256 * 1024;
|
|
61
|
+
const MAX_ENV_ENTRIES = 256;
|
|
61
62
|
const MAX_NATIVE_SESSION_RESERVATIONS = 256;
|
|
62
63
|
|
|
63
64
|
function hasOwnEntries(value: unknown): boolean {
|
|
@@ -68,6 +69,19 @@ function hasOwnEntries(value: unknown): boolean {
|
|
|
68
69
|
return false;
|
|
69
70
|
}
|
|
70
71
|
|
|
72
|
+
function providerOptionsExceedPreflightLimits(value: unknown): boolean {
|
|
73
|
+
if (
|
|
74
|
+
boundedJsonBytes(value, MAX_NESTED_OPTION_BYTES, MAX_ENV_ENTRIES) === Number.POSITIVE_INFINITY
|
|
75
|
+
) {
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
79
|
+
const unrelatedOptions = Object.fromEntries(
|
|
80
|
+
Object.entries(value).filter(([key]) => key !== "env" && key !== "inheritEnv"),
|
|
81
|
+
);
|
|
82
|
+
return boundedJsonBytes(unrelatedOptions, MAX_NESTED_OPTION_BYTES) === Number.POSITIVE_INFINITY;
|
|
83
|
+
}
|
|
84
|
+
|
|
71
85
|
function preflightProviderInput(input: unknown): void {
|
|
72
86
|
if (!input || typeof input !== "object") throw new OmpPublicError("Invalid provider request");
|
|
73
87
|
const record = input as Record<string, unknown>;
|
|
@@ -82,7 +96,16 @@ function preflightProviderInput(input: unknown): void {
|
|
|
82
96
|
throw new OmpPublicError("Session persistence input is too large");
|
|
83
97
|
}
|
|
84
98
|
const config = record.config as Record<string, unknown> | undefined;
|
|
85
|
-
|
|
99
|
+
if (
|
|
100
|
+
(config?.env !== undefined &&
|
|
101
|
+
boundedJsonBytes(config.env, MAX_NESTED_OPTION_BYTES, MAX_ENV_ENTRIES) ===
|
|
102
|
+
Number.POSITIVE_INFINITY) ||
|
|
103
|
+
(config?.providerOptions !== undefined &&
|
|
104
|
+
providerOptionsExceedPreflightLimits(config.providerOptions))
|
|
105
|
+
) {
|
|
106
|
+
throw new OmpPublicError("Session configuration is too large");
|
|
107
|
+
}
|
|
108
|
+
for (const value of [config?.mcpServers, config?.settings]) {
|
|
86
109
|
if (
|
|
87
110
|
value !== undefined &&
|
|
88
111
|
boundedJsonBytes(value, MAX_NESTED_OPTION_BYTES) === Number.POSITIVE_INFINITY
|
|
@@ -109,13 +132,17 @@ function preflightProviderInput(input: unknown): void {
|
|
|
109
132
|
}
|
|
110
133
|
}
|
|
111
134
|
if (record.type === "catalog" || record.type === "sessions") {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
)
|
|
117
|
-
|
|
118
|
-
|
|
135
|
+
if (
|
|
136
|
+
record.providerOptions !== undefined &&
|
|
137
|
+
providerOptionsExceedPreflightLimits(record.providerOptions)
|
|
138
|
+
) {
|
|
139
|
+
throw new OmpPublicError("Provider configuration is too large");
|
|
140
|
+
}
|
|
141
|
+
if (
|
|
142
|
+
record.settings !== undefined &&
|
|
143
|
+
boundedJsonBytes(record.settings, MAX_NESTED_OPTION_BYTES) === Number.POSITIVE_INFINITY
|
|
144
|
+
) {
|
|
145
|
+
throw new OmpPublicError("Provider configuration is too large");
|
|
119
146
|
}
|
|
120
147
|
}
|
|
121
148
|
if (record.type === "session.prompt") {
|
|
@@ -4,7 +4,13 @@ import { isAbsolute, join } from "node:path";
|
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
import { ompDataDir } from "../paths";
|
|
6
6
|
import { isValidImagePayload } from "./image";
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
boundedJsonBytes,
|
|
9
|
+
boundedJsonMetrics,
|
|
10
|
+
OmpCleanupFailure,
|
|
11
|
+
OmpPublicError,
|
|
12
|
+
utf8Bytes,
|
|
13
|
+
} from "./security";
|
|
8
14
|
import {
|
|
9
15
|
listOmpSessionDescriptors,
|
|
10
16
|
type OmpSessionDescriptor,
|
|
@@ -56,6 +62,14 @@ const MAX_PENDING_ONE_WAY_WRITES = 256;
|
|
|
56
62
|
const MAX_PENDING_WRITE_BYTES = 8 * 1024 * 1024;
|
|
57
63
|
const MAX_LINE_PARTS = 4_096;
|
|
58
64
|
const MAX_ARRAY_ITEMS = 512;
|
|
65
|
+
// OMP read metadata can contain one source entry per displayed line. Bound this optional,
|
|
66
|
+
// opaque field separately so over-budget metadata can be omitted without losing completion events.
|
|
67
|
+
const MAX_OPTIONAL_METADATA_BYTES = MAX_TOOL_PAYLOAD_LENGTH;
|
|
68
|
+
const MAX_OPTIONAL_METADATA_ITEMS = 2_048;
|
|
69
|
+
const MAX_OPTIONAL_METADATA_NODES = 4_096;
|
|
70
|
+
const MAX_TASK_CORRELATION_BYTES = 256 * 1024;
|
|
71
|
+
const MAX_TASK_CORRELATION_ITEMS = 1_024;
|
|
72
|
+
const MAX_TASK_CORRELATION_NODES = 4_096;
|
|
59
73
|
// Tool-intensive OMP turns legitimately exceed 64 blocks; transport byte/node budgets remain the
|
|
60
74
|
// primary resource bounds.
|
|
61
75
|
export const OMP_MAX_CONTENT_PARTS = 4_096;
|
|
@@ -118,6 +132,148 @@ function isBoundedJson(
|
|
|
118
132
|
boundedJsonBytes(value, maxBytes, maxItems, maxBytes, maxNodes) !== Number.POSITIVE_INFINITY
|
|
119
133
|
);
|
|
120
134
|
}
|
|
135
|
+
function optionalMetadataMetrics(value: unknown) {
|
|
136
|
+
return boundedJsonMetrics(
|
|
137
|
+
value,
|
|
138
|
+
MAX_OPTIONAL_METADATA_BYTES,
|
|
139
|
+
MAX_OPTIONAL_METADATA_ITEMS,
|
|
140
|
+
MAX_OPTIONAL_METADATA_BYTES,
|
|
141
|
+
MAX_OPTIONAL_METADATA_NODES,
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function optionalMetadataIsBounded(value: unknown): boolean {
|
|
146
|
+
return optionalMetadataMetrics(value) !== undefined;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function omitUnsafeOptionalDetails(value: unknown): unknown {
|
|
150
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return value;
|
|
151
|
+
const record = value as Record<string, unknown>;
|
|
152
|
+
if (!Object.hasOwn(record, "details") || optionalMetadataIsBounded(record.details)) return value;
|
|
153
|
+
const { details: _details, ...safe } = record;
|
|
154
|
+
return safe;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const TASK_RESULT_STATUSES: Readonly<Record<string, true>> = {
|
|
158
|
+
pending: true,
|
|
159
|
+
running: true,
|
|
160
|
+
completed: true,
|
|
161
|
+
failed: true,
|
|
162
|
+
error: true,
|
|
163
|
+
aborted: true,
|
|
164
|
+
canceled: true,
|
|
165
|
+
cancelled: true,
|
|
166
|
+
};
|
|
167
|
+
const TASK_PROGRESS_STATUSES: Readonly<Record<string, true>> = {
|
|
168
|
+
pending: true,
|
|
169
|
+
running: true,
|
|
170
|
+
completed: true,
|
|
171
|
+
failed: true,
|
|
172
|
+
aborted: true,
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
function boundedTaskId(value: unknown): value is string {
|
|
176
|
+
return typeof value === "string" && value.length > 0 && utf8Bytes(value) <= MAX_ID_LENGTH;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function taskCorrelationDetails(value: unknown): unknown {
|
|
180
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return;
|
|
181
|
+
const details = value as Record<string, unknown>;
|
|
182
|
+
if (!Array.isArray(details.results) || details.results.length > MAX_TASK_CORRELATION_ITEMS)
|
|
183
|
+
return;
|
|
184
|
+
const results: Record<string, unknown>[] = [];
|
|
185
|
+
for (const value of details.results) {
|
|
186
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return;
|
|
187
|
+
const result = value as Record<string, unknown>;
|
|
188
|
+
if (!boundedTaskId(result.id)) return;
|
|
189
|
+
const safe: Record<string, unknown> = { id: result.id };
|
|
190
|
+
if (typeof result.status === "string" && Object.hasOwn(TASK_RESULT_STATUSES, result.status)) {
|
|
191
|
+
safe.status = result.status;
|
|
192
|
+
}
|
|
193
|
+
if (typeof result.aborted === "boolean") safe.aborted = result.aborted;
|
|
194
|
+
if (typeof result.exitCode === "number" && Number.isFinite(result.exitCode)) {
|
|
195
|
+
safe.exitCode = result.exitCode;
|
|
196
|
+
}
|
|
197
|
+
if (
|
|
198
|
+
result.error !== undefined &&
|
|
199
|
+
boundedJsonMetrics(result.error, 4_096, 32, 4_096, 64) !== undefined
|
|
200
|
+
) {
|
|
201
|
+
safe.error = result.error;
|
|
202
|
+
}
|
|
203
|
+
results.push(safe);
|
|
204
|
+
}
|
|
205
|
+
let progress: Record<string, unknown>[] | undefined;
|
|
206
|
+
if (details.progress !== undefined) {
|
|
207
|
+
if (!Array.isArray(details.progress) || details.progress.length > MAX_TASK_CORRELATION_ITEMS) {
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
progress = [];
|
|
211
|
+
for (const value of details.progress) {
|
|
212
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return;
|
|
213
|
+
const item = value as Record<string, unknown>;
|
|
214
|
+
if (
|
|
215
|
+
!boundedTaskId(item.id) ||
|
|
216
|
+
typeof item.index !== "number" ||
|
|
217
|
+
!Number.isInteger(item.index) ||
|
|
218
|
+
item.index < 0 ||
|
|
219
|
+
item.index >= MAX_TASK_CORRELATION_ITEMS ||
|
|
220
|
+
typeof item.status !== "string" ||
|
|
221
|
+
!Object.hasOwn(TASK_PROGRESS_STATUSES, item.status)
|
|
222
|
+
) {
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
progress.push({ id: item.id, index: item.index, status: item.status });
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
const correlation = { results, ...(progress ? { progress } : {}) };
|
|
229
|
+
return boundedJsonMetrics(
|
|
230
|
+
correlation,
|
|
231
|
+
MAX_TASK_CORRELATION_BYTES,
|
|
232
|
+
MAX_TASK_CORRELATION_ITEMS,
|
|
233
|
+
MAX_TASK_CORRELATION_BYTES,
|
|
234
|
+
MAX_TASK_CORRELATION_NODES,
|
|
235
|
+
)
|
|
236
|
+
? correlation
|
|
237
|
+
: undefined;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function omitOptionalDetails(value: unknown, preserveTaskCorrelation = false): unknown {
|
|
241
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return value;
|
|
242
|
+
const record = value as Record<string, unknown>;
|
|
243
|
+
if (!Object.hasOwn(record, "details")) return value;
|
|
244
|
+
const { details: _details, ...structural } = record;
|
|
245
|
+
if (!preserveTaskCorrelation) return structural;
|
|
246
|
+
const correlation = taskCorrelationDetails(record.details);
|
|
247
|
+
return correlation === undefined ? structural : { ...structural, details: correlation };
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function createOptionalMetadataSanitizer(): (value: unknown, taskResult?: boolean) => unknown {
|
|
251
|
+
let retainedBytes = 0;
|
|
252
|
+
let retainedNodes = 0;
|
|
253
|
+
return (value, taskResult = false) => {
|
|
254
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return value;
|
|
255
|
+
const record = value as Record<string, unknown>;
|
|
256
|
+
if (!Object.hasOwn(record, "details")) return value;
|
|
257
|
+
const metrics = optionalMetadataMetrics(record.details);
|
|
258
|
+
if (
|
|
259
|
+
metrics &&
|
|
260
|
+
retainedBytes + metrics.bytes <= MAX_OPTIONAL_METADATA_BYTES &&
|
|
261
|
+
retainedNodes + metrics.nodes <= MAX_OPTIONAL_METADATA_NODES
|
|
262
|
+
) {
|
|
263
|
+
retainedBytes += metrics.bytes;
|
|
264
|
+
retainedNodes += metrics.nodes;
|
|
265
|
+
return value;
|
|
266
|
+
}
|
|
267
|
+
const correlation = taskResult ? taskCorrelationDetails(record.details) : undefined;
|
|
268
|
+
const { details: _details, ...safe } = record;
|
|
269
|
+
return correlation === undefined ? safe : { ...safe, details: correlation };
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const OmpOptionalMetadataSchema = z.preprocess(
|
|
274
|
+
(value) => (optionalMetadataIsBounded(value) ? value : undefined),
|
|
275
|
+
z.unknown().optional(),
|
|
276
|
+
);
|
|
121
277
|
|
|
122
278
|
const OmpContentPartSchema = z
|
|
123
279
|
.object({
|
|
@@ -165,10 +321,7 @@ const OmpMessageIdentityShape = {
|
|
|
165
321
|
responseId: IDENTIFIER.optional(),
|
|
166
322
|
images: OmpImageArraySchema.optional(),
|
|
167
323
|
timestamp: z.number().finite().optional(),
|
|
168
|
-
details:
|
|
169
|
-
.unknown()
|
|
170
|
-
.refine((value) => isBoundedJson(value, MAX_SEMANTIC_FRAME_BYTES, 1_024, 4_096))
|
|
171
|
-
.optional(),
|
|
324
|
+
details: OmpOptionalMetadataSchema,
|
|
172
325
|
};
|
|
173
326
|
type OmpContentPart = z.infer<typeof OmpContentPartSchema>;
|
|
174
327
|
type OmpMessageIdentity = {
|
|
@@ -409,6 +562,16 @@ const JsonObjectSchema = z.record(z.string(), z.unknown());
|
|
|
409
562
|
const BoundedToolPayloadSchema = z
|
|
410
563
|
.unknown()
|
|
411
564
|
.refine((value) => isBoundedJson(value, MAX_SEMANTIC_FRAME_BYTES, 1_024, 4_096));
|
|
565
|
+
const OmpToolResultPayloadSchema = z.preprocess(
|
|
566
|
+
omitUnsafeOptionalDetails,
|
|
567
|
+
z
|
|
568
|
+
.unknown()
|
|
569
|
+
.refine(
|
|
570
|
+
(value) =>
|
|
571
|
+
isBoundedJson(value, MAX_SEMANTIC_FRAME_BYTES, MAX_OPTIONAL_METADATA_ITEMS, 8_192) &&
|
|
572
|
+
isBoundedJson(omitOptionalDetails(value), MAX_SEMANTIC_FRAME_BYTES, 1_024, 4_096),
|
|
573
|
+
),
|
|
574
|
+
);
|
|
412
575
|
const OmpHostToolDefinitionSchema = z.object({
|
|
413
576
|
name: NAME,
|
|
414
577
|
label: NAME.optional(),
|
|
@@ -616,13 +779,13 @@ const OmpAgentSessionEventSchema = z.discriminatedUnion("type", [
|
|
|
616
779
|
toolCallId: IDENTIFIER,
|
|
617
780
|
toolName: NAME,
|
|
618
781
|
args: BoundedToolPayloadSchema.optional(),
|
|
619
|
-
partialResult:
|
|
782
|
+
partialResult: OmpToolResultPayloadSchema,
|
|
620
783
|
}),
|
|
621
784
|
z.object({
|
|
622
785
|
type: z.literal("tool_execution_end"),
|
|
623
786
|
toolCallId: IDENTIFIER,
|
|
624
787
|
toolName: NAME,
|
|
625
|
-
result:
|
|
788
|
+
result: OmpToolResultPayloadSchema,
|
|
626
789
|
isError: z.boolean().optional(),
|
|
627
790
|
}),
|
|
628
791
|
OmpCompactionStartSchema,
|
|
@@ -873,6 +1036,102 @@ const OmpRuntimeEventSchema = z.discriminatedUnion("type", [
|
|
|
873
1036
|
OmpToolApprovalCancelSchema,
|
|
874
1037
|
z.object({ type: z.literal("advisor_yielded") }),
|
|
875
1038
|
]);
|
|
1039
|
+
type OptionalDetailsMapper = (value: unknown, taskResult?: boolean) => unknown;
|
|
1040
|
+
|
|
1041
|
+
function mapRecordField(
|
|
1042
|
+
record: Record<string, unknown>,
|
|
1043
|
+
key: string,
|
|
1044
|
+
map: OptionalDetailsMapper,
|
|
1045
|
+
taskResult = false,
|
|
1046
|
+
): Record<string, unknown> {
|
|
1047
|
+
if (!Object.hasOwn(record, key)) return record;
|
|
1048
|
+
const next = map(record[key], taskResult);
|
|
1049
|
+
return next === record[key] ? record : { ...record, [key]: next };
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
function mapMessageList(value: unknown, map: OptionalDetailsMapper): unknown {
|
|
1053
|
+
if (!Array.isArray(value)) return value;
|
|
1054
|
+
let changed = false;
|
|
1055
|
+
const messages = value.map((message) => {
|
|
1056
|
+
const taskResult =
|
|
1057
|
+
message !== null &&
|
|
1058
|
+
typeof message === "object" &&
|
|
1059
|
+
!Array.isArray(message) &&
|
|
1060
|
+
(message as Record<string, unknown>).role === "toolResult" &&
|
|
1061
|
+
(message as Record<string, unknown>).toolName === "task";
|
|
1062
|
+
const next = map(message, taskResult);
|
|
1063
|
+
changed ||= next !== message;
|
|
1064
|
+
return next;
|
|
1065
|
+
});
|
|
1066
|
+
return changed ? messages : value;
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
function mapAgentEventDetails(
|
|
1070
|
+
frame: Record<string, unknown>,
|
|
1071
|
+
map: OptionalDetailsMapper,
|
|
1072
|
+
): Record<string, unknown> {
|
|
1073
|
+
switch (frame.type) {
|
|
1074
|
+
case "message_start":
|
|
1075
|
+
case "message_update":
|
|
1076
|
+
case "message_end": {
|
|
1077
|
+
const message = frame.message;
|
|
1078
|
+
const taskResult =
|
|
1079
|
+
message !== null &&
|
|
1080
|
+
typeof message === "object" &&
|
|
1081
|
+
!Array.isArray(message) &&
|
|
1082
|
+
(message as Record<string, unknown>).role === "toolResult" &&
|
|
1083
|
+
(message as Record<string, unknown>).toolName === "task";
|
|
1084
|
+
return mapRecordField(frame, "message", map, taskResult);
|
|
1085
|
+
}
|
|
1086
|
+
case "tool_execution_update":
|
|
1087
|
+
return mapRecordField(frame, "partialResult", map, frame.toolName === "task");
|
|
1088
|
+
case "tool_execution_end":
|
|
1089
|
+
return mapRecordField(frame, "result", map, frame.toolName === "task");
|
|
1090
|
+
case "agent_end":
|
|
1091
|
+
return mapRecordField(frame, "messages", (messages) => mapMessageList(messages, map));
|
|
1092
|
+
default:
|
|
1093
|
+
return frame;
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
function mapRuntimeFrameDetails(
|
|
1098
|
+
frame: Record<string, unknown>,
|
|
1099
|
+
map: OptionalDetailsMapper,
|
|
1100
|
+
): Record<string, unknown> {
|
|
1101
|
+
if (frame.type !== "subagent_event") return mapAgentEventDetails(frame, map);
|
|
1102
|
+
if (frame.payload === null || typeof frame.payload !== "object" || Array.isArray(frame.payload)) {
|
|
1103
|
+
return frame;
|
|
1104
|
+
}
|
|
1105
|
+
const payload = frame.payload as Record<string, unknown>;
|
|
1106
|
+
if (payload.event === null || typeof payload.event !== "object" || Array.isArray(payload.event)) {
|
|
1107
|
+
return frame;
|
|
1108
|
+
}
|
|
1109
|
+
const event = mapAgentEventDetails(payload.event as Record<string, unknown>, map);
|
|
1110
|
+
return event === payload.event ? frame : { ...frame, payload: { ...payload, event } };
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
function sanitizeMessageListMetadata(value: unknown): unknown {
|
|
1114
|
+
return mapMessageList(value, createOptionalMetadataSanitizer());
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
function sanitizeHistoryResponseData(value: unknown): unknown {
|
|
1118
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return value;
|
|
1119
|
+
return mapRecordField(value as Record<string, unknown>, "messages", sanitizeMessageListMetadata);
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
function runtimeFrameCollectionLimit(frame: Record<string, unknown>): number {
|
|
1123
|
+
const type = frame.type;
|
|
1124
|
+
if (
|
|
1125
|
+
type === "message_start" ||
|
|
1126
|
+
type === "message_update" ||
|
|
1127
|
+
type === "message_end" ||
|
|
1128
|
+
type === "agent_end" ||
|
|
1129
|
+
type === "subagent_event"
|
|
1130
|
+
) {
|
|
1131
|
+
return OMP_MAX_CONTENT_PARTS;
|
|
1132
|
+
}
|
|
1133
|
+
return 1_024;
|
|
1134
|
+
}
|
|
876
1135
|
const OmpModelsResultSchema = z.object({
|
|
877
1136
|
models: z.array(OmpModelSchema).min(1).max(256),
|
|
878
1137
|
});
|
|
@@ -1989,14 +2248,6 @@ class OmpRpcProcess {
|
|
|
1989
2248
|
return;
|
|
1990
2249
|
}
|
|
1991
2250
|
if (this.receiveKnownResponse(decoded)) return;
|
|
1992
|
-
if (this.receiveDegradedAgentEnd(decoded, true)) return;
|
|
1993
|
-
if (
|
|
1994
|
-
boundedJsonBytes(decoded, MAX_SEMANTIC_FRAME_BYTES, 1_024, MAX_IMAGE_DATA_LENGTH, 4_096) ===
|
|
1995
|
-
Number.POSITIVE_INFINITY
|
|
1996
|
-
) {
|
|
1997
|
-
this.recordProtocolViolation();
|
|
1998
|
-
return;
|
|
1999
|
-
}
|
|
2000
2251
|
const frame = JsonObjectSchema.safeParse(decoded);
|
|
2001
2252
|
if (!frame.success) {
|
|
2002
2253
|
this.recordProtocolViolation();
|
|
@@ -2073,19 +2324,6 @@ class OmpRpcProcess {
|
|
|
2073
2324
|
return;
|
|
2074
2325
|
}
|
|
2075
2326
|
if (this.receiveKnownResponse(decodedFrame)) return;
|
|
2076
|
-
if (this.receiveDegradedAgentEnd(decodedFrame, true)) return;
|
|
2077
|
-
if (
|
|
2078
|
-
boundedJsonBytes(
|
|
2079
|
-
decodedFrame,
|
|
2080
|
-
MAX_SEMANTIC_FRAME_BYTES,
|
|
2081
|
-
1_024,
|
|
2082
|
-
MAX_IMAGE_DATA_LENGTH,
|
|
2083
|
-
4_096,
|
|
2084
|
-
) === Number.POSITIVE_INFINITY
|
|
2085
|
-
) {
|
|
2086
|
-
this.recordProtocolViolation();
|
|
2087
|
-
return;
|
|
2088
|
-
}
|
|
2089
2327
|
const frameObject = JsonObjectSchema.safeParse(decodedFrame);
|
|
2090
2328
|
if (!frameObject.success) {
|
|
2091
2329
|
this.recordProtocolViolation();
|
|
@@ -2130,6 +2368,11 @@ class OmpRpcProcess {
|
|
|
2130
2368
|
const isBranchHistory = pending.command === "get_branch_messages";
|
|
2131
2369
|
const isHistory =
|
|
2132
2370
|
pending.command === "get_messages" || pending.command === "get_subagent_messages";
|
|
2371
|
+
const responseData = isHistory
|
|
2372
|
+
? sanitizeHistoryResponseData(response.data.data)
|
|
2373
|
+
: response.data.data;
|
|
2374
|
+
const boundedFrame =
|
|
2375
|
+
responseData === response.data.data ? frame : { ...frame, data: responseData };
|
|
2133
2376
|
const responseItemLimit = isBranchHistory ? 1_024 : isHistory ? 100_000 : MAX_ARRAY_ITEMS;
|
|
2134
2377
|
const responseByteLimit =
|
|
2135
2378
|
isBranchHistory || isHistory
|
|
@@ -2146,7 +2389,7 @@ class OmpRpcProcess {
|
|
|
2146
2389
|
: 2_048;
|
|
2147
2390
|
if (
|
|
2148
2391
|
boundedJsonBytes(
|
|
2149
|
-
|
|
2392
|
+
boundedFrame,
|
|
2150
2393
|
responseByteLimit,
|
|
2151
2394
|
responseItemLimit,
|
|
2152
2395
|
MAX_IMAGE_DATA_LENGTH,
|
|
@@ -2162,7 +2405,7 @@ class OmpRpcProcess {
|
|
|
2162
2405
|
if (!settled) return;
|
|
2163
2406
|
if (response.data.success) {
|
|
2164
2407
|
try {
|
|
2165
|
-
settled.beforeResolve?.(
|
|
2408
|
+
settled.beforeResolve?.(responseData);
|
|
2166
2409
|
if (settled.command === "prompt") {
|
|
2167
2410
|
if (this.acceptedPromptIds.size >= MAX_PENDING_REQUESTS) {
|
|
2168
2411
|
const oldest = this.acceptedPromptIds.values().next().value;
|
|
@@ -2170,7 +2413,7 @@ class OmpRpcProcess {
|
|
|
2170
2413
|
}
|
|
2171
2414
|
this.acceptedPromptIds.add(response.data.id);
|
|
2172
2415
|
}
|
|
2173
|
-
settled.resolve(
|
|
2416
|
+
settled.resolve(responseData);
|
|
2174
2417
|
} catch {
|
|
2175
2418
|
settled.reject(new Error("OMP RPC response is invalid"));
|
|
2176
2419
|
}
|
|
@@ -2219,21 +2462,27 @@ class OmpRpcProcess {
|
|
|
2219
2462
|
this.fail(new Error("OMP emitted invalid terminal metadata"));
|
|
2220
2463
|
return true;
|
|
2221
2464
|
}
|
|
2465
|
+
const structuralFrame = mapRuntimeFrameDetails(frame, omitOptionalDetails);
|
|
2222
2466
|
const messagesAreSafe =
|
|
2223
2467
|
frame.messages === undefined ||
|
|
2224
2468
|
(Array.isArray(frame.messages) &&
|
|
2225
2469
|
frame.messages.length <= MAX_ARRAY_ITEMS &&
|
|
2226
2470
|
boundedJsonBytes(
|
|
2227
|
-
|
|
2471
|
+
structuralFrame.messages as unknown[],
|
|
2228
2472
|
MAX_SEMANTIC_FRAME_BYTES,
|
|
2229
|
-
|
|
2473
|
+
OMP_MAX_CONTENT_PARTS,
|
|
2230
2474
|
MAX_TEXT_LENGTH,
|
|
2231
2475
|
4_096,
|
|
2232
2476
|
) !== Number.POSITIVE_INFINITY);
|
|
2233
2477
|
const payloadIsSafe =
|
|
2234
2478
|
messagesAreSafe &&
|
|
2235
|
-
boundedJsonBytes(
|
|
2236
|
-
|
|
2479
|
+
boundedJsonBytes(
|
|
2480
|
+
structuralFrame,
|
|
2481
|
+
MAX_SEMANTIC_FRAME_BYTES,
|
|
2482
|
+
OMP_MAX_CONTENT_PARTS,
|
|
2483
|
+
MAX_IMAGE_DATA_LENGTH,
|
|
2484
|
+
4_096,
|
|
2485
|
+
) !== Number.POSITIVE_INFINITY;
|
|
2237
2486
|
if (onlyUnsafePayload && payloadIsSafe) return false;
|
|
2238
2487
|
if (envelope.data.isTerminal === false) {
|
|
2239
2488
|
this.fail(new Error("OMP emitted an invalid nonterminal agent_end payload"));
|
|
@@ -2260,16 +2509,22 @@ class OmpRpcProcess {
|
|
|
2260
2509
|
this.recordProtocolViolation();
|
|
2261
2510
|
return;
|
|
2262
2511
|
}
|
|
2263
|
-
|
|
2512
|
+
const safeFrame = mapRuntimeFrameDetails(frame, createOptionalMetadataSanitizer());
|
|
2513
|
+
if (this.receiveDegradedAgentEnd(safeFrame, true)) return;
|
|
2264
2514
|
if (
|
|
2265
|
-
boundedJsonBytes(
|
|
2266
|
-
|
|
2515
|
+
boundedJsonBytes(
|
|
2516
|
+
mapRuntimeFrameDetails(safeFrame, omitOptionalDetails),
|
|
2517
|
+
MAX_SEMANTIC_FRAME_BYTES,
|
|
2518
|
+
runtimeFrameCollectionLimit(safeFrame),
|
|
2519
|
+
MAX_IMAGE_DATA_LENGTH,
|
|
2520
|
+
4_096,
|
|
2521
|
+
) === Number.POSITIVE_INFINITY
|
|
2267
2522
|
) {
|
|
2268
2523
|
this.recordProtocolViolation();
|
|
2269
2524
|
return;
|
|
2270
2525
|
}
|
|
2271
2526
|
if (type === "rpc_chunk") {
|
|
2272
|
-
const chunk = OmpChunkFrameSchema.safeParse(
|
|
2527
|
+
const chunk = OmpChunkFrameSchema.safeParse(safeFrame);
|
|
2273
2528
|
if (!chunk.success) this.rejectChunk();
|
|
2274
2529
|
else this.receiveChunk(chunk.data);
|
|
2275
2530
|
return;
|
|
@@ -2287,7 +2542,7 @@ class OmpRpcProcess {
|
|
|
2287
2542
|
this.recordProtocolViolation();
|
|
2288
2543
|
return;
|
|
2289
2544
|
}
|
|
2290
|
-
const ready = OmpReadyFrameSchema.safeParse(
|
|
2545
|
+
const ready = OmpReadyFrameSchema.safeParse(safeFrame);
|
|
2291
2546
|
if (!ready.success) {
|
|
2292
2547
|
this.recordProtocolViolation();
|
|
2293
2548
|
} else {
|
|
@@ -2297,13 +2552,13 @@ class OmpRpcProcess {
|
|
|
2297
2552
|
return;
|
|
2298
2553
|
}
|
|
2299
2554
|
if (type === "response") {
|
|
2300
|
-
this.receiveResponse(
|
|
2555
|
+
this.receiveResponse(safeFrame);
|
|
2301
2556
|
return;
|
|
2302
2557
|
}
|
|
2303
|
-
const event = OmpRuntimeEventSchema.safeParse(
|
|
2558
|
+
const event = OmpRuntimeEventSchema.safeParse(safeFrame);
|
|
2304
2559
|
if (!event.success) {
|
|
2305
|
-
this.rejectMatchingToolApproval(
|
|
2306
|
-
if (type === "agent_end" && this.receiveDegradedAgentEnd(
|
|
2560
|
+
this.rejectMatchingToolApproval(safeFrame);
|
|
2561
|
+
if (type === "agent_end" && this.receiveDegradedAgentEnd(safeFrame, false)) return;
|
|
2307
2562
|
this.recordProtocolViolation();
|
|
2308
2563
|
return;
|
|
2309
2564
|
}
|
|
@@ -2728,7 +2983,10 @@ export class OmpRpcRuntime implements OmpRuntime {
|
|
|
2728
2983
|
);
|
|
2729
2984
|
return {
|
|
2730
2985
|
...transcript,
|
|
2731
|
-
messages: z
|
|
2986
|
+
messages: z
|
|
2987
|
+
.array(OmpMessageSchema)
|
|
2988
|
+
.max(100_000)
|
|
2989
|
+
.parse(sanitizeMessageListMetadata(transcript.messages)),
|
|
2732
2990
|
};
|
|
2733
2991
|
}
|
|
2734
2992
|
async readPersistedSubagentTranscript(options: {
|
|
@@ -2745,7 +3003,10 @@ export class OmpRpcRuntime implements OmpRuntime {
|
|
|
2745
3003
|
);
|
|
2746
3004
|
return {
|
|
2747
3005
|
...transcript,
|
|
2748
|
-
messages: z
|
|
3006
|
+
messages: z
|
|
3007
|
+
.array(OmpMessageSchema)
|
|
3008
|
+
.max(100_000)
|
|
3009
|
+
.parse(sanitizeMessageListMetadata(transcript.messages)),
|
|
2749
3010
|
};
|
|
2750
3011
|
}
|
|
2751
3012
|
|
|
@@ -94,6 +94,18 @@ const TaskResultDetailsSchema = z.object({
|
|
|
94
94
|
exitCode: z.number().optional(),
|
|
95
95
|
error: z.unknown().optional(),
|
|
96
96
|
aborted: z.boolean().optional(),
|
|
97
|
+
status: z
|
|
98
|
+
.enum([
|
|
99
|
+
"pending",
|
|
100
|
+
"running",
|
|
101
|
+
"completed",
|
|
102
|
+
"failed",
|
|
103
|
+
"error",
|
|
104
|
+
"aborted",
|
|
105
|
+
"canceled",
|
|
106
|
+
"cancelled",
|
|
107
|
+
])
|
|
108
|
+
.optional(),
|
|
97
109
|
}),
|
|
98
110
|
)
|
|
99
111
|
.max(MAX_CHILDREN),
|
|
@@ -192,8 +204,15 @@ function replayChildren(messages: readonly OmpMessage[]): ReplayChildRef[] {
|
|
|
192
204
|
const details = taskResultDetails(message);
|
|
193
205
|
const results = details?.results ?? [];
|
|
194
206
|
for (const result of results) {
|
|
207
|
+
const canceled =
|
|
208
|
+
result.aborted === true ||
|
|
209
|
+
result.status === "aborted" ||
|
|
210
|
+
result.status === "canceled" ||
|
|
211
|
+
result.status === "cancelled";
|
|
195
212
|
const failed =
|
|
196
213
|
message.isError === true ||
|
|
214
|
+
result.status === "failed" ||
|
|
215
|
+
result.status === "error" ||
|
|
197
216
|
Boolean(result.error) ||
|
|
198
217
|
(typeof result.exitCode === "number" && result.exitCode !== 0);
|
|
199
218
|
children.push({
|
|
@@ -201,7 +220,7 @@ function replayChildren(messages: readonly OmpMessage[]): ReplayChildRef[] {
|
|
|
201
220
|
agent: result.agent ?? call?.title,
|
|
202
221
|
description: call?.description,
|
|
203
222
|
parentToolCallId: message.toolCallId,
|
|
204
|
-
status:
|
|
223
|
+
status: canceled ? "canceled" : failed ? "failed" : "completed",
|
|
205
224
|
});
|
|
206
225
|
}
|
|
207
226
|
const resultIds = new Set(results.map((result) => result.id));
|