@omercnet/paseo-omp 0.4.0 → 1.0.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/README.md +8 -8
- package/client/external-url.ts +2 -11
- package/client/omp-config-styles.ts +284 -0
- package/client/omp-config-surface.tsx +16 -286
- package/client/omp-routing-editor.tsx +1 -3
- package/client/provider-launch-settings.tsx +118 -0
- package/index.client.tsx +21 -2
- package/index.server.ts +10 -0
- package/package.json +14 -9
- package/paseo-plugin.json +2 -1
- package/scripts/prepare-dependencies.mjs +44 -11
- package/server/omp-models.ts +2 -6
- package/server/package-version.ts +1 -1
- package/server/protocol-violation-diagnostics.ts +1 -1
- package/server/provider/catalog.ts +2 -1
- package/server/provider/config-normalization.ts +19 -3
- package/server/provider/connection.ts +30 -0
- package/server/provider/host-tools.ts +2 -2
- package/server/provider/mcp-transport.ts +9 -2
- package/server/provider/omp-rpc-environment.ts +368 -0
- package/server/provider/omp-rpc-process.ts +119 -0
- package/server/provider/omp-rpc-protocol.ts +1949 -0
- package/server/provider/omp-rpc-transport.ts +1373 -0
- package/server/provider/omp-rpc-values.ts +31 -0
- package/server/provider/omp-rpc.ts +267 -3311
- package/server/provider/profile-providers.ts +2 -1
- package/server/provider/prompt-payload.ts +253 -0
- package/server/provider/registration.ts +10 -2
- package/server/provider/session-compaction.ts +187 -0
- package/server/provider/session-descriptors.ts +52 -18
- package/server/provider/session-events.ts +42 -0
- package/server/provider/session-permissions.ts +987 -0
- package/server/provider/session-terminal.ts +307 -0
- package/server/provider/session-usage.ts +234 -0
- package/server/provider/session.ts +145 -1853
- package/server/provider/settings.ts +8 -5
- package/server/provider/subsessions.ts +30 -9
- package/server/provider/timeline-projector.ts +140 -29
- package/shared/external-url.ts +0 -9
- package/shared/provider-launch-settings.ts +18 -0
|
@@ -0,0 +1,1949 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { isValidImagePayload } from "./image";
|
|
3
|
+
import { MAX_ID_LENGTH, MAX_NAME_LENGTH, MAX_PATH_LENGTH, MAX_TEXT_LENGTH } from "./omp-rpc-values";
|
|
4
|
+
import { boundedJsonBytes, boundedJsonMetrics, utf8Bytes } from "./security";
|
|
5
|
+
|
|
6
|
+
export const OMP_MAX_CONTENT_PARTS = 4_096;
|
|
7
|
+
|
|
8
|
+
export const MAX_CHUNK_BYTES = 256 * 1024;
|
|
9
|
+
export const MAX_ENCODED_CHUNK_BYTES = Math.ceil(MAX_CHUNK_BYTES / 3) * 4;
|
|
10
|
+
export const MAX_REASSEMBLED_FRAME_BYTES = 64 * 1024 * 1024;
|
|
11
|
+
export const MAX_SEMANTIC_FRAME_BYTES = 12 * 1024 * 1024;
|
|
12
|
+
export const MAX_CHUNK_COUNT = MAX_REASSEMBLED_FRAME_BYTES / MAX_CHUNK_BYTES;
|
|
13
|
+
export const MAX_CONFIG_EVENT_TEXT_BYTES = 64 * 1024;
|
|
14
|
+
export const MAX_IMAGE_DATA_LENGTH = 8 * 1024 * 1024;
|
|
15
|
+
export const MAX_TOOL_PAYLOAD_LENGTH = 256 * 1024;
|
|
16
|
+
export const MAX_TOOL_APPROVAL_FRAME_BYTES = 64 * 1024;
|
|
17
|
+
export const MAX_TOOL_APPROVAL_STRING_BYTES = 8 * 1024;
|
|
18
|
+
export const MAX_TOOL_APPROVAL_COLLECTION_ITEMS = 32;
|
|
19
|
+
export const MAX_TOOL_APPROVAL_INPUT_NODES = 256;
|
|
20
|
+
export const MAX_TOOL_APPROVAL_DEPTH = 4;
|
|
21
|
+
export const MAX_TOOL_APPROVAL_ID_BYTES = 512;
|
|
22
|
+
export const MAX_TOOL_APPROVAL_NAME_BYTES = 256;
|
|
23
|
+
export const MAX_TOOL_APPROVAL_DETAIL_LINES = 16;
|
|
24
|
+
export const MAX_TOOL_APPROVAL_DETAIL_BYTES = 2 * 1024;
|
|
25
|
+
export const MAX_TOOL_APPROVAL_METADATA_FIELDS = 33;
|
|
26
|
+
export const MAX_TOOL_APPROVAL_METADATA_FIELD_BYTES = 64;
|
|
27
|
+
export const MAX_TOOL_APPROVAL_TIMEOUT_MS = 24 * 60 * 60 * 1000;
|
|
28
|
+
export const MAX_TOOL_APPROVAL_PATH_BYTES = 4 * 1024;
|
|
29
|
+
export const MAX_TOOL_APPROVAL_CONTENT_BYTES = 20 * 1024;
|
|
30
|
+
export const MAX_ARRAY_ITEMS = 512;
|
|
31
|
+
export const MAX_OPTIONAL_METADATA_BYTES = MAX_TOOL_PAYLOAD_LENGTH;
|
|
32
|
+
export const MAX_OPTIONAL_METADATA_ITEMS = 2_048;
|
|
33
|
+
export const MAX_OPTIONAL_METADATA_NODES = 4_096;
|
|
34
|
+
export const MAX_TASK_CORRELATION_BYTES = 256 * 1024;
|
|
35
|
+
export const MAX_TASK_CORRELATION_ITEMS = 1_024;
|
|
36
|
+
export const MAX_TASK_CORRELATION_NODES = 4_096;
|
|
37
|
+
export const MAX_MODEL_CATALOG_ITEMS = 4_096;
|
|
38
|
+
export const MAX_TODOS = 256;
|
|
39
|
+
export const MAX_TOKEN_COUNT = Number.MAX_SAFE_INTEGER;
|
|
40
|
+
export const MAX_COST_USD = 1_000_000_000;
|
|
41
|
+
export const MAX_RPC_ERROR_BYTES = 4_096;
|
|
42
|
+
export const MAX_RPC_ERROR_CODE_BYTES = 256;
|
|
43
|
+
export const MAX_REPLAY_MESSAGES = 100_000;
|
|
44
|
+
export const OMP_MESSAGE_PAGE_LIMIT = 256;
|
|
45
|
+
|
|
46
|
+
export const OMP_MESSAGE_REPLAY_POLICIES = {
|
|
47
|
+
assistant: "render",
|
|
48
|
+
user: "render",
|
|
49
|
+
developer: "ignore",
|
|
50
|
+
toolResult: "render",
|
|
51
|
+
bashExecution: "normalize",
|
|
52
|
+
pythonExecution: "normalize",
|
|
53
|
+
custom: "normalize",
|
|
54
|
+
hookMessage: "normalize",
|
|
55
|
+
branchSummary: "ignore",
|
|
56
|
+
compactionSummary: "ignore",
|
|
57
|
+
fileMention: "ignore",
|
|
58
|
+
} as const;
|
|
59
|
+
|
|
60
|
+
export const OMP_SESSION_EVENT_POLICIES = {
|
|
61
|
+
agent_start: "lifecycle",
|
|
62
|
+
agent_end: "lifecycle",
|
|
63
|
+
turn_start: "lifecycle",
|
|
64
|
+
turn_end: "lifecycle",
|
|
65
|
+
message_start: "normalize",
|
|
66
|
+
message_update: "normalize",
|
|
67
|
+
message_end: "normalize",
|
|
68
|
+
tool_execution_start: "normalize",
|
|
69
|
+
tool_execution_update: "normalize",
|
|
70
|
+
tool_stream_update: "normalize",
|
|
71
|
+
tool_execution_end: "normalize",
|
|
72
|
+
auto_compaction_start: "normalize",
|
|
73
|
+
auto_compaction_end: "normalize",
|
|
74
|
+
auto_retry_start: "normalize",
|
|
75
|
+
auto_retry_end: "normalize",
|
|
76
|
+
retry_fallback_applied: "normalize",
|
|
77
|
+
retry_fallback_succeeded: "normalize",
|
|
78
|
+
model_changed: "normalize",
|
|
79
|
+
config_warnings_changed: "ignore",
|
|
80
|
+
advisor_cost_changed: "ignore",
|
|
81
|
+
advisor_yielded: "normalize",
|
|
82
|
+
ttsr_triggered: "ignore",
|
|
83
|
+
todo_reminder: "normalize",
|
|
84
|
+
todo_auto_clear: "normalize",
|
|
85
|
+
irc_message: "normalize",
|
|
86
|
+
notice: "normalize",
|
|
87
|
+
thinking_level_changed: "normalize",
|
|
88
|
+
goal_updated: "normalize",
|
|
89
|
+
} as const;
|
|
90
|
+
|
|
91
|
+
export const OMP_RPC_COMMAND_POLICIES = {
|
|
92
|
+
negotiate_protocol: "implemented",
|
|
93
|
+
prompt: "implemented",
|
|
94
|
+
steer: "implemented",
|
|
95
|
+
follow_up: "implemented",
|
|
96
|
+
abort: "implemented",
|
|
97
|
+
abort_and_prompt: "unsupported",
|
|
98
|
+
new_session: "unsupported",
|
|
99
|
+
get_state: "implemented",
|
|
100
|
+
set_fast_mode: "unsupported",
|
|
101
|
+
get_available_commands: "implemented",
|
|
102
|
+
set_todos: "unsupported",
|
|
103
|
+
set_host_tools: "implemented",
|
|
104
|
+
set_host_uri_schemes: "unsupported",
|
|
105
|
+
set_subagent_subscription: "implemented",
|
|
106
|
+
get_subagents: "implemented",
|
|
107
|
+
get_subagent_messages: "implemented",
|
|
108
|
+
set_model: "implemented",
|
|
109
|
+
cycle_model: "unsupported",
|
|
110
|
+
get_available_models: "implemented",
|
|
111
|
+
set_thinking_level: "implemented",
|
|
112
|
+
cycle_thinking_level: "unsupported",
|
|
113
|
+
set_steering_mode: "unsupported",
|
|
114
|
+
set_follow_up_mode: "unsupported",
|
|
115
|
+
set_interrupt_mode: "unsupported",
|
|
116
|
+
compact: "implemented",
|
|
117
|
+
set_auto_compaction: "implemented",
|
|
118
|
+
set_auto_retry: "unsupported",
|
|
119
|
+
abort_retry: "unsupported",
|
|
120
|
+
bash: "unsupported",
|
|
121
|
+
abort_bash: "unsupported",
|
|
122
|
+
get_session_stats: "implemented",
|
|
123
|
+
export_html: "unsupported",
|
|
124
|
+
switch_session: "unsupported",
|
|
125
|
+
branch: "implemented",
|
|
126
|
+
get_branch_messages: "implemented",
|
|
127
|
+
get_last_assistant_text: "unsupported",
|
|
128
|
+
set_session_name: "unsupported",
|
|
129
|
+
handoff: "implemented",
|
|
130
|
+
get_messages: "implemented",
|
|
131
|
+
get_messages_page: "implemented",
|
|
132
|
+
get_login_providers: "unsupported",
|
|
133
|
+
login: "unsupported",
|
|
134
|
+
} as const;
|
|
135
|
+
export const OMP_RPC_RESPONSE_POLICIES = OMP_RPC_COMMAND_POLICIES;
|
|
136
|
+
export const OMP_PROTOCOL_VIOLATION_CATEGORIES = [
|
|
137
|
+
"duplicate-ready",
|
|
138
|
+
"frame-limit",
|
|
139
|
+
"incomplete-frame",
|
|
140
|
+
"interleaved-chunk",
|
|
141
|
+
"invalid-chunk",
|
|
142
|
+
"invalid-envelope",
|
|
143
|
+
"invalid-event",
|
|
144
|
+
"invalid-event-state",
|
|
145
|
+
"invalid-json",
|
|
146
|
+
"invalid-ready",
|
|
147
|
+
"invalid-response",
|
|
148
|
+
"remote-frame-error",
|
|
149
|
+
] as const;
|
|
150
|
+
export type OmpProtocolViolationCategory = (typeof OMP_PROTOCOL_VIOLATION_CATEGORIES)[number];
|
|
151
|
+
export const OMP_PROTOCOL_VIOLATION_REASONS = [
|
|
152
|
+
"output-ended-mid-frame",
|
|
153
|
+
"physical-frame-limit",
|
|
154
|
+
"semantic-frame-limit",
|
|
155
|
+
"json-decode",
|
|
156
|
+
"chunk-json-decode",
|
|
157
|
+
"object-envelope",
|
|
158
|
+
"missing-frame-type",
|
|
159
|
+
"response-schema",
|
|
160
|
+
"chunk-schema",
|
|
161
|
+
"chunk-metadata",
|
|
162
|
+
"chunk-size",
|
|
163
|
+
"chunk-start-index",
|
|
164
|
+
"chunk-sequence",
|
|
165
|
+
"chunk-byte-count",
|
|
166
|
+
"chunk-timeout",
|
|
167
|
+
"frame-interleaved-with-chunk",
|
|
168
|
+
"remote-frame-error",
|
|
169
|
+
"ready-already-received",
|
|
170
|
+
"ready-schema",
|
|
171
|
+
"notice-level-type",
|
|
172
|
+
"notice-message-type",
|
|
173
|
+
"notice-schema",
|
|
174
|
+
"event-schema",
|
|
175
|
+
"message-event-schema",
|
|
176
|
+
"tool-event-schema",
|
|
177
|
+
"lifecycle-event-schema",
|
|
178
|
+
"subagent-event-schema",
|
|
179
|
+
"configuration-event-schema",
|
|
180
|
+
"extension-ui-schema",
|
|
181
|
+
"unknown-event-type",
|
|
182
|
+
"event-state-transition",
|
|
183
|
+
] as const;
|
|
184
|
+
export type OmpProtocolViolationReason = (typeof OMP_PROTOCOL_VIOLATION_REASONS)[number];
|
|
185
|
+
export const OMP_PROTOCOL_DIAGNOSTIC_PHASES = [
|
|
186
|
+
"startup",
|
|
187
|
+
"negotiation",
|
|
188
|
+
"idle",
|
|
189
|
+
"active-turn",
|
|
190
|
+
"closing",
|
|
191
|
+
] as const;
|
|
192
|
+
export type OmpProtocolDiagnosticPhase = (typeof OMP_PROTOCOL_DIAGNOSTIC_PHASES)[number];
|
|
193
|
+
|
|
194
|
+
export const OMP_PROTOCOL_EVENT_TYPES = [
|
|
195
|
+
"agent_start",
|
|
196
|
+
"agent_end",
|
|
197
|
+
"turn_start",
|
|
198
|
+
"turn_end",
|
|
199
|
+
"message_start",
|
|
200
|
+
"message_update",
|
|
201
|
+
"message_end",
|
|
202
|
+
"tool_execution_start",
|
|
203
|
+
"tool_execution_update",
|
|
204
|
+
"tool_execution_end",
|
|
205
|
+
"tool_stream_update",
|
|
206
|
+
"compaction_start",
|
|
207
|
+
"compaction_end",
|
|
208
|
+
"subagent_lifecycle",
|
|
209
|
+
"subagent_progress",
|
|
210
|
+
"subagent_event",
|
|
211
|
+
"todo_reminder",
|
|
212
|
+
"model_changed",
|
|
213
|
+
"config_warnings_changed",
|
|
214
|
+
"advisor_cost_changed",
|
|
215
|
+
"ttsr_triggered",
|
|
216
|
+
"irc_message",
|
|
217
|
+
"thinking_level_changed",
|
|
218
|
+
"goal_updated",
|
|
219
|
+
"auto_retry_start",
|
|
220
|
+
"auto_retry_end",
|
|
221
|
+
"retry_fallback_applied",
|
|
222
|
+
"retry_fallback_succeeded",
|
|
223
|
+
"todo_auto_clear",
|
|
224
|
+
"auto_compaction_start",
|
|
225
|
+
"auto_compaction_end",
|
|
226
|
+
"available_commands_update",
|
|
227
|
+
"notice",
|
|
228
|
+
"command_output",
|
|
229
|
+
"extension_ui_request",
|
|
230
|
+
"prompt_result",
|
|
231
|
+
"host_tool_call",
|
|
232
|
+
"host_tool_cancel",
|
|
233
|
+
"tool_approval_request",
|
|
234
|
+
"tool_approval_cancel",
|
|
235
|
+
"advisor_yielded",
|
|
236
|
+
] as const;
|
|
237
|
+
export type OmpProtocolEventType = (typeof OMP_PROTOCOL_EVENT_TYPES)[number];
|
|
238
|
+
|
|
239
|
+
export const OMP_PROTOCOL_DIAGNOSTIC_FIELDS = [
|
|
240
|
+
"frame",
|
|
241
|
+
"frame.byteLength",
|
|
242
|
+
"frame.type",
|
|
243
|
+
"response",
|
|
244
|
+
"ready",
|
|
245
|
+
"event",
|
|
246
|
+
"event.sequence",
|
|
247
|
+
"notice.level",
|
|
248
|
+
"notice.message",
|
|
249
|
+
"chunk",
|
|
250
|
+
"chunk.data",
|
|
251
|
+
"chunk.index",
|
|
252
|
+
"chunk.sequence",
|
|
253
|
+
"chunk.byteLength",
|
|
254
|
+
] as const;
|
|
255
|
+
export type OmpProtocolDiagnosticField = (typeof OMP_PROTOCOL_DIAGNOSTIC_FIELDS)[number];
|
|
256
|
+
|
|
257
|
+
export const OMP_PROTOCOL_DIAGNOSTIC_EXPECTATIONS = [
|
|
258
|
+
"complete-json-line",
|
|
259
|
+
"within-byte-limit",
|
|
260
|
+
"valid-json",
|
|
261
|
+
"object-envelope",
|
|
262
|
+
"bounded-frame-type",
|
|
263
|
+
"valid-response-frame",
|
|
264
|
+
"valid-ready-frame",
|
|
265
|
+
"single-ready-frame",
|
|
266
|
+
"valid-event-frame",
|
|
267
|
+
"valid-event-state-transition",
|
|
268
|
+
"valid-message-event",
|
|
269
|
+
"valid-tool-event",
|
|
270
|
+
"valid-lifecycle-event",
|
|
271
|
+
"valid-subagent-event",
|
|
272
|
+
"valid-configuration-event",
|
|
273
|
+
"valid-extension-ui-event",
|
|
274
|
+
"known-event-type",
|
|
275
|
+
"notice-level-enum",
|
|
276
|
+
"notice-message-string",
|
|
277
|
+
"valid-chunk-frame",
|
|
278
|
+
"valid-base64-chunk",
|
|
279
|
+
"first-chunk-index-zero",
|
|
280
|
+
"contiguous-chunk-sequence",
|
|
281
|
+
"declared-chunk-byte-count",
|
|
282
|
+
"chunk-before-deadline",
|
|
283
|
+
"no-interleaved-frame",
|
|
284
|
+
"no-remote-frame-error",
|
|
285
|
+
] as const;
|
|
286
|
+
export type OmpProtocolDiagnosticExpectation =
|
|
287
|
+
(typeof OMP_PROTOCOL_DIAGNOSTIC_EXPECTATIONS)[number];
|
|
288
|
+
|
|
289
|
+
export const OMP_PROTOCOL_DIAGNOSTIC_ACTUAL_TYPES = [
|
|
290
|
+
"missing",
|
|
291
|
+
"null",
|
|
292
|
+
"array",
|
|
293
|
+
"object",
|
|
294
|
+
"string",
|
|
295
|
+
"number",
|
|
296
|
+
"boolean",
|
|
297
|
+
"invalid-json",
|
|
298
|
+
"partial-frame",
|
|
299
|
+
"oversized",
|
|
300
|
+
"duplicate",
|
|
301
|
+
"interleaved",
|
|
302
|
+
"out-of-order",
|
|
303
|
+
"timeout",
|
|
304
|
+
"remote-error",
|
|
305
|
+
"mismatched",
|
|
306
|
+
] as const;
|
|
307
|
+
export type OmpProtocolDiagnosticActualType = (typeof OMP_PROTOCOL_DIAGNOSTIC_ACTUAL_TYPES)[number];
|
|
308
|
+
|
|
309
|
+
export interface OmpProtocolViolationDiagnostic {
|
|
310
|
+
category: OmpProtocolViolationCategory;
|
|
311
|
+
reason: OmpProtocolViolationReason;
|
|
312
|
+
phase: OmpProtocolDiagnosticPhase;
|
|
313
|
+
occurrenceCount: number;
|
|
314
|
+
eventType?: OmpProtocolEventType;
|
|
315
|
+
frameType?: "ready" | "response" | "rpc_chunk" | "rpc_frame_error" | "notice";
|
|
316
|
+
field?: OmpProtocolDiagnosticField;
|
|
317
|
+
expected?: OmpProtocolDiagnosticExpectation;
|
|
318
|
+
actualType?: OmpProtocolDiagnosticActualType;
|
|
319
|
+
maxByteSize?: number;
|
|
320
|
+
limitBytes?: number;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
export function protocolDiagnosticActualType(value: unknown): OmpProtocolDiagnosticActualType {
|
|
324
|
+
if (value === undefined) return "missing";
|
|
325
|
+
if (value === null) return "null";
|
|
326
|
+
if (Array.isArray(value)) return "array";
|
|
327
|
+
if (typeof value === "object") return "object";
|
|
328
|
+
if (typeof value === "string") return "string";
|
|
329
|
+
if (typeof value === "number") return "number";
|
|
330
|
+
if (typeof value === "boolean") return "boolean";
|
|
331
|
+
return "object";
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function isProtocolEventType(value: string): value is OmpProtocolEventType {
|
|
335
|
+
return (OMP_PROTOCOL_EVENT_TYPES as readonly string[]).includes(value);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function eventSchemaClassification(
|
|
339
|
+
eventType: OmpProtocolEventType,
|
|
340
|
+
): Pick<OmpProtocolViolationDiagnostic, "reason" | "expected"> {
|
|
341
|
+
switch (eventType) {
|
|
342
|
+
case "message_start":
|
|
343
|
+
case "message_update":
|
|
344
|
+
case "message_end":
|
|
345
|
+
return { reason: "message-event-schema", expected: "valid-message-event" };
|
|
346
|
+
case "irc_message":
|
|
347
|
+
return { reason: "message-event-schema", expected: "valid-message-event" };
|
|
348
|
+
case "tool_execution_start":
|
|
349
|
+
case "tool_execution_update":
|
|
350
|
+
case "tool_stream_update":
|
|
351
|
+
case "tool_execution_end":
|
|
352
|
+
case "host_tool_call":
|
|
353
|
+
case "host_tool_cancel":
|
|
354
|
+
case "tool_approval_request":
|
|
355
|
+
case "tool_approval_cancel":
|
|
356
|
+
return { reason: "tool-event-schema", expected: "valid-tool-event" };
|
|
357
|
+
case "subagent_lifecycle":
|
|
358
|
+
case "subagent_progress":
|
|
359
|
+
case "subagent_event":
|
|
360
|
+
return { reason: "subagent-event-schema", expected: "valid-subagent-event" };
|
|
361
|
+
case "model_changed":
|
|
362
|
+
case "thinking_level_changed":
|
|
363
|
+
case "goal_updated":
|
|
364
|
+
case "todo_reminder":
|
|
365
|
+
case "todo_auto_clear":
|
|
366
|
+
case "available_commands_update":
|
|
367
|
+
case "retry_fallback_applied":
|
|
368
|
+
case "retry_fallback_succeeded":
|
|
369
|
+
case "config_warnings_changed":
|
|
370
|
+
case "advisor_cost_changed":
|
|
371
|
+
case "ttsr_triggered":
|
|
372
|
+
return { reason: "configuration-event-schema", expected: "valid-configuration-event" };
|
|
373
|
+
case "extension_ui_request":
|
|
374
|
+
return { reason: "extension-ui-schema", expected: "valid-extension-ui-event" };
|
|
375
|
+
default:
|
|
376
|
+
return { reason: "lifecycle-event-schema", expected: "valid-lifecycle-event" };
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
export function invalidEventDiagnosticMetadata(
|
|
381
|
+
frame: Record<string, unknown>,
|
|
382
|
+
type: string,
|
|
383
|
+
): Pick<
|
|
384
|
+
OmpProtocolViolationDiagnostic,
|
|
385
|
+
"reason" | "eventType" | "frameType" | "field" | "expected" | "actualType"
|
|
386
|
+
> {
|
|
387
|
+
if (!isProtocolEventType(type)) {
|
|
388
|
+
return {
|
|
389
|
+
reason: "unknown-event-type",
|
|
390
|
+
field: "frame.type",
|
|
391
|
+
expected: "known-event-type",
|
|
392
|
+
actualType: "string",
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
if (type !== "notice") {
|
|
396
|
+
return {
|
|
397
|
+
...eventSchemaClassification(type),
|
|
398
|
+
eventType: type,
|
|
399
|
+
field: "event",
|
|
400
|
+
actualType: "object",
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
if (typeof frame.level !== "string") {
|
|
404
|
+
return {
|
|
405
|
+
reason: "notice-level-type",
|
|
406
|
+
eventType: "notice",
|
|
407
|
+
frameType: "notice",
|
|
408
|
+
field: "notice.level",
|
|
409
|
+
expected: "notice-level-enum",
|
|
410
|
+
actualType: protocolDiagnosticActualType(frame.level),
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
if (typeof frame.message !== "string") {
|
|
414
|
+
return {
|
|
415
|
+
reason: "notice-message-type",
|
|
416
|
+
eventType: "notice",
|
|
417
|
+
frameType: "notice",
|
|
418
|
+
field: "notice.message",
|
|
419
|
+
expected: "notice-message-string",
|
|
420
|
+
actualType: protocolDiagnosticActualType(frame.message),
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
return {
|
|
424
|
+
reason: "notice-schema",
|
|
425
|
+
eventType: "notice",
|
|
426
|
+
frameType: "notice",
|
|
427
|
+
field: "event",
|
|
428
|
+
expected: "valid-event-frame",
|
|
429
|
+
actualType: "object",
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
export type PendingProtocolViolation = Omit<OmpProtocolViolationDiagnostic, "occurrenceCount"> & {
|
|
434
|
+
occurrenceCount: number;
|
|
435
|
+
};
|
|
436
|
+
export type ProtocolViolationKey = `${OmpProtocolViolationCategory}:${OmpProtocolViolationReason}`;
|
|
437
|
+
const MAX_CONTEXT_PERCENT = 1_000_000;
|
|
438
|
+
function boundedJsonString(maxBytes: number, minBytes = 0) {
|
|
439
|
+
return z.string().refine((value) => {
|
|
440
|
+
const bytes = Buffer.byteLength(JSON.stringify(value), "utf8") - 2;
|
|
441
|
+
return bytes >= minBytes && bytes <= maxBytes;
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
export function isBoundedToolApprovalId(value: unknown): value is string {
|
|
445
|
+
return typeof value === "string" && value.length > 0 && Buffer.byteLength(value, "utf8") <= 512;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
export const OMP_HOST_TOOL_FRAME_LIMIT_ERROR =
|
|
449
|
+
"MCP host tool result exceeds the OMP RPC frame limit";
|
|
450
|
+
export const MIN_HOST_TOOL_RESULT_FRAME_BYTES = Buffer.byteLength(
|
|
451
|
+
`${JSON.stringify({
|
|
452
|
+
type: "host_tool_result",
|
|
453
|
+
id: "\0".repeat(MAX_ID_LENGTH),
|
|
454
|
+
result: {
|
|
455
|
+
content: [{ type: "text", text: OMP_HOST_TOOL_FRAME_LIMIT_ERROR }],
|
|
456
|
+
details: {},
|
|
457
|
+
isError: true,
|
|
458
|
+
},
|
|
459
|
+
isError: true,
|
|
460
|
+
})}\n`,
|
|
461
|
+
);
|
|
462
|
+
function boundedString(maxBytes: number, minBytes = 0) {
|
|
463
|
+
return z.string().refine((value) => {
|
|
464
|
+
const bytes = utf8Bytes(value);
|
|
465
|
+
return bytes >= minBytes && bytes <= maxBytes;
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
function isSafeOpaqueCursor(value: string): boolean {
|
|
469
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
470
|
+
const code = value.charCodeAt(index);
|
|
471
|
+
if (code <= 0x1f || code === 0x7f) return false;
|
|
472
|
+
}
|
|
473
|
+
return true;
|
|
474
|
+
}
|
|
475
|
+
function boundedOptionalStringArray(maxItems: number, maxBytes: number) {
|
|
476
|
+
return z.unknown().transform((value): string[] | undefined => {
|
|
477
|
+
if (!Array.isArray(value)) return undefined;
|
|
478
|
+
return value
|
|
479
|
+
.filter(
|
|
480
|
+
(item): item is string =>
|
|
481
|
+
typeof item === "string" && item.length > 0 && utf8Bytes(item) <= maxBytes,
|
|
482
|
+
)
|
|
483
|
+
.slice(0, maxItems);
|
|
484
|
+
});
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
const IDENTIFIER = boundedString(MAX_ID_LENGTH, 1);
|
|
488
|
+
export const NAME = boundedString(MAX_NAME_LENGTH, 1);
|
|
489
|
+
const OMP_PROVIDER_NAME = NAME.refine((provider) => !provider.includes("/"));
|
|
490
|
+
const TEXT = boundedString(MAX_TEXT_LENGTH);
|
|
491
|
+
const RAW_DISPLAY_TEXT = boundedString(MAX_IMAGE_DATA_LENGTH);
|
|
492
|
+
const DISPLAY_TRUNCATION_MARKER = "<truncated>";
|
|
493
|
+
|
|
494
|
+
function boundRawDisplayContent(value: unknown): unknown {
|
|
495
|
+
if (typeof value === "string") {
|
|
496
|
+
return utf8Bytes(value) <= MAX_IMAGE_DATA_LENGTH ? value : DISPLAY_TRUNCATION_MARKER;
|
|
497
|
+
}
|
|
498
|
+
if (!Array.isArray(value)) return value;
|
|
499
|
+
let totalBytes = 0;
|
|
500
|
+
for (const part of value) {
|
|
501
|
+
if (!part || typeof part !== "object" || Array.isArray(part)) continue;
|
|
502
|
+
const record = part as Record<string, unknown>;
|
|
503
|
+
if (typeof record.text === "string") totalBytes += utf8Bytes(record.text);
|
|
504
|
+
if (typeof record.thinking === "string") totalBytes += utf8Bytes(record.thinking);
|
|
505
|
+
}
|
|
506
|
+
if (totalBytes <= MAX_IMAGE_DATA_LENGTH) return value;
|
|
507
|
+
|
|
508
|
+
let retainedMarker = false;
|
|
509
|
+
return value.map((part) => {
|
|
510
|
+
if (!part || typeof part !== "object" || Array.isArray(part)) return part;
|
|
511
|
+
const copy = { ...(part as Record<string, unknown>) };
|
|
512
|
+
for (const key of ["text", "thinking"] as const) {
|
|
513
|
+
if (typeof copy[key] !== "string") continue;
|
|
514
|
+
if (retainedMarker) delete copy[key];
|
|
515
|
+
else {
|
|
516
|
+
copy[key] = DISPLAY_TRUNCATION_MARKER;
|
|
517
|
+
retainedMarker = true;
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
return copy;
|
|
521
|
+
});
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
function sanitizeLiveMessageDisplay(value: unknown): unknown {
|
|
525
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return value;
|
|
526
|
+
const message = value as Record<string, unknown>;
|
|
527
|
+
if (message.role === "assistant") {
|
|
528
|
+
const content = boundRawDisplayContent(message.content);
|
|
529
|
+
return content === message.content ? value : { ...message, content };
|
|
530
|
+
}
|
|
531
|
+
if (
|
|
532
|
+
(message.role === "bashExecution" || message.role === "pythonExecution") &&
|
|
533
|
+
typeof message.output === "string" &&
|
|
534
|
+
utf8Bytes(message.output) > MAX_IMAGE_DATA_LENGTH
|
|
535
|
+
) {
|
|
536
|
+
return { ...message, output: DISPLAY_TRUNCATION_MARKER };
|
|
537
|
+
}
|
|
538
|
+
return value;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
export function sanitizeLiveDisplayFrame(value: unknown): unknown {
|
|
542
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return value;
|
|
543
|
+
const frame = value as Record<string, unknown>;
|
|
544
|
+
if (
|
|
545
|
+
frame.type === "message_start" ||
|
|
546
|
+
frame.type === "message_update" ||
|
|
547
|
+
frame.type === "message_end"
|
|
548
|
+
) {
|
|
549
|
+
const message = sanitizeLiveMessageDisplay(frame.message);
|
|
550
|
+
return message === frame.message ? value : { ...frame, message };
|
|
551
|
+
}
|
|
552
|
+
if (frame.type !== "agent_end" || !Array.isArray(frame.messages)) return value;
|
|
553
|
+
let changed = false;
|
|
554
|
+
const messages = frame.messages.map((message) => {
|
|
555
|
+
const sanitized = sanitizeLiveMessageDisplay(message);
|
|
556
|
+
if (sanitized !== message) changed = true;
|
|
557
|
+
return sanitized;
|
|
558
|
+
});
|
|
559
|
+
return changed ? { ...frame, messages } : value;
|
|
560
|
+
}
|
|
561
|
+
const OmpCurrentThinkingLevelSchema = boundedString(32).optional().catch(undefined);
|
|
562
|
+
|
|
563
|
+
function isBoundedJson(
|
|
564
|
+
value: unknown,
|
|
565
|
+
maxBytes = MAX_TOOL_PAYLOAD_LENGTH,
|
|
566
|
+
maxItems = MAX_ARRAY_ITEMS,
|
|
567
|
+
maxNodes = 2_048,
|
|
568
|
+
): boolean {
|
|
569
|
+
return (
|
|
570
|
+
boundedJsonBytes(value, maxBytes, maxItems, maxBytes, maxNodes) !== Number.POSITIVE_INFINITY
|
|
571
|
+
);
|
|
572
|
+
}
|
|
573
|
+
const MAX_RESPONSE_DEPTH = 16;
|
|
574
|
+
export const OMP_RPC_BOUND_DIMENSIONS = [
|
|
575
|
+
"bytes",
|
|
576
|
+
"items",
|
|
577
|
+
"nodes",
|
|
578
|
+
"depth",
|
|
579
|
+
"string-bytes",
|
|
580
|
+
] as const;
|
|
581
|
+
export type OmpRpcBoundDimension = (typeof OMP_RPC_BOUND_DIMENSIONS)[number];
|
|
582
|
+
export const OMP_RPC_DIAGNOSTIC_COMMANDS = [
|
|
583
|
+
"negotiate_protocol",
|
|
584
|
+
"get_state",
|
|
585
|
+
"get_session_stats",
|
|
586
|
+
"get_available_models",
|
|
587
|
+
"get_available_commands",
|
|
588
|
+
"set_subagent_subscription",
|
|
589
|
+
"get_subagents",
|
|
590
|
+
"get_subagent_messages",
|
|
591
|
+
"prompt",
|
|
592
|
+
"compact",
|
|
593
|
+
"set_auto_compaction",
|
|
594
|
+
"set_model",
|
|
595
|
+
"set_thinking_level",
|
|
596
|
+
"handoff",
|
|
597
|
+
"get_branch_messages",
|
|
598
|
+
"branch",
|
|
599
|
+
"get_messages_page",
|
|
600
|
+
"get_messages",
|
|
601
|
+
"abort",
|
|
602
|
+
"set_host_tools",
|
|
603
|
+
] as const;
|
|
604
|
+
export type OmpRpcDiagnosticCommand = (typeof OMP_RPC_DIAGNOSTIC_COMMANDS)[number];
|
|
605
|
+
type JsonBoundViolation = {
|
|
606
|
+
dimension: OmpRpcBoundDimension;
|
|
607
|
+
actual: number;
|
|
608
|
+
limit: number;
|
|
609
|
+
};
|
|
610
|
+
type JsonBoundInspection = { bytes: number; nodes: number; violation?: JsonBoundViolation };
|
|
611
|
+
|
|
612
|
+
export function rpcDiagnosticCommand(value: string): OmpRpcDiagnosticCommand | undefined {
|
|
613
|
+
return (OMP_RPC_DIAGNOSTIC_COMMANDS as readonly string[]).includes(value)
|
|
614
|
+
? (value as OmpRpcDiagnosticCommand)
|
|
615
|
+
: undefined;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
function encodedJsonStringBytes(value: string): number {
|
|
619
|
+
let bytes = 2;
|
|
620
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
621
|
+
const code = value.charCodeAt(index);
|
|
622
|
+
if (code === 0x22 || code === 0x5c) {
|
|
623
|
+
bytes += 2;
|
|
624
|
+
} else if (code <= 0x1f) {
|
|
625
|
+
bytes +=
|
|
626
|
+
code === 0x08 || code === 0x09 || code === 0x0a || code === 0x0c || code === 0x0d ? 2 : 6;
|
|
627
|
+
} else if (code >= 0xd800 && code <= 0xdbff) {
|
|
628
|
+
const low = value.charCodeAt(index + 1);
|
|
629
|
+
if (low >= 0xdc00 && low <= 0xdfff) {
|
|
630
|
+
bytes += 4;
|
|
631
|
+
index += 1;
|
|
632
|
+
} else {
|
|
633
|
+
bytes += 6;
|
|
634
|
+
}
|
|
635
|
+
} else if (code >= 0xdc00 && code <= 0xdfff) {
|
|
636
|
+
bytes += 6;
|
|
637
|
+
} else {
|
|
638
|
+
bytes += code <= 0x7f ? 1 : code <= 0x7ff ? 2 : 3;
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
return bytes;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
export function inspectJsonBounds(
|
|
645
|
+
value: unknown,
|
|
646
|
+
maxBytes: number,
|
|
647
|
+
maxItems: number,
|
|
648
|
+
maxStringBytes: number,
|
|
649
|
+
maxNodes: number,
|
|
650
|
+
): JsonBoundInspection {
|
|
651
|
+
const stack: Array<{ value: unknown; depth: number }> = [{ value, depth: 0 }];
|
|
652
|
+
let nodes = 0;
|
|
653
|
+
let bytes = 0;
|
|
654
|
+
const addBytes = (count: number): JsonBoundViolation | undefined => {
|
|
655
|
+
bytes += count;
|
|
656
|
+
return bytes > maxBytes ? { dimension: "bytes", actual: bytes, limit: maxBytes } : undefined;
|
|
657
|
+
};
|
|
658
|
+
while (stack.length > 0) {
|
|
659
|
+
const current = stack.pop();
|
|
660
|
+
if (!current) break;
|
|
661
|
+
nodes += 1;
|
|
662
|
+
if (nodes > maxNodes)
|
|
663
|
+
return { bytes, nodes, violation: { dimension: "nodes", actual: nodes, limit: maxNodes } };
|
|
664
|
+
if (current.depth > MAX_RESPONSE_DEPTH) {
|
|
665
|
+
return {
|
|
666
|
+
bytes,
|
|
667
|
+
nodes,
|
|
668
|
+
violation: { dimension: "depth", actual: current.depth, limit: MAX_RESPONSE_DEPTH },
|
|
669
|
+
};
|
|
670
|
+
}
|
|
671
|
+
const item = current.value;
|
|
672
|
+
if (item === null) {
|
|
673
|
+
const violation = addBytes(4);
|
|
674
|
+
if (violation) return { bytes, nodes, violation };
|
|
675
|
+
continue;
|
|
676
|
+
}
|
|
677
|
+
if (typeof item === "boolean") {
|
|
678
|
+
const violation = addBytes(item ? 4 : 5);
|
|
679
|
+
if (violation) return { bytes, nodes, violation };
|
|
680
|
+
continue;
|
|
681
|
+
}
|
|
682
|
+
if (typeof item === "number") {
|
|
683
|
+
const violation = addBytes((JSON.stringify(item) ?? "null").length);
|
|
684
|
+
if (violation) return { bytes, nodes, violation };
|
|
685
|
+
continue;
|
|
686
|
+
}
|
|
687
|
+
if (typeof item === "string") {
|
|
688
|
+
const itemBytes = utf8Bytes(item);
|
|
689
|
+
if (itemBytes > maxStringBytes) {
|
|
690
|
+
return {
|
|
691
|
+
bytes,
|
|
692
|
+
nodes,
|
|
693
|
+
violation: { dimension: "string-bytes", actual: itemBytes, limit: maxStringBytes },
|
|
694
|
+
};
|
|
695
|
+
}
|
|
696
|
+
const violation = addBytes(encodedJsonStringBytes(item));
|
|
697
|
+
if (violation) return { bytes, nodes, violation };
|
|
698
|
+
continue;
|
|
699
|
+
}
|
|
700
|
+
if (typeof item !== "object") continue;
|
|
701
|
+
if (Array.isArray(item)) {
|
|
702
|
+
if (item.length > maxItems) {
|
|
703
|
+
return {
|
|
704
|
+
bytes,
|
|
705
|
+
nodes,
|
|
706
|
+
violation: { dimension: "items", actual: item.length, limit: maxItems },
|
|
707
|
+
};
|
|
708
|
+
}
|
|
709
|
+
const violation = addBytes(2 + Math.max(0, item.length - 1));
|
|
710
|
+
if (violation) return { bytes, nodes, violation };
|
|
711
|
+
for (let index = item.length - 1; index >= 0; index -= 1) {
|
|
712
|
+
stack.push({ value: item[index], depth: current.depth + 1 });
|
|
713
|
+
}
|
|
714
|
+
continue;
|
|
715
|
+
}
|
|
716
|
+
const entries = Object.entries(item).filter(([, child]) => child !== undefined);
|
|
717
|
+
if (entries.length > maxItems) {
|
|
718
|
+
return {
|
|
719
|
+
bytes,
|
|
720
|
+
nodes,
|
|
721
|
+
violation: { dimension: "items", actual: entries.length, limit: maxItems },
|
|
722
|
+
};
|
|
723
|
+
}
|
|
724
|
+
let violation = addBytes(2 + Math.max(0, entries.length - 1) + entries.length);
|
|
725
|
+
if (violation) return { bytes, nodes, violation };
|
|
726
|
+
for (let index = entries.length - 1; index >= 0; index -= 1) {
|
|
727
|
+
const [key, child] = entries[index] as [string, unknown];
|
|
728
|
+
violation = addBytes(encodedJsonStringBytes(key));
|
|
729
|
+
if (violation) return { bytes, nodes, violation };
|
|
730
|
+
stack.push({ value: child, depth: current.depth + 1 });
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
return { bytes, nodes };
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
export function jsonBoundViolation(
|
|
737
|
+
value: unknown,
|
|
738
|
+
maxBytes: number,
|
|
739
|
+
maxItems: number,
|
|
740
|
+
maxStringBytes: number,
|
|
741
|
+
maxNodes: number,
|
|
742
|
+
): JsonBoundViolation | undefined {
|
|
743
|
+
return inspectJsonBounds(value, maxBytes, maxItems, maxStringBytes, maxNodes).violation;
|
|
744
|
+
}
|
|
745
|
+
function optionalMetadataMetrics(value: unknown) {
|
|
746
|
+
return boundedJsonMetrics(
|
|
747
|
+
value,
|
|
748
|
+
MAX_OPTIONAL_METADATA_BYTES,
|
|
749
|
+
MAX_OPTIONAL_METADATA_ITEMS,
|
|
750
|
+
MAX_OPTIONAL_METADATA_BYTES,
|
|
751
|
+
MAX_OPTIONAL_METADATA_NODES,
|
|
752
|
+
);
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
function optionalMetadataIsBounded(value: unknown): boolean {
|
|
756
|
+
return optionalMetadataMetrics(value) !== undefined;
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
function omitUnsafeOptionalDetails(value: unknown): unknown {
|
|
760
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return value;
|
|
761
|
+
const record = value as Record<string, unknown>;
|
|
762
|
+
if (!Object.hasOwn(record, "details") || optionalMetadataIsBounded(record.details)) return value;
|
|
763
|
+
const { details: _details, ...safe } = record;
|
|
764
|
+
return safe;
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
const TASK_RESULT_STATUSES: Readonly<Record<string, true>> = {
|
|
768
|
+
pending: true,
|
|
769
|
+
running: true,
|
|
770
|
+
completed: true,
|
|
771
|
+
failed: true,
|
|
772
|
+
error: true,
|
|
773
|
+
aborted: true,
|
|
774
|
+
canceled: true,
|
|
775
|
+
cancelled: true,
|
|
776
|
+
};
|
|
777
|
+
const TASK_PROGRESS_STATUSES: Readonly<Record<string, true>> = {
|
|
778
|
+
pending: true,
|
|
779
|
+
running: true,
|
|
780
|
+
completed: true,
|
|
781
|
+
failed: true,
|
|
782
|
+
aborted: true,
|
|
783
|
+
};
|
|
784
|
+
|
|
785
|
+
function boundedTaskId(value: unknown): value is string {
|
|
786
|
+
return typeof value === "string" && value.length > 0 && utf8Bytes(value) <= MAX_ID_LENGTH;
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
function taskCorrelationDetails(value: unknown): unknown {
|
|
790
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return;
|
|
791
|
+
const details = value as Record<string, unknown>;
|
|
792
|
+
if (!Array.isArray(details.results) || details.results.length > MAX_TASK_CORRELATION_ITEMS)
|
|
793
|
+
return;
|
|
794
|
+
const results: Record<string, unknown>[] = [];
|
|
795
|
+
for (const value of details.results) {
|
|
796
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return;
|
|
797
|
+
const result = value as Record<string, unknown>;
|
|
798
|
+
if (!boundedTaskId(result.id)) return;
|
|
799
|
+
const safe: Record<string, unknown> = { id: result.id };
|
|
800
|
+
if (typeof result.status === "string" && Object.hasOwn(TASK_RESULT_STATUSES, result.status)) {
|
|
801
|
+
safe.status = result.status;
|
|
802
|
+
}
|
|
803
|
+
if (typeof result.aborted === "boolean") safe.aborted = result.aborted;
|
|
804
|
+
if (typeof result.exitCode === "number" && Number.isFinite(result.exitCode)) {
|
|
805
|
+
safe.exitCode = result.exitCode;
|
|
806
|
+
}
|
|
807
|
+
if (
|
|
808
|
+
result.error !== undefined &&
|
|
809
|
+
boundedJsonMetrics(result.error, 4_096, 32, 4_096, 64) !== undefined
|
|
810
|
+
) {
|
|
811
|
+
safe.error = result.error;
|
|
812
|
+
}
|
|
813
|
+
results.push(safe);
|
|
814
|
+
}
|
|
815
|
+
let progress: Record<string, unknown>[] | undefined;
|
|
816
|
+
if (details.progress !== undefined) {
|
|
817
|
+
if (!Array.isArray(details.progress) || details.progress.length > MAX_TASK_CORRELATION_ITEMS) {
|
|
818
|
+
return;
|
|
819
|
+
}
|
|
820
|
+
progress = [];
|
|
821
|
+
for (const value of details.progress) {
|
|
822
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return;
|
|
823
|
+
const item = value as Record<string, unknown>;
|
|
824
|
+
if (
|
|
825
|
+
!boundedTaskId(item.id) ||
|
|
826
|
+
typeof item.index !== "number" ||
|
|
827
|
+
!Number.isInteger(item.index) ||
|
|
828
|
+
item.index < 0 ||
|
|
829
|
+
item.index >= MAX_TASK_CORRELATION_ITEMS ||
|
|
830
|
+
typeof item.status !== "string" ||
|
|
831
|
+
!Object.hasOwn(TASK_PROGRESS_STATUSES, item.status)
|
|
832
|
+
) {
|
|
833
|
+
return;
|
|
834
|
+
}
|
|
835
|
+
progress.push({ id: item.id, index: item.index, status: item.status });
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
const correlation = { results, ...(progress ? { progress } : {}) };
|
|
839
|
+
return boundedJsonMetrics(
|
|
840
|
+
correlation,
|
|
841
|
+
MAX_TASK_CORRELATION_BYTES,
|
|
842
|
+
MAX_TASK_CORRELATION_ITEMS,
|
|
843
|
+
MAX_TASK_CORRELATION_BYTES,
|
|
844
|
+
MAX_TASK_CORRELATION_NODES,
|
|
845
|
+
)
|
|
846
|
+
? correlation
|
|
847
|
+
: undefined;
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
export function omitOptionalDetails(value: unknown, preserveTaskCorrelation = false): unknown {
|
|
851
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return value;
|
|
852
|
+
const record = value as Record<string, unknown>;
|
|
853
|
+
if (!Object.hasOwn(record, "details")) return value;
|
|
854
|
+
const { details: _details, ...structural } = record;
|
|
855
|
+
if (!preserveTaskCorrelation) return structural;
|
|
856
|
+
const correlation = taskCorrelationDetails(record.details);
|
|
857
|
+
return correlation === undefined ? structural : { ...structural, details: correlation };
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
export function createOptionalMetadataSanitizer(): (
|
|
861
|
+
value: unknown,
|
|
862
|
+
taskResult?: boolean,
|
|
863
|
+
) => unknown {
|
|
864
|
+
let retainedBytes = 0;
|
|
865
|
+
let retainedNodes = 0;
|
|
866
|
+
return (value, taskResult = false) => {
|
|
867
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return value;
|
|
868
|
+
const record = value as Record<string, unknown>;
|
|
869
|
+
if (!Object.hasOwn(record, "details")) return value;
|
|
870
|
+
const metrics = optionalMetadataMetrics(record.details);
|
|
871
|
+
if (
|
|
872
|
+
metrics &&
|
|
873
|
+
retainedBytes + metrics.bytes <= MAX_OPTIONAL_METADATA_BYTES &&
|
|
874
|
+
retainedNodes + metrics.nodes <= MAX_OPTIONAL_METADATA_NODES
|
|
875
|
+
) {
|
|
876
|
+
retainedBytes += metrics.bytes;
|
|
877
|
+
retainedNodes += metrics.nodes;
|
|
878
|
+
return value;
|
|
879
|
+
}
|
|
880
|
+
const correlation = taskResult ? taskCorrelationDetails(record.details) : undefined;
|
|
881
|
+
const { details: _details, ...safe } = record;
|
|
882
|
+
return correlation === undefined ? safe : { ...safe, details: correlation };
|
|
883
|
+
};
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
const OmpOptionalMetadataSchema = z.preprocess(
|
|
887
|
+
(value) => (optionalMetadataIsBounded(value) ? value : undefined),
|
|
888
|
+
z.unknown().optional(),
|
|
889
|
+
);
|
|
890
|
+
|
|
891
|
+
const OmpContentPartSchema = z
|
|
892
|
+
.object({
|
|
893
|
+
type: NAME,
|
|
894
|
+
text: TEXT.optional(),
|
|
895
|
+
thinking: TEXT.optional(),
|
|
896
|
+
data: boundedString(MAX_IMAGE_DATA_LENGTH).optional(),
|
|
897
|
+
mimeType: boundedString(128).optional(),
|
|
898
|
+
id: IDENTIFIER.optional(),
|
|
899
|
+
name: NAME.optional(),
|
|
900
|
+
arguments: z
|
|
901
|
+
.unknown()
|
|
902
|
+
.refine((value) => isBoundedJson(value, MAX_TOOL_PAYLOAD_LENGTH, 1_024, 4_096))
|
|
903
|
+
.optional(),
|
|
904
|
+
})
|
|
905
|
+
.superRefine((part, context) => {
|
|
906
|
+
if (part.type === "toolCall" && (!part.id || !part.name || part.arguments === undefined)) {
|
|
907
|
+
context.addIssue({ code: "custom", message: "invalid tool call payload" });
|
|
908
|
+
return;
|
|
909
|
+
}
|
|
910
|
+
if (part.type !== "image") return;
|
|
911
|
+
if (
|
|
912
|
+
part.data === undefined ||
|
|
913
|
+
part.mimeType === undefined ||
|
|
914
|
+
!isValidImagePayload(part.data, part.mimeType, MAX_IMAGE_DATA_LENGTH)
|
|
915
|
+
) {
|
|
916
|
+
context.addIssue({ code: "custom", message: "invalid image payload" });
|
|
917
|
+
}
|
|
918
|
+
});
|
|
919
|
+
const OmpAssistantContentPartSchema = OmpContentPartSchema.safeExtend({
|
|
920
|
+
text: RAW_DISPLAY_TEXT.optional(),
|
|
921
|
+
thinking: RAW_DISPLAY_TEXT.optional(),
|
|
922
|
+
});
|
|
923
|
+
const OmpAssistantDisplayContentSchema = z.preprocess(
|
|
924
|
+
boundRawDisplayContent,
|
|
925
|
+
z.union([RAW_DISPLAY_TEXT, z.array(OmpAssistantContentPartSchema).max(OMP_MAX_CONTENT_PARTS)]),
|
|
926
|
+
);
|
|
927
|
+
const OmpDisplayContentSchema = z.union([
|
|
928
|
+
TEXT,
|
|
929
|
+
z.array(OmpContentPartSchema).max(OMP_MAX_CONTENT_PARTS),
|
|
930
|
+
]);
|
|
931
|
+
const OmpImageArraySchema = z
|
|
932
|
+
.array(OmpContentPartSchema)
|
|
933
|
+
.max(OMP_MAX_CONTENT_PARTS)
|
|
934
|
+
.superRefine((parts, context) => {
|
|
935
|
+
if (parts.some((part) => part.type !== "image")) {
|
|
936
|
+
context.addIssue({ code: "custom", message: "invalid image collection" });
|
|
937
|
+
}
|
|
938
|
+
});
|
|
939
|
+
const OmpMessageIdentityShape = {
|
|
940
|
+
id: IDENTIFIER.optional(),
|
|
941
|
+
entryId: IDENTIFIER.optional(),
|
|
942
|
+
responseId: IDENTIFIER.optional(),
|
|
943
|
+
images: OmpImageArraySchema.optional(),
|
|
944
|
+
timestamp: z.number().finite().optional(),
|
|
945
|
+
details: OmpOptionalMetadataSchema,
|
|
946
|
+
};
|
|
947
|
+
const OmpOpaqueMessagePayloadSchema = z
|
|
948
|
+
.unknown()
|
|
949
|
+
.refine((value) => isBoundedJson(value, MAX_SEMANTIC_FRAME_BYTES, 1_024, 8_192));
|
|
950
|
+
const OmpExecutionOutputSchema = z.preprocess(
|
|
951
|
+
(value) =>
|
|
952
|
+
typeof value === "string" && utf8Bytes(value) > MAX_IMAGE_DATA_LENGTH
|
|
953
|
+
? DISPLAY_TRUNCATION_MARKER
|
|
954
|
+
: value,
|
|
955
|
+
RAW_DISPLAY_TEXT.optional(),
|
|
956
|
+
);
|
|
957
|
+
const OmpExecutionMessageShape = {
|
|
958
|
+
output: OmpExecutionOutputSchema,
|
|
959
|
+
exitCode: z.number().int().nullable().optional(),
|
|
960
|
+
cancelled: z.boolean().optional(),
|
|
961
|
+
truncated: z.boolean().optional(),
|
|
962
|
+
excludeFromContext: z.boolean().optional(),
|
|
963
|
+
meta: OmpOptionalMetadataSchema,
|
|
964
|
+
};
|
|
965
|
+
const OmpCustomMessageShape = {
|
|
966
|
+
customType: NAME.optional(),
|
|
967
|
+
content: OmpDisplayContentSchema.optional(),
|
|
968
|
+
display: z.boolean().optional(),
|
|
969
|
+
attribution: OmpOpaqueMessagePayloadSchema.optional(),
|
|
970
|
+
};
|
|
971
|
+
const OmpFileMentionSchema = z
|
|
972
|
+
.object({
|
|
973
|
+
path: boundedString(MAX_PATH_LENGTH),
|
|
974
|
+
content: TEXT,
|
|
975
|
+
lineCount: z.number().int().nonnegative().safe().optional(),
|
|
976
|
+
byteSize: z.number().int().nonnegative().safe().optional(),
|
|
977
|
+
skippedReason: z.enum(["tooLarge", "binary"]).optional(),
|
|
978
|
+
image: OmpContentPartSchema.optional(),
|
|
979
|
+
})
|
|
980
|
+
.superRefine((file, context) => {
|
|
981
|
+
if (file.image && file.image.type !== "image") {
|
|
982
|
+
context.addIssue({ code: "custom", message: "invalid file mention image" });
|
|
983
|
+
}
|
|
984
|
+
});
|
|
985
|
+
|
|
986
|
+
export const OmpMessageSchema = z.discriminatedUnion("role", [
|
|
987
|
+
z.object({
|
|
988
|
+
role: z.literal("assistant"),
|
|
989
|
+
content: OmpAssistantDisplayContentSchema.optional(),
|
|
990
|
+
...OmpMessageIdentityShape,
|
|
991
|
+
errorMessage: boundedString(4_096).nullable().optional(),
|
|
992
|
+
stopReason: boundedString(64).optional(),
|
|
993
|
+
}),
|
|
994
|
+
z.object({
|
|
995
|
+
role: z.literal("user"),
|
|
996
|
+
content: OmpDisplayContentSchema,
|
|
997
|
+
...OmpMessageIdentityShape,
|
|
998
|
+
}),
|
|
999
|
+
z.object({
|
|
1000
|
+
role: z.literal("developer"),
|
|
1001
|
+
content: OmpDisplayContentSchema,
|
|
1002
|
+
attribution: OmpOpaqueMessagePayloadSchema.optional(),
|
|
1003
|
+
synthetic: z.boolean().optional(),
|
|
1004
|
+
userInitiated: z.boolean().optional(),
|
|
1005
|
+
providerPayload: OmpOpaqueMessagePayloadSchema.optional(),
|
|
1006
|
+
...OmpMessageIdentityShape,
|
|
1007
|
+
}),
|
|
1008
|
+
z.object({
|
|
1009
|
+
role: z.literal("toolResult"),
|
|
1010
|
+
toolCallId: IDENTIFIER,
|
|
1011
|
+
toolName: NAME,
|
|
1012
|
+
content: OmpOpaqueMessagePayloadSchema,
|
|
1013
|
+
isError: z.boolean().optional(),
|
|
1014
|
+
attribution: OmpOpaqueMessagePayloadSchema.optional(),
|
|
1015
|
+
prunedAt: z.number().finite().nonnegative().optional(),
|
|
1016
|
+
providerMetadata: OmpOpaqueMessagePayloadSchema.optional(),
|
|
1017
|
+
useless: z.boolean().optional(),
|
|
1018
|
+
...OmpMessageIdentityShape,
|
|
1019
|
+
}),
|
|
1020
|
+
z.object({
|
|
1021
|
+
role: z.literal("bashExecution"),
|
|
1022
|
+
command: TEXT,
|
|
1023
|
+
...OmpExecutionMessageShape,
|
|
1024
|
+
...OmpMessageIdentityShape,
|
|
1025
|
+
}),
|
|
1026
|
+
z.object({
|
|
1027
|
+
role: z.literal("pythonExecution"),
|
|
1028
|
+
code: TEXT,
|
|
1029
|
+
...OmpExecutionMessageShape,
|
|
1030
|
+
...OmpMessageIdentityShape,
|
|
1031
|
+
}),
|
|
1032
|
+
z.object({ role: z.literal("custom"), ...OmpCustomMessageShape, ...OmpMessageIdentityShape }),
|
|
1033
|
+
z.object({
|
|
1034
|
+
role: z.literal("hookMessage"),
|
|
1035
|
+
...OmpCustomMessageShape,
|
|
1036
|
+
...OmpMessageIdentityShape,
|
|
1037
|
+
}),
|
|
1038
|
+
z.object({
|
|
1039
|
+
role: z.literal("branchSummary"),
|
|
1040
|
+
summary: TEXT,
|
|
1041
|
+
fromId: IDENTIFIER,
|
|
1042
|
+
...OmpMessageIdentityShape,
|
|
1043
|
+
}),
|
|
1044
|
+
z.object({
|
|
1045
|
+
role: z.literal("compactionSummary"),
|
|
1046
|
+
summary: TEXT,
|
|
1047
|
+
shortSummary: TEXT.optional(),
|
|
1048
|
+
tokensBefore: z.number().finite().nonnegative(),
|
|
1049
|
+
tokensAfter: z.number().finite().nonnegative().optional(),
|
|
1050
|
+
method: NAME.optional(),
|
|
1051
|
+
providerPayload: OmpOpaqueMessagePayloadSchema.optional(),
|
|
1052
|
+
blocks: z.array(OmpContentPartSchema).max(OMP_MAX_CONTENT_PARTS).optional(),
|
|
1053
|
+
warning: boundedString(64 * 1024).optional(),
|
|
1054
|
+
...OmpMessageIdentityShape,
|
|
1055
|
+
}),
|
|
1056
|
+
z.object({
|
|
1057
|
+
role: z.literal("fileMention"),
|
|
1058
|
+
files: z.array(OmpFileMentionSchema).max(MAX_ARRAY_ITEMS),
|
|
1059
|
+
...OmpMessageIdentityShape,
|
|
1060
|
+
}),
|
|
1061
|
+
]);
|
|
1062
|
+
export type OmpMessage = z.infer<typeof OmpMessageSchema>;
|
|
1063
|
+
|
|
1064
|
+
const OmpAssistantMessageEventSchema = z
|
|
1065
|
+
.object({
|
|
1066
|
+
type: NAME,
|
|
1067
|
+
contentIndex: z
|
|
1068
|
+
.number()
|
|
1069
|
+
.int()
|
|
1070
|
+
.nonnegative()
|
|
1071
|
+
.max(OMP_MAX_CONTENT_PARTS - 1)
|
|
1072
|
+
.optional(),
|
|
1073
|
+
delta: TEXT.optional(),
|
|
1074
|
+
content: z
|
|
1075
|
+
.unknown()
|
|
1076
|
+
.refine((value) => isBoundedJson(value, MAX_IMAGE_DATA_LENGTH + 1_024))
|
|
1077
|
+
.optional(),
|
|
1078
|
+
})
|
|
1079
|
+
.superRefine((event, context) => {
|
|
1080
|
+
const content = event.content;
|
|
1081
|
+
const imageLike =
|
|
1082
|
+
content !== null &&
|
|
1083
|
+
typeof content === "object" &&
|
|
1084
|
+
!Array.isArray(content) &&
|
|
1085
|
+
"type" in content &&
|
|
1086
|
+
content.type === "image";
|
|
1087
|
+
if (!event.type.startsWith("image_") && !imageLike) return;
|
|
1088
|
+
const image = OmpContentPartSchema.safeParse(content);
|
|
1089
|
+
if (!image.success || image.data.type !== "image") {
|
|
1090
|
+
context.addIssue({ code: "custom", message: "invalid image event" });
|
|
1091
|
+
}
|
|
1092
|
+
});
|
|
1093
|
+
|
|
1094
|
+
const OmpAvailableCommandSchema = z.object({
|
|
1095
|
+
name: NAME,
|
|
1096
|
+
aliases: z.array(NAME).max(32).optional(),
|
|
1097
|
+
description: boundedString(4_096).optional(),
|
|
1098
|
+
input: z
|
|
1099
|
+
.object({ hint: boundedString(1_024).optional() })
|
|
1100
|
+
.nullable()
|
|
1101
|
+
.optional(),
|
|
1102
|
+
subcommands: z
|
|
1103
|
+
.array(
|
|
1104
|
+
z.object({
|
|
1105
|
+
name: NAME,
|
|
1106
|
+
description: boundedString(4_096).optional(),
|
|
1107
|
+
usage: boundedString(1_024).optional(),
|
|
1108
|
+
}),
|
|
1109
|
+
)
|
|
1110
|
+
.max(128)
|
|
1111
|
+
.optional(),
|
|
1112
|
+
source: boundedString(64).optional(),
|
|
1113
|
+
});
|
|
1114
|
+
const OmpThinkingMetadataSchema = z
|
|
1115
|
+
.unknown()
|
|
1116
|
+
.transform((value): { efforts?: string[]; defaultLevel?: string } | undefined => {
|
|
1117
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return undefined;
|
|
1118
|
+
const record = value as Record<string, unknown>;
|
|
1119
|
+
const efforts = boundedOptionalStringArray(16, 32).parse(record.efforts);
|
|
1120
|
+
const defaultLevel = boundedString(32).optional().catch(undefined).parse(record.defaultLevel);
|
|
1121
|
+
return {
|
|
1122
|
+
...(efforts !== undefined ? { efforts } : {}),
|
|
1123
|
+
...(defaultLevel !== undefined ? { defaultLevel } : {}),
|
|
1124
|
+
};
|
|
1125
|
+
});
|
|
1126
|
+
export const OmpModelSchema = z.object({
|
|
1127
|
+
provider: OMP_PROVIDER_NAME,
|
|
1128
|
+
id: NAME,
|
|
1129
|
+
name: boundedString(MAX_NAME_LENGTH).optional().catch(undefined),
|
|
1130
|
+
reasoning: z.boolean().optional().catch(undefined),
|
|
1131
|
+
thinking: OmpThinkingMetadataSchema.optional(),
|
|
1132
|
+
input: boundedOptionalStringArray(16, MAX_NAME_LENGTH).optional(),
|
|
1133
|
+
contextWindow: z
|
|
1134
|
+
.number()
|
|
1135
|
+
.int()
|
|
1136
|
+
.nonnegative()
|
|
1137
|
+
.max(100_000_000)
|
|
1138
|
+
.nullable()
|
|
1139
|
+
.optional()
|
|
1140
|
+
.catch(undefined),
|
|
1141
|
+
});
|
|
1142
|
+
const TokenCountSchema = z.number().int().nonnegative().max(MAX_TOKEN_COUNT);
|
|
1143
|
+
const OptionalTokenCountSchema = TokenCountSchema.nullable().optional();
|
|
1144
|
+
const OptionalCostSchema = z
|
|
1145
|
+
.number()
|
|
1146
|
+
.finite()
|
|
1147
|
+
.nonnegative()
|
|
1148
|
+
.max(MAX_COST_USD)
|
|
1149
|
+
.nullable()
|
|
1150
|
+
.optional();
|
|
1151
|
+
const OmpContextUsageSchema = z.object({
|
|
1152
|
+
tokens: OptionalTokenCountSchema,
|
|
1153
|
+
contextWindow: TokenCountSchema.max(100_000_000).nullable().optional(),
|
|
1154
|
+
percent: z.number().finite().nonnegative().max(MAX_CONTEXT_PERCENT).nullable().optional(),
|
|
1155
|
+
});
|
|
1156
|
+
export const OmpSessionStatsSchema = z.object({
|
|
1157
|
+
userMessages: OptionalTokenCountSchema,
|
|
1158
|
+
assistantMessages: OptionalTokenCountSchema,
|
|
1159
|
+
toolCalls: OptionalTokenCountSchema,
|
|
1160
|
+
toolResults: OptionalTokenCountSchema,
|
|
1161
|
+
totalMessages: OptionalTokenCountSchema,
|
|
1162
|
+
tokens: z
|
|
1163
|
+
.object({
|
|
1164
|
+
input: OptionalTokenCountSchema,
|
|
1165
|
+
output: OptionalTokenCountSchema,
|
|
1166
|
+
reasoning: OptionalTokenCountSchema,
|
|
1167
|
+
cacheRead: OptionalTokenCountSchema,
|
|
1168
|
+
cacheWrite: OptionalTokenCountSchema,
|
|
1169
|
+
total: OptionalTokenCountSchema,
|
|
1170
|
+
})
|
|
1171
|
+
.nullable()
|
|
1172
|
+
.optional(),
|
|
1173
|
+
cost: OptionalCostSchema,
|
|
1174
|
+
premiumRequests: OptionalTokenCountSchema,
|
|
1175
|
+
credits: z
|
|
1176
|
+
.object({
|
|
1177
|
+
cost: OptionalCostSchema,
|
|
1178
|
+
committedCost: OptionalCostSchema,
|
|
1179
|
+
acuCost: OptionalCostSchema,
|
|
1180
|
+
})
|
|
1181
|
+
.nullable()
|
|
1182
|
+
.optional(),
|
|
1183
|
+
routedModels: z.record(NAME, OptionalTokenCountSchema).nullable().optional(),
|
|
1184
|
+
contextUsage: OmpContextUsageSchema.nullable().optional(),
|
|
1185
|
+
});
|
|
1186
|
+
export const OmpCompactionResultSchema = z.object({
|
|
1187
|
+
tokensBefore: OptionalTokenCountSchema,
|
|
1188
|
+
preTokens: OptionalTokenCountSchema,
|
|
1189
|
+
});
|
|
1190
|
+
export const OmpSessionStateSchema = z.object({
|
|
1191
|
+
model: OmpModelSchema.nullable().optional(),
|
|
1192
|
+
thinkingLevel: OmpCurrentThinkingLevelSchema,
|
|
1193
|
+
isStreaming: z.boolean(),
|
|
1194
|
+
isCompacting: z.boolean(),
|
|
1195
|
+
sessionId: IDENTIFIER,
|
|
1196
|
+
autoCompactionEnabled: z.boolean().optional(),
|
|
1197
|
+
contextUsage: OmpContextUsageSchema.nullable().optional().catch(undefined),
|
|
1198
|
+
sessionFile: boundedString(MAX_PATH_LENGTH).optional().catch(undefined),
|
|
1199
|
+
});
|
|
1200
|
+
export const OmpReadyFrameSchema = z.object({
|
|
1201
|
+
type: z.literal("ready"),
|
|
1202
|
+
protocolVersion: z.number().int().positive().max(16).optional(),
|
|
1203
|
+
supportedProtocolVersions: z.array(z.number().int().positive().max(16)).max(8).optional(),
|
|
1204
|
+
maxFrameBytes: z.number().int().positive().optional(),
|
|
1205
|
+
maxReassembledFrameBytes: z.number().int().positive().optional(),
|
|
1206
|
+
features: z.record(z.string(), z.unknown()).optional(),
|
|
1207
|
+
});
|
|
1208
|
+
export const OmpResponseFrameSchema = z.object({
|
|
1209
|
+
type: z.literal("response"),
|
|
1210
|
+
id: IDENTIFIER,
|
|
1211
|
+
command: boundedString(64, 1).optional(),
|
|
1212
|
+
success: z.boolean(),
|
|
1213
|
+
data: z.unknown().optional(),
|
|
1214
|
+
error: boundedString(MAX_RPC_ERROR_BYTES).optional(),
|
|
1215
|
+
code: boundedString(MAX_RPC_ERROR_CODE_BYTES, 1).optional(),
|
|
1216
|
+
});
|
|
1217
|
+
export const OmpChunkFrameSchema = z.object({
|
|
1218
|
+
type: z.literal("rpc_chunk"),
|
|
1219
|
+
chunkId: IDENTIFIER,
|
|
1220
|
+
index: z.number().int().nonnegative(),
|
|
1221
|
+
count: z.number().int().positive().max(MAX_CHUNK_COUNT),
|
|
1222
|
+
byteLength: z.number().int().nonnegative().max(MAX_REASSEMBLED_FRAME_BYTES),
|
|
1223
|
+
data: boundedString(MAX_ENCODED_CHUNK_BYTES),
|
|
1224
|
+
});
|
|
1225
|
+
export const JsonObjectSchema = z.record(z.string(), z.unknown());
|
|
1226
|
+
const BoundedToolPayloadSchema = z
|
|
1227
|
+
.unknown()
|
|
1228
|
+
.refine((value) => isBoundedJson(value, MAX_SEMANTIC_FRAME_BYTES, 1_024, 4_096));
|
|
1229
|
+
const OmpToolResultPayloadSchema = z.preprocess(
|
|
1230
|
+
omitUnsafeOptionalDetails,
|
|
1231
|
+
z
|
|
1232
|
+
.unknown()
|
|
1233
|
+
.refine(
|
|
1234
|
+
(value) =>
|
|
1235
|
+
isBoundedJson(value, MAX_SEMANTIC_FRAME_BYTES, MAX_OPTIONAL_METADATA_ITEMS, 8_192) &&
|
|
1236
|
+
isBoundedJson(omitOptionalDetails(value), MAX_SEMANTIC_FRAME_BYTES, 1_024, 4_096),
|
|
1237
|
+
),
|
|
1238
|
+
);
|
|
1239
|
+
export const OmpHostToolDefinitionSchema = z.object({
|
|
1240
|
+
name: NAME,
|
|
1241
|
+
label: NAME.optional(),
|
|
1242
|
+
description: boundedString(MAX_TEXT_LENGTH),
|
|
1243
|
+
loadMode: z.enum(["essential", "discoverable"]).optional(),
|
|
1244
|
+
parameters: JsonObjectSchema,
|
|
1245
|
+
});
|
|
1246
|
+
const OmpHostToolCallSchema = z.object({
|
|
1247
|
+
type: z.literal("host_tool_call"),
|
|
1248
|
+
id: IDENTIFIER,
|
|
1249
|
+
toolCallId: IDENTIFIER,
|
|
1250
|
+
toolName: NAME,
|
|
1251
|
+
arguments: JsonObjectSchema,
|
|
1252
|
+
});
|
|
1253
|
+
const OmpHostToolCancelSchema = z.object({
|
|
1254
|
+
type: z.literal("host_tool_cancel"),
|
|
1255
|
+
id: IDENTIFIER,
|
|
1256
|
+
targetId: IDENTIFIER,
|
|
1257
|
+
});
|
|
1258
|
+
const OmpHostToolContentSchema = z.object({ type: NAME, text: TEXT.optional() }).passthrough();
|
|
1259
|
+
const OmpHostToolAgentResultSchema = z.object({
|
|
1260
|
+
content: z.array(OmpHostToolContentSchema).max(MAX_ARRAY_ITEMS),
|
|
1261
|
+
details: BoundedToolPayloadSchema.optional(),
|
|
1262
|
+
isError: z.boolean().optional(),
|
|
1263
|
+
});
|
|
1264
|
+
export const OmpHostToolResultSchema = z.object({
|
|
1265
|
+
type: z.literal("host_tool_result"),
|
|
1266
|
+
id: IDENTIFIER,
|
|
1267
|
+
result: OmpHostToolAgentResultSchema,
|
|
1268
|
+
isError: z.boolean().optional(),
|
|
1269
|
+
});
|
|
1270
|
+
export const OmpHostToolUpdateSchema = z.object({
|
|
1271
|
+
type: z.literal("host_tool_update"),
|
|
1272
|
+
id: IDENTIFIER,
|
|
1273
|
+
partialResult: OmpHostToolAgentResultSchema,
|
|
1274
|
+
});
|
|
1275
|
+
const OmpToolApprovalIdentitySchema = z.discriminatedUnion("kind", [
|
|
1276
|
+
z.object({ kind: z.literal("shell"), command: boundedJsonString(24 * 1024, 1) }).strict(),
|
|
1277
|
+
z
|
|
1278
|
+
.object({
|
|
1279
|
+
kind: z.literal("edit"),
|
|
1280
|
+
paths: z.array(boundedJsonString(MAX_TOOL_APPROVAL_PATH_BYTES, 1)).min(1).max(16),
|
|
1281
|
+
content: boundedJsonString(MAX_TOOL_APPROVAL_CONTENT_BYTES),
|
|
1282
|
+
})
|
|
1283
|
+
.strict(),
|
|
1284
|
+
z
|
|
1285
|
+
.object({
|
|
1286
|
+
kind: z.literal("write"),
|
|
1287
|
+
path: boundedJsonString(MAX_TOOL_APPROVAL_PATH_BYTES, 1),
|
|
1288
|
+
content: boundedJsonString(MAX_TOOL_APPROVAL_CONTENT_BYTES),
|
|
1289
|
+
})
|
|
1290
|
+
.strict(),
|
|
1291
|
+
z.object({ kind: z.literal("other") }).strict(),
|
|
1292
|
+
]);
|
|
1293
|
+
type OmpToolApprovalValue =
|
|
1294
|
+
| string
|
|
1295
|
+
| number
|
|
1296
|
+
| boolean
|
|
1297
|
+
| null
|
|
1298
|
+
| OmpToolApprovalValue[]
|
|
1299
|
+
| { [key: string]: OmpToolApprovalValue };
|
|
1300
|
+
const OmpToolApprovalValueSchema: z.ZodType<OmpToolApprovalValue> = z.lazy(() =>
|
|
1301
|
+
z.union([
|
|
1302
|
+
boundedString(MAX_TOOL_APPROVAL_STRING_BYTES),
|
|
1303
|
+
z.number().finite(),
|
|
1304
|
+
z.boolean(),
|
|
1305
|
+
z.null(),
|
|
1306
|
+
z.array(OmpToolApprovalValueSchema).max(MAX_TOOL_APPROVAL_COLLECTION_ITEMS),
|
|
1307
|
+
z.record(boundedString(128, 1), OmpToolApprovalValueSchema),
|
|
1308
|
+
]),
|
|
1309
|
+
);
|
|
1310
|
+
function approvalInputWithinBounds(value: unknown): boolean {
|
|
1311
|
+
const pending: Array<{ value: unknown; depth: number }> = [{ value, depth: 0 }];
|
|
1312
|
+
let nodes = 0;
|
|
1313
|
+
while (pending.length > 0) {
|
|
1314
|
+
const current = pending.pop();
|
|
1315
|
+
if (!current) break;
|
|
1316
|
+
nodes += 1;
|
|
1317
|
+
if (nodes > MAX_TOOL_APPROVAL_INPUT_NODES) return false;
|
|
1318
|
+
if (current.value === null || typeof current.value !== "object") continue;
|
|
1319
|
+
if (current.depth >= MAX_TOOL_APPROVAL_DEPTH) return false;
|
|
1320
|
+
for (const child of Array.isArray(current.value)
|
|
1321
|
+
? current.value
|
|
1322
|
+
: Object.values(current.value as Record<string, unknown>)) {
|
|
1323
|
+
pending.push({ value: child, depth: current.depth + 1 });
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
return true;
|
|
1327
|
+
}
|
|
1328
|
+
const OmpToolApprovalRequestSchema = z
|
|
1329
|
+
.object({
|
|
1330
|
+
type: z.literal("tool_approval_request"),
|
|
1331
|
+
id: boundedString(MAX_TOOL_APPROVAL_ID_BYTES, 1),
|
|
1332
|
+
toolCallId: boundedString(MAX_TOOL_APPROVAL_ID_BYTES, 1),
|
|
1333
|
+
toolKind: z.enum(["shell", "edit", "write", "other"]),
|
|
1334
|
+
toolName: boundedString(MAX_TOOL_APPROVAL_NAME_BYTES, 1),
|
|
1335
|
+
tier: z.enum(["read", "write", "exec"]),
|
|
1336
|
+
identity: OmpToolApprovalIdentitySchema,
|
|
1337
|
+
input: z
|
|
1338
|
+
.record(boundedString(128, 1), OmpToolApprovalValueSchema)
|
|
1339
|
+
.refine(approvalInputWithinBounds),
|
|
1340
|
+
detail: z
|
|
1341
|
+
.object({
|
|
1342
|
+
lines: z
|
|
1343
|
+
.array(boundedString(MAX_TOOL_APPROVAL_DETAIL_BYTES))
|
|
1344
|
+
.max(MAX_TOOL_APPROVAL_DETAIL_LINES),
|
|
1345
|
+
truncated: z.boolean(),
|
|
1346
|
+
truncatedFields: z
|
|
1347
|
+
.array(boundedString(MAX_TOOL_APPROVAL_METADATA_FIELD_BYTES))
|
|
1348
|
+
.max(MAX_TOOL_APPROVAL_METADATA_FIELDS),
|
|
1349
|
+
redacted: z.boolean(),
|
|
1350
|
+
redactedFields: z
|
|
1351
|
+
.array(boundedString(MAX_TOOL_APPROVAL_METADATA_FIELD_BYTES))
|
|
1352
|
+
.max(MAX_TOOL_APPROVAL_METADATA_FIELDS),
|
|
1353
|
+
reason: boundedString(MAX_TOOL_APPROVAL_DETAIL_BYTES).optional(),
|
|
1354
|
+
providerSafetyChecks: z
|
|
1355
|
+
.array(boundedString(MAX_TOOL_APPROVAL_DETAIL_BYTES))
|
|
1356
|
+
.max(MAX_TOOL_APPROVAL_DETAIL_LINES)
|
|
1357
|
+
.optional(),
|
|
1358
|
+
})
|
|
1359
|
+
.strict(),
|
|
1360
|
+
timeout: z.number().finite().nonnegative().max(MAX_TOOL_APPROVAL_TIMEOUT_MS).optional(),
|
|
1361
|
+
})
|
|
1362
|
+
.strict()
|
|
1363
|
+
.superRefine((request, context) => {
|
|
1364
|
+
if (request.toolKind !== request.identity.kind) {
|
|
1365
|
+
context.addIssue({ code: "custom", message: "tool approval identity kind mismatch" });
|
|
1366
|
+
}
|
|
1367
|
+
if (Buffer.byteLength(JSON.stringify(request), "utf8") + 1 > MAX_TOOL_APPROVAL_FRAME_BYTES) {
|
|
1368
|
+
context.addIssue({ code: "custom", message: "tool approval request exceeds bounds" });
|
|
1369
|
+
}
|
|
1370
|
+
});
|
|
1371
|
+
const OmpToolApprovalCancelSchema = z
|
|
1372
|
+
.object({
|
|
1373
|
+
type: z.literal("tool_approval_cancel"),
|
|
1374
|
+
id: boundedString(MAX_TOOL_APPROVAL_ID_BYTES, 1),
|
|
1375
|
+
targetId: boundedString(MAX_TOOL_APPROVAL_ID_BYTES, 1),
|
|
1376
|
+
toolCallId: boundedString(MAX_TOOL_APPROVAL_ID_BYTES, 1),
|
|
1377
|
+
})
|
|
1378
|
+
.strict();
|
|
1379
|
+
export const OmpToolApprovalResponseSchema = z.union([
|
|
1380
|
+
z
|
|
1381
|
+
.object({
|
|
1382
|
+
type: z.literal("tool_approval_response"),
|
|
1383
|
+
id: boundedString(MAX_TOOL_APPROVAL_ID_BYTES, 1),
|
|
1384
|
+
toolCallId: boundedString(MAX_TOOL_APPROVAL_ID_BYTES, 1),
|
|
1385
|
+
approved: z.boolean(),
|
|
1386
|
+
})
|
|
1387
|
+
.strict(),
|
|
1388
|
+
z
|
|
1389
|
+
.object({
|
|
1390
|
+
type: z.literal("tool_approval_response"),
|
|
1391
|
+
id: boundedString(MAX_TOOL_APPROVAL_ID_BYTES, 1),
|
|
1392
|
+
toolCallId: boundedString(MAX_TOOL_APPROVAL_ID_BYTES, 1),
|
|
1393
|
+
cancelled: z.literal(true),
|
|
1394
|
+
timedOut: z.boolean().optional(),
|
|
1395
|
+
})
|
|
1396
|
+
.strict(),
|
|
1397
|
+
]);
|
|
1398
|
+
export const OmpAgentEndEnvelopeSchema = z.object({
|
|
1399
|
+
type: z.literal("agent_end"),
|
|
1400
|
+
requestId: IDENTIFIER.optional(),
|
|
1401
|
+
messageCount: z.number().int().nonnegative().optional(),
|
|
1402
|
+
isTerminal: z.boolean().optional(),
|
|
1403
|
+
});
|
|
1404
|
+
const OmpCompactionStartSchema = z.object({
|
|
1405
|
+
type: z.literal("compaction_start"),
|
|
1406
|
+
reason: boundedString(4_096).optional(),
|
|
1407
|
+
});
|
|
1408
|
+
const OmpCompactionEndSchema = z.object({
|
|
1409
|
+
type: z.literal("compaction_end"),
|
|
1410
|
+
reason: boundedString(4_096).optional(),
|
|
1411
|
+
result: BoundedToolPayloadSchema.optional(),
|
|
1412
|
+
aborted: z.boolean().optional(),
|
|
1413
|
+
willRetry: z.boolean().optional(),
|
|
1414
|
+
errorMessage: boundedString(4_096).optional(),
|
|
1415
|
+
skipped: z.boolean().optional(),
|
|
1416
|
+
});
|
|
1417
|
+
const OmpCoreAgentEventSchema = z.discriminatedUnion("type", [
|
|
1418
|
+
z.object({ type: z.literal("agent_start") }),
|
|
1419
|
+
z.object({
|
|
1420
|
+
type: z.literal("agent_end"),
|
|
1421
|
+
requestId: IDENTIFIER.optional(),
|
|
1422
|
+
messages: z.array(OmpMessageSchema).max(MAX_ARRAY_ITEMS).optional(),
|
|
1423
|
+
messageCount: z.number().int().nonnegative().optional(),
|
|
1424
|
+
isTerminal: z.boolean().optional(),
|
|
1425
|
+
}),
|
|
1426
|
+
z.object({ type: z.literal("turn_start") }),
|
|
1427
|
+
z.object({ type: z.literal("turn_end") }),
|
|
1428
|
+
z.object({ type: z.literal("message_start"), message: OmpMessageSchema }),
|
|
1429
|
+
z.object({
|
|
1430
|
+
type: z.literal("message_update"),
|
|
1431
|
+
message: OmpMessageSchema,
|
|
1432
|
+
assistantMessageEvent: OmpAssistantMessageEventSchema.optional(),
|
|
1433
|
+
}),
|
|
1434
|
+
z.object({ type: z.literal("message_end"), message: OmpMessageSchema }),
|
|
1435
|
+
z.object({
|
|
1436
|
+
type: z.literal("tool_execution_start"),
|
|
1437
|
+
toolCallId: IDENTIFIER,
|
|
1438
|
+
toolName: NAME,
|
|
1439
|
+
args: BoundedToolPayloadSchema,
|
|
1440
|
+
}),
|
|
1441
|
+
z.object({
|
|
1442
|
+
type: z.literal("tool_execution_update"),
|
|
1443
|
+
toolCallId: IDENTIFIER,
|
|
1444
|
+
toolName: NAME,
|
|
1445
|
+
args: BoundedToolPayloadSchema.optional(),
|
|
1446
|
+
partialResult: OmpToolResultPayloadSchema,
|
|
1447
|
+
}),
|
|
1448
|
+
z.object({
|
|
1449
|
+
type: z.literal("tool_stream_update"),
|
|
1450
|
+
toolCallId: IDENTIFIER,
|
|
1451
|
+
toolName: NAME,
|
|
1452
|
+
update: BoundedToolPayloadSchema,
|
|
1453
|
+
}),
|
|
1454
|
+
z.object({
|
|
1455
|
+
type: z.literal("tool_execution_end"),
|
|
1456
|
+
toolCallId: IDENTIFIER,
|
|
1457
|
+
toolName: NAME,
|
|
1458
|
+
result: OmpToolResultPayloadSchema,
|
|
1459
|
+
isError: z.boolean().optional(),
|
|
1460
|
+
}),
|
|
1461
|
+
OmpCompactionStartSchema,
|
|
1462
|
+
OmpCompactionEndSchema,
|
|
1463
|
+
]);
|
|
1464
|
+
const OmpGoalSchema = z.object({
|
|
1465
|
+
id: IDENTIFIER.optional(),
|
|
1466
|
+
objective: TEXT.optional(),
|
|
1467
|
+
status: boundedString(256).optional(),
|
|
1468
|
+
tokenBudget: z.number().finite().nonnegative().optional(),
|
|
1469
|
+
tokensUsed: z.number().finite().nonnegative().optional(),
|
|
1470
|
+
timeUsedSeconds: z.number().finite().nonnegative().optional(),
|
|
1471
|
+
createdAt: boundedString(128).optional(),
|
|
1472
|
+
updatedAt: boundedString(128).optional(),
|
|
1473
|
+
});
|
|
1474
|
+
const OmpGoalModeStateSchema = z.object({
|
|
1475
|
+
enabled: z.boolean().optional(),
|
|
1476
|
+
mode: boundedString(256).optional(),
|
|
1477
|
+
reason: boundedString(4_096).optional(),
|
|
1478
|
+
goal: OmpGoalSchema.optional(),
|
|
1479
|
+
});
|
|
1480
|
+
const OmpTodoReminderEventSchema = z.object({
|
|
1481
|
+
type: z.literal("todo_reminder"),
|
|
1482
|
+
todos: z
|
|
1483
|
+
.array(
|
|
1484
|
+
z.object({
|
|
1485
|
+
id: IDENTIFIER.optional(),
|
|
1486
|
+
content: boundedString(16_384),
|
|
1487
|
+
status: z.enum(["pending", "in_progress", "blocked", "completed", "abandoned"]),
|
|
1488
|
+
}),
|
|
1489
|
+
)
|
|
1490
|
+
.max(MAX_TODOS),
|
|
1491
|
+
attempt: z.number().int().nonnegative().safe().optional(),
|
|
1492
|
+
maxAttempts: z.number().int().nonnegative().safe().optional(),
|
|
1493
|
+
});
|
|
1494
|
+
const OmpThinkingLevelChangedEventSchema = z.object({
|
|
1495
|
+
type: z.literal("thinking_level_changed"),
|
|
1496
|
+
thinkingLevel: boundedString(MAX_CONFIG_EVENT_TEXT_BYTES).optional(),
|
|
1497
|
+
configured: boundedString(MAX_CONFIG_EVENT_TEXT_BYTES).optional(),
|
|
1498
|
+
resolved: boundedString(MAX_CONFIG_EVENT_TEXT_BYTES).optional(),
|
|
1499
|
+
});
|
|
1500
|
+
const OmpAutoRetryStartEventSchema = z.object({
|
|
1501
|
+
type: z.literal("auto_retry_start"),
|
|
1502
|
+
attempt: z.number().int().nonnegative().safe(),
|
|
1503
|
+
maxAttempts: z.number().int().positive().safe(),
|
|
1504
|
+
delayMs: z.number().int().nonnegative().safe(),
|
|
1505
|
+
errorMessage: boundedString(64 * 1024),
|
|
1506
|
+
errorId: z.number().int().safe().optional(),
|
|
1507
|
+
});
|
|
1508
|
+
const OmpAutoRetryEndEventSchema = z.object({
|
|
1509
|
+
type: z.literal("auto_retry_end"),
|
|
1510
|
+
success: z.boolean(),
|
|
1511
|
+
attempt: z.number().int().nonnegative().safe(),
|
|
1512
|
+
finalError: boundedString(64 * 1024).optional(),
|
|
1513
|
+
retryErrors: BoundedToolPayloadSchema.optional(),
|
|
1514
|
+
recoveredErrors: BoundedToolPayloadSchema.optional(),
|
|
1515
|
+
});
|
|
1516
|
+
const OmpAgentSessionEventSchema = z.discriminatedUnion("type", [
|
|
1517
|
+
...OmpCoreAgentEventSchema.options,
|
|
1518
|
+
z.object({ type: z.literal("config_warnings_changed") }),
|
|
1519
|
+
z.object({ type: z.literal("advisor_cost_changed") }),
|
|
1520
|
+
z.object({ type: z.literal("advisor_yielded") }),
|
|
1521
|
+
z.object({
|
|
1522
|
+
type: z.literal("ttsr_triggered"),
|
|
1523
|
+
rules: z.array(BoundedToolPayloadSchema).max(MAX_ARRAY_ITEMS),
|
|
1524
|
+
}),
|
|
1525
|
+
OmpTodoReminderEventSchema,
|
|
1526
|
+
z.object({ type: z.literal("todo_auto_clear") }),
|
|
1527
|
+
z.object({
|
|
1528
|
+
type: z.literal("irc_message"),
|
|
1529
|
+
message: OmpMessageSchema.refine((message) => message.role === "custom"),
|
|
1530
|
+
}),
|
|
1531
|
+
z.object({ type: z.literal("model_changed") }),
|
|
1532
|
+
OmpThinkingLevelChangedEventSchema,
|
|
1533
|
+
z.object({
|
|
1534
|
+
type: z.literal("goal_updated"),
|
|
1535
|
+
goal: OmpGoalSchema.nullable().optional(),
|
|
1536
|
+
state: OmpGoalModeStateSchema.optional(),
|
|
1537
|
+
}),
|
|
1538
|
+
OmpAutoRetryStartEventSchema,
|
|
1539
|
+
OmpAutoRetryEndEventSchema,
|
|
1540
|
+
z.object({
|
|
1541
|
+
type: z.literal("retry_fallback_applied"),
|
|
1542
|
+
from: boundedString(MAX_CONFIG_EVENT_TEXT_BYTES),
|
|
1543
|
+
to: boundedString(MAX_CONFIG_EVENT_TEXT_BYTES),
|
|
1544
|
+
role: boundedString(MAX_CONFIG_EVENT_TEXT_BYTES),
|
|
1545
|
+
}),
|
|
1546
|
+
z.object({
|
|
1547
|
+
type: z.literal("retry_fallback_succeeded"),
|
|
1548
|
+
model: boundedString(MAX_CONFIG_EVENT_TEXT_BYTES),
|
|
1549
|
+
role: boundedString(MAX_CONFIG_EVENT_TEXT_BYTES),
|
|
1550
|
+
}),
|
|
1551
|
+
z.object({
|
|
1552
|
+
type: z.literal("auto_compaction_start"),
|
|
1553
|
+
reason: boundedString(4_096),
|
|
1554
|
+
action: boundedString(MAX_CONFIG_EVENT_TEXT_BYTES),
|
|
1555
|
+
}),
|
|
1556
|
+
z.object({
|
|
1557
|
+
type: z.literal("auto_compaction_end"),
|
|
1558
|
+
action: NAME.optional(),
|
|
1559
|
+
result: OmpCompactionResultSchema.nullable().optional(),
|
|
1560
|
+
aborted: z.boolean().optional(),
|
|
1561
|
+
willRetry: z.boolean().optional(),
|
|
1562
|
+
errorMessage: boundedString(64 * 1024).optional(),
|
|
1563
|
+
skipped: z.boolean().optional(),
|
|
1564
|
+
}),
|
|
1565
|
+
z.object({
|
|
1566
|
+
type: z.literal("notice"),
|
|
1567
|
+
id: IDENTIFIER.optional(),
|
|
1568
|
+
level: z.enum(["info", "warning", "error"]),
|
|
1569
|
+
message: boundedString(64 * 1024),
|
|
1570
|
+
source: boundedString(MAX_NAME_LENGTH).optional(),
|
|
1571
|
+
}),
|
|
1572
|
+
]);
|
|
1573
|
+
const OmpSubagentStatusSchema = z.enum([
|
|
1574
|
+
"pending",
|
|
1575
|
+
"running",
|
|
1576
|
+
"started",
|
|
1577
|
+
"completed",
|
|
1578
|
+
"failed",
|
|
1579
|
+
"aborted",
|
|
1580
|
+
]);
|
|
1581
|
+
const OmpSubagentLifecyclePayloadSchema = z.object({
|
|
1582
|
+
id: IDENTIFIER,
|
|
1583
|
+
agent: NAME,
|
|
1584
|
+
agentSource: NAME.optional(),
|
|
1585
|
+
description: boundedString(64 * 1024).optional(),
|
|
1586
|
+
status: OmpSubagentStatusSchema,
|
|
1587
|
+
sessionFile: boundedString(MAX_PATH_LENGTH).optional(),
|
|
1588
|
+
parentToolCallId: IDENTIFIER.optional(),
|
|
1589
|
+
index: z.number().int().nonnegative().max(10_000),
|
|
1590
|
+
detached: z.boolean().optional(),
|
|
1591
|
+
});
|
|
1592
|
+
const OmpSubagentProgressSchema = z.object({
|
|
1593
|
+
id: IDENTIFIER,
|
|
1594
|
+
status: OmpSubagentStatusSchema,
|
|
1595
|
+
description: boundedString(64 * 1024).optional(),
|
|
1596
|
+
currentTool: BoundedToolPayloadSchema.optional(),
|
|
1597
|
+
recentTools: z.array(BoundedToolPayloadSchema).max(64).optional(),
|
|
1598
|
+
recentOutput: z.array(BoundedToolPayloadSchema).max(128).optional(),
|
|
1599
|
+
resolvedModel: NAME.optional(),
|
|
1600
|
+
});
|
|
1601
|
+
const OmpSubagentProgressPayloadSchema = z.object({
|
|
1602
|
+
index: z.number().int().nonnegative().max(10_000),
|
|
1603
|
+
agent: NAME,
|
|
1604
|
+
agentSource: NAME.optional(),
|
|
1605
|
+
task: TEXT,
|
|
1606
|
+
parentToolCallId: IDENTIFIER.optional(),
|
|
1607
|
+
assignment: TEXT.optional(),
|
|
1608
|
+
progress: OmpSubagentProgressSchema,
|
|
1609
|
+
sessionFile: boundedString(MAX_PATH_LENGTH).optional(),
|
|
1610
|
+
detached: z.boolean().optional(),
|
|
1611
|
+
});
|
|
1612
|
+
const ExtensionUiBase = { type: z.literal("extension_ui_request"), id: IDENTIFIER };
|
|
1613
|
+
const OmpExtensionUiRequestSchema = z.discriminatedUnion("method", [
|
|
1614
|
+
z
|
|
1615
|
+
.object({
|
|
1616
|
+
...ExtensionUiBase,
|
|
1617
|
+
method: z.literal("select"),
|
|
1618
|
+
title: boundedString(4_096),
|
|
1619
|
+
options: z.array(boundedString(4_096)).min(1).max(128),
|
|
1620
|
+
optionDetails: z
|
|
1621
|
+
.array(z.object({ description: boundedString(16_384).optional() }).strict())
|
|
1622
|
+
.max(128)
|
|
1623
|
+
.optional(),
|
|
1624
|
+
timeout: z.number().nonnegative().finite().optional(),
|
|
1625
|
+
})
|
|
1626
|
+
.strict()
|
|
1627
|
+
.superRefine((request, context) => {
|
|
1628
|
+
if (request.optionDetails && request.optionDetails.length !== request.options.length) {
|
|
1629
|
+
context.addIssue({ code: "custom", message: "invalid extension UI request" });
|
|
1630
|
+
}
|
|
1631
|
+
}),
|
|
1632
|
+
z
|
|
1633
|
+
.object({
|
|
1634
|
+
...ExtensionUiBase,
|
|
1635
|
+
method: z.literal("confirm"),
|
|
1636
|
+
title: boundedString(4_096),
|
|
1637
|
+
message: boundedString(64 * 1024),
|
|
1638
|
+
timeout: z.number().nonnegative().finite().optional(),
|
|
1639
|
+
})
|
|
1640
|
+
.strict(),
|
|
1641
|
+
z
|
|
1642
|
+
.object({
|
|
1643
|
+
...ExtensionUiBase,
|
|
1644
|
+
method: z.literal("input"),
|
|
1645
|
+
title: boundedString(4_096),
|
|
1646
|
+
placeholder: boundedString(4_096).optional(),
|
|
1647
|
+
prefill: TEXT.optional(),
|
|
1648
|
+
timeout: z.number().nonnegative().finite().optional(),
|
|
1649
|
+
})
|
|
1650
|
+
.strict(),
|
|
1651
|
+
z
|
|
1652
|
+
.object({
|
|
1653
|
+
...ExtensionUiBase,
|
|
1654
|
+
method: z.literal("editor"),
|
|
1655
|
+
title: boundedString(4_096),
|
|
1656
|
+
prefill: TEXT.optional(),
|
|
1657
|
+
promptStyle: z.boolean().optional(),
|
|
1658
|
+
timeout: z.number().nonnegative().finite().optional(),
|
|
1659
|
+
})
|
|
1660
|
+
.strict(),
|
|
1661
|
+
z.object({ ...ExtensionUiBase, method: z.literal("cancel"), targetId: IDENTIFIER }).strict(),
|
|
1662
|
+
z
|
|
1663
|
+
.object({
|
|
1664
|
+
...ExtensionUiBase,
|
|
1665
|
+
method: z.literal("notify"),
|
|
1666
|
+
message: boundedString(64 * 1024),
|
|
1667
|
+
notifyType: z.enum(["info", "warning", "error"]).optional(),
|
|
1668
|
+
})
|
|
1669
|
+
.strict(),
|
|
1670
|
+
z
|
|
1671
|
+
.object({
|
|
1672
|
+
...ExtensionUiBase,
|
|
1673
|
+
method: z.literal("setStatus"),
|
|
1674
|
+
statusKey: NAME,
|
|
1675
|
+
statusText: boundedString(16_384).optional(),
|
|
1676
|
+
})
|
|
1677
|
+
.strict(),
|
|
1678
|
+
z
|
|
1679
|
+
.object({
|
|
1680
|
+
...ExtensionUiBase,
|
|
1681
|
+
method: z.literal("setWidget"),
|
|
1682
|
+
widgetKey: NAME,
|
|
1683
|
+
widgetLines: z.array(boundedString(16_384)).max(128).optional(),
|
|
1684
|
+
widgetPlacement: z.enum(["aboveEditor", "belowEditor"]).optional(),
|
|
1685
|
+
})
|
|
1686
|
+
.strict(),
|
|
1687
|
+
z
|
|
1688
|
+
.object({ ...ExtensionUiBase, method: z.literal("setTitle"), title: boundedString(4_096) })
|
|
1689
|
+
.strict(),
|
|
1690
|
+
z.object({ ...ExtensionUiBase, method: z.literal("set_editor_text"), text: TEXT }).strict(),
|
|
1691
|
+
z
|
|
1692
|
+
.object({
|
|
1693
|
+
...ExtensionUiBase,
|
|
1694
|
+
method: z.literal("open_url"),
|
|
1695
|
+
url: boundedString(16_384),
|
|
1696
|
+
launchUrl: boundedString(16_384).optional(),
|
|
1697
|
+
instructions: boundedString(64 * 1024).optional(),
|
|
1698
|
+
})
|
|
1699
|
+
.strict(),
|
|
1700
|
+
]);
|
|
1701
|
+
export const OmpRuntimeEventSchema = z.discriminatedUnion("type", [
|
|
1702
|
+
...OmpAgentSessionEventSchema.options,
|
|
1703
|
+
z.object({
|
|
1704
|
+
type: z.literal("subagent_lifecycle"),
|
|
1705
|
+
payload: OmpSubagentLifecyclePayloadSchema,
|
|
1706
|
+
}),
|
|
1707
|
+
z.object({
|
|
1708
|
+
type: z.literal("subagent_progress"),
|
|
1709
|
+
payload: OmpSubagentProgressPayloadSchema,
|
|
1710
|
+
}),
|
|
1711
|
+
z.object({
|
|
1712
|
+
type: z.literal("subagent_event"),
|
|
1713
|
+
payload: z.object({ id: IDENTIFIER, event: OmpAgentSessionEventSchema }),
|
|
1714
|
+
}),
|
|
1715
|
+
z.object({
|
|
1716
|
+
type: z.literal("available_commands_update"),
|
|
1717
|
+
commands: z.array(OmpAvailableCommandSchema).max(MAX_ARRAY_ITEMS),
|
|
1718
|
+
}),
|
|
1719
|
+
z.object({ type: z.literal("command_output"), text: TEXT.optional() }),
|
|
1720
|
+
OmpExtensionUiRequestSchema,
|
|
1721
|
+
z.object({
|
|
1722
|
+
type: z.literal("prompt_result"),
|
|
1723
|
+
id: IDENTIFIER.optional(),
|
|
1724
|
+
agentInvoked: z.boolean(),
|
|
1725
|
+
}),
|
|
1726
|
+
OmpHostToolCallSchema,
|
|
1727
|
+
OmpHostToolCancelSchema,
|
|
1728
|
+
OmpToolApprovalRequestSchema,
|
|
1729
|
+
OmpToolApprovalCancelSchema,
|
|
1730
|
+
]);
|
|
1731
|
+
type OptionalDetailsMapper = (value: unknown, taskResult?: boolean) => unknown;
|
|
1732
|
+
|
|
1733
|
+
function mapRecordField(
|
|
1734
|
+
record: Record<string, unknown>,
|
|
1735
|
+
key: string,
|
|
1736
|
+
map: OptionalDetailsMapper,
|
|
1737
|
+
taskResult = false,
|
|
1738
|
+
): Record<string, unknown> {
|
|
1739
|
+
if (!Object.hasOwn(record, key)) return record;
|
|
1740
|
+
const next = map(record[key], taskResult);
|
|
1741
|
+
return next === record[key] ? record : { ...record, [key]: next };
|
|
1742
|
+
}
|
|
1743
|
+
|
|
1744
|
+
function mapMessageList(value: unknown, map: OptionalDetailsMapper): unknown {
|
|
1745
|
+
if (!Array.isArray(value)) return value;
|
|
1746
|
+
let changed = false;
|
|
1747
|
+
const messages = value.map((message) => {
|
|
1748
|
+
const taskResult =
|
|
1749
|
+
message !== null &&
|
|
1750
|
+
typeof message === "object" &&
|
|
1751
|
+
!Array.isArray(message) &&
|
|
1752
|
+
(message as Record<string, unknown>).role === "toolResult" &&
|
|
1753
|
+
(message as Record<string, unknown>).toolName === "task";
|
|
1754
|
+
const next = map(message, taskResult);
|
|
1755
|
+
changed ||= next !== message;
|
|
1756
|
+
return next;
|
|
1757
|
+
});
|
|
1758
|
+
return changed ? messages : value;
|
|
1759
|
+
}
|
|
1760
|
+
|
|
1761
|
+
function mapAgentEventDetails(
|
|
1762
|
+
frame: Record<string, unknown>,
|
|
1763
|
+
map: OptionalDetailsMapper,
|
|
1764
|
+
): Record<string, unknown> {
|
|
1765
|
+
switch (frame.type) {
|
|
1766
|
+
case "message_start":
|
|
1767
|
+
case "message_update":
|
|
1768
|
+
case "message_end": {
|
|
1769
|
+
const message = frame.message;
|
|
1770
|
+
const taskResult =
|
|
1771
|
+
message !== null &&
|
|
1772
|
+
typeof message === "object" &&
|
|
1773
|
+
!Array.isArray(message) &&
|
|
1774
|
+
(message as Record<string, unknown>).role === "toolResult" &&
|
|
1775
|
+
(message as Record<string, unknown>).toolName === "task";
|
|
1776
|
+
return mapRecordField(frame, "message", map, taskResult);
|
|
1777
|
+
}
|
|
1778
|
+
case "tool_execution_update":
|
|
1779
|
+
return mapRecordField(frame, "partialResult", map, frame.toolName === "task");
|
|
1780
|
+
case "tool_execution_end":
|
|
1781
|
+
return mapRecordField(frame, "result", map, frame.toolName === "task");
|
|
1782
|
+
case "agent_end":
|
|
1783
|
+
return mapRecordField(frame, "messages", (messages) => mapMessageList(messages, map));
|
|
1784
|
+
case "irc_message":
|
|
1785
|
+
return mapRecordField(frame, "message", map);
|
|
1786
|
+
default:
|
|
1787
|
+
return frame;
|
|
1788
|
+
}
|
|
1789
|
+
}
|
|
1790
|
+
|
|
1791
|
+
export function mapRuntimeFrameDetails(
|
|
1792
|
+
frame: Record<string, unknown>,
|
|
1793
|
+
map: OptionalDetailsMapper,
|
|
1794
|
+
): Record<string, unknown> {
|
|
1795
|
+
if (frame.type !== "subagent_event") return mapAgentEventDetails(frame, map);
|
|
1796
|
+
if (frame.payload === null || typeof frame.payload !== "object" || Array.isArray(frame.payload)) {
|
|
1797
|
+
return frame;
|
|
1798
|
+
}
|
|
1799
|
+
const payload = frame.payload as Record<string, unknown>;
|
|
1800
|
+
if (payload.event === null || typeof payload.event !== "object" || Array.isArray(payload.event)) {
|
|
1801
|
+
return frame;
|
|
1802
|
+
}
|
|
1803
|
+
const event = mapAgentEventDetails(payload.event as Record<string, unknown>, map);
|
|
1804
|
+
return event === payload.event ? frame : { ...frame, payload: { ...payload, event } };
|
|
1805
|
+
}
|
|
1806
|
+
|
|
1807
|
+
export function sanitizeMessageListMetadata(value: unknown): unknown {
|
|
1808
|
+
return mapMessageList(value, createOptionalMetadataSanitizer());
|
|
1809
|
+
}
|
|
1810
|
+
|
|
1811
|
+
export function sanitizeHistoryResponseData(value: unknown): unknown {
|
|
1812
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return value;
|
|
1813
|
+
return mapRecordField(value as Record<string, unknown>, "messages", sanitizeMessageListMetadata);
|
|
1814
|
+
}
|
|
1815
|
+
|
|
1816
|
+
export function projectSessionStateResponseData(value: unknown): unknown {
|
|
1817
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return value;
|
|
1818
|
+
const source = value as Record<string, unknown>;
|
|
1819
|
+
const projected: Record<string, unknown> = {};
|
|
1820
|
+
for (const key of [
|
|
1821
|
+
"model",
|
|
1822
|
+
"thinkingLevel",
|
|
1823
|
+
"isStreaming",
|
|
1824
|
+
"isCompacting",
|
|
1825
|
+
"sessionId",
|
|
1826
|
+
"autoCompactionEnabled",
|
|
1827
|
+
"contextUsage",
|
|
1828
|
+
"sessionFile",
|
|
1829
|
+
] as const) {
|
|
1830
|
+
if (Object.hasOwn(source, key)) projected[key] = source[key];
|
|
1831
|
+
}
|
|
1832
|
+
return projected;
|
|
1833
|
+
}
|
|
1834
|
+
|
|
1835
|
+
export function runtimeFrameCollectionLimit(frame: Record<string, unknown>): number {
|
|
1836
|
+
const type = frame.type;
|
|
1837
|
+
if (
|
|
1838
|
+
type === "message_start" ||
|
|
1839
|
+
type === "message_update" ||
|
|
1840
|
+
type === "message_end" ||
|
|
1841
|
+
type === "agent_end" ||
|
|
1842
|
+
type === "subagent_event"
|
|
1843
|
+
) {
|
|
1844
|
+
return OMP_MAX_CONTENT_PARTS;
|
|
1845
|
+
}
|
|
1846
|
+
return 1_024;
|
|
1847
|
+
}
|
|
1848
|
+
export const OmpModelsResultSchema = z.object({
|
|
1849
|
+
models: z.array(OmpModelSchema).min(1).max(MAX_MODEL_CATALOG_ITEMS),
|
|
1850
|
+
});
|
|
1851
|
+
export const OmpPromptAckSchema = z.object({ agentInvoked: z.boolean().optional() }).optional();
|
|
1852
|
+
export const OmpAvailableCommandsResultSchema = z.object({
|
|
1853
|
+
commands: z.array(OmpAvailableCommandSchema).max(MAX_ARRAY_ITEMS),
|
|
1854
|
+
});
|
|
1855
|
+
export const OmpBranchMessagesResultSchema = z.object({
|
|
1856
|
+
messages: z.array(z.object({ entryId: IDENTIFIER, text: TEXT })).max(1_024),
|
|
1857
|
+
});
|
|
1858
|
+
export const OmpBranchResultSchema = z.object({ text: TEXT, cancelled: z.boolean() });
|
|
1859
|
+
export const OmpMessagesResultSchema = z.object({
|
|
1860
|
+
messages: z.array(OmpMessageSchema).max(MAX_REPLAY_MESSAGES),
|
|
1861
|
+
});
|
|
1862
|
+
export const OmpMessagesPageResultSchema = z.object({
|
|
1863
|
+
messages: z.array(OmpMessageSchema).max(OMP_MESSAGE_PAGE_LIMIT),
|
|
1864
|
+
nextCursor: boundedString(2_048, 1).refine(isSafeOpaqueCursor).optional(),
|
|
1865
|
+
totalMessages: z.number().int().nonnegative().max(MAX_REPLAY_MESSAGES),
|
|
1866
|
+
});
|
|
1867
|
+
export const OmpSubagentsResultSchema = z.object({
|
|
1868
|
+
subagents: z
|
|
1869
|
+
.array(
|
|
1870
|
+
z.object({
|
|
1871
|
+
id: IDENTIFIER,
|
|
1872
|
+
index: z.number().int().nonnegative().max(10_000),
|
|
1873
|
+
agent: NAME,
|
|
1874
|
+
agentSource: NAME.optional(),
|
|
1875
|
+
description: TEXT.optional(),
|
|
1876
|
+
status: OmpSubagentStatusSchema,
|
|
1877
|
+
task: TEXT.optional(),
|
|
1878
|
+
assignment: TEXT.optional(),
|
|
1879
|
+
sessionFile: boundedString(MAX_PATH_LENGTH).optional(),
|
|
1880
|
+
lastUpdate: z.number().finite().nonnegative(),
|
|
1881
|
+
parentToolCallId: IDENTIFIER.optional(),
|
|
1882
|
+
}),
|
|
1883
|
+
)
|
|
1884
|
+
.max(1_024),
|
|
1885
|
+
});
|
|
1886
|
+
export const OmpSubagentMessagesResultSchema = z.object({
|
|
1887
|
+
sessionFile: boundedString(MAX_PATH_LENGTH),
|
|
1888
|
+
fromByte: z.number().int().nonnegative(),
|
|
1889
|
+
nextByte: z.number().int().nonnegative(),
|
|
1890
|
+
reset: z.boolean(),
|
|
1891
|
+
messages: z.array(OmpMessageSchema).max(MAX_REPLAY_MESSAGES),
|
|
1892
|
+
});
|
|
1893
|
+
export const ProtocolNegotiationResultSchema = z.object({
|
|
1894
|
+
protocolVersion: z.literal(2),
|
|
1895
|
+
clientCapabilities: z.object({ typedToolApprovals: z.literal(1).optional() }).optional(),
|
|
1896
|
+
});
|
|
1897
|
+
|
|
1898
|
+
export type OmpModel = z.infer<typeof OmpModelSchema>;
|
|
1899
|
+
export type OmpBranchResult = z.infer<typeof OmpBranchResultSchema>;
|
|
1900
|
+
export type OmpSessionState = z.infer<typeof OmpSessionStateSchema>;
|
|
1901
|
+
export type OmpSessionStats = z.infer<typeof OmpSessionStatsSchema>;
|
|
1902
|
+
export type OmpCompactionResult = z.infer<typeof OmpCompactionResultSchema>;
|
|
1903
|
+
export type OmpHostToolDefinition = z.infer<typeof OmpHostToolDefinitionSchema>;
|
|
1904
|
+
export type OmpHostToolCall = z.infer<typeof OmpHostToolCallSchema>;
|
|
1905
|
+
export type OmpHostToolResult = z.infer<typeof OmpHostToolResultSchema>;
|
|
1906
|
+
export type OmpHostToolUpdate = z.infer<typeof OmpHostToolUpdateSchema>;
|
|
1907
|
+
export type OmpToolApprovalRequest = z.infer<typeof OmpToolApprovalRequestSchema>;
|
|
1908
|
+
export type OmpToolApprovalCancel = z.infer<typeof OmpToolApprovalCancelSchema>;
|
|
1909
|
+
export type OmpToolApprovalResponse = z.infer<typeof OmpToolApprovalResponseSchema>;
|
|
1910
|
+
export function parseOmpHostToolAgentResult(value: unknown): OmpHostToolResult["result"] {
|
|
1911
|
+
return OmpHostToolAgentResultSchema.parse(value);
|
|
1912
|
+
}
|
|
1913
|
+
export type OmpRpcEvent =
|
|
1914
|
+
| z.infer<typeof OmpRuntimeEventSchema>
|
|
1915
|
+
| { type: "prompt_error"; id: string; error: string; code?: string }
|
|
1916
|
+
| { type: "process_exit"; error: string };
|
|
1917
|
+
export type OmpAgentSessionEvent = z.infer<typeof OmpAgentSessionEventSchema>;
|
|
1918
|
+
export type OmpSubagentSnapshot = z.infer<typeof OmpSubagentsResultSchema>["subagents"][number];
|
|
1919
|
+
export type OmpSubagentEvent = Extract<
|
|
1920
|
+
z.infer<typeof OmpRuntimeEventSchema>,
|
|
1921
|
+
{ type: "subagent_lifecycle" | "subagent_progress" | "subagent_event" }
|
|
1922
|
+
>;
|
|
1923
|
+
export interface OmpSubagentMessagesResult {
|
|
1924
|
+
sessionFile: string;
|
|
1925
|
+
fromByte: number;
|
|
1926
|
+
nextByte: number;
|
|
1927
|
+
reset: boolean;
|
|
1928
|
+
messages: OmpMessage[];
|
|
1929
|
+
}
|
|
1930
|
+
export interface OmpPersistedSubagentMessages {
|
|
1931
|
+
sessionFile: string;
|
|
1932
|
+
nativeSessionId: string;
|
|
1933
|
+
byteLength: number;
|
|
1934
|
+
messages: OmpMessage[];
|
|
1935
|
+
}
|
|
1936
|
+
export interface OmpPersistedSessionMessages {
|
|
1937
|
+
sessionFile: string;
|
|
1938
|
+
nativeSessionId: string;
|
|
1939
|
+
byteLength: number;
|
|
1940
|
+
messages: OmpMessage[];
|
|
1941
|
+
imageReplayWarning?: true;
|
|
1942
|
+
}
|
|
1943
|
+
|
|
1944
|
+
export type OmpAvailableCommand = z.infer<typeof OmpAvailableCommandSchema>;
|
|
1945
|
+
export type OmpImage = { type: "image"; data: string; mimeType: string };
|
|
1946
|
+
export type OmpExtensionUiResponse =
|
|
1947
|
+
| { type: "extension_ui_response"; id: string; value: string }
|
|
1948
|
+
| { type: "extension_ui_response"; id: string; confirmed: boolean }
|
|
1949
|
+
| { type: "extension_ui_response"; id: string; cancelled: true; timedOut?: boolean };
|