@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
|
@@ -1,1660 +1,90 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { isAbsolute, join } from "node:path";
|
|
1
|
+
import type { ChildProcessWithoutNullStreams } from "node:child_process";
|
|
2
|
+
import { join } from "node:path";
|
|
4
3
|
import { z } from "zod";
|
|
5
4
|
import { ompDataDir } from "../paths";
|
|
6
|
-
import {
|
|
5
|
+
import type { OmpSpawnRequest, OmpStartOptions } from "./omp-rpc-environment";
|
|
6
|
+
import { waitMs } from "./omp-rpc-process";
|
|
7
7
|
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
8
|
+
inspectJsonBounds,
|
|
9
|
+
MAX_IMAGE_DATA_LENGTH,
|
|
10
|
+
MAX_REPLAY_MESSAGES,
|
|
11
|
+
NAME,
|
|
12
|
+
OMP_MESSAGE_PAGE_LIMIT,
|
|
13
|
+
type OmpAvailableCommand,
|
|
14
|
+
OmpAvailableCommandsResultSchema,
|
|
15
|
+
OmpBranchMessagesResultSchema,
|
|
16
|
+
type OmpBranchResult,
|
|
17
|
+
OmpBranchResultSchema,
|
|
18
|
+
type OmpCompactionResult,
|
|
19
|
+
OmpCompactionResultSchema,
|
|
20
|
+
type OmpExtensionUiResponse,
|
|
21
|
+
type OmpHostToolDefinition,
|
|
22
|
+
OmpHostToolDefinitionSchema,
|
|
23
|
+
type OmpHostToolResult,
|
|
24
|
+
type OmpHostToolUpdate,
|
|
25
|
+
type OmpImage,
|
|
26
|
+
type OmpMessage,
|
|
27
|
+
OmpMessageSchema,
|
|
28
|
+
OmpMessagesPageResultSchema,
|
|
29
|
+
OmpMessagesResultSchema,
|
|
30
|
+
type OmpModel,
|
|
31
|
+
OmpModelSchema,
|
|
32
|
+
OmpModelsResultSchema,
|
|
33
|
+
type OmpPersistedSessionMessages,
|
|
34
|
+
type OmpPersistedSubagentMessages,
|
|
35
|
+
OmpPromptAckSchema,
|
|
36
|
+
type OmpProtocolViolationDiagnostic,
|
|
37
|
+
type OmpRpcEvent,
|
|
38
|
+
type OmpSessionState,
|
|
39
|
+
OmpSessionStateSchema,
|
|
40
|
+
type OmpSessionStats,
|
|
41
|
+
OmpSessionStatsSchema,
|
|
42
|
+
type OmpSubagentMessagesResult,
|
|
43
|
+
OmpSubagentMessagesResultSchema,
|
|
44
|
+
type OmpSubagentSnapshot,
|
|
45
|
+
OmpSubagentsResultSchema,
|
|
46
|
+
type OmpToolApprovalResponse,
|
|
47
|
+
OmpToolApprovalResponseSchema,
|
|
48
|
+
ProtocolNegotiationResultSchema,
|
|
49
|
+
sanitizeMessageListMetadata,
|
|
50
|
+
} from "./omp-rpc-protocol";
|
|
51
|
+
import {
|
|
52
|
+
OmpRpcProcess,
|
|
53
|
+
OmpRpcRequestRejectedError,
|
|
54
|
+
OmpRpcResponseLimitError,
|
|
55
|
+
validateReadyMetadata,
|
|
56
|
+
waitWithTimeout,
|
|
57
|
+
} from "./omp-rpc-transport";
|
|
58
|
+
import {
|
|
59
|
+
MAX_ID_LENGTH,
|
|
60
|
+
MAX_NAME_LENGTH,
|
|
61
|
+
MAX_PATH_LENGTH,
|
|
62
|
+
MAX_TEXT_LENGTH,
|
|
63
|
+
OmpThinkingLevelSchema,
|
|
64
|
+
validateBoundedText,
|
|
65
|
+
} from "./omp-rpc-values";
|
|
66
|
+
import { buildOmpPromptRequest } from "./prompt-payload";
|
|
67
|
+
import { OmpCleanupFailure, OmpPublicError } from "./security";
|
|
14
68
|
import {
|
|
15
69
|
listOmpSessionDescriptors,
|
|
16
70
|
type OmpSessionDescriptor,
|
|
17
71
|
type OmpSessionListOptions,
|
|
18
72
|
readOmpPersistedSessionTranscript,
|
|
19
73
|
readOmpPersistedSubagentTranscript,
|
|
20
|
-
validateNativeSessionId,
|
|
21
74
|
} from "./session-descriptors";
|
|
22
|
-
import type { OmpOutputRedaction } from "./settings";
|
|
23
75
|
|
|
24
76
|
const READY_TIMEOUT_MS = 20_000;
|
|
25
|
-
const REQUEST_TIMEOUT_MS = 60_000;
|
|
26
|
-
const PROCESS_STOP_TIMEOUT_MS = 750;
|
|
27
|
-
const CHUNK_STALE_MS = 30_000;
|
|
28
|
-
const MAX_PHYSICAL_FRAME_BYTES = 1024 * 1024;
|
|
29
|
-
const MAX_CHUNK_BYTES = 256 * 1024;
|
|
30
|
-
const MAX_ENCODED_CHUNK_BYTES = Math.ceil(MAX_CHUNK_BYTES / 3) * 4;
|
|
31
|
-
const MAX_REASSEMBLED_FRAME_BYTES = 64 * 1024 * 1024;
|
|
32
|
-
const MAX_SEMANTIC_FRAME_BYTES = 12 * 1024 * 1024;
|
|
33
|
-
const MAX_CHUNK_COUNT = MAX_REASSEMBLED_FRAME_BYTES / MAX_CHUNK_BYTES;
|
|
34
|
-
const MAX_ID_LENGTH = 256;
|
|
35
|
-
const MAX_NAME_LENGTH = 256;
|
|
36
|
-
const MAX_MODEL_SELECTOR_BYTES = MAX_NAME_LENGTH * 2 + 1;
|
|
37
|
-
const MAX_CONFIG_EVENT_TEXT_BYTES = 64 * 1024;
|
|
38
|
-
const MAX_TEXT_LENGTH = 1024 * 1024;
|
|
39
|
-
const MAX_STREAM_TEXT_LENGTH = 4 * 1024 * 1024;
|
|
40
|
-
const MAX_SYSTEM_PROMPT_LENGTH = 64 * 1024;
|
|
41
|
-
const MAX_IMAGE_DATA_LENGTH = 8 * 1024 * 1024;
|
|
42
|
-
const MAX_TOOL_PAYLOAD_LENGTH = 256 * 1024;
|
|
43
|
-
const MAX_ACTIVE_TOOLS = 64;
|
|
44
77
|
const MAX_HOST_TOOLS = 256;
|
|
45
|
-
const MAX_TOOL_APPROVAL_FRAME_BYTES = 64 * 1024;
|
|
46
|
-
const MAX_TOOL_APPROVAL_STRING_BYTES = 8 * 1024;
|
|
47
|
-
const MAX_TOOL_APPROVAL_COLLECTION_ITEMS = 32;
|
|
48
|
-
const MAX_TOOL_APPROVAL_INPUT_NODES = 256;
|
|
49
|
-
const MAX_TOOL_APPROVAL_DEPTH = 4;
|
|
50
|
-
const MAX_TOOL_APPROVAL_ID_BYTES = 512;
|
|
51
|
-
const MAX_TOOL_APPROVAL_NAME_BYTES = 256;
|
|
52
|
-
const MAX_TOOL_APPROVAL_DETAIL_LINES = 16;
|
|
53
|
-
const MAX_TOOL_APPROVAL_DETAIL_BYTES = 2 * 1024;
|
|
54
|
-
const MAX_TOOL_APPROVAL_METADATA_FIELDS = 33;
|
|
55
|
-
const MAX_TOOL_APPROVAL_METADATA_FIELD_BYTES = 64;
|
|
56
|
-
const MAX_TOOL_APPROVAL_TIMEOUT_MS = 24 * 60 * 60 * 1000;
|
|
57
|
-
const MAX_TOOL_APPROVAL_PATH_BYTES = 4 * 1024;
|
|
58
|
-
const MAX_TOOL_APPROVAL_CONTENT_BYTES = 20 * 1024;
|
|
59
|
-
type TimerHandle = ReturnType<typeof setTimeout>;
|
|
60
|
-
const MAX_PENDING_REQUESTS = 256;
|
|
61
|
-
const MAX_PENDING_ONE_WAY_WRITES = 256;
|
|
62
|
-
const MAX_PENDING_WRITE_BYTES = 8 * 1024 * 1024;
|
|
63
|
-
const MAX_LINE_PARTS = 4_096;
|
|
64
|
-
const MAX_ARRAY_ITEMS = 512;
|
|
65
78
|
// OMP read metadata can contain one source entry per displayed line. Bound this optional,
|
|
66
79
|
// 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;
|
|
73
|
-
const MAX_MODEL_CATALOG_ITEMS = 4_096;
|
|
74
80
|
// Tool-intensive OMP turns legitimately exceed 64 blocks; transport byte/node budgets remain the
|
|
75
81
|
// primary resource bounds.
|
|
76
|
-
|
|
77
|
-
const
|
|
78
|
-
const
|
|
79
|
-
const
|
|
80
|
-
const
|
|
81
|
-
const
|
|
82
|
-
const WINDOWS_DEFAULT_SYSTEM_ROOT = "C:\\Windows";
|
|
83
|
-
const MAX_TOKEN_COUNT = Number.MAX_SAFE_INTEGER;
|
|
84
|
-
const MAX_COST_USD = 1_000_000_000;
|
|
85
|
-
const MAX_RPC_ERROR_BYTES = 4_096;
|
|
86
|
-
const MAX_RPC_ERROR_CODE_BYTES = 256;
|
|
87
|
-
const PROMPT_SCHEDULING_FAILURE = "OMP prompt scheduling failed";
|
|
88
|
-
const PROTOCOL_VIOLATION_COALESCE_MS = 10_000;
|
|
89
|
-
|
|
90
|
-
export const OMP_PROTOCOL_VIOLATION_CATEGORIES = [
|
|
91
|
-
"duplicate-ready",
|
|
92
|
-
"frame-limit",
|
|
93
|
-
"incomplete-frame",
|
|
94
|
-
"interleaved-chunk",
|
|
95
|
-
"invalid-chunk",
|
|
96
|
-
"invalid-envelope",
|
|
97
|
-
"invalid-event",
|
|
98
|
-
"invalid-event-state",
|
|
99
|
-
"invalid-json",
|
|
100
|
-
"invalid-ready",
|
|
101
|
-
"invalid-response",
|
|
102
|
-
"remote-frame-error",
|
|
103
|
-
] as const;
|
|
104
|
-
export type OmpProtocolViolationCategory = (typeof OMP_PROTOCOL_VIOLATION_CATEGORIES)[number];
|
|
105
|
-
export const OMP_PROTOCOL_VIOLATION_REASONS = [
|
|
106
|
-
"output-ended-mid-frame",
|
|
107
|
-
"physical-frame-limit",
|
|
108
|
-
"semantic-frame-limit",
|
|
109
|
-
"json-decode",
|
|
110
|
-
"chunk-json-decode",
|
|
111
|
-
"object-envelope",
|
|
112
|
-
"missing-frame-type",
|
|
113
|
-
"response-schema",
|
|
114
|
-
"chunk-schema",
|
|
115
|
-
"chunk-metadata",
|
|
116
|
-
"chunk-size",
|
|
117
|
-
"chunk-start-index",
|
|
118
|
-
"chunk-sequence",
|
|
119
|
-
"chunk-byte-count",
|
|
120
|
-
"chunk-timeout",
|
|
121
|
-
"frame-interleaved-with-chunk",
|
|
122
|
-
"remote-frame-error",
|
|
123
|
-
"ready-already-received",
|
|
124
|
-
"ready-schema",
|
|
125
|
-
"notice-level-type",
|
|
126
|
-
"notice-message-type",
|
|
127
|
-
"notice-schema",
|
|
128
|
-
"event-schema",
|
|
129
|
-
"message-event-schema",
|
|
130
|
-
"tool-event-schema",
|
|
131
|
-
"lifecycle-event-schema",
|
|
132
|
-
"subagent-event-schema",
|
|
133
|
-
"configuration-event-schema",
|
|
134
|
-
"extension-ui-schema",
|
|
135
|
-
"unknown-event-type",
|
|
136
|
-
"event-state-transition",
|
|
137
|
-
] as const;
|
|
138
|
-
export type OmpProtocolViolationReason = (typeof OMP_PROTOCOL_VIOLATION_REASONS)[number];
|
|
139
|
-
export const OMP_PROTOCOL_DIAGNOSTIC_PHASES = [
|
|
140
|
-
"startup",
|
|
141
|
-
"negotiation",
|
|
142
|
-
"idle",
|
|
143
|
-
"active-turn",
|
|
144
|
-
"closing",
|
|
145
|
-
] as const;
|
|
146
|
-
export type OmpProtocolDiagnosticPhase = (typeof OMP_PROTOCOL_DIAGNOSTIC_PHASES)[number];
|
|
147
|
-
|
|
148
|
-
export const OMP_PROTOCOL_EVENT_TYPES = [
|
|
149
|
-
"agent_start",
|
|
150
|
-
"agent_end",
|
|
151
|
-
"turn_start",
|
|
152
|
-
"turn_end",
|
|
153
|
-
"message_start",
|
|
154
|
-
"message_update",
|
|
155
|
-
"message_end",
|
|
156
|
-
"tool_execution_start",
|
|
157
|
-
"tool_execution_update",
|
|
158
|
-
"tool_execution_end",
|
|
159
|
-
"compaction_start",
|
|
160
|
-
"compaction_end",
|
|
161
|
-
"subagent_lifecycle",
|
|
162
|
-
"subagent_progress",
|
|
163
|
-
"subagent_event",
|
|
164
|
-
"todo_reminder",
|
|
165
|
-
"model_changed",
|
|
166
|
-
"thinking_level_changed",
|
|
167
|
-
"goal_updated",
|
|
168
|
-
"auto_retry_start",
|
|
169
|
-
"auto_retry_end",
|
|
170
|
-
"retry_fallback_applied",
|
|
171
|
-
"retry_fallback_succeeded",
|
|
172
|
-
"todo_auto_clear",
|
|
173
|
-
"auto_compaction_start",
|
|
174
|
-
"auto_compaction_end",
|
|
175
|
-
"available_commands_update",
|
|
176
|
-
"notice",
|
|
177
|
-
"command_output",
|
|
178
|
-
"extension_ui_request",
|
|
179
|
-
"prompt_result",
|
|
180
|
-
"host_tool_call",
|
|
181
|
-
"host_tool_cancel",
|
|
182
|
-
"tool_approval_request",
|
|
183
|
-
"tool_approval_cancel",
|
|
184
|
-
"advisor_yielded",
|
|
185
|
-
] as const;
|
|
186
|
-
export type OmpProtocolEventType = (typeof OMP_PROTOCOL_EVENT_TYPES)[number];
|
|
187
|
-
|
|
188
|
-
export const OMP_PROTOCOL_DIAGNOSTIC_FIELDS = [
|
|
189
|
-
"frame",
|
|
190
|
-
"frame.byteLength",
|
|
191
|
-
"frame.type",
|
|
192
|
-
"response",
|
|
193
|
-
"ready",
|
|
194
|
-
"event",
|
|
195
|
-
"event.sequence",
|
|
196
|
-
"notice.level",
|
|
197
|
-
"notice.message",
|
|
198
|
-
"chunk",
|
|
199
|
-
"chunk.data",
|
|
200
|
-
"chunk.index",
|
|
201
|
-
"chunk.sequence",
|
|
202
|
-
"chunk.byteLength",
|
|
203
|
-
] as const;
|
|
204
|
-
export type OmpProtocolDiagnosticField = (typeof OMP_PROTOCOL_DIAGNOSTIC_FIELDS)[number];
|
|
205
|
-
|
|
206
|
-
export const OMP_PROTOCOL_DIAGNOSTIC_EXPECTATIONS = [
|
|
207
|
-
"complete-json-line",
|
|
208
|
-
"within-byte-limit",
|
|
209
|
-
"valid-json",
|
|
210
|
-
"object-envelope",
|
|
211
|
-
"bounded-frame-type",
|
|
212
|
-
"valid-response-frame",
|
|
213
|
-
"valid-ready-frame",
|
|
214
|
-
"single-ready-frame",
|
|
215
|
-
"valid-event-frame",
|
|
216
|
-
"valid-event-state-transition",
|
|
217
|
-
"valid-message-event",
|
|
218
|
-
"valid-tool-event",
|
|
219
|
-
"valid-lifecycle-event",
|
|
220
|
-
"valid-subagent-event",
|
|
221
|
-
"valid-configuration-event",
|
|
222
|
-
"valid-extension-ui-event",
|
|
223
|
-
"known-event-type",
|
|
224
|
-
"notice-level-enum",
|
|
225
|
-
"notice-message-string",
|
|
226
|
-
"valid-chunk-frame",
|
|
227
|
-
"valid-base64-chunk",
|
|
228
|
-
"first-chunk-index-zero",
|
|
229
|
-
"contiguous-chunk-sequence",
|
|
230
|
-
"declared-chunk-byte-count",
|
|
231
|
-
"chunk-before-deadline",
|
|
232
|
-
"no-interleaved-frame",
|
|
233
|
-
"no-remote-frame-error",
|
|
234
|
-
] as const;
|
|
235
|
-
export type OmpProtocolDiagnosticExpectation =
|
|
236
|
-
(typeof OMP_PROTOCOL_DIAGNOSTIC_EXPECTATIONS)[number];
|
|
237
|
-
|
|
238
|
-
export const OMP_PROTOCOL_DIAGNOSTIC_ACTUAL_TYPES = [
|
|
239
|
-
"missing",
|
|
240
|
-
"null",
|
|
241
|
-
"array",
|
|
242
|
-
"object",
|
|
243
|
-
"string",
|
|
244
|
-
"number",
|
|
245
|
-
"boolean",
|
|
246
|
-
"invalid-json",
|
|
247
|
-
"partial-frame",
|
|
248
|
-
"oversized",
|
|
249
|
-
"duplicate",
|
|
250
|
-
"interleaved",
|
|
251
|
-
"out-of-order",
|
|
252
|
-
"timeout",
|
|
253
|
-
"remote-error",
|
|
254
|
-
"mismatched",
|
|
255
|
-
] as const;
|
|
256
|
-
export type OmpProtocolDiagnosticActualType = (typeof OMP_PROTOCOL_DIAGNOSTIC_ACTUAL_TYPES)[number];
|
|
257
|
-
|
|
258
|
-
export interface OmpProtocolViolationDiagnostic {
|
|
259
|
-
category: OmpProtocolViolationCategory;
|
|
260
|
-
reason: OmpProtocolViolationReason;
|
|
261
|
-
phase: OmpProtocolDiagnosticPhase;
|
|
262
|
-
occurrenceCount: number;
|
|
263
|
-
eventType?: OmpProtocolEventType;
|
|
264
|
-
frameType?: "ready" | "response" | "rpc_chunk" | "rpc_frame_error" | "notice";
|
|
265
|
-
field?: OmpProtocolDiagnosticField;
|
|
266
|
-
expected?: OmpProtocolDiagnosticExpectation;
|
|
267
|
-
actualType?: OmpProtocolDiagnosticActualType;
|
|
268
|
-
maxByteSize?: number;
|
|
269
|
-
limitBytes?: number;
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
function protocolDiagnosticActualType(value: unknown): OmpProtocolDiagnosticActualType {
|
|
273
|
-
if (value === undefined) return "missing";
|
|
274
|
-
if (value === null) return "null";
|
|
275
|
-
if (Array.isArray(value)) return "array";
|
|
276
|
-
if (typeof value === "object") return "object";
|
|
277
|
-
if (typeof value === "string") return "string";
|
|
278
|
-
if (typeof value === "number") return "number";
|
|
279
|
-
if (typeof value === "boolean") return "boolean";
|
|
280
|
-
return "object";
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
function isProtocolEventType(value: string): value is OmpProtocolEventType {
|
|
284
|
-
return (OMP_PROTOCOL_EVENT_TYPES as readonly string[]).includes(value);
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
function eventSchemaClassification(
|
|
288
|
-
eventType: OmpProtocolEventType,
|
|
289
|
-
): Pick<OmpProtocolViolationDiagnostic, "reason" | "expected"> {
|
|
290
|
-
switch (eventType) {
|
|
291
|
-
case "message_start":
|
|
292
|
-
case "message_update":
|
|
293
|
-
case "message_end":
|
|
294
|
-
return { reason: "message-event-schema", expected: "valid-message-event" };
|
|
295
|
-
case "tool_execution_start":
|
|
296
|
-
case "tool_execution_update":
|
|
297
|
-
case "tool_execution_end":
|
|
298
|
-
case "host_tool_call":
|
|
299
|
-
case "host_tool_cancel":
|
|
300
|
-
case "tool_approval_request":
|
|
301
|
-
case "tool_approval_cancel":
|
|
302
|
-
return { reason: "tool-event-schema", expected: "valid-tool-event" };
|
|
303
|
-
case "subagent_lifecycle":
|
|
304
|
-
case "subagent_progress":
|
|
305
|
-
case "subagent_event":
|
|
306
|
-
return { reason: "subagent-event-schema", expected: "valid-subagent-event" };
|
|
307
|
-
case "model_changed":
|
|
308
|
-
case "thinking_level_changed":
|
|
309
|
-
case "goal_updated":
|
|
310
|
-
case "todo_reminder":
|
|
311
|
-
case "todo_auto_clear":
|
|
312
|
-
case "available_commands_update":
|
|
313
|
-
case "retry_fallback_applied":
|
|
314
|
-
case "retry_fallback_succeeded":
|
|
315
|
-
return { reason: "configuration-event-schema", expected: "valid-configuration-event" };
|
|
316
|
-
case "extension_ui_request":
|
|
317
|
-
return { reason: "extension-ui-schema", expected: "valid-extension-ui-event" };
|
|
318
|
-
default:
|
|
319
|
-
return { reason: "lifecycle-event-schema", expected: "valid-lifecycle-event" };
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
function invalidEventDiagnosticMetadata(
|
|
324
|
-
frame: Record<string, unknown>,
|
|
325
|
-
type: string,
|
|
326
|
-
): Pick<
|
|
327
|
-
OmpProtocolViolationDiagnostic,
|
|
328
|
-
"reason" | "eventType" | "frameType" | "field" | "expected" | "actualType"
|
|
329
|
-
> {
|
|
330
|
-
if (!isProtocolEventType(type)) {
|
|
331
|
-
return {
|
|
332
|
-
reason: "unknown-event-type",
|
|
333
|
-
field: "frame.type",
|
|
334
|
-
expected: "known-event-type",
|
|
335
|
-
actualType: "string",
|
|
336
|
-
};
|
|
337
|
-
}
|
|
338
|
-
if (type !== "notice") {
|
|
339
|
-
return {
|
|
340
|
-
...eventSchemaClassification(type),
|
|
341
|
-
eventType: type,
|
|
342
|
-
field: "event",
|
|
343
|
-
actualType: "object",
|
|
344
|
-
};
|
|
345
|
-
}
|
|
346
|
-
if (typeof frame.level !== "string") {
|
|
347
|
-
return {
|
|
348
|
-
reason: "notice-level-type",
|
|
349
|
-
eventType: "notice",
|
|
350
|
-
frameType: "notice",
|
|
351
|
-
field: "notice.level",
|
|
352
|
-
expected: "notice-level-enum",
|
|
353
|
-
actualType: protocolDiagnosticActualType(frame.level),
|
|
354
|
-
};
|
|
355
|
-
}
|
|
356
|
-
if (typeof frame.message !== "string") {
|
|
357
|
-
return {
|
|
358
|
-
reason: "notice-message-type",
|
|
359
|
-
eventType: "notice",
|
|
360
|
-
frameType: "notice",
|
|
361
|
-
field: "notice.message",
|
|
362
|
-
expected: "notice-message-string",
|
|
363
|
-
actualType: protocolDiagnosticActualType(frame.message),
|
|
364
|
-
};
|
|
365
|
-
}
|
|
366
|
-
return {
|
|
367
|
-
reason: "notice-schema",
|
|
368
|
-
eventType: "notice",
|
|
369
|
-
frameType: "notice",
|
|
370
|
-
field: "event",
|
|
371
|
-
expected: "valid-event-frame",
|
|
372
|
-
actualType: "object",
|
|
373
|
-
};
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
type PendingProtocolViolation = Omit<OmpProtocolViolationDiagnostic, "occurrenceCount"> & {
|
|
377
|
-
occurrenceCount: number;
|
|
378
|
-
};
|
|
379
|
-
type ProtocolViolationKey = `${OmpProtocolViolationCategory}:${OmpProtocolViolationReason}`;
|
|
380
|
-
const MAX_CONTEXT_PERCENT = 1_000_000;
|
|
381
|
-
function boundedJsonString(maxBytes: number, minBytes = 0) {
|
|
382
|
-
return z.string().refine((value) => {
|
|
383
|
-
const bytes = Buffer.byteLength(JSON.stringify(value), "utf8") - 2;
|
|
384
|
-
return bytes >= minBytes && bytes <= maxBytes;
|
|
385
|
-
});
|
|
386
|
-
}
|
|
387
|
-
function isBoundedToolApprovalId(value: unknown): value is string {
|
|
388
|
-
return typeof value === "string" && value.length > 0 && Buffer.byteLength(value, "utf8") <= 512;
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
export const OMP_HOST_TOOL_FRAME_LIMIT_ERROR =
|
|
392
|
-
"MCP host tool result exceeds the OMP RPC frame limit";
|
|
393
|
-
const MIN_HOST_TOOL_RESULT_FRAME_BYTES = Buffer.byteLength(
|
|
394
|
-
`${JSON.stringify({
|
|
395
|
-
type: "host_tool_result",
|
|
396
|
-
id: "\0".repeat(MAX_ID_LENGTH),
|
|
397
|
-
result: {
|
|
398
|
-
content: [{ type: "text", text: OMP_HOST_TOOL_FRAME_LIMIT_ERROR }],
|
|
399
|
-
details: {},
|
|
400
|
-
isError: true,
|
|
401
|
-
},
|
|
402
|
-
isError: true,
|
|
403
|
-
})}\n`,
|
|
404
|
-
);
|
|
405
|
-
function boundedString(maxBytes: number, minBytes = 0) {
|
|
406
|
-
return z.string().refine((value) => {
|
|
407
|
-
const bytes = utf8Bytes(value);
|
|
408
|
-
return bytes >= minBytes && bytes <= maxBytes;
|
|
409
|
-
});
|
|
410
|
-
}
|
|
411
|
-
function boundedOptionalStringArray(maxItems: number, maxBytes: number) {
|
|
412
|
-
return z.unknown().transform((value): string[] | undefined => {
|
|
413
|
-
if (!Array.isArray(value)) return undefined;
|
|
414
|
-
return value
|
|
415
|
-
.filter(
|
|
416
|
-
(item): item is string =>
|
|
417
|
-
typeof item === "string" && item.length > 0 && utf8Bytes(item) <= maxBytes,
|
|
418
|
-
)
|
|
419
|
-
.slice(0, maxItems);
|
|
420
|
-
});
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
const IDENTIFIER = boundedString(MAX_ID_LENGTH, 1);
|
|
424
|
-
const NAME = boundedString(MAX_NAME_LENGTH, 1);
|
|
425
|
-
const OMP_PROVIDER_NAME = NAME.refine((provider) => !provider.includes("/"));
|
|
426
|
-
const TEXT = boundedString(MAX_TEXT_LENGTH);
|
|
427
|
-
const RAW_DISPLAY_TEXT = boundedString(MAX_IMAGE_DATA_LENGTH);
|
|
428
|
-
const DISPLAY_TRUNCATION_MARKER = "<truncated>";
|
|
429
|
-
|
|
430
|
-
function boundRawDisplayContent(value: unknown): unknown {
|
|
431
|
-
if (typeof value === "string") {
|
|
432
|
-
return utf8Bytes(value) <= MAX_IMAGE_DATA_LENGTH ? value : DISPLAY_TRUNCATION_MARKER;
|
|
433
|
-
}
|
|
434
|
-
if (!Array.isArray(value)) return value;
|
|
435
|
-
let totalBytes = 0;
|
|
436
|
-
for (const part of value) {
|
|
437
|
-
if (!part || typeof part !== "object" || Array.isArray(part)) continue;
|
|
438
|
-
const record = part as Record<string, unknown>;
|
|
439
|
-
if (typeof record.text === "string") totalBytes += utf8Bytes(record.text);
|
|
440
|
-
if (typeof record.thinking === "string") totalBytes += utf8Bytes(record.thinking);
|
|
441
|
-
}
|
|
442
|
-
if (totalBytes <= MAX_IMAGE_DATA_LENGTH) return value;
|
|
443
|
-
|
|
444
|
-
let retainedMarker = false;
|
|
445
|
-
return value.map((part) => {
|
|
446
|
-
if (!part || typeof part !== "object" || Array.isArray(part)) return part;
|
|
447
|
-
const copy = { ...(part as Record<string, unknown>) };
|
|
448
|
-
for (const key of ["text", "thinking"] as const) {
|
|
449
|
-
if (typeof copy[key] !== "string") continue;
|
|
450
|
-
if (retainedMarker) delete copy[key];
|
|
451
|
-
else {
|
|
452
|
-
copy[key] = DISPLAY_TRUNCATION_MARKER;
|
|
453
|
-
retainedMarker = true;
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
|
-
return copy;
|
|
457
|
-
});
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
function sanitizeLiveMessageDisplay(value: unknown): unknown {
|
|
461
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return value;
|
|
462
|
-
const message = value as Record<string, unknown>;
|
|
463
|
-
if (message.role === "assistant") {
|
|
464
|
-
const content = boundRawDisplayContent(message.content);
|
|
465
|
-
return content === message.content ? value : { ...message, content };
|
|
466
|
-
}
|
|
467
|
-
if (
|
|
468
|
-
message.role === "bashExecution" &&
|
|
469
|
-
typeof message.output === "string" &&
|
|
470
|
-
utf8Bytes(message.output) > MAX_IMAGE_DATA_LENGTH
|
|
471
|
-
) {
|
|
472
|
-
return { ...message, output: DISPLAY_TRUNCATION_MARKER };
|
|
473
|
-
}
|
|
474
|
-
return value;
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
function sanitizeLiveDisplayFrame(value: unknown): unknown {
|
|
478
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return value;
|
|
479
|
-
const frame = value as Record<string, unknown>;
|
|
480
|
-
if (
|
|
481
|
-
frame.type === "message_start" ||
|
|
482
|
-
frame.type === "message_update" ||
|
|
483
|
-
frame.type === "message_end"
|
|
484
|
-
) {
|
|
485
|
-
const message = sanitizeLiveMessageDisplay(frame.message);
|
|
486
|
-
return message === frame.message ? value : { ...frame, message };
|
|
487
|
-
}
|
|
488
|
-
if (frame.type !== "agent_end" || !Array.isArray(frame.messages)) return value;
|
|
489
|
-
let changed = false;
|
|
490
|
-
const messages = frame.messages.map((message) => {
|
|
491
|
-
const sanitized = sanitizeLiveMessageDisplay(message);
|
|
492
|
-
if (sanitized !== message) changed = true;
|
|
493
|
-
return sanitized;
|
|
494
|
-
});
|
|
495
|
-
return changed ? { ...frame, messages } : value;
|
|
496
|
-
}
|
|
497
|
-
const OmpThinkingLevelSchema = z.enum(["off", "minimal", "low", "medium", "high", "xhigh", "max"]);
|
|
498
|
-
const OmpCurrentThinkingLevelSchema = boundedString(32).optional().catch(undefined);
|
|
499
|
-
|
|
500
|
-
function isBoundedJson(
|
|
501
|
-
value: unknown,
|
|
502
|
-
maxBytes = MAX_TOOL_PAYLOAD_LENGTH,
|
|
503
|
-
maxItems = MAX_ARRAY_ITEMS,
|
|
504
|
-
maxNodes = 2_048,
|
|
505
|
-
): boolean {
|
|
506
|
-
return (
|
|
507
|
-
boundedJsonBytes(value, maxBytes, maxItems, maxBytes, maxNodes) !== Number.POSITIVE_INFINITY
|
|
508
|
-
);
|
|
509
|
-
}
|
|
510
|
-
function optionalMetadataMetrics(value: unknown) {
|
|
511
|
-
return boundedJsonMetrics(
|
|
512
|
-
value,
|
|
513
|
-
MAX_OPTIONAL_METADATA_BYTES,
|
|
514
|
-
MAX_OPTIONAL_METADATA_ITEMS,
|
|
515
|
-
MAX_OPTIONAL_METADATA_BYTES,
|
|
516
|
-
MAX_OPTIONAL_METADATA_NODES,
|
|
517
|
-
);
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
function optionalMetadataIsBounded(value: unknown): boolean {
|
|
521
|
-
return optionalMetadataMetrics(value) !== undefined;
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
function omitUnsafeOptionalDetails(value: unknown): unknown {
|
|
525
|
-
if (value === null || typeof value !== "object" || Array.isArray(value)) return value;
|
|
526
|
-
const record = value as Record<string, unknown>;
|
|
527
|
-
if (!Object.hasOwn(record, "details") || optionalMetadataIsBounded(record.details)) return value;
|
|
528
|
-
const { details: _details, ...safe } = record;
|
|
529
|
-
return safe;
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
const TASK_RESULT_STATUSES: Readonly<Record<string, true>> = {
|
|
533
|
-
pending: true,
|
|
534
|
-
running: true,
|
|
535
|
-
completed: true,
|
|
536
|
-
failed: true,
|
|
537
|
-
error: true,
|
|
538
|
-
aborted: true,
|
|
539
|
-
canceled: true,
|
|
540
|
-
cancelled: true,
|
|
541
|
-
};
|
|
542
|
-
const TASK_PROGRESS_STATUSES: Readonly<Record<string, true>> = {
|
|
543
|
-
pending: true,
|
|
544
|
-
running: true,
|
|
545
|
-
completed: true,
|
|
546
|
-
failed: true,
|
|
547
|
-
aborted: true,
|
|
548
|
-
};
|
|
549
|
-
|
|
550
|
-
function boundedTaskId(value: unknown): value is string {
|
|
551
|
-
return typeof value === "string" && value.length > 0 && utf8Bytes(value) <= MAX_ID_LENGTH;
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
function taskCorrelationDetails(value: unknown): unknown {
|
|
555
|
-
if (value === null || typeof value !== "object" || Array.isArray(value)) return;
|
|
556
|
-
const details = value as Record<string, unknown>;
|
|
557
|
-
if (!Array.isArray(details.results) || details.results.length > MAX_TASK_CORRELATION_ITEMS)
|
|
558
|
-
return;
|
|
559
|
-
const results: Record<string, unknown>[] = [];
|
|
560
|
-
for (const value of details.results) {
|
|
561
|
-
if (value === null || typeof value !== "object" || Array.isArray(value)) return;
|
|
562
|
-
const result = value as Record<string, unknown>;
|
|
563
|
-
if (!boundedTaskId(result.id)) return;
|
|
564
|
-
const safe: Record<string, unknown> = { id: result.id };
|
|
565
|
-
if (typeof result.status === "string" && Object.hasOwn(TASK_RESULT_STATUSES, result.status)) {
|
|
566
|
-
safe.status = result.status;
|
|
567
|
-
}
|
|
568
|
-
if (typeof result.aborted === "boolean") safe.aborted = result.aborted;
|
|
569
|
-
if (typeof result.exitCode === "number" && Number.isFinite(result.exitCode)) {
|
|
570
|
-
safe.exitCode = result.exitCode;
|
|
571
|
-
}
|
|
572
|
-
if (
|
|
573
|
-
result.error !== undefined &&
|
|
574
|
-
boundedJsonMetrics(result.error, 4_096, 32, 4_096, 64) !== undefined
|
|
575
|
-
) {
|
|
576
|
-
safe.error = result.error;
|
|
577
|
-
}
|
|
578
|
-
results.push(safe);
|
|
579
|
-
}
|
|
580
|
-
let progress: Record<string, unknown>[] | undefined;
|
|
581
|
-
if (details.progress !== undefined) {
|
|
582
|
-
if (!Array.isArray(details.progress) || details.progress.length > MAX_TASK_CORRELATION_ITEMS) {
|
|
583
|
-
return;
|
|
584
|
-
}
|
|
585
|
-
progress = [];
|
|
586
|
-
for (const value of details.progress) {
|
|
587
|
-
if (value === null || typeof value !== "object" || Array.isArray(value)) return;
|
|
588
|
-
const item = value as Record<string, unknown>;
|
|
589
|
-
if (
|
|
590
|
-
!boundedTaskId(item.id) ||
|
|
591
|
-
typeof item.index !== "number" ||
|
|
592
|
-
!Number.isInteger(item.index) ||
|
|
593
|
-
item.index < 0 ||
|
|
594
|
-
item.index >= MAX_TASK_CORRELATION_ITEMS ||
|
|
595
|
-
typeof item.status !== "string" ||
|
|
596
|
-
!Object.hasOwn(TASK_PROGRESS_STATUSES, item.status)
|
|
597
|
-
) {
|
|
598
|
-
return;
|
|
599
|
-
}
|
|
600
|
-
progress.push({ id: item.id, index: item.index, status: item.status });
|
|
601
|
-
}
|
|
602
|
-
}
|
|
603
|
-
const correlation = { results, ...(progress ? { progress } : {}) };
|
|
604
|
-
return boundedJsonMetrics(
|
|
605
|
-
correlation,
|
|
606
|
-
MAX_TASK_CORRELATION_BYTES,
|
|
607
|
-
MAX_TASK_CORRELATION_ITEMS,
|
|
608
|
-
MAX_TASK_CORRELATION_BYTES,
|
|
609
|
-
MAX_TASK_CORRELATION_NODES,
|
|
610
|
-
)
|
|
611
|
-
? correlation
|
|
612
|
-
: undefined;
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
function omitOptionalDetails(value: unknown, preserveTaskCorrelation = false): unknown {
|
|
616
|
-
if (value === null || typeof value !== "object" || Array.isArray(value)) return value;
|
|
617
|
-
const record = value as Record<string, unknown>;
|
|
618
|
-
if (!Object.hasOwn(record, "details")) return value;
|
|
619
|
-
const { details: _details, ...structural } = record;
|
|
620
|
-
if (!preserveTaskCorrelation) return structural;
|
|
621
|
-
const correlation = taskCorrelationDetails(record.details);
|
|
622
|
-
return correlation === undefined ? structural : { ...structural, details: correlation };
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
function createOptionalMetadataSanitizer(): (value: unknown, taskResult?: boolean) => unknown {
|
|
626
|
-
let retainedBytes = 0;
|
|
627
|
-
let retainedNodes = 0;
|
|
628
|
-
return (value, taskResult = false) => {
|
|
629
|
-
if (value === null || typeof value !== "object" || Array.isArray(value)) return value;
|
|
630
|
-
const record = value as Record<string, unknown>;
|
|
631
|
-
if (!Object.hasOwn(record, "details")) return value;
|
|
632
|
-
const metrics = optionalMetadataMetrics(record.details);
|
|
633
|
-
if (
|
|
634
|
-
metrics &&
|
|
635
|
-
retainedBytes + metrics.bytes <= MAX_OPTIONAL_METADATA_BYTES &&
|
|
636
|
-
retainedNodes + metrics.nodes <= MAX_OPTIONAL_METADATA_NODES
|
|
637
|
-
) {
|
|
638
|
-
retainedBytes += metrics.bytes;
|
|
639
|
-
retainedNodes += metrics.nodes;
|
|
640
|
-
return value;
|
|
641
|
-
}
|
|
642
|
-
const correlation = taskResult ? taskCorrelationDetails(record.details) : undefined;
|
|
643
|
-
const { details: _details, ...safe } = record;
|
|
644
|
-
return correlation === undefined ? safe : { ...safe, details: correlation };
|
|
645
|
-
};
|
|
646
|
-
}
|
|
647
|
-
|
|
648
|
-
const OmpOptionalMetadataSchema = z.preprocess(
|
|
649
|
-
(value) => (optionalMetadataIsBounded(value) ? value : undefined),
|
|
650
|
-
z.unknown().optional(),
|
|
651
|
-
);
|
|
652
|
-
|
|
653
|
-
const OmpContentPartSchema = z
|
|
654
|
-
.object({
|
|
655
|
-
type: NAME,
|
|
656
|
-
text: TEXT.optional(),
|
|
657
|
-
thinking: TEXT.optional(),
|
|
658
|
-
data: boundedString(MAX_IMAGE_DATA_LENGTH).optional(),
|
|
659
|
-
mimeType: boundedString(128).optional(),
|
|
660
|
-
id: IDENTIFIER.optional(),
|
|
661
|
-
name: NAME.optional(),
|
|
662
|
-
arguments: z
|
|
663
|
-
.unknown()
|
|
664
|
-
.refine((value) => isBoundedJson(value, MAX_TOOL_PAYLOAD_LENGTH, 1_024, 4_096))
|
|
665
|
-
.optional(),
|
|
666
|
-
})
|
|
667
|
-
.superRefine((part, context) => {
|
|
668
|
-
if (part.type === "toolCall" && (!part.id || !part.name || part.arguments === undefined)) {
|
|
669
|
-
context.addIssue({ code: "custom", message: "invalid tool call payload" });
|
|
670
|
-
return;
|
|
671
|
-
}
|
|
672
|
-
if (part.type !== "image") return;
|
|
673
|
-
if (
|
|
674
|
-
part.data === undefined ||
|
|
675
|
-
part.mimeType === undefined ||
|
|
676
|
-
!isValidImagePayload(part.data, part.mimeType, MAX_IMAGE_DATA_LENGTH)
|
|
677
|
-
) {
|
|
678
|
-
context.addIssue({ code: "custom", message: "invalid image payload" });
|
|
679
|
-
}
|
|
680
|
-
});
|
|
681
|
-
const OmpAssistantContentPartSchema = OmpContentPartSchema.safeExtend({
|
|
682
|
-
text: RAW_DISPLAY_TEXT.optional(),
|
|
683
|
-
thinking: RAW_DISPLAY_TEXT.optional(),
|
|
684
|
-
});
|
|
685
|
-
const OmpAssistantDisplayContentSchema = z.preprocess(
|
|
686
|
-
boundRawDisplayContent,
|
|
687
|
-
z.union([RAW_DISPLAY_TEXT, z.array(OmpAssistantContentPartSchema).max(OMP_MAX_CONTENT_PARTS)]),
|
|
688
|
-
);
|
|
689
|
-
const OmpDisplayContentSchema = z.union([
|
|
690
|
-
TEXT,
|
|
691
|
-
z.array(OmpContentPartSchema).max(OMP_MAX_CONTENT_PARTS),
|
|
692
|
-
]);
|
|
693
|
-
const OmpImageArraySchema = z
|
|
694
|
-
.array(OmpContentPartSchema)
|
|
695
|
-
.max(OMP_MAX_CONTENT_PARTS)
|
|
696
|
-
.superRefine((parts, context) => {
|
|
697
|
-
if (parts.some((part) => part.type !== "image")) {
|
|
698
|
-
context.addIssue({ code: "custom", message: "invalid image collection" });
|
|
699
|
-
}
|
|
700
|
-
});
|
|
701
|
-
const OmpMessageIdentityShape = {
|
|
702
|
-
id: IDENTIFIER.optional(),
|
|
703
|
-
entryId: IDENTIFIER.optional(),
|
|
704
|
-
responseId: IDENTIFIER.optional(),
|
|
705
|
-
images: OmpImageArraySchema.optional(),
|
|
706
|
-
timestamp: z.number().finite().optional(),
|
|
707
|
-
details: OmpOptionalMetadataSchema,
|
|
708
|
-
};
|
|
709
|
-
type OmpContentPart = z.infer<typeof OmpContentPartSchema>;
|
|
710
|
-
type OmpMessageIdentity = {
|
|
711
|
-
id?: string;
|
|
712
|
-
entryId?: string;
|
|
713
|
-
responseId?: string;
|
|
714
|
-
images?: OmpContentPart[];
|
|
715
|
-
timestamp?: number;
|
|
716
|
-
details?: unknown;
|
|
717
|
-
display?: boolean;
|
|
718
|
-
customType?: string;
|
|
719
|
-
content?: unknown;
|
|
720
|
-
command?: string;
|
|
721
|
-
output?: string;
|
|
722
|
-
exitCode?: number | null;
|
|
723
|
-
cancelled?: boolean;
|
|
724
|
-
truncated?: boolean;
|
|
725
|
-
};
|
|
726
|
-
export type OmpMessage = OmpMessageIdentity &
|
|
727
|
-
(
|
|
728
|
-
| {
|
|
729
|
-
role: "assistant";
|
|
730
|
-
content?: string | OmpContentPart[];
|
|
731
|
-
errorMessage?: string | null;
|
|
732
|
-
stopReason?: string;
|
|
733
|
-
}
|
|
734
|
-
| { role: "user"; content: string | OmpContentPart[] }
|
|
735
|
-
| {
|
|
736
|
-
role: "toolResult";
|
|
737
|
-
toolCallId: string;
|
|
738
|
-
toolName: string;
|
|
739
|
-
content: unknown;
|
|
740
|
-
details?: unknown;
|
|
741
|
-
isError?: boolean;
|
|
742
|
-
}
|
|
743
|
-
| {
|
|
744
|
-
role: "bashExecution";
|
|
745
|
-
command: string;
|
|
746
|
-
output?: string;
|
|
747
|
-
exitCode?: number | null;
|
|
748
|
-
cancelled?: boolean;
|
|
749
|
-
truncated?: boolean;
|
|
750
|
-
}
|
|
751
|
-
| { role: "custom"; customType?: string; content?: unknown; display?: boolean }
|
|
752
|
-
);
|
|
753
|
-
|
|
754
|
-
const OmpMessageSchema: z.ZodType<OmpMessage> = z.union([
|
|
755
|
-
z.object({
|
|
756
|
-
role: z.literal("assistant"),
|
|
757
|
-
content: OmpAssistantDisplayContentSchema.optional(),
|
|
758
|
-
...OmpMessageIdentityShape,
|
|
759
|
-
errorMessage: boundedString(4_096).nullable().optional(),
|
|
760
|
-
stopReason: boundedString(64).optional(),
|
|
761
|
-
}),
|
|
762
|
-
z.object({
|
|
763
|
-
role: z.literal("user"),
|
|
764
|
-
content: OmpDisplayContentSchema,
|
|
765
|
-
...OmpMessageIdentityShape,
|
|
766
|
-
}),
|
|
767
|
-
z.object({
|
|
768
|
-
role: z.literal("toolResult"),
|
|
769
|
-
toolCallId: IDENTIFIER,
|
|
770
|
-
toolName: NAME,
|
|
771
|
-
content: z
|
|
772
|
-
.unknown()
|
|
773
|
-
.refine((value) => isBoundedJson(value, MAX_SEMANTIC_FRAME_BYTES, 1_024, 8_192)),
|
|
774
|
-
isError: z.boolean().optional(),
|
|
775
|
-
...OmpMessageIdentityShape,
|
|
776
|
-
}),
|
|
777
|
-
z.object({
|
|
778
|
-
role: z.literal("bashExecution"),
|
|
779
|
-
command: TEXT,
|
|
780
|
-
output: z.preprocess(
|
|
781
|
-
(value) =>
|
|
782
|
-
typeof value === "string" && utf8Bytes(value) > MAX_IMAGE_DATA_LENGTH
|
|
783
|
-
? DISPLAY_TRUNCATION_MARKER
|
|
784
|
-
: value,
|
|
785
|
-
RAW_DISPLAY_TEXT.optional(),
|
|
786
|
-
),
|
|
787
|
-
exitCode: z.number().int().nullable().optional(),
|
|
788
|
-
cancelled: z.boolean().optional(),
|
|
789
|
-
truncated: z.boolean().optional(),
|
|
790
|
-
...OmpMessageIdentityShape,
|
|
791
|
-
}),
|
|
792
|
-
z.object({
|
|
793
|
-
role: z.literal("custom"),
|
|
794
|
-
customType: NAME.optional(),
|
|
795
|
-
content: z
|
|
796
|
-
.unknown()
|
|
797
|
-
.refine((value) => isBoundedJson(value, MAX_SEMANTIC_FRAME_BYTES, 1_024, 8_192))
|
|
798
|
-
.optional(),
|
|
799
|
-
display: z.boolean().optional(),
|
|
800
|
-
...OmpMessageIdentityShape,
|
|
801
|
-
}),
|
|
802
|
-
]);
|
|
803
|
-
|
|
804
|
-
const OmpAssistantMessageEventSchema = z
|
|
805
|
-
.object({
|
|
806
|
-
type: NAME,
|
|
807
|
-
contentIndex: z
|
|
808
|
-
.number()
|
|
809
|
-
.int()
|
|
810
|
-
.nonnegative()
|
|
811
|
-
.max(OMP_MAX_CONTENT_PARTS - 1)
|
|
812
|
-
.optional(),
|
|
813
|
-
delta: TEXT.optional(),
|
|
814
|
-
content: z
|
|
815
|
-
.unknown()
|
|
816
|
-
.refine((value) => isBoundedJson(value, MAX_IMAGE_DATA_LENGTH + 1_024))
|
|
817
|
-
.optional(),
|
|
818
|
-
})
|
|
819
|
-
.superRefine((event, context) => {
|
|
820
|
-
const content = event.content;
|
|
821
|
-
const imageLike =
|
|
822
|
-
content !== null &&
|
|
823
|
-
typeof content === "object" &&
|
|
824
|
-
!Array.isArray(content) &&
|
|
825
|
-
"type" in content &&
|
|
826
|
-
content.type === "image";
|
|
827
|
-
if (!event.type.startsWith("image_") && !imageLike) return;
|
|
828
|
-
const image = OmpContentPartSchema.safeParse(content);
|
|
829
|
-
if (!image.success || image.data.type !== "image") {
|
|
830
|
-
context.addIssue({ code: "custom", message: "invalid image event" });
|
|
831
|
-
}
|
|
832
|
-
});
|
|
833
|
-
|
|
834
|
-
const OmpAvailableCommandSchema = z.object({
|
|
835
|
-
name: NAME,
|
|
836
|
-
aliases: z.array(NAME).max(32).optional(),
|
|
837
|
-
description: boundedString(4_096).optional(),
|
|
838
|
-
input: z
|
|
839
|
-
.object({ hint: boundedString(1_024).optional() })
|
|
840
|
-
.nullable()
|
|
841
|
-
.optional(),
|
|
842
|
-
subcommands: z
|
|
843
|
-
.array(
|
|
844
|
-
z.object({
|
|
845
|
-
name: NAME,
|
|
846
|
-
description: boundedString(4_096).optional(),
|
|
847
|
-
usage: boundedString(1_024).optional(),
|
|
848
|
-
}),
|
|
849
|
-
)
|
|
850
|
-
.max(128)
|
|
851
|
-
.optional(),
|
|
852
|
-
source: boundedString(64).optional(),
|
|
853
|
-
});
|
|
854
|
-
const OmpThinkingMetadataSchema = z
|
|
855
|
-
.unknown()
|
|
856
|
-
.transform((value): { efforts?: string[]; defaultLevel?: string } | undefined => {
|
|
857
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return undefined;
|
|
858
|
-
const record = value as Record<string, unknown>;
|
|
859
|
-
const efforts = boundedOptionalStringArray(16, 32).parse(record.efforts);
|
|
860
|
-
const defaultLevel = boundedString(32).optional().catch(undefined).parse(record.defaultLevel);
|
|
861
|
-
return {
|
|
862
|
-
...(efforts !== undefined ? { efforts } : {}),
|
|
863
|
-
...(defaultLevel !== undefined ? { defaultLevel } : {}),
|
|
864
|
-
};
|
|
865
|
-
});
|
|
866
|
-
const OmpModelSchema = z.object({
|
|
867
|
-
provider: OMP_PROVIDER_NAME,
|
|
868
|
-
id: NAME,
|
|
869
|
-
name: boundedString(MAX_NAME_LENGTH).optional().catch(undefined),
|
|
870
|
-
reasoning: z.boolean().optional().catch(undefined),
|
|
871
|
-
thinking: OmpThinkingMetadataSchema.optional(),
|
|
872
|
-
input: boundedOptionalStringArray(16, MAX_NAME_LENGTH).optional(),
|
|
873
|
-
contextWindow: z
|
|
874
|
-
.number()
|
|
875
|
-
.int()
|
|
876
|
-
.nonnegative()
|
|
877
|
-
.max(100_000_000)
|
|
878
|
-
.nullable()
|
|
879
|
-
.optional()
|
|
880
|
-
.catch(undefined),
|
|
881
|
-
});
|
|
882
|
-
const TokenCountSchema = z.number().int().nonnegative().max(MAX_TOKEN_COUNT);
|
|
883
|
-
const OptionalTokenCountSchema = TokenCountSchema.nullable().optional();
|
|
884
|
-
const OptionalCostSchema = z
|
|
885
|
-
.number()
|
|
886
|
-
.finite()
|
|
887
|
-
.nonnegative()
|
|
888
|
-
.max(MAX_COST_USD)
|
|
889
|
-
.nullable()
|
|
890
|
-
.optional();
|
|
891
|
-
const OmpContextUsageSchema = z.object({
|
|
892
|
-
tokens: OptionalTokenCountSchema,
|
|
893
|
-
contextWindow: TokenCountSchema.max(100_000_000).nullable().optional(),
|
|
894
|
-
percent: z.number().finite().nonnegative().max(MAX_CONTEXT_PERCENT).nullable().optional(),
|
|
895
|
-
});
|
|
896
|
-
const OmpSessionStatsSchema = z.object({
|
|
897
|
-
userMessages: OptionalTokenCountSchema,
|
|
898
|
-
assistantMessages: OptionalTokenCountSchema,
|
|
899
|
-
toolCalls: OptionalTokenCountSchema,
|
|
900
|
-
toolResults: OptionalTokenCountSchema,
|
|
901
|
-
totalMessages: OptionalTokenCountSchema,
|
|
902
|
-
tokens: z
|
|
903
|
-
.object({
|
|
904
|
-
input: OptionalTokenCountSchema,
|
|
905
|
-
output: OptionalTokenCountSchema,
|
|
906
|
-
reasoning: OptionalTokenCountSchema,
|
|
907
|
-
cacheRead: OptionalTokenCountSchema,
|
|
908
|
-
cacheWrite: OptionalTokenCountSchema,
|
|
909
|
-
total: OptionalTokenCountSchema,
|
|
910
|
-
})
|
|
911
|
-
.nullable()
|
|
912
|
-
.optional(),
|
|
913
|
-
cost: OptionalCostSchema,
|
|
914
|
-
premiumRequests: OptionalTokenCountSchema,
|
|
915
|
-
credits: z
|
|
916
|
-
.object({
|
|
917
|
-
cost: OptionalCostSchema,
|
|
918
|
-
committedCost: OptionalCostSchema,
|
|
919
|
-
acuCost: OptionalCostSchema,
|
|
920
|
-
})
|
|
921
|
-
.nullable()
|
|
922
|
-
.optional(),
|
|
923
|
-
routedModels: z.record(NAME, OptionalTokenCountSchema).nullable().optional(),
|
|
924
|
-
contextUsage: OmpContextUsageSchema.nullable().optional(),
|
|
925
|
-
});
|
|
926
|
-
const OmpCompactionResultSchema = z.object({
|
|
927
|
-
tokensBefore: OptionalTokenCountSchema,
|
|
928
|
-
preTokens: OptionalTokenCountSchema,
|
|
929
|
-
});
|
|
930
|
-
const OmpSessionStateSchema = z.object({
|
|
931
|
-
model: OmpModelSchema.nullable().optional(),
|
|
932
|
-
thinkingLevel: OmpCurrentThinkingLevelSchema,
|
|
933
|
-
isStreaming: z.boolean(),
|
|
934
|
-
isCompacting: z.boolean(),
|
|
935
|
-
sessionId: IDENTIFIER,
|
|
936
|
-
autoCompactionEnabled: z.boolean().optional(),
|
|
937
|
-
contextUsage: OmpContextUsageSchema.nullable().optional().catch(undefined),
|
|
938
|
-
sessionFile: boundedString(MAX_PATH_LENGTH).optional().catch(undefined),
|
|
939
|
-
});
|
|
940
|
-
const OmpReadyFrameSchema = z.object({
|
|
941
|
-
type: z.literal("ready"),
|
|
942
|
-
protocolVersion: z.number().int().positive().max(16).optional(),
|
|
943
|
-
supportedProtocolVersions: z.array(z.number().int().positive().max(16)).max(8).optional(),
|
|
944
|
-
maxFrameBytes: z.number().int().positive().optional(),
|
|
945
|
-
maxReassembledFrameBytes: z.number().int().positive().optional(),
|
|
946
|
-
features: z.record(z.string(), z.unknown()).optional(),
|
|
947
|
-
});
|
|
948
|
-
const OmpResponseFrameSchema = z.object({
|
|
949
|
-
type: z.literal("response"),
|
|
950
|
-
id: IDENTIFIER,
|
|
951
|
-
success: z.boolean(),
|
|
952
|
-
data: z.unknown().optional(),
|
|
953
|
-
error: boundedString(MAX_RPC_ERROR_BYTES).optional(),
|
|
954
|
-
code: boundedString(MAX_RPC_ERROR_CODE_BYTES, 1).optional(),
|
|
955
|
-
});
|
|
956
|
-
const OmpChunkFrameSchema = z.object({
|
|
957
|
-
type: z.literal("rpc_chunk"),
|
|
958
|
-
chunkId: IDENTIFIER,
|
|
959
|
-
index: z.number().int().nonnegative(),
|
|
960
|
-
count: z.number().int().positive().max(MAX_CHUNK_COUNT),
|
|
961
|
-
byteLength: z.number().int().nonnegative().max(MAX_REASSEMBLED_FRAME_BYTES),
|
|
962
|
-
data: boundedString(MAX_ENCODED_CHUNK_BYTES),
|
|
963
|
-
});
|
|
964
|
-
const JsonObjectSchema = z.record(z.string(), z.unknown());
|
|
965
|
-
const BoundedToolPayloadSchema = z
|
|
966
|
-
.unknown()
|
|
967
|
-
.refine((value) => isBoundedJson(value, MAX_SEMANTIC_FRAME_BYTES, 1_024, 4_096));
|
|
968
|
-
const OmpToolResultPayloadSchema = z.preprocess(
|
|
969
|
-
omitUnsafeOptionalDetails,
|
|
970
|
-
z
|
|
971
|
-
.unknown()
|
|
972
|
-
.refine(
|
|
973
|
-
(value) =>
|
|
974
|
-
isBoundedJson(value, MAX_SEMANTIC_FRAME_BYTES, MAX_OPTIONAL_METADATA_ITEMS, 8_192) &&
|
|
975
|
-
isBoundedJson(omitOptionalDetails(value), MAX_SEMANTIC_FRAME_BYTES, 1_024, 4_096),
|
|
976
|
-
),
|
|
977
|
-
);
|
|
978
|
-
const OmpHostToolDefinitionSchema = z.object({
|
|
979
|
-
name: NAME,
|
|
980
|
-
label: NAME.optional(),
|
|
981
|
-
description: boundedString(MAX_TEXT_LENGTH),
|
|
982
|
-
loadMode: z.enum(["essential", "discoverable"]).optional(),
|
|
983
|
-
parameters: JsonObjectSchema,
|
|
984
|
-
});
|
|
985
|
-
const OmpHostToolCallSchema = z.object({
|
|
986
|
-
type: z.literal("host_tool_call"),
|
|
987
|
-
id: IDENTIFIER,
|
|
988
|
-
toolCallId: IDENTIFIER,
|
|
989
|
-
toolName: NAME,
|
|
990
|
-
arguments: JsonObjectSchema,
|
|
991
|
-
});
|
|
992
|
-
const OmpHostToolCancelSchema = z.object({
|
|
993
|
-
type: z.literal("host_tool_cancel"),
|
|
994
|
-
id: IDENTIFIER,
|
|
995
|
-
targetId: IDENTIFIER,
|
|
996
|
-
});
|
|
997
|
-
const OmpHostToolContentSchema = z.object({ type: NAME, text: TEXT.optional() }).passthrough();
|
|
998
|
-
const OmpHostToolAgentResultSchema = z.object({
|
|
999
|
-
content: z.array(OmpHostToolContentSchema).max(MAX_ARRAY_ITEMS),
|
|
1000
|
-
details: BoundedToolPayloadSchema.optional(),
|
|
1001
|
-
isError: z.boolean().optional(),
|
|
1002
|
-
});
|
|
1003
|
-
const OmpHostToolResultSchema = z.object({
|
|
1004
|
-
type: z.literal("host_tool_result"),
|
|
1005
|
-
id: IDENTIFIER,
|
|
1006
|
-
result: OmpHostToolAgentResultSchema,
|
|
1007
|
-
isError: z.boolean().optional(),
|
|
1008
|
-
});
|
|
1009
|
-
const OmpHostToolUpdateSchema = z.object({
|
|
1010
|
-
type: z.literal("host_tool_update"),
|
|
1011
|
-
id: IDENTIFIER,
|
|
1012
|
-
partialResult: OmpHostToolAgentResultSchema,
|
|
1013
|
-
});
|
|
1014
|
-
const OmpToolApprovalIdentitySchema = z.discriminatedUnion("kind", [
|
|
1015
|
-
z.object({ kind: z.literal("shell"), command: boundedJsonString(24 * 1024, 1) }).strict(),
|
|
1016
|
-
z
|
|
1017
|
-
.object({
|
|
1018
|
-
kind: z.literal("edit"),
|
|
1019
|
-
paths: z.array(boundedJsonString(MAX_TOOL_APPROVAL_PATH_BYTES, 1)).min(1).max(16),
|
|
1020
|
-
content: boundedJsonString(MAX_TOOL_APPROVAL_CONTENT_BYTES),
|
|
1021
|
-
})
|
|
1022
|
-
.strict(),
|
|
1023
|
-
z
|
|
1024
|
-
.object({
|
|
1025
|
-
kind: z.literal("write"),
|
|
1026
|
-
path: boundedJsonString(MAX_TOOL_APPROVAL_PATH_BYTES, 1),
|
|
1027
|
-
content: boundedJsonString(MAX_TOOL_APPROVAL_CONTENT_BYTES),
|
|
1028
|
-
})
|
|
1029
|
-
.strict(),
|
|
1030
|
-
z.object({ kind: z.literal("other") }).strict(),
|
|
1031
|
-
]);
|
|
1032
|
-
type OmpToolApprovalValue =
|
|
1033
|
-
| string
|
|
1034
|
-
| number
|
|
1035
|
-
| boolean
|
|
1036
|
-
| null
|
|
1037
|
-
| OmpToolApprovalValue[]
|
|
1038
|
-
| { [key: string]: OmpToolApprovalValue };
|
|
1039
|
-
const OmpToolApprovalValueSchema: z.ZodType<OmpToolApprovalValue> = z.lazy(() =>
|
|
1040
|
-
z.union([
|
|
1041
|
-
boundedString(MAX_TOOL_APPROVAL_STRING_BYTES),
|
|
1042
|
-
z.number().finite(),
|
|
1043
|
-
z.boolean(),
|
|
1044
|
-
z.null(),
|
|
1045
|
-
z.array(OmpToolApprovalValueSchema).max(MAX_TOOL_APPROVAL_COLLECTION_ITEMS),
|
|
1046
|
-
z.record(boundedString(128, 1), OmpToolApprovalValueSchema),
|
|
1047
|
-
]),
|
|
1048
|
-
);
|
|
1049
|
-
function approvalInputWithinBounds(value: unknown): boolean {
|
|
1050
|
-
const pending: Array<{ value: unknown; depth: number }> = [{ value, depth: 0 }];
|
|
1051
|
-
let nodes = 0;
|
|
1052
|
-
while (pending.length > 0) {
|
|
1053
|
-
const current = pending.pop();
|
|
1054
|
-
if (!current) break;
|
|
1055
|
-
nodes += 1;
|
|
1056
|
-
if (nodes > MAX_TOOL_APPROVAL_INPUT_NODES) return false;
|
|
1057
|
-
if (current.value === null || typeof current.value !== "object") continue;
|
|
1058
|
-
if (current.depth >= MAX_TOOL_APPROVAL_DEPTH) return false;
|
|
1059
|
-
for (const child of Array.isArray(current.value)
|
|
1060
|
-
? current.value
|
|
1061
|
-
: Object.values(current.value as Record<string, unknown>)) {
|
|
1062
|
-
pending.push({ value: child, depth: current.depth + 1 });
|
|
1063
|
-
}
|
|
1064
|
-
}
|
|
1065
|
-
return true;
|
|
1066
|
-
}
|
|
1067
|
-
const OmpToolApprovalRequestSchema = z
|
|
1068
|
-
.object({
|
|
1069
|
-
type: z.literal("tool_approval_request"),
|
|
1070
|
-
id: boundedString(MAX_TOOL_APPROVAL_ID_BYTES, 1),
|
|
1071
|
-
toolCallId: boundedString(MAX_TOOL_APPROVAL_ID_BYTES, 1),
|
|
1072
|
-
toolKind: z.enum(["shell", "edit", "write", "other"]),
|
|
1073
|
-
toolName: boundedString(MAX_TOOL_APPROVAL_NAME_BYTES, 1),
|
|
1074
|
-
tier: z.enum(["read", "write", "exec"]),
|
|
1075
|
-
identity: OmpToolApprovalIdentitySchema,
|
|
1076
|
-
input: z
|
|
1077
|
-
.record(boundedString(128, 1), OmpToolApprovalValueSchema)
|
|
1078
|
-
.refine(approvalInputWithinBounds),
|
|
1079
|
-
detail: z
|
|
1080
|
-
.object({
|
|
1081
|
-
lines: z
|
|
1082
|
-
.array(boundedString(MAX_TOOL_APPROVAL_DETAIL_BYTES))
|
|
1083
|
-
.max(MAX_TOOL_APPROVAL_DETAIL_LINES),
|
|
1084
|
-
truncated: z.boolean(),
|
|
1085
|
-
truncatedFields: z
|
|
1086
|
-
.array(boundedString(MAX_TOOL_APPROVAL_METADATA_FIELD_BYTES))
|
|
1087
|
-
.max(MAX_TOOL_APPROVAL_METADATA_FIELDS),
|
|
1088
|
-
redacted: z.boolean(),
|
|
1089
|
-
redactedFields: z
|
|
1090
|
-
.array(boundedString(MAX_TOOL_APPROVAL_METADATA_FIELD_BYTES))
|
|
1091
|
-
.max(MAX_TOOL_APPROVAL_METADATA_FIELDS),
|
|
1092
|
-
reason: boundedString(MAX_TOOL_APPROVAL_DETAIL_BYTES).optional(),
|
|
1093
|
-
providerSafetyChecks: z
|
|
1094
|
-
.array(boundedString(MAX_TOOL_APPROVAL_DETAIL_BYTES))
|
|
1095
|
-
.max(MAX_TOOL_APPROVAL_DETAIL_LINES)
|
|
1096
|
-
.optional(),
|
|
1097
|
-
})
|
|
1098
|
-
.strict(),
|
|
1099
|
-
timeout: z.number().finite().nonnegative().max(MAX_TOOL_APPROVAL_TIMEOUT_MS).optional(),
|
|
1100
|
-
})
|
|
1101
|
-
.strict()
|
|
1102
|
-
.superRefine((request, context) => {
|
|
1103
|
-
if (request.toolKind !== request.identity.kind) {
|
|
1104
|
-
context.addIssue({ code: "custom", message: "tool approval identity kind mismatch" });
|
|
1105
|
-
}
|
|
1106
|
-
if (Buffer.byteLength(JSON.stringify(request), "utf8") + 1 > MAX_TOOL_APPROVAL_FRAME_BYTES) {
|
|
1107
|
-
context.addIssue({ code: "custom", message: "tool approval request exceeds bounds" });
|
|
1108
|
-
}
|
|
1109
|
-
});
|
|
1110
|
-
const OmpToolApprovalCancelSchema = z
|
|
1111
|
-
.object({
|
|
1112
|
-
type: z.literal("tool_approval_cancel"),
|
|
1113
|
-
id: boundedString(MAX_TOOL_APPROVAL_ID_BYTES, 1),
|
|
1114
|
-
targetId: boundedString(MAX_TOOL_APPROVAL_ID_BYTES, 1),
|
|
1115
|
-
toolCallId: boundedString(MAX_TOOL_APPROVAL_ID_BYTES, 1),
|
|
1116
|
-
})
|
|
1117
|
-
.strict();
|
|
1118
|
-
const OmpToolApprovalResponseSchema = z.union([
|
|
1119
|
-
z
|
|
1120
|
-
.object({
|
|
1121
|
-
type: z.literal("tool_approval_response"),
|
|
1122
|
-
id: boundedString(MAX_TOOL_APPROVAL_ID_BYTES, 1),
|
|
1123
|
-
toolCallId: boundedString(MAX_TOOL_APPROVAL_ID_BYTES, 1),
|
|
1124
|
-
approved: z.boolean(),
|
|
1125
|
-
})
|
|
1126
|
-
.strict(),
|
|
1127
|
-
z
|
|
1128
|
-
.object({
|
|
1129
|
-
type: z.literal("tool_approval_response"),
|
|
1130
|
-
id: boundedString(MAX_TOOL_APPROVAL_ID_BYTES, 1),
|
|
1131
|
-
toolCallId: boundedString(MAX_TOOL_APPROVAL_ID_BYTES, 1),
|
|
1132
|
-
cancelled: z.literal(true),
|
|
1133
|
-
timedOut: z.boolean().optional(),
|
|
1134
|
-
})
|
|
1135
|
-
.strict(),
|
|
1136
|
-
]);
|
|
1137
|
-
const OmpAgentEndEnvelopeSchema = z.object({
|
|
1138
|
-
type: z.literal("agent_end"),
|
|
1139
|
-
requestId: IDENTIFIER.optional(),
|
|
1140
|
-
messageCount: z.number().int().nonnegative().optional(),
|
|
1141
|
-
isTerminal: z.boolean().optional(),
|
|
1142
|
-
});
|
|
1143
|
-
const OmpCompactionStartSchema = z.object({
|
|
1144
|
-
type: z.literal("compaction_start"),
|
|
1145
|
-
reason: boundedString(4_096).optional(),
|
|
1146
|
-
});
|
|
1147
|
-
const OmpCompactionEndSchema = z.object({
|
|
1148
|
-
type: z.literal("compaction_end"),
|
|
1149
|
-
reason: boundedString(4_096).optional(),
|
|
1150
|
-
result: BoundedToolPayloadSchema.optional(),
|
|
1151
|
-
aborted: z.boolean().optional(),
|
|
1152
|
-
willRetry: z.boolean().optional(),
|
|
1153
|
-
errorMessage: boundedString(4_096).optional(),
|
|
1154
|
-
skipped: z.boolean().optional(),
|
|
1155
|
-
});
|
|
1156
|
-
const OmpAgentSessionEventSchema = z.discriminatedUnion("type", [
|
|
1157
|
-
z.object({ type: z.literal("agent_start") }),
|
|
1158
|
-
z.object({
|
|
1159
|
-
type: z.literal("agent_end"),
|
|
1160
|
-
requestId: IDENTIFIER.optional(),
|
|
1161
|
-
messages: z.array(OmpMessageSchema).max(MAX_ARRAY_ITEMS).optional(),
|
|
1162
|
-
messageCount: z.number().int().nonnegative().optional(),
|
|
1163
|
-
isTerminal: z.boolean().optional(),
|
|
1164
|
-
}),
|
|
1165
|
-
z.object({ type: z.literal("turn_start") }),
|
|
1166
|
-
z.object({ type: z.literal("turn_end") }),
|
|
1167
|
-
z.object({ type: z.literal("message_start"), message: OmpMessageSchema }),
|
|
1168
|
-
z.object({
|
|
1169
|
-
type: z.literal("message_update"),
|
|
1170
|
-
message: OmpMessageSchema,
|
|
1171
|
-
assistantMessageEvent: OmpAssistantMessageEventSchema.optional(),
|
|
1172
|
-
}),
|
|
1173
|
-
z.object({ type: z.literal("message_end"), message: OmpMessageSchema }),
|
|
1174
|
-
z.object({
|
|
1175
|
-
type: z.literal("tool_execution_start"),
|
|
1176
|
-
toolCallId: IDENTIFIER,
|
|
1177
|
-
toolName: NAME,
|
|
1178
|
-
args: BoundedToolPayloadSchema,
|
|
1179
|
-
}),
|
|
1180
|
-
z.object({
|
|
1181
|
-
type: z.literal("tool_execution_update"),
|
|
1182
|
-
toolCallId: IDENTIFIER,
|
|
1183
|
-
toolName: NAME,
|
|
1184
|
-
args: BoundedToolPayloadSchema.optional(),
|
|
1185
|
-
partialResult: OmpToolResultPayloadSchema,
|
|
1186
|
-
}),
|
|
1187
|
-
z.object({
|
|
1188
|
-
type: z.literal("tool_execution_end"),
|
|
1189
|
-
toolCallId: IDENTIFIER,
|
|
1190
|
-
toolName: NAME,
|
|
1191
|
-
result: OmpToolResultPayloadSchema,
|
|
1192
|
-
isError: z.boolean().optional(),
|
|
1193
|
-
}),
|
|
1194
|
-
OmpCompactionStartSchema,
|
|
1195
|
-
OmpCompactionEndSchema,
|
|
1196
|
-
]);
|
|
1197
|
-
const OmpGoalSchema = z.object({
|
|
1198
|
-
id: IDENTIFIER.optional(),
|
|
1199
|
-
objective: TEXT.optional(),
|
|
1200
|
-
status: boundedString(256).optional(),
|
|
1201
|
-
tokenBudget: z.number().finite().nonnegative().optional(),
|
|
1202
|
-
tokensUsed: z.number().finite().nonnegative().optional(),
|
|
1203
|
-
timeUsedSeconds: z.number().finite().nonnegative().optional(),
|
|
1204
|
-
createdAt: boundedString(128).optional(),
|
|
1205
|
-
updatedAt: boundedString(128).optional(),
|
|
1206
|
-
});
|
|
1207
|
-
const OmpGoalModeStateSchema = z.object({
|
|
1208
|
-
enabled: z.boolean().optional(),
|
|
1209
|
-
mode: boundedString(256).optional(),
|
|
1210
|
-
reason: boundedString(4_096).optional(),
|
|
1211
|
-
goal: OmpGoalSchema.optional(),
|
|
1212
|
-
});
|
|
1213
|
-
const OmpSubagentStatusSchema = z.enum([
|
|
1214
|
-
"pending",
|
|
1215
|
-
"running",
|
|
1216
|
-
"started",
|
|
1217
|
-
"completed",
|
|
1218
|
-
"failed",
|
|
1219
|
-
"aborted",
|
|
1220
|
-
]);
|
|
1221
|
-
const OmpSubagentLifecyclePayloadSchema = z.object({
|
|
1222
|
-
id: IDENTIFIER,
|
|
1223
|
-
agent: NAME,
|
|
1224
|
-
agentSource: NAME.optional(),
|
|
1225
|
-
description: boundedString(64 * 1024).optional(),
|
|
1226
|
-
status: OmpSubagentStatusSchema,
|
|
1227
|
-
sessionFile: boundedString(MAX_PATH_LENGTH).optional(),
|
|
1228
|
-
parentToolCallId: IDENTIFIER.optional(),
|
|
1229
|
-
index: z.number().int().nonnegative().max(10_000),
|
|
1230
|
-
detached: z.boolean().optional(),
|
|
1231
|
-
});
|
|
1232
|
-
const OmpSubagentProgressSchema = z.object({
|
|
1233
|
-
id: IDENTIFIER,
|
|
1234
|
-
status: OmpSubagentStatusSchema,
|
|
1235
|
-
description: boundedString(64 * 1024).optional(),
|
|
1236
|
-
currentTool: BoundedToolPayloadSchema.optional(),
|
|
1237
|
-
recentTools: z.array(BoundedToolPayloadSchema).max(64).optional(),
|
|
1238
|
-
recentOutput: z.array(BoundedToolPayloadSchema).max(128).optional(),
|
|
1239
|
-
resolvedModel: NAME.optional(),
|
|
1240
|
-
});
|
|
1241
|
-
const OmpSubagentProgressPayloadSchema = z.object({
|
|
1242
|
-
index: z.number().int().nonnegative().max(10_000),
|
|
1243
|
-
agent: NAME,
|
|
1244
|
-
agentSource: NAME.optional(),
|
|
1245
|
-
task: TEXT,
|
|
1246
|
-
parentToolCallId: IDENTIFIER.optional(),
|
|
1247
|
-
assignment: TEXT.optional(),
|
|
1248
|
-
progress: OmpSubagentProgressSchema,
|
|
1249
|
-
sessionFile: boundedString(MAX_PATH_LENGTH).optional(),
|
|
1250
|
-
detached: z.boolean().optional(),
|
|
1251
|
-
});
|
|
1252
|
-
const ExtensionUiBase = { type: z.literal("extension_ui_request"), id: IDENTIFIER };
|
|
1253
|
-
const OmpExtensionUiRequestSchema = z.discriminatedUnion("method", [
|
|
1254
|
-
z
|
|
1255
|
-
.object({
|
|
1256
|
-
...ExtensionUiBase,
|
|
1257
|
-
method: z.literal("select"),
|
|
1258
|
-
title: boundedString(4_096),
|
|
1259
|
-
options: z.array(boundedString(4_096)).min(1).max(128),
|
|
1260
|
-
optionDetails: z
|
|
1261
|
-
.array(z.object({ description: boundedString(16_384).optional() }).strict())
|
|
1262
|
-
.max(128)
|
|
1263
|
-
.optional(),
|
|
1264
|
-
timeout: z.number().nonnegative().finite().optional(),
|
|
1265
|
-
})
|
|
1266
|
-
.strict()
|
|
1267
|
-
.superRefine((request, context) => {
|
|
1268
|
-
if (request.optionDetails && request.optionDetails.length !== request.options.length) {
|
|
1269
|
-
context.addIssue({ code: "custom", message: "invalid extension UI request" });
|
|
1270
|
-
}
|
|
1271
|
-
}),
|
|
1272
|
-
z
|
|
1273
|
-
.object({
|
|
1274
|
-
...ExtensionUiBase,
|
|
1275
|
-
method: z.literal("confirm"),
|
|
1276
|
-
title: boundedString(4_096),
|
|
1277
|
-
message: boundedString(64 * 1024),
|
|
1278
|
-
timeout: z.number().nonnegative().finite().optional(),
|
|
1279
|
-
})
|
|
1280
|
-
.strict(),
|
|
1281
|
-
z
|
|
1282
|
-
.object({
|
|
1283
|
-
...ExtensionUiBase,
|
|
1284
|
-
method: z.literal("input"),
|
|
1285
|
-
title: boundedString(4_096),
|
|
1286
|
-
placeholder: boundedString(4_096).optional(),
|
|
1287
|
-
prefill: TEXT.optional(),
|
|
1288
|
-
timeout: z.number().nonnegative().finite().optional(),
|
|
1289
|
-
})
|
|
1290
|
-
.strict(),
|
|
1291
|
-
z
|
|
1292
|
-
.object({
|
|
1293
|
-
...ExtensionUiBase,
|
|
1294
|
-
method: z.literal("editor"),
|
|
1295
|
-
title: boundedString(4_096),
|
|
1296
|
-
prefill: TEXT.optional(),
|
|
1297
|
-
promptStyle: z.boolean().optional(),
|
|
1298
|
-
timeout: z.number().nonnegative().finite().optional(),
|
|
1299
|
-
})
|
|
1300
|
-
.strict(),
|
|
1301
|
-
z.object({ ...ExtensionUiBase, method: z.literal("cancel"), targetId: IDENTIFIER }).strict(),
|
|
1302
|
-
z
|
|
1303
|
-
.object({
|
|
1304
|
-
...ExtensionUiBase,
|
|
1305
|
-
method: z.literal("notify"),
|
|
1306
|
-
message: boundedString(64 * 1024),
|
|
1307
|
-
notifyType: z.enum(["info", "warning", "error"]).optional(),
|
|
1308
|
-
})
|
|
1309
|
-
.strict(),
|
|
1310
|
-
z
|
|
1311
|
-
.object({
|
|
1312
|
-
...ExtensionUiBase,
|
|
1313
|
-
method: z.literal("setStatus"),
|
|
1314
|
-
statusKey: NAME,
|
|
1315
|
-
statusText: boundedString(16_384).optional(),
|
|
1316
|
-
})
|
|
1317
|
-
.strict(),
|
|
1318
|
-
z
|
|
1319
|
-
.object({
|
|
1320
|
-
...ExtensionUiBase,
|
|
1321
|
-
method: z.literal("setWidget"),
|
|
1322
|
-
widgetKey: NAME,
|
|
1323
|
-
widgetLines: z.array(boundedString(16_384)).max(128).optional(),
|
|
1324
|
-
widgetPlacement: z.enum(["aboveEditor", "belowEditor"]).optional(),
|
|
1325
|
-
})
|
|
1326
|
-
.strict(),
|
|
1327
|
-
z
|
|
1328
|
-
.object({ ...ExtensionUiBase, method: z.literal("setTitle"), title: boundedString(4_096) })
|
|
1329
|
-
.strict(),
|
|
1330
|
-
z.object({ ...ExtensionUiBase, method: z.literal("set_editor_text"), text: TEXT }).strict(),
|
|
1331
|
-
z
|
|
1332
|
-
.object({
|
|
1333
|
-
...ExtensionUiBase,
|
|
1334
|
-
method: z.literal("open_url"),
|
|
1335
|
-
url: boundedString(16_384),
|
|
1336
|
-
launchUrl: boundedString(16_384).optional(),
|
|
1337
|
-
instructions: boundedString(64 * 1024).optional(),
|
|
1338
|
-
})
|
|
1339
|
-
.strict(),
|
|
1340
|
-
]);
|
|
1341
|
-
const OmpRuntimeEventSchema = z.discriminatedUnion("type", [
|
|
1342
|
-
...OmpAgentSessionEventSchema.options,
|
|
1343
|
-
z.object({
|
|
1344
|
-
type: z.literal("subagent_lifecycle"),
|
|
1345
|
-
payload: OmpSubagentLifecyclePayloadSchema,
|
|
1346
|
-
}),
|
|
1347
|
-
z.object({
|
|
1348
|
-
type: z.literal("subagent_progress"),
|
|
1349
|
-
payload: OmpSubagentProgressPayloadSchema,
|
|
1350
|
-
}),
|
|
1351
|
-
z.object({
|
|
1352
|
-
type: z.literal("subagent_event"),
|
|
1353
|
-
payload: z.object({ id: IDENTIFIER, event: OmpAgentSessionEventSchema }),
|
|
1354
|
-
}),
|
|
1355
|
-
z.object({
|
|
1356
|
-
type: z.literal("todo_reminder"),
|
|
1357
|
-
todos: z
|
|
1358
|
-
.array(
|
|
1359
|
-
z.object({
|
|
1360
|
-
id: IDENTIFIER.optional(),
|
|
1361
|
-
content: boundedString(16_384),
|
|
1362
|
-
status: z.enum(["pending", "in_progress", "blocked", "completed", "abandoned"]),
|
|
1363
|
-
}),
|
|
1364
|
-
)
|
|
1365
|
-
.max(MAX_TODOS),
|
|
1366
|
-
}),
|
|
1367
|
-
z.object({ type: z.literal("model_changed") }),
|
|
1368
|
-
z.object({
|
|
1369
|
-
type: z.literal("thinking_level_changed"),
|
|
1370
|
-
thinkingLevel: boundedString(MAX_CONFIG_EVENT_TEXT_BYTES).optional(),
|
|
1371
|
-
}),
|
|
1372
|
-
z.object({
|
|
1373
|
-
type: z.literal("goal_updated"),
|
|
1374
|
-
goal: OmpGoalSchema.nullable().optional(),
|
|
1375
|
-
state: OmpGoalModeStateSchema.optional(),
|
|
1376
|
-
}),
|
|
1377
|
-
z.object({
|
|
1378
|
-
type: z.literal("auto_retry_start"),
|
|
1379
|
-
attempt: z.number().int().nonnegative().safe(),
|
|
1380
|
-
maxAttempts: z.number().int().positive().safe(),
|
|
1381
|
-
delayMs: z.number().int().nonnegative().safe(),
|
|
1382
|
-
errorMessage: boundedString(64 * 1024),
|
|
1383
|
-
errorId: z.number().int().safe().optional(),
|
|
1384
|
-
}),
|
|
1385
|
-
z.object({
|
|
1386
|
-
type: z.literal("auto_retry_end"),
|
|
1387
|
-
success: z.boolean(),
|
|
1388
|
-
attempt: z.number().int().nonnegative().safe(),
|
|
1389
|
-
finalError: boundedString(64 * 1024).optional(),
|
|
1390
|
-
recoveredErrors: BoundedToolPayloadSchema.optional(),
|
|
1391
|
-
}),
|
|
1392
|
-
z.object({
|
|
1393
|
-
type: z.literal("retry_fallback_applied"),
|
|
1394
|
-
from: boundedString(MAX_CONFIG_EVENT_TEXT_BYTES),
|
|
1395
|
-
to: boundedString(MAX_CONFIG_EVENT_TEXT_BYTES),
|
|
1396
|
-
role: boundedString(MAX_CONFIG_EVENT_TEXT_BYTES),
|
|
1397
|
-
}),
|
|
1398
|
-
z.object({
|
|
1399
|
-
type: z.literal("retry_fallback_succeeded"),
|
|
1400
|
-
model: boundedString(MAX_CONFIG_EVENT_TEXT_BYTES),
|
|
1401
|
-
role: boundedString(MAX_CONFIG_EVENT_TEXT_BYTES),
|
|
1402
|
-
}),
|
|
1403
|
-
z.object({ type: z.literal("todo_auto_clear") }),
|
|
1404
|
-
z.object({
|
|
1405
|
-
type: z.literal("auto_compaction_start"),
|
|
1406
|
-
reason: boundedString(4_096),
|
|
1407
|
-
action: boundedString(MAX_CONFIG_EVENT_TEXT_BYTES),
|
|
1408
|
-
}),
|
|
1409
|
-
z.object({
|
|
1410
|
-
type: z.literal("auto_compaction_end"),
|
|
1411
|
-
action: NAME.optional(),
|
|
1412
|
-
result: OmpCompactionResultSchema.nullable().optional(),
|
|
1413
|
-
aborted: z.boolean().optional(),
|
|
1414
|
-
willRetry: z.boolean().optional(),
|
|
1415
|
-
errorMessage: boundedString(64 * 1024).optional(),
|
|
1416
|
-
skipped: z.boolean().optional(),
|
|
1417
|
-
}),
|
|
1418
|
-
z.object({
|
|
1419
|
-
type: z.literal("available_commands_update"),
|
|
1420
|
-
commands: z.array(OmpAvailableCommandSchema).max(MAX_ARRAY_ITEMS),
|
|
1421
|
-
}),
|
|
1422
|
-
z.object({
|
|
1423
|
-
type: z.literal("notice"),
|
|
1424
|
-
id: IDENTIFIER.optional(),
|
|
1425
|
-
level: z.enum(["info", "warning", "error"]),
|
|
1426
|
-
message: boundedString(64 * 1024),
|
|
1427
|
-
source: boundedString(MAX_NAME_LENGTH).optional(),
|
|
1428
|
-
}),
|
|
1429
|
-
z.object({ type: z.literal("command_output"), text: TEXT.optional() }),
|
|
1430
|
-
OmpExtensionUiRequestSchema,
|
|
1431
|
-
z.object({
|
|
1432
|
-
type: z.literal("prompt_result"),
|
|
1433
|
-
id: IDENTIFIER.optional(),
|
|
1434
|
-
agentInvoked: z.boolean(),
|
|
1435
|
-
}),
|
|
1436
|
-
OmpHostToolCallSchema,
|
|
1437
|
-
OmpHostToolCancelSchema,
|
|
1438
|
-
OmpToolApprovalRequestSchema,
|
|
1439
|
-
OmpToolApprovalCancelSchema,
|
|
1440
|
-
z.object({ type: z.literal("advisor_yielded") }),
|
|
1441
|
-
]);
|
|
1442
|
-
type OptionalDetailsMapper = (value: unknown, taskResult?: boolean) => unknown;
|
|
1443
|
-
|
|
1444
|
-
function mapRecordField(
|
|
1445
|
-
record: Record<string, unknown>,
|
|
1446
|
-
key: string,
|
|
1447
|
-
map: OptionalDetailsMapper,
|
|
1448
|
-
taskResult = false,
|
|
1449
|
-
): Record<string, unknown> {
|
|
1450
|
-
if (!Object.hasOwn(record, key)) return record;
|
|
1451
|
-
const next = map(record[key], taskResult);
|
|
1452
|
-
return next === record[key] ? record : { ...record, [key]: next };
|
|
1453
|
-
}
|
|
1454
|
-
|
|
1455
|
-
function mapMessageList(value: unknown, map: OptionalDetailsMapper): unknown {
|
|
1456
|
-
if (!Array.isArray(value)) return value;
|
|
1457
|
-
let changed = false;
|
|
1458
|
-
const messages = value.map((message) => {
|
|
1459
|
-
const taskResult =
|
|
1460
|
-
message !== null &&
|
|
1461
|
-
typeof message === "object" &&
|
|
1462
|
-
!Array.isArray(message) &&
|
|
1463
|
-
(message as Record<string, unknown>).role === "toolResult" &&
|
|
1464
|
-
(message as Record<string, unknown>).toolName === "task";
|
|
1465
|
-
const next = map(message, taskResult);
|
|
1466
|
-
changed ||= next !== message;
|
|
1467
|
-
return next;
|
|
1468
|
-
});
|
|
1469
|
-
return changed ? messages : value;
|
|
1470
|
-
}
|
|
1471
|
-
|
|
1472
|
-
function mapAgentEventDetails(
|
|
1473
|
-
frame: Record<string, unknown>,
|
|
1474
|
-
map: OptionalDetailsMapper,
|
|
1475
|
-
): Record<string, unknown> {
|
|
1476
|
-
switch (frame.type) {
|
|
1477
|
-
case "message_start":
|
|
1478
|
-
case "message_update":
|
|
1479
|
-
case "message_end": {
|
|
1480
|
-
const message = frame.message;
|
|
1481
|
-
const taskResult =
|
|
1482
|
-
message !== null &&
|
|
1483
|
-
typeof message === "object" &&
|
|
1484
|
-
!Array.isArray(message) &&
|
|
1485
|
-
(message as Record<string, unknown>).role === "toolResult" &&
|
|
1486
|
-
(message as Record<string, unknown>).toolName === "task";
|
|
1487
|
-
return mapRecordField(frame, "message", map, taskResult);
|
|
1488
|
-
}
|
|
1489
|
-
case "tool_execution_update":
|
|
1490
|
-
return mapRecordField(frame, "partialResult", map, frame.toolName === "task");
|
|
1491
|
-
case "tool_execution_end":
|
|
1492
|
-
return mapRecordField(frame, "result", map, frame.toolName === "task");
|
|
1493
|
-
case "agent_end":
|
|
1494
|
-
return mapRecordField(frame, "messages", (messages) => mapMessageList(messages, map));
|
|
1495
|
-
default:
|
|
1496
|
-
return frame;
|
|
1497
|
-
}
|
|
1498
|
-
}
|
|
1499
|
-
|
|
1500
|
-
function mapRuntimeFrameDetails(
|
|
1501
|
-
frame: Record<string, unknown>,
|
|
1502
|
-
map: OptionalDetailsMapper,
|
|
1503
|
-
): Record<string, unknown> {
|
|
1504
|
-
if (frame.type !== "subagent_event") return mapAgentEventDetails(frame, map);
|
|
1505
|
-
if (frame.payload === null || typeof frame.payload !== "object" || Array.isArray(frame.payload)) {
|
|
1506
|
-
return frame;
|
|
1507
|
-
}
|
|
1508
|
-
const payload = frame.payload as Record<string, unknown>;
|
|
1509
|
-
if (payload.event === null || typeof payload.event !== "object" || Array.isArray(payload.event)) {
|
|
1510
|
-
return frame;
|
|
1511
|
-
}
|
|
1512
|
-
const event = mapAgentEventDetails(payload.event as Record<string, unknown>, map);
|
|
1513
|
-
return event === payload.event ? frame : { ...frame, payload: { ...payload, event } };
|
|
1514
|
-
}
|
|
1515
|
-
|
|
1516
|
-
function sanitizeMessageListMetadata(value: unknown): unknown {
|
|
1517
|
-
return mapMessageList(value, createOptionalMetadataSanitizer());
|
|
1518
|
-
}
|
|
1519
|
-
|
|
1520
|
-
function sanitizeHistoryResponseData(value: unknown): unknown {
|
|
1521
|
-
if (value === null || typeof value !== "object" || Array.isArray(value)) return value;
|
|
1522
|
-
return mapRecordField(value as Record<string, unknown>, "messages", sanitizeMessageListMetadata);
|
|
1523
|
-
}
|
|
1524
|
-
|
|
1525
|
-
function runtimeFrameCollectionLimit(frame: Record<string, unknown>): number {
|
|
1526
|
-
const type = frame.type;
|
|
1527
|
-
if (
|
|
1528
|
-
type === "message_start" ||
|
|
1529
|
-
type === "message_update" ||
|
|
1530
|
-
type === "message_end" ||
|
|
1531
|
-
type === "agent_end" ||
|
|
1532
|
-
type === "subagent_event"
|
|
1533
|
-
) {
|
|
1534
|
-
return OMP_MAX_CONTENT_PARTS;
|
|
1535
|
-
}
|
|
1536
|
-
return 1_024;
|
|
1537
|
-
}
|
|
1538
|
-
const OmpModelsResultSchema = z.object({
|
|
1539
|
-
models: z.array(OmpModelSchema).min(1).max(MAX_MODEL_CATALOG_ITEMS),
|
|
1540
|
-
});
|
|
1541
|
-
const OmpPromptAckSchema = z.object({ agentInvoked: z.boolean().optional() }).optional();
|
|
1542
|
-
const OmpAvailableCommandsResultSchema = z.object({
|
|
1543
|
-
commands: z.array(OmpAvailableCommandSchema).max(MAX_ARRAY_ITEMS),
|
|
1544
|
-
});
|
|
1545
|
-
const OmpBranchMessagesResultSchema = z.object({
|
|
1546
|
-
messages: z.array(z.object({ entryId: IDENTIFIER, text: TEXT })).max(1_024),
|
|
1547
|
-
});
|
|
1548
|
-
const OmpBranchResultSchema = z.object({ text: TEXT, cancelled: z.boolean() });
|
|
1549
|
-
const OmpMessagesResultSchema = z.object({
|
|
1550
|
-
messages: z.array(OmpMessageSchema).max(100_000),
|
|
1551
|
-
});
|
|
1552
|
-
const OmpSubagentsResultSchema = z.object({
|
|
1553
|
-
subagents: z
|
|
1554
|
-
.array(
|
|
1555
|
-
z.object({
|
|
1556
|
-
id: IDENTIFIER,
|
|
1557
|
-
index: z.number().int().nonnegative().max(10_000),
|
|
1558
|
-
agent: NAME,
|
|
1559
|
-
agentSource: NAME.optional(),
|
|
1560
|
-
description: TEXT.optional(),
|
|
1561
|
-
status: OmpSubagentStatusSchema,
|
|
1562
|
-
task: TEXT.optional(),
|
|
1563
|
-
assignment: TEXT.optional(),
|
|
1564
|
-
sessionFile: boundedString(MAX_PATH_LENGTH).optional(),
|
|
1565
|
-
lastUpdate: z.number().finite().nonnegative(),
|
|
1566
|
-
parentToolCallId: IDENTIFIER.optional(),
|
|
1567
|
-
}),
|
|
1568
|
-
)
|
|
1569
|
-
.max(1_024),
|
|
1570
|
-
});
|
|
1571
|
-
const OmpSubagentMessagesResultSchema = z.object({
|
|
1572
|
-
sessionFile: boundedString(MAX_PATH_LENGTH),
|
|
1573
|
-
fromByte: z.number().int().nonnegative(),
|
|
1574
|
-
nextByte: z.number().int().nonnegative(),
|
|
1575
|
-
reset: z.boolean(),
|
|
1576
|
-
messages: z.array(OmpMessageSchema).max(100_000),
|
|
1577
|
-
});
|
|
1578
|
-
const ProtocolNegotiationResultSchema = z.object({
|
|
1579
|
-
protocolVersion: z.literal(2),
|
|
1580
|
-
clientCapabilities: z.object({ typedToolApprovals: z.literal(1).optional() }).optional(),
|
|
1581
|
-
});
|
|
1582
|
-
|
|
1583
|
-
export type OmpModel = z.infer<typeof OmpModelSchema>;
|
|
1584
|
-
export type OmpBranchResult = z.infer<typeof OmpBranchResultSchema>;
|
|
1585
|
-
export type OmpSessionState = z.infer<typeof OmpSessionStateSchema>;
|
|
1586
|
-
export type OmpSessionStats = z.infer<typeof OmpSessionStatsSchema>;
|
|
1587
|
-
export type OmpCompactionResult = z.infer<typeof OmpCompactionResultSchema>;
|
|
1588
|
-
export type OmpHostToolDefinition = z.infer<typeof OmpHostToolDefinitionSchema>;
|
|
1589
|
-
export type OmpHostToolCall = z.infer<typeof OmpHostToolCallSchema>;
|
|
1590
|
-
export type OmpHostToolResult = z.infer<typeof OmpHostToolResultSchema>;
|
|
1591
|
-
export type OmpHostToolUpdate = z.infer<typeof OmpHostToolUpdateSchema>;
|
|
1592
|
-
export type OmpToolApprovalRequest = z.infer<typeof OmpToolApprovalRequestSchema>;
|
|
1593
|
-
export type OmpToolApprovalCancel = z.infer<typeof OmpToolApprovalCancelSchema>;
|
|
1594
|
-
export type OmpToolApprovalResponse = z.infer<typeof OmpToolApprovalResponseSchema>;
|
|
1595
|
-
export function parseOmpHostToolAgentResult(value: unknown): OmpHostToolResult["result"] {
|
|
1596
|
-
return OmpHostToolAgentResultSchema.parse(value);
|
|
1597
|
-
}
|
|
1598
|
-
export type OmpRpcEvent =
|
|
1599
|
-
| z.infer<typeof OmpRuntimeEventSchema>
|
|
1600
|
-
| { type: "prompt_error"; id: string; error: string; code?: string }
|
|
1601
|
-
| { type: "process_exit"; error: string };
|
|
1602
|
-
export type OmpAgentSessionEvent = z.infer<typeof OmpAgentSessionEventSchema>;
|
|
1603
|
-
export type OmpSubagentSnapshot = z.infer<typeof OmpSubagentsResultSchema>["subagents"][number];
|
|
1604
|
-
export type OmpSubagentEvent = Extract<
|
|
1605
|
-
z.infer<typeof OmpRuntimeEventSchema>,
|
|
1606
|
-
{ type: "subagent_lifecycle" | "subagent_progress" | "subagent_event" }
|
|
1607
|
-
>;
|
|
1608
|
-
export interface OmpSubagentMessagesResult {
|
|
1609
|
-
sessionFile: string;
|
|
1610
|
-
fromByte: number;
|
|
1611
|
-
nextByte: number;
|
|
1612
|
-
reset: boolean;
|
|
1613
|
-
messages: OmpMessage[];
|
|
1614
|
-
}
|
|
1615
|
-
export interface OmpPersistedSubagentMessages {
|
|
1616
|
-
sessionFile: string;
|
|
1617
|
-
nativeSessionId: string;
|
|
1618
|
-
byteLength: number;
|
|
1619
|
-
messages: OmpMessage[];
|
|
1620
|
-
}
|
|
1621
|
-
export interface OmpPersistedSessionMessages {
|
|
1622
|
-
sessionFile: string;
|
|
1623
|
-
nativeSessionId: string;
|
|
1624
|
-
byteLength: number;
|
|
1625
|
-
messages: OmpMessage[];
|
|
1626
|
-
imageReplayWarning?: true;
|
|
1627
|
-
}
|
|
1628
|
-
|
|
1629
|
-
export interface OmpStartOptions {
|
|
1630
|
-
cwd: string;
|
|
1631
|
-
env?: Readonly<Record<string, string>>;
|
|
1632
|
-
outputRedaction?: OmpOutputRedaction;
|
|
1633
|
-
inheritEnv?: readonly string[];
|
|
1634
|
-
/** Server-owned environment source; tests provide isolated roots instead of ambient process.env. */
|
|
1635
|
-
environment?: NodeJS.ProcessEnv;
|
|
1636
|
-
command?: readonly string[];
|
|
1637
|
-
model?: string;
|
|
1638
|
-
mode?: "full" | "write" | "ask";
|
|
1639
|
-
thinkingOption?: string;
|
|
1640
|
-
systemPrompt?: string;
|
|
1641
|
-
roleModels?: Readonly<{ smol?: string; slow?: string; plan?: string }>;
|
|
1642
|
-
tools?: readonly string[];
|
|
1643
|
-
sessionDir?: string;
|
|
1644
|
-
readyTimeoutMs?: number;
|
|
1645
|
-
requestTimeoutMs?: number;
|
|
1646
|
-
/** Resume this exact native OMP session; never use this to start a new conversation. */
|
|
1647
|
-
resumeSessionId?: string;
|
|
1648
|
-
noSession?: boolean;
|
|
1649
|
-
signal?: AbortSignal;
|
|
1650
|
-
}
|
|
1651
|
-
|
|
1652
|
-
export type OmpAvailableCommand = z.infer<typeof OmpAvailableCommandSchema>;
|
|
1653
|
-
export type OmpImage = { type: "image"; data: string; mimeType: string };
|
|
1654
|
-
export type OmpExtensionUiResponse =
|
|
1655
|
-
| { type: "extension_ui_response"; id: string; value: string }
|
|
1656
|
-
| { type: "extension_ui_response"; id: string; confirmed: boolean }
|
|
1657
|
-
| { type: "extension_ui_response"; id: string; cancelled: true; timedOut?: boolean };
|
|
82
|
+
const MAX_REPLAY_BYTES = 64 * 1024 * 1024;
|
|
83
|
+
const MAX_REPLAY_NODES = 400_000;
|
|
84
|
+
const MAX_MESSAGE_PAGES = 512;
|
|
85
|
+
const MAX_MESSAGE_PAGE_BUSY_RETRIES = 4;
|
|
86
|
+
const MAX_MESSAGE_PAGE_STALE_RESTARTS = 2;
|
|
87
|
+
const MESSAGE_PAGE_RETRY_BASE_MS = 50;
|
|
1658
88
|
|
|
1659
89
|
export interface OmpRuntimeSession {
|
|
1660
90
|
readonly maxHostToolFrameBytes?: number;
|
|
@@ -1691,7 +121,7 @@ export interface OmpRuntimeSession {
|
|
|
1691
121
|
getBranchMessages(): Promise<Array<{ entryId: string; text: string }>>;
|
|
1692
122
|
branch(entryId: string): Promise<OmpBranchResult>;
|
|
1693
123
|
readonly canReplayHistory: boolean;
|
|
1694
|
-
getMessages(): Promise<OmpMessage[]>;
|
|
124
|
+
getMessages(signal?: AbortSignal): Promise<OmpMessage[]>;
|
|
1695
125
|
abort(): Promise<void>;
|
|
1696
126
|
setHostTools(tools: readonly OmpHostToolDefinition[]): Promise<string[]>;
|
|
1697
127
|
sendHostToolResult(result: OmpHostToolResult): void;
|
|
@@ -1712,20 +142,12 @@ export interface OmpRuntime {
|
|
|
1712
142
|
readPersistedSubagentTranscript(options: {
|
|
1713
143
|
parentSessionFile: string;
|
|
1714
144
|
childTranscriptId: string;
|
|
145
|
+
sessionFile?: string;
|
|
1715
146
|
cwd: string;
|
|
1716
147
|
signal?: AbortSignal;
|
|
1717
148
|
}): Promise<OmpPersistedSubagentMessages>;
|
|
1718
149
|
}
|
|
1719
150
|
|
|
1720
|
-
export interface OmpSpawnRequest {
|
|
1721
|
-
command: string;
|
|
1722
|
-
args: string[];
|
|
1723
|
-
cwd: string;
|
|
1724
|
-
env: NodeJS.ProcessEnv;
|
|
1725
|
-
detached: boolean;
|
|
1726
|
-
inheritedRedactionValues: string[];
|
|
1727
|
-
}
|
|
1728
|
-
|
|
1729
151
|
export interface OmpRpcRuntimeOptions {
|
|
1730
152
|
spawnProcess?: (request: OmpSpawnRequest) => ChildProcessWithoutNullStreams;
|
|
1731
153
|
terminateProcessTree?: (pid: number) => Promise<boolean | "uncertain">;
|
|
@@ -1737,1680 +159,222 @@ export interface OmpRpcRuntimeOptions {
|
|
|
1737
159
|
) => OmpSessionDescriptor[] | Promise<OmpSessionDescriptor[]>;
|
|
1738
160
|
}
|
|
1739
161
|
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
this.name = "OmpRpcRequestRejectedError";
|
|
1744
|
-
}
|
|
1745
|
-
}
|
|
1746
|
-
|
|
1747
|
-
type PendingRequest = {
|
|
1748
|
-
resolve(value: unknown): void;
|
|
1749
|
-
reject(error: Error): void;
|
|
1750
|
-
timer?: TimerHandle;
|
|
1751
|
-
command: string;
|
|
1752
|
-
beforeResolve?: (value: unknown) => void;
|
|
1753
|
-
};
|
|
1754
|
-
type StartedRequest = { id: string; promise: Promise<unknown> };
|
|
1755
|
-
|
|
1756
|
-
type ReadyFrame = z.infer<typeof OmpReadyFrameSchema>;
|
|
1757
|
-
type ChunkFrame = z.infer<typeof OmpChunkFrameSchema>;
|
|
1758
|
-
|
|
1759
|
-
type ChunkState = {
|
|
1760
|
-
id: string;
|
|
1761
|
-
count: number;
|
|
1762
|
-
byteLength: number;
|
|
1763
|
-
parts: Buffer[];
|
|
1764
|
-
receivedBytes: number;
|
|
1765
|
-
timer: TimerHandle;
|
|
1766
|
-
};
|
|
1767
|
-
|
|
1768
|
-
// The daemon contributes only process/runtime discovery variables plus provider authentication
|
|
1769
|
-
// families. Session-scoped values are explicit host input and are overlaid after rejecting loader
|
|
1770
|
-
// and executable-resolution controls; this keeps provider credentials available without copying
|
|
1771
|
-
const INHERITED_RUNTIME_ENV: Readonly<Record<string, true>> = {
|
|
1772
|
-
ALL_PROXY: true,
|
|
1773
|
-
APPDATA: true,
|
|
1774
|
-
COLORTERM: true,
|
|
1775
|
-
HOME: true,
|
|
1776
|
-
HTTPS_PROXY: true,
|
|
1777
|
-
HTTP_PROXY: true,
|
|
1778
|
-
LANG: true,
|
|
1779
|
-
LC_ALL: true,
|
|
1780
|
-
LC_CTYPE: true,
|
|
1781
|
-
LOCALAPPDATA: true,
|
|
1782
|
-
LOGNAME: true,
|
|
1783
|
-
NO_PROXY: true,
|
|
1784
|
-
OMP_PROFILE: true,
|
|
1785
|
-
PATH: true,
|
|
1786
|
-
PATHEXT: true,
|
|
1787
|
-
PI_CODING_AGENT_DIR: true,
|
|
1788
|
-
PI_CONFIG_DIR: true,
|
|
1789
|
-
PI_PROFILE: true,
|
|
1790
|
-
SHELL: true,
|
|
1791
|
-
SSH_AUTH_SOCK: true,
|
|
1792
|
-
SSL_CERT_DIR: true,
|
|
1793
|
-
SSL_CERT_FILE: true,
|
|
1794
|
-
SYSTEMROOT: true,
|
|
1795
|
-
TEMP: true,
|
|
1796
|
-
TMP: true,
|
|
1797
|
-
TMPDIR: true,
|
|
1798
|
-
TZ: true,
|
|
1799
|
-
USER: true,
|
|
1800
|
-
USERPROFILE: true,
|
|
1801
|
-
XDG_CACHE_HOME: true,
|
|
1802
|
-
XDG_CONFIG_HOME: true,
|
|
1803
|
-
XDG_DATA_HOME: true,
|
|
1804
|
-
XDG_STATE_HOME: true,
|
|
1805
|
-
XDG_RUNTIME_DIR: true,
|
|
1806
|
-
};
|
|
1807
|
-
const INHERITED_PROVIDER_AUTH_ENV: Readonly<Record<string, true>> = {
|
|
1808
|
-
AI_GATEWAY_API_KEY: true,
|
|
1809
|
-
AIAND_API_KEY: true,
|
|
1810
|
-
ALIBABA_CODING_PLAN_API_KEY: true,
|
|
1811
|
-
ALIBABA_TOKEN_PLAN_API_KEY: true,
|
|
1812
|
-
ANTHROPIC_API_KEY: true,
|
|
1813
|
-
ANTHROPIC_FOUNDRY_API_KEY: true,
|
|
1814
|
-
ANTHROPIC_OAUTH_TOKEN: true,
|
|
1815
|
-
AWS_ACCESS_KEY_ID: true,
|
|
1816
|
-
AWS_DEFAULT_REGION: true,
|
|
1817
|
-
AWS_PROFILE: true,
|
|
1818
|
-
AWS_REGION: true,
|
|
1819
|
-
AWS_SECRET_ACCESS_KEY: true,
|
|
1820
|
-
AWS_SESSION_TOKEN: true,
|
|
1821
|
-
AZURE_CLIENT_ID: true,
|
|
1822
|
-
AZURE_CLIENT_SECRET: true,
|
|
1823
|
-
AZURE_OPENAI_API_KEY: true,
|
|
1824
|
-
AZURE_OPENAI_ENDPOINT: true,
|
|
1825
|
-
AZURE_TENANT_ID: true,
|
|
1826
|
-
BAILIAN_TOKEN_PLAN_API_KEY: true,
|
|
1827
|
-
BASETEN_API_KEY: true,
|
|
1828
|
-
CEREBRAS_API_KEY: true,
|
|
1829
|
-
CHARM_HYPER_API_KEY: true,
|
|
1830
|
-
CLINE_API_KEY: true,
|
|
1831
|
-
CLOUDFLARE_AI_GATEWAY_API_KEY: true,
|
|
1832
|
-
COHERE_API_KEY: true,
|
|
1833
|
-
COMMAND_CODE_API_KEY: true,
|
|
1834
|
-
COREWEAVE_API_KEY: true,
|
|
1835
|
-
CURSOR_ACCESS_TOKEN: true,
|
|
1836
|
-
DEEPINFRA_API_KEY: true,
|
|
1837
|
-
DEEPSEEK_API_KEY: true,
|
|
1838
|
-
DEVIN_API_KEY: true,
|
|
1839
|
-
FIREPASS_API_KEY: true,
|
|
1840
|
-
FIREWORKS_API_KEY: true,
|
|
1841
|
-
FUGU_API_KEY: true,
|
|
1842
|
-
GEMINI_API_KEY: true,
|
|
1843
|
-
GMI_API_KEY: true,
|
|
1844
|
-
GOOGLE_API_KEY: true,
|
|
1845
|
-
GOOGLE_APPLICATION_CREDENTIALS: true,
|
|
1846
|
-
GROQ_API_KEY: true,
|
|
1847
|
-
HF_TOKEN: true,
|
|
1848
|
-
HUGGINGFACE_HUB_TOKEN: true,
|
|
1849
|
-
LLAMA_CPP_API_KEY: true,
|
|
1850
|
-
LM_STUDIO_API_KEY: true,
|
|
1851
|
-
META_API_KEY: true,
|
|
1852
|
-
MINIMAX_API_KEY: true,
|
|
1853
|
-
MINIMAX_CODE_API_KEY: true,
|
|
1854
|
-
MINIMAX_CODE_CN_API_KEY: true,
|
|
1855
|
-
MISTRAL_API_KEY: true,
|
|
1856
|
-
MODEL_API_KEY: true,
|
|
1857
|
-
MOONSHOT_API_KEY: true,
|
|
1858
|
-
NANO_GPT_API_KEY: true,
|
|
1859
|
-
NVIDIA_API_KEY: true,
|
|
1860
|
-
NOVITA_API_KEY: true,
|
|
1861
|
-
OLLAMA_API_KEY: true,
|
|
1862
|
-
OLLAMA_CLOUD_API_KEY: true,
|
|
1863
|
-
OLLAMA_HOST: true,
|
|
1864
|
-
OMP_AUTH_BROKER_TOKEN: true,
|
|
1865
|
-
OMP_AUTH_BROKER_URL: true,
|
|
1866
|
-
OPENCODE_API_KEY: true,
|
|
1867
|
-
OPENAI_API_KEY: true,
|
|
1868
|
-
OPENAI_CODEX_OAUTH_TOKEN: true,
|
|
1869
|
-
OPENROUTER_API_KEY: true,
|
|
1870
|
-
PLEXUS_API_KEY: true,
|
|
1871
|
-
QIANFAN_API_KEY: true,
|
|
1872
|
-
QWEN_OAUTH_TOKEN: true,
|
|
1873
|
-
QWEN_PORTAL_API_KEY: true,
|
|
1874
|
-
SAKANA_API_KEY: true,
|
|
1875
|
-
SILICONFLOW_API_KEY: true,
|
|
1876
|
-
SILICONFLOW_CN_API_KEY: true,
|
|
1877
|
-
SYNTHETIC_API_KEY: true,
|
|
1878
|
-
TOGETHER_API_KEY: true,
|
|
1879
|
-
UMANS_AI_CODING_PLAN_API_KEY: true,
|
|
1880
|
-
VENICE_API_KEY: true,
|
|
1881
|
-
VLLM_API_KEY: true,
|
|
1882
|
-
WAFER_SERVERLESS_API_KEY: true,
|
|
1883
|
-
WANDB_API_KEY: true,
|
|
1884
|
-
XAI_API_KEY: true,
|
|
1885
|
-
XAI_OAUTH_TOKEN: true,
|
|
1886
|
-
XIAOMI_API_KEY: true,
|
|
1887
|
-
XIAOMI_TOKEN_PLAN_AMS_API_KEY: true,
|
|
1888
|
-
XIAOMI_TOKEN_PLAN_CN_API_KEY: true,
|
|
1889
|
-
XIAOMI_TOKEN_PLAN_SGP_API_KEY: true,
|
|
1890
|
-
YOLO_AUTO_API_KEY: true,
|
|
1891
|
-
ZAI_API_KEY: true,
|
|
1892
|
-
ZENMUX_API_KEY: true,
|
|
1893
|
-
ZHIPU_API_KEY: true,
|
|
1894
|
-
};
|
|
1895
|
-
const BLOCKED_SESSION_ENV =
|
|
1896
|
-
/^(?:BASH_ENV|BUN_INSTALL.*|BUN_OPTIONS|CLASSPATH|CLAUDE_BASH_NO_CI|CLAUDE_BASH_NO_LOGIN|CLAUDE_CODE_SHELL_PREFIX|DYLD_.*|EDITOR|ELECTRON_RUN_AS_NODE|ENV|GEM_HOME|GEM_PATH|GIT_CONFIG.*|GIT_SSH_COMMAND|HOME|JAVA_TOOL_OPTIONS|LD_.*|NODE_OPTIONS|NODE_PATH|NPM_CONFIG_.*|OMP_AUTORESEARCH_DB_DIR|OMP_COMMAND|OMP_GITHUB_CACHE_DB|OMP_PROFILE|OMP_WORKTREE_DIR|PATH|PATHEXT|PERL5LIB|PERL5OPT|PI_BASH_NO_CI|PI_BASH_NO_LOGIN|PI_CODING_AGENT_DIR|PI_CODING_AGENT_SESSION_DIR|PI_CONFIG_DIR|PI_CONFIG_FILES|PI_GIT_COMMON_DIR|PI_PACKAGE_DIR|PI_PROFILE|PI_PROJECT_DIR|PI_SESSION_ID|PI_SHELL_PREFIX|PI_SUBPROCESS_CMD|PI_WORKTREE_DIR|PWD|PYTHONHOME|PYTHONINSPECT|PYTHONPATH|PYTHONSTARTUP|RUBYLIB|RUBYOPT|SHELL|SYSTEMROOT|USERPROFILE|VISUAL|XDG_CACHE_HOME|XDG_CONFIG_HOME|XDG_DATA_HOME|XDG_RUNTIME_DIR|XDG_STATE_HOME|_JAVA_OPTIONS)$/u;
|
|
1897
|
-
const ENV_NAME = /^[A-Za-z_][A-Za-z0-9_]{0,127}$/u;
|
|
1898
|
-
|
|
1899
|
-
function validateBoundedText(value: unknown, field: string, maxBytes: number): string {
|
|
1900
|
-
if (
|
|
1901
|
-
typeof value !== "string" ||
|
|
1902
|
-
utf8Bytes(value) === 0 ||
|
|
1903
|
-
utf8Bytes(value) > maxBytes ||
|
|
1904
|
-
value.includes("\0")
|
|
1905
|
-
) {
|
|
1906
|
-
throw new Error(`Invalid OMP ${field}`);
|
|
1907
|
-
}
|
|
1908
|
-
return value;
|
|
1909
|
-
}
|
|
1910
|
-
|
|
1911
|
-
function inheritedEnvironmentNames(inheritEnv: readonly string[] | undefined): ReadonlySet<string> {
|
|
1912
|
-
if (inheritEnv === undefined) return new Set();
|
|
1913
|
-
if (!Array.isArray(inheritEnv)) {
|
|
1914
|
-
throw new Error("OMP inherited environment names are invalid");
|
|
1915
|
-
}
|
|
1916
|
-
if (inheritEnv.length > MAX_ENV_ENTRIES) {
|
|
1917
|
-
throw new Error("OMP inherited environment has too many entries");
|
|
1918
|
-
}
|
|
1919
|
-
const names = new Set<string>();
|
|
1920
|
-
for (const name of inheritEnv) {
|
|
1921
|
-
if (typeof name !== "string" || !ENV_NAME.test(name)) {
|
|
1922
|
-
throw new Error("OMP inherited environment contains an invalid name");
|
|
1923
|
-
}
|
|
1924
|
-
if (BLOCKED_SESSION_ENV.test(name.toUpperCase())) {
|
|
1925
|
-
throw new Error("OMP inherited environment contains a forbidden variable");
|
|
1926
|
-
}
|
|
1927
|
-
names.add(name);
|
|
1928
|
-
}
|
|
1929
|
-
return names;
|
|
1930
|
-
}
|
|
1931
|
-
|
|
1932
|
-
function buildOmpEnvironment(
|
|
1933
|
-
sessionEnv: Readonly<Record<string, string>> | undefined,
|
|
1934
|
-
inheritEnv: readonly string[] | undefined,
|
|
1935
|
-
sourceEnv: NodeJS.ProcessEnv,
|
|
1936
|
-
): { env: NodeJS.ProcessEnv; inheritedRedactionValues: string[] } {
|
|
1937
|
-
if (
|
|
1938
|
-
sessionEnv !== undefined &&
|
|
1939
|
-
(sessionEnv === null || typeof sessionEnv !== "object" || Array.isArray(sessionEnv))
|
|
1940
|
-
) {
|
|
1941
|
-
throw new Error("OMP session environment is invalid");
|
|
1942
|
-
}
|
|
1943
|
-
const explicitlyInherited = inheritedEnvironmentNames(inheritEnv);
|
|
1944
|
-
const explicitlyInheritedNormalized = new Set(
|
|
1945
|
-
[...explicitlyInherited].map((name) => name.toUpperCase()),
|
|
1946
|
-
);
|
|
1947
|
-
const explicitNames = new Set(Object.keys(sessionEnv ?? {}).map((name) => name.toUpperCase()));
|
|
1948
|
-
const env: NodeJS.ProcessEnv = {};
|
|
1949
|
-
const inheritedRedactionValues: string[] = [];
|
|
1950
|
-
let totalBytes = 0;
|
|
1951
|
-
for (const [name, value] of Object.entries(sourceEnv)) {
|
|
1952
|
-
if (value === undefined || name.toUpperCase() === "OMP_COMMAND") continue;
|
|
1953
|
-
const normalizedName = name.toUpperCase();
|
|
1954
|
-
const isRuntime =
|
|
1955
|
-
process.platform === "win32"
|
|
1956
|
-
? normalizedName in INHERITED_RUNTIME_ENV
|
|
1957
|
-
: name in INHERITED_RUNTIME_ENV;
|
|
1958
|
-
const isProviderAuth =
|
|
1959
|
-
process.platform === "win32"
|
|
1960
|
-
? normalizedName in INHERITED_PROVIDER_AUTH_ENV
|
|
1961
|
-
: name in INHERITED_PROVIDER_AUTH_ENV;
|
|
1962
|
-
const isExplicitlyInherited =
|
|
1963
|
-
process.platform === "win32"
|
|
1964
|
-
? explicitlyInheritedNormalized.has(normalizedName)
|
|
1965
|
-
: explicitlyInherited.has(name);
|
|
1966
|
-
if (!isRuntime && !isProviderAuth && !isExplicitlyInherited) continue;
|
|
1967
|
-
if (explicitNames.has(normalizedName)) continue;
|
|
1968
|
-
const valueBytes = utf8Bytes(value);
|
|
1969
|
-
if (!ENV_NAME.test(name) || valueBytes > MAX_ENV_VALUE_LENGTH || value.includes("\0")) {
|
|
1970
|
-
if (isExplicitlyInherited) {
|
|
1971
|
-
throw new Error("OMP inherited environment contains an invalid value");
|
|
1972
|
-
}
|
|
1973
|
-
continue;
|
|
1974
|
-
}
|
|
1975
|
-
if (isExplicitlyInherited && valueBytes > 0 && valueBytes < 4) {
|
|
1976
|
-
throw new OmpPublicError("OMP inherited environment value is too short for safe redaction");
|
|
1977
|
-
}
|
|
1978
|
-
totalBytes += utf8Bytes(name) + valueBytes;
|
|
1979
|
-
if (totalBytes > MAX_ENV_TOTAL_LENGTH) {
|
|
1980
|
-
throw new Error("OMP inherited environment is too large");
|
|
1981
|
-
}
|
|
1982
|
-
env[name] = value;
|
|
1983
|
-
if (isExplicitlyInherited && valueBytes > 0) inheritedRedactionValues.push(value);
|
|
1984
|
-
}
|
|
1985
|
-
let entryCount = 0;
|
|
1986
|
-
for (const name in sessionEnv ?? {}) {
|
|
1987
|
-
if (!Object.hasOwn(sessionEnv ?? {}, name)) continue;
|
|
1988
|
-
entryCount += 1;
|
|
1989
|
-
if (entryCount > MAX_ENV_ENTRIES)
|
|
1990
|
-
throw new Error("OMP session environment has too many entries");
|
|
1991
|
-
const value = (sessionEnv as Readonly<Record<string, string>>)[name];
|
|
1992
|
-
const normalizedName = name.toUpperCase();
|
|
1993
|
-
if (!ENV_NAME.test(name) || BLOCKED_SESSION_ENV.test(normalizedName)) {
|
|
1994
|
-
throw new Error("OMP session environment contains a forbidden variable");
|
|
1995
|
-
}
|
|
1996
|
-
if (
|
|
1997
|
-
typeof value !== "string" ||
|
|
1998
|
-
utf8Bytes(value) > MAX_ENV_VALUE_LENGTH ||
|
|
1999
|
-
value.includes("\0")
|
|
2000
|
-
) {
|
|
2001
|
-
throw new Error("OMP session environment contains an invalid value");
|
|
2002
|
-
}
|
|
2003
|
-
const valueBytes = utf8Bytes(value);
|
|
2004
|
-
totalBytes += utf8Bytes(name) + valueBytes;
|
|
2005
|
-
if (totalBytes > MAX_ENV_TOTAL_LENGTH) throw new Error("OMP session environment is too large");
|
|
2006
|
-
for (const inheritedName of Object.keys(env)) {
|
|
2007
|
-
if (inheritedName !== name && inheritedName.toUpperCase() === normalizedName) {
|
|
2008
|
-
delete env[inheritedName];
|
|
2009
|
-
}
|
|
2010
|
-
}
|
|
2011
|
-
env[name] = value;
|
|
2012
|
-
}
|
|
2013
|
-
return { env, inheritedRedactionValues };
|
|
2014
|
-
}
|
|
2015
|
-
|
|
2016
|
-
export function buildOmpSpawnRequest(
|
|
2017
|
-
options: OmpStartOptions,
|
|
2018
|
-
sourceEnv: NodeJS.ProcessEnv = process.env,
|
|
2019
|
-
): OmpSpawnRequest {
|
|
2020
|
-
const environmentSource = options.environment ?? sourceEnv;
|
|
2021
|
-
const cwd = validateBoundedText(options.cwd, "working directory", MAX_PATH_LENGTH);
|
|
2022
|
-
if (!isAbsolute(cwd)) throw new Error("OMP working directory must be absolute");
|
|
2023
|
-
const commandPrefix = options.command ?? [environmentSource.OMP_COMMAND ?? "omp"];
|
|
2024
|
-
if (commandPrefix.length === 0) throw new Error("Invalid OMP command");
|
|
2025
|
-
const [rawCommand, ...rawPrefixArgs] = commandPrefix;
|
|
2026
|
-
const command = validateBoundedText(rawCommand, "command", MAX_PATH_LENGTH);
|
|
2027
|
-
const args = rawPrefixArgs.map((argument) =>
|
|
2028
|
-
validateBoundedText(argument, "command argument", MAX_PATH_LENGTH),
|
|
2029
|
-
);
|
|
2030
|
-
if (/[\r\n]/u.test(command)) throw new Error("Invalid OMP command");
|
|
2031
|
-
const mode = options.mode ?? "full";
|
|
2032
|
-
if (mode !== "full" && mode !== "write" && mode !== "ask") throw new Error("Invalid OMP mode");
|
|
2033
|
-
const approvalMode = mode === "full" ? "yolo" : mode === "write" ? "write" : "always-ask";
|
|
2034
|
-
if (!args.some((argument) => argument === "--mode" || argument.startsWith("--mode="))) {
|
|
2035
|
-
args.push("--mode", "rpc-ui");
|
|
2036
|
-
}
|
|
2037
|
-
args.push("--approval-mode", approvalMode);
|
|
2038
|
-
if (options.tools) {
|
|
2039
|
-
if (options.tools.length === 0) args.push("--no-tools");
|
|
2040
|
-
else args.push("--tools", options.tools.join(","));
|
|
2041
|
-
}
|
|
2042
|
-
if (options.model !== undefined) {
|
|
2043
|
-
args.push("--model", validateBoundedText(options.model, "model", MAX_MODEL_SELECTOR_BYTES));
|
|
2044
|
-
}
|
|
2045
|
-
if (options.thinkingOption !== undefined) {
|
|
2046
|
-
const thinking = OmpThinkingLevelSchema.safeParse(options.thinkingOption);
|
|
2047
|
-
if (!thinking.success) throw new Error("Invalid OMP thinking option");
|
|
2048
|
-
args.push("--thinking", thinking.data);
|
|
2049
|
-
}
|
|
2050
|
-
if (options.roleModels?.smol) {
|
|
2051
|
-
args.push(
|
|
2052
|
-
"--smol",
|
|
2053
|
-
validateBoundedText(options.roleModels.smol, "smol model", MAX_MODEL_SELECTOR_BYTES),
|
|
2054
|
-
);
|
|
2055
|
-
}
|
|
2056
|
-
if (options.roleModels?.slow) {
|
|
2057
|
-
args.push(
|
|
2058
|
-
"--slow",
|
|
2059
|
-
validateBoundedText(options.roleModels.slow, "slow model", MAX_MODEL_SELECTOR_BYTES),
|
|
2060
|
-
);
|
|
2061
|
-
}
|
|
2062
|
-
if (options.roleModels?.plan) {
|
|
2063
|
-
args.push(
|
|
2064
|
-
"--plan",
|
|
2065
|
-
validateBoundedText(options.roleModels.plan, "plan model", MAX_MODEL_SELECTOR_BYTES),
|
|
2066
|
-
);
|
|
2067
|
-
}
|
|
2068
|
-
if (options.sessionDir !== undefined) {
|
|
2069
|
-
args.push(
|
|
2070
|
-
"--session-dir",
|
|
2071
|
-
validateBoundedText(options.sessionDir, "session directory", MAX_PATH_LENGTH),
|
|
2072
|
-
);
|
|
2073
|
-
}
|
|
2074
|
-
if (options.resumeSessionId !== undefined) {
|
|
2075
|
-
args.push("--resume", validateNativeSessionId(options.resumeSessionId));
|
|
2076
|
-
}
|
|
2077
|
-
if (options.noSession) args.push("--no-session");
|
|
2078
|
-
const systemPrompt = options.systemPrompt?.trim();
|
|
2079
|
-
if (systemPrompt) {
|
|
2080
|
-
args.push(
|
|
2081
|
-
"--append-system-prompt",
|
|
2082
|
-
validateBoundedText(systemPrompt, "system prompt", MAX_SYSTEM_PROMPT_LENGTH),
|
|
2083
|
-
);
|
|
2084
|
-
}
|
|
2085
|
-
const { env, inheritedRedactionValues } = buildOmpEnvironment(
|
|
2086
|
-
options.env,
|
|
2087
|
-
options.inheritEnv,
|
|
2088
|
-
environmentSource,
|
|
2089
|
-
);
|
|
2090
|
-
env.OMP_NO_WEBP = "1";
|
|
2091
|
-
return {
|
|
2092
|
-
command,
|
|
2093
|
-
args,
|
|
2094
|
-
cwd,
|
|
2095
|
-
env,
|
|
2096
|
-
inheritedRedactionValues,
|
|
2097
|
-
detached: process.platform !== "win32",
|
|
2098
|
-
};
|
|
2099
|
-
}
|
|
2100
|
-
|
|
2101
|
-
function waitWithTimeout<T>(promise: Promise<T>, timeoutMs: number, message: string): Promise<T> {
|
|
2102
|
-
const result = Promise.withResolvers<T>();
|
|
2103
|
-
const timer = setTimeout(() => result.reject(new Error(message)), timeoutMs);
|
|
2104
|
-
promise.then(
|
|
2105
|
-
(value) => {
|
|
2106
|
-
clearTimeout(timer);
|
|
2107
|
-
result.resolve(value);
|
|
2108
|
-
},
|
|
2109
|
-
(error) => {
|
|
2110
|
-
clearTimeout(timer);
|
|
2111
|
-
result.reject(error);
|
|
2112
|
-
},
|
|
2113
|
-
);
|
|
2114
|
-
return result.promise;
|
|
2115
|
-
}
|
|
2116
|
-
|
|
2117
|
-
function waitMs(ms: number): Promise<void> {
|
|
2118
|
-
const result = Promise.withResolvers<void>();
|
|
2119
|
-
setTimeout(result.resolve, ms);
|
|
2120
|
-
return result.promise;
|
|
2121
|
-
}
|
|
2122
|
-
|
|
2123
|
-
function processIsGone(error: unknown): boolean {
|
|
2124
|
-
return (error as NodeJS.ErrnoException)?.code === "ESRCH";
|
|
2125
|
-
}
|
|
2126
|
-
|
|
2127
|
-
function isConfirmedNoProcessSpawnFailure(error: unknown): boolean {
|
|
2128
|
-
const code = (error as NodeJS.ErrnoException)?.code;
|
|
2129
|
-
return code === "ENOENT" || code === "EACCES" || code === "EPERM";
|
|
2130
|
-
}
|
|
2131
|
-
|
|
2132
|
-
/**
|
|
2133
|
-
* Terminates the detached process group created for OMP. This covers descendants that remain in
|
|
2134
|
-
* that group after the leader exits; descendants that deliberately re-parent into another process
|
|
2135
|
-
* group are outside this transport's containment boundary.
|
|
2136
|
-
*/
|
|
2137
|
-
export async function terminatePosixProcessTree(
|
|
2138
|
-
pid: number,
|
|
2139
|
-
graceMs: number,
|
|
2140
|
-
signalProcess: (pid: number, signal: NodeJS.Signals | 0) => void = process.kill,
|
|
2141
|
-
wait: (ms: number) => Promise<void> = waitMs,
|
|
2142
|
-
): Promise<boolean> {
|
|
2143
|
-
try {
|
|
2144
|
-
signalProcess(-pid, 0);
|
|
2145
|
-
} catch (error) {
|
|
2146
|
-
return processIsGone(error);
|
|
2147
|
-
}
|
|
2148
|
-
try {
|
|
2149
|
-
signalProcess(-pid, "SIGTERM");
|
|
2150
|
-
} catch (error) {
|
|
2151
|
-
if (!processIsGone(error)) return false;
|
|
2152
|
-
}
|
|
2153
|
-
await wait(graceMs);
|
|
2154
|
-
try {
|
|
2155
|
-
signalProcess(-pid, 0);
|
|
2156
|
-
} catch (error) {
|
|
2157
|
-
return processIsGone(error);
|
|
2158
|
-
}
|
|
2159
|
-
try {
|
|
2160
|
-
signalProcess(-pid, "SIGKILL");
|
|
2161
|
-
} catch (error) {
|
|
2162
|
-
if (!processIsGone(error)) return false;
|
|
2163
|
-
}
|
|
2164
|
-
await wait(graceMs);
|
|
2165
|
-
try {
|
|
2166
|
-
signalProcess(-pid, 0);
|
|
2167
|
-
return false;
|
|
2168
|
-
} catch (error) {
|
|
2169
|
-
return processIsGone(error);
|
|
162
|
+
class OmpRpcSession implements OmpRuntimeSession {
|
|
163
|
+
get maxHostToolFrameBytes(): number {
|
|
164
|
+
return this.process.outboundFrameLimit;
|
|
2170
165
|
}
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
type ProcessTreeCleanup = "verified" | "uncertain" | "failed";
|
|
2174
|
-
|
|
2175
|
-
async function stopWindowsTree(pid: number): Promise<ProcessTreeCleanup> {
|
|
2176
|
-
const result = Promise.withResolvers<ProcessTreeCleanup>();
|
|
2177
|
-
const systemRoot = process.env.SystemRoot ?? WINDOWS_DEFAULT_SYSTEM_ROOT;
|
|
2178
|
-
let taskkill: ChildProcessWithoutNullStreams;
|
|
2179
|
-
try {
|
|
2180
|
-
taskkill = spawn(
|
|
2181
|
-
join(systemRoot, "System32", "taskkill.exe"),
|
|
2182
|
-
["/PID", String(pid), "/T", "/F"],
|
|
2183
|
-
{
|
|
2184
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
2185
|
-
windowsHide: true,
|
|
2186
|
-
env: { SystemRoot: systemRoot },
|
|
2187
|
-
},
|
|
2188
|
-
);
|
|
2189
|
-
} catch {
|
|
2190
|
-
return "failed";
|
|
166
|
+
get maxInputFrameBytes(): number {
|
|
167
|
+
return this.process.outboundFrameLimit;
|
|
2191
168
|
}
|
|
2192
|
-
taskkill.stdout.resume();
|
|
2193
|
-
taskkill.stderr.resume();
|
|
2194
|
-
let settled = false;
|
|
2195
|
-
let deadline: TimerHandle | undefined;
|
|
2196
|
-
let finalDeadline: TimerHandle | undefined;
|
|
2197
|
-
const finish = (outcome: ProcessTreeCleanup) => {
|
|
2198
|
-
if (settled) return;
|
|
2199
|
-
settled = true;
|
|
2200
|
-
clearTimeout(deadline);
|
|
2201
|
-
clearTimeout(finalDeadline);
|
|
2202
|
-
result.resolve(outcome);
|
|
2203
|
-
};
|
|
2204
|
-
deadline = setTimeout(() => {
|
|
2205
|
-
taskkill.kill("SIGKILL");
|
|
2206
|
-
finalDeadline = setTimeout(() => finish("failed"), PROCESS_STOP_TIMEOUT_MS);
|
|
2207
|
-
}, PROCESS_STOP_TIMEOUT_MS);
|
|
2208
|
-
taskkill.once("error", () => finish("failed"));
|
|
2209
|
-
taskkill.once("close", (code, signal) => {
|
|
2210
|
-
finish(
|
|
2211
|
-
code === 0 && signal === null
|
|
2212
|
-
? "verified"
|
|
2213
|
-
: code === 128 && signal === null
|
|
2214
|
-
? "uncertain"
|
|
2215
|
-
: "failed",
|
|
2216
|
-
);
|
|
2217
|
-
});
|
|
2218
|
-
return result.promise;
|
|
2219
|
-
}
|
|
2220
|
-
|
|
2221
|
-
export async function terminateSpawnedProcessTree(
|
|
2222
|
-
pid: number,
|
|
2223
|
-
platform: NodeJS.Platform = process.platform,
|
|
2224
|
-
): Promise<boolean> {
|
|
2225
|
-
if (platform === "win32") return (await stopWindowsTree(pid)) === "verified";
|
|
2226
|
-
return await terminatePosixProcessTree(pid, PROCESS_STOP_TIMEOUT_MS);
|
|
2227
|
-
}
|
|
2228
|
-
|
|
2229
|
-
class OmpRpcProcess {
|
|
2230
|
-
readonly ready: Promise<ReadyFrame>;
|
|
2231
|
-
readonly inheritedRedactionValues: readonly string[];
|
|
2232
|
-
|
|
2233
|
-
private readonly child: ChildProcessWithoutNullStreams;
|
|
2234
|
-
private readonly listeners = new Set<(event: OmpRpcEvent) => void>();
|
|
2235
|
-
private readonly pending = new Map<string, PendingRequest>();
|
|
2236
|
-
private readonly queuedWrites = new Map<string, number>();
|
|
2237
|
-
private readonly pendingOneWayWrites = new Map<
|
|
2238
|
-
string,
|
|
2239
|
-
{ reject(error: Error): void; timer: TimerHandle }
|
|
2240
|
-
>();
|
|
2241
|
-
private readonly acceptedPromptIds = new Set<string>();
|
|
2242
|
-
private readonly exitPromise: Promise<void>;
|
|
2243
|
-
private readonly resolveExit: () => void;
|
|
2244
|
-
private readonly resolveReady: (frame: ReadyFrame) => void;
|
|
2245
|
-
private readonly rejectReady: (error: Error) => void;
|
|
2246
|
-
private readonly terminateProcessTree: (pid: number) => Promise<ProcessTreeCleanup>;
|
|
2247
|
-
private readonly streamedBlocks = new Map<number, string>();
|
|
2248
|
-
private readonly activeToolCallIds = new Set<string>();
|
|
2249
|
-
private pendingWriteBytes = 0;
|
|
2250
|
-
private commandTextLength = 0;
|
|
2251
|
-
private lineParts: Buffer[] = [];
|
|
2252
|
-
private lineBytes = 0;
|
|
2253
|
-
private discardingLine = false;
|
|
2254
|
-
private discardedLineBytes = 0;
|
|
2255
|
-
private chunk: ChunkState | null = null;
|
|
2256
|
-
private physicalFrameLimit = MAX_PHYSICAL_FRAME_BYTES;
|
|
2257
|
-
private reassembledFrameLimit = MAX_REASSEMBLED_FRAME_BYTES;
|
|
2258
|
-
private closed = false;
|
|
2259
|
-
private exited = false;
|
|
2260
|
-
private fatalError: Error | null = null;
|
|
2261
|
-
private closePromise: Promise<void> | null = null;
|
|
2262
|
-
private treeCleanupPromise: Promise<ProcessTreeCleanup> | null = null;
|
|
2263
|
-
private spawnFailedWithoutProcess = false;
|
|
2264
|
-
private readyReceived = false;
|
|
2265
|
-
private outputSettled = false;
|
|
2266
|
-
private turnActive = false;
|
|
2267
|
-
private readonly pendingProtocolViolations = new Map<
|
|
2268
|
-
ProtocolViolationKey,
|
|
2269
|
-
PendingProtocolViolation
|
|
2270
|
-
>();
|
|
2271
|
-
private protocolViolationTimer: TimerHandle | null = null;
|
|
2272
169
|
|
|
2273
170
|
constructor(
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
) {
|
|
2282
|
-
const ready = Promise.withResolvers<ReadyFrame>();
|
|
2283
|
-
this.rejectReady = ready.reject;
|
|
2284
|
-
this.ready = ready.promise;
|
|
2285
|
-
this.resolveReady = ready.resolve;
|
|
2286
|
-
const request = buildOmpSpawnRequest(options);
|
|
2287
|
-
this.inheritedRedactionValues = request.inheritedRedactionValues;
|
|
2288
|
-
this.terminateProcessTree = terminateProcessTree
|
|
2289
|
-
? async (pid) => {
|
|
2290
|
-
const outcome = await terminateProcessTree(pid);
|
|
2291
|
-
return outcome === true ? "verified" : outcome === "uncertain" ? "uncertain" : "failed";
|
|
2292
|
-
}
|
|
2293
|
-
: async (pid) =>
|
|
2294
|
-
process.platform === "win32"
|
|
2295
|
-
? stopWindowsTree(pid)
|
|
2296
|
-
: (await terminatePosixProcessTree(pid, PROCESS_STOP_TIMEOUT_MS))
|
|
2297
|
-
? "verified"
|
|
2298
|
-
: "failed";
|
|
2299
|
-
try {
|
|
2300
|
-
this.child = spawnProcess
|
|
2301
|
-
? spawnProcess(request)
|
|
2302
|
-
: spawn(request.command, request.args, {
|
|
2303
|
-
cwd: request.cwd,
|
|
2304
|
-
env: request.env,
|
|
2305
|
-
detached: request.detached,
|
|
2306
|
-
windowsHide: true,
|
|
2307
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
2308
|
-
});
|
|
2309
|
-
} catch (cause) {
|
|
2310
|
-
const code = (cause as NodeJS.ErrnoException)?.code;
|
|
2311
|
-
throw new Error(
|
|
2312
|
-
code === "ENOENT"
|
|
2313
|
-
? "OMP executable was not found"
|
|
2314
|
-
: code === "EACCES" || code === "EPERM"
|
|
2315
|
-
? "OMP executable is not runnable"
|
|
2316
|
-
: "OMP process could not be launched",
|
|
2317
|
-
);
|
|
2318
|
-
}
|
|
2319
|
-
this.child.stdout.on("data", (chunk: Buffer | string) => this.receiveData(chunk));
|
|
2320
|
-
this.child.stdout.once("end", () => this.handleStdoutEnd());
|
|
2321
|
-
this.child.stderr.on("data", () => {
|
|
2322
|
-
// Stderr is intentionally drained and discarded. It may contain credentials or paths.
|
|
2323
|
-
});
|
|
2324
|
-
this.child.stdin.on("error", () => {
|
|
2325
|
-
this.fail(new Error("OMP RPC input channel failed"));
|
|
2326
|
-
});
|
|
2327
|
-
const exited = Promise.withResolvers<void>();
|
|
2328
|
-
this.exitPromise = exited.promise;
|
|
2329
|
-
this.resolveExit = exited.resolve;
|
|
2330
|
-
this.child.once("exit", (code, signal) => this.handleProcessExit(code, signal));
|
|
2331
|
-
this.child.once("close", () => this.settleOutput());
|
|
2332
|
-
this.child.once("error", (cause) => {
|
|
2333
|
-
const code = (cause as NodeJS.ErrnoException)?.code;
|
|
2334
|
-
if (this.child.pid === undefined && isConfirmedNoProcessSpawnFailure(cause)) {
|
|
2335
|
-
this.spawnFailedWithoutProcess = true;
|
|
2336
|
-
}
|
|
2337
|
-
this.fail(
|
|
2338
|
-
new Error(
|
|
2339
|
-
code === "ENOENT"
|
|
2340
|
-
? "OMP executable was not found"
|
|
2341
|
-
: code === "EACCES" || code === "EPERM"
|
|
2342
|
-
? "OMP executable is not runnable"
|
|
2343
|
-
: "OMP process could not be launched",
|
|
2344
|
-
),
|
|
2345
|
-
);
|
|
2346
|
-
});
|
|
2347
|
-
}
|
|
2348
|
-
|
|
2349
|
-
private handleProcessExit(code: number | null, signal: NodeJS.Signals | null): void {
|
|
2350
|
-
if (this.exited) return;
|
|
2351
|
-
this.exited = true;
|
|
2352
|
-
void this.startTreeCleanup();
|
|
2353
|
-
const detail = code === null ? `signal ${signal ?? "unknown"}` : `code ${code}`;
|
|
2354
|
-
const error = new Error(`OMP RPC process exited (${detail})`);
|
|
2355
|
-
this.rejectReady(error);
|
|
2356
|
-
this.failPending(error);
|
|
2357
|
-
this.resolveExit();
|
|
2358
|
-
if (!this.closed && !this.fatalError) this.fail(error);
|
|
2359
|
-
}
|
|
2360
|
-
|
|
2361
|
-
private handleStdoutEnd(): void {
|
|
2362
|
-
this.settleOutput();
|
|
2363
|
-
if (!this.exited && !this.closed) this.fail(new Error("OMP RPC output channel closed"));
|
|
2364
|
-
}
|
|
2365
|
-
|
|
2366
|
-
private settleOutput(): void {
|
|
2367
|
-
if (this.outputSettled) return;
|
|
2368
|
-
this.outputSettled = true;
|
|
2369
|
-
if (this.lineBytes > 0 || this.discardingLine) {
|
|
2370
|
-
this.recordProtocolViolation("incomplete-frame", {
|
|
2371
|
-
reason: "output-ended-mid-frame",
|
|
2372
|
-
field: "frame",
|
|
2373
|
-
expected: "complete-json-line",
|
|
2374
|
-
actualType: "partial-frame",
|
|
2375
|
-
maxByteSize: this.discardingLine ? this.discardedLineBytes : this.lineBytes,
|
|
2376
|
-
});
|
|
2377
|
-
}
|
|
2378
|
-
this.lineParts = [];
|
|
2379
|
-
this.lineBytes = 0;
|
|
2380
|
-
this.discardingLine = false;
|
|
2381
|
-
this.discardedLineBytes = 0;
|
|
2382
|
-
}
|
|
2383
|
-
|
|
2384
|
-
get outboundFrameLimit(): number {
|
|
2385
|
-
return this.physicalFrameLimit;
|
|
2386
|
-
}
|
|
171
|
+
private readonly process: OmpRpcProcess,
|
|
172
|
+
private readonly removeAbortListener: () => void,
|
|
173
|
+
readonly canReplayHistory: boolean,
|
|
174
|
+
readonly inheritedRedactionValues: readonly string[],
|
|
175
|
+
readonly supportsTypedToolApprovals: boolean,
|
|
176
|
+
) {}
|
|
177
|
+
private messagePagingSupport: "unknown" | "supported" | "unsupported" = "unknown";
|
|
2387
178
|
|
|
2388
179
|
onEvent(listener: (event: OmpRpcEvent) => void): () => void {
|
|
2389
|
-
this.
|
|
2390
|
-
return () => this.listeners.delete(listener);
|
|
2391
|
-
}
|
|
2392
|
-
|
|
2393
|
-
applyReadyLimits(frame: ReadyFrame): void {
|
|
2394
|
-
if (frame.maxFrameBytes !== undefined) this.physicalFrameLimit = frame.maxFrameBytes;
|
|
2395
|
-
if (frame.maxReassembledFrameBytes !== undefined) {
|
|
2396
|
-
this.reassembledFrameLimit = frame.maxReassembledFrameBytes;
|
|
2397
|
-
}
|
|
2398
|
-
}
|
|
2399
|
-
|
|
2400
|
-
startRequest(
|
|
2401
|
-
command: Record<string, unknown>,
|
|
2402
|
-
timeoutMs: number | null = this.requestTimeoutMs,
|
|
2403
|
-
beforeResolve?: (value: unknown) => void,
|
|
2404
|
-
): StartedRequest {
|
|
2405
|
-
const id = randomUUID();
|
|
2406
|
-
if (this.fatalError) return { id, promise: Promise.reject(this.fatalError) };
|
|
2407
|
-
if (this.closed || this.exited || !this.child.stdin.writable) {
|
|
2408
|
-
return { id, promise: Promise.reject(new Error("OMP RPC process is closed")) };
|
|
2409
|
-
}
|
|
2410
|
-
let payload: Buffer;
|
|
2411
|
-
try {
|
|
2412
|
-
payload = Buffer.from(`${JSON.stringify({ ...command, id })}\n`);
|
|
2413
|
-
} catch {
|
|
2414
|
-
return { id, promise: Promise.reject(new Error("OMP RPC request could not be encoded")) };
|
|
2415
|
-
}
|
|
2416
|
-
if (payload.byteLength > this.physicalFrameLimit) {
|
|
2417
|
-
return {
|
|
2418
|
-
id,
|
|
2419
|
-
promise: Promise.reject(new Error("OMP RPC request exceeds the negotiated frame limit")),
|
|
2420
|
-
};
|
|
2421
|
-
}
|
|
2422
|
-
if (
|
|
2423
|
-
this.pending.size >= MAX_PENDING_REQUESTS ||
|
|
2424
|
-
this.pendingWriteBytes + payload.byteLength > MAX_PENDING_WRITE_BYTES
|
|
2425
|
-
) {
|
|
2426
|
-
return { id, promise: Promise.reject(new Error("OMP RPC has too many pending requests")) };
|
|
2427
|
-
}
|
|
2428
|
-
const result = Promise.withResolvers<unknown>();
|
|
2429
|
-
const timer =
|
|
2430
|
-
timeoutMs === null
|
|
2431
|
-
? undefined
|
|
2432
|
-
: setTimeout(() => {
|
|
2433
|
-
this.pending.delete(id);
|
|
2434
|
-
result.reject(new Error("OMP RPC request timed out"));
|
|
2435
|
-
}, timeoutMs);
|
|
2436
|
-
this.pending.set(id, {
|
|
2437
|
-
resolve: result.resolve,
|
|
2438
|
-
reject: result.reject,
|
|
2439
|
-
timer,
|
|
2440
|
-
command: typeof command.type === "string" ? command.type : "unknown",
|
|
2441
|
-
...(beforeResolve ? { beforeResolve } : {}),
|
|
2442
|
-
});
|
|
2443
|
-
this.queuedWrites.set(id, payload.byteLength);
|
|
2444
|
-
this.pendingWriteBytes += payload.byteLength;
|
|
2445
|
-
try {
|
|
2446
|
-
this.child.stdin.write(payload, (cause) => {
|
|
2447
|
-
this.releaseQueuedWrite(id);
|
|
2448
|
-
if (cause) this.fail(new Error("OMP RPC input channel failed"));
|
|
2449
|
-
});
|
|
2450
|
-
} catch {
|
|
2451
|
-
this.releaseQueuedWrite(id);
|
|
2452
|
-
this.fail(new Error("OMP RPC input channel failed"));
|
|
2453
|
-
}
|
|
2454
|
-
return { id, promise: result.promise };
|
|
2455
|
-
}
|
|
2456
|
-
|
|
2457
|
-
request(
|
|
2458
|
-
command: Record<string, unknown>,
|
|
2459
|
-
timeoutMs: number | null = this.requestTimeoutMs,
|
|
2460
|
-
): Promise<unknown> {
|
|
2461
|
-
return this.startRequest(command, timeoutMs).promise;
|
|
2462
|
-
}
|
|
2463
|
-
|
|
2464
|
-
send(frame: OmpHostToolResult | OmpHostToolUpdate): void {
|
|
2465
|
-
if (this.fatalError) throw this.fatalError;
|
|
2466
|
-
if (this.closed || this.exited || !this.child.stdin.writable) {
|
|
2467
|
-
throw new Error("OMP RPC process is closed");
|
|
2468
|
-
}
|
|
2469
|
-
const parsed =
|
|
2470
|
-
frame.type === "host_tool_result"
|
|
2471
|
-
? OmpHostToolResultSchema.parse(frame)
|
|
2472
|
-
: OmpHostToolUpdateSchema.parse(frame);
|
|
2473
|
-
const payload = Buffer.from(`${JSON.stringify(parsed)}\n`);
|
|
2474
|
-
if (payload.byteLength > this.physicalFrameLimit) {
|
|
2475
|
-
throw new Error("OMP host tool frame exceeds the negotiated frame limit");
|
|
2476
|
-
}
|
|
2477
|
-
if (this.pendingWriteBytes + payload.byteLength > MAX_PENDING_WRITE_BYTES) {
|
|
2478
|
-
throw new Error("OMP RPC has too many pending writes");
|
|
2479
|
-
}
|
|
2480
|
-
const writeId = randomUUID();
|
|
2481
|
-
this.queuedWrites.set(writeId, payload.byteLength);
|
|
2482
|
-
this.pendingWriteBytes += payload.byteLength;
|
|
2483
|
-
try {
|
|
2484
|
-
this.child.stdin.write(payload, (cause) => {
|
|
2485
|
-
this.releaseQueuedWrite(writeId);
|
|
2486
|
-
if (cause) this.fail(new Error("OMP RPC input channel failed"));
|
|
2487
|
-
});
|
|
2488
|
-
} catch {
|
|
2489
|
-
this.releaseQueuedWrite(writeId);
|
|
2490
|
-
this.fail(new Error("OMP RPC input channel failed"));
|
|
2491
|
-
throw new Error("OMP RPC input channel failed");
|
|
2492
|
-
}
|
|
2493
|
-
}
|
|
2494
|
-
|
|
2495
|
-
sendFrame(frame: Record<string, unknown>, timeoutMs = this.requestTimeoutMs): Promise<void> {
|
|
2496
|
-
if (this.fatalError) return Promise.reject(this.fatalError);
|
|
2497
|
-
if (this.closed || this.exited || !this.child.stdin.writable) {
|
|
2498
|
-
return Promise.reject(new Error("OMP RPC process is closed"));
|
|
2499
|
-
}
|
|
2500
|
-
let payload: Buffer;
|
|
2501
|
-
try {
|
|
2502
|
-
payload = Buffer.from(`${JSON.stringify(frame)}\n`);
|
|
2503
|
-
} catch {
|
|
2504
|
-
return Promise.reject(new Error("OMP RPC frame could not be encoded"));
|
|
2505
|
-
}
|
|
2506
|
-
if (payload.byteLength > this.physicalFrameLimit) {
|
|
2507
|
-
return Promise.reject(new Error("OMP RPC frame exceeds the negotiated frame limit"));
|
|
2508
|
-
}
|
|
2509
|
-
if (
|
|
2510
|
-
this.pendingOneWayWrites.size >= MAX_PENDING_ONE_WAY_WRITES ||
|
|
2511
|
-
this.pendingWriteBytes + payload.byteLength > MAX_PENDING_WRITE_BYTES
|
|
2512
|
-
) {
|
|
2513
|
-
return Promise.reject(new Error("OMP RPC has too many pending writes"));
|
|
2514
|
-
}
|
|
2515
|
-
const token = randomUUID();
|
|
2516
|
-
const written = Promise.withResolvers<void>();
|
|
2517
|
-
const timer = setTimeout(() => {
|
|
2518
|
-
if (!this.pendingOneWayWrites.delete(token)) return;
|
|
2519
|
-
this.releaseQueuedWrite(token);
|
|
2520
|
-
const error = new Error("OMP RPC write timed out");
|
|
2521
|
-
written.reject(error);
|
|
2522
|
-
this.fail(error);
|
|
2523
|
-
}, timeoutMs);
|
|
2524
|
-
this.pendingOneWayWrites.set(token, { reject: written.reject, timer });
|
|
2525
|
-
this.queuedWrites.set(token, payload.byteLength);
|
|
2526
|
-
this.pendingWriteBytes += payload.byteLength;
|
|
2527
|
-
try {
|
|
2528
|
-
this.child.stdin.write(payload, (cause) => {
|
|
2529
|
-
const pending = this.pendingOneWayWrites.get(token);
|
|
2530
|
-
if (!pending) return;
|
|
2531
|
-
clearTimeout(pending.timer);
|
|
2532
|
-
this.pendingOneWayWrites.delete(token);
|
|
2533
|
-
this.releaseQueuedWrite(token);
|
|
2534
|
-
if (cause) {
|
|
2535
|
-
const error = new Error("OMP RPC input channel failed");
|
|
2536
|
-
written.reject(error);
|
|
2537
|
-
this.fail(error);
|
|
2538
|
-
} else {
|
|
2539
|
-
written.resolve();
|
|
2540
|
-
}
|
|
2541
|
-
});
|
|
2542
|
-
} catch {
|
|
2543
|
-
clearTimeout(timer);
|
|
2544
|
-
this.pendingOneWayWrites.delete(token);
|
|
2545
|
-
this.releaseQueuedWrite(token);
|
|
2546
|
-
const error = new Error("OMP RPC input channel failed");
|
|
2547
|
-
written.reject(error);
|
|
2548
|
-
this.fail(error);
|
|
2549
|
-
}
|
|
2550
|
-
return written.promise;
|
|
2551
|
-
}
|
|
2552
|
-
|
|
2553
|
-
close(): Promise<void> {
|
|
2554
|
-
this.closePromise ??= this.closeProcess();
|
|
2555
|
-
return this.closePromise;
|
|
2556
|
-
}
|
|
2557
|
-
|
|
2558
|
-
private async closeProcess(): Promise<void> {
|
|
2559
|
-
this.closed = true;
|
|
2560
|
-
this.flushProtocolViolations();
|
|
2561
|
-
this.clearChunk();
|
|
2562
|
-
this.failPending(new Error("OMP RPC process was closed"));
|
|
2563
|
-
const cleanupPromise = this.startTreeCleanup();
|
|
2564
|
-
if (!this.exited) {
|
|
2565
|
-
try {
|
|
2566
|
-
this.child.stdin.end();
|
|
2567
|
-
} catch {
|
|
2568
|
-
// Continue waiting for process-tree cleanup when the input channel is already closed.
|
|
2569
|
-
}
|
|
2570
|
-
}
|
|
2571
|
-
const cleanup = await cleanupPromise;
|
|
2572
|
-
if (cleanup !== "verified") throw new Error("OMP RPC process tree cleanup failed");
|
|
2573
|
-
if (
|
|
2574
|
-
!this.spawnFailedWithoutProcess &&
|
|
2575
|
-
!this.exited &&
|
|
2576
|
-
!(await this.waitForExit(PROCESS_STOP_TIMEOUT_MS))
|
|
2577
|
-
) {
|
|
2578
|
-
throw new Error("OMP RPC process did not close after tree cleanup");
|
|
2579
|
-
}
|
|
2580
|
-
}
|
|
2581
|
-
|
|
2582
|
-
private startTreeCleanup(): Promise<ProcessTreeCleanup> {
|
|
2583
|
-
if (this.treeCleanupPromise) return this.treeCleanupPromise;
|
|
2584
|
-
const pid = this.child.pid;
|
|
2585
|
-
this.treeCleanupPromise = (
|
|
2586
|
-
pid === undefined
|
|
2587
|
-
? Promise.resolve<ProcessTreeCleanup>(
|
|
2588
|
-
this.spawnFailedWithoutProcess ? "verified" : "uncertain",
|
|
2589
|
-
)
|
|
2590
|
-
: this.terminateProcessTree(pid)
|
|
2591
|
-
).catch(() => "failed");
|
|
2592
|
-
return this.treeCleanupPromise;
|
|
2593
|
-
}
|
|
2594
|
-
|
|
2595
|
-
private async waitForExit(timeoutMs: number): Promise<boolean> {
|
|
2596
|
-
const timeout = Promise.withResolvers<false>();
|
|
2597
|
-
const timer = setTimeout(() => timeout.resolve(false), timeoutMs);
|
|
2598
|
-
try {
|
|
2599
|
-
return await Promise.race([this.exitPromise.then(() => true), timeout.promise]);
|
|
2600
|
-
} finally {
|
|
2601
|
-
clearTimeout(timer);
|
|
2602
|
-
}
|
|
2603
|
-
}
|
|
2604
|
-
|
|
2605
|
-
private receiveData(chunk: Buffer | string): void {
|
|
2606
|
-
const bytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
2607
|
-
let start = 0;
|
|
2608
|
-
for (let index = 0; index < bytes.length; index += 1) {
|
|
2609
|
-
if (bytes[index] !== 10) continue;
|
|
2610
|
-
const part = bytes.subarray(start, index);
|
|
2611
|
-
if (this.discardingLine) {
|
|
2612
|
-
this.discardedLineBytes += part.byteLength;
|
|
2613
|
-
if (this.discardedLineBytes > MAX_SEMANTIC_FRAME_BYTES) {
|
|
2614
|
-
this.fail(new Error("OMP RPC frame exceeds the semantic byte limit"));
|
|
2615
|
-
return;
|
|
2616
|
-
}
|
|
2617
|
-
this.resetDiscardedLine();
|
|
2618
|
-
} else {
|
|
2619
|
-
this.appendLinePart(part);
|
|
2620
|
-
if (this.fatalError) return;
|
|
2621
|
-
if (this.discardingLine) this.resetDiscardedLine();
|
|
2622
|
-
else this.completeLine();
|
|
2623
|
-
}
|
|
2624
|
-
start = index + 1;
|
|
2625
|
-
}
|
|
2626
|
-
if (start >= bytes.length) return;
|
|
2627
|
-
const trailing = bytes.subarray(start);
|
|
2628
|
-
if (this.discardingLine) {
|
|
2629
|
-
this.discardedLineBytes += trailing.byteLength;
|
|
2630
|
-
if (this.discardedLineBytes > MAX_SEMANTIC_FRAME_BYTES) {
|
|
2631
|
-
this.fail(new Error("OMP RPC frame exceeds the semantic byte limit"));
|
|
2632
|
-
}
|
|
2633
|
-
} else {
|
|
2634
|
-
this.appendLinePart(trailing);
|
|
2635
|
-
}
|
|
2636
|
-
}
|
|
2637
|
-
|
|
2638
|
-
private resetDiscardedLine(): void {
|
|
2639
|
-
this.discardingLine = false;
|
|
2640
|
-
this.discardedLineBytes = 0;
|
|
2641
|
-
this.lineParts = [];
|
|
2642
|
-
this.lineBytes = 0;
|
|
2643
|
-
}
|
|
2644
|
-
|
|
2645
|
-
private appendLinePart(part: Buffer): void {
|
|
2646
|
-
if (part.byteLength === 0) return;
|
|
2647
|
-
const nextBytes = this.lineBytes + part.byteLength;
|
|
2648
|
-
if (nextBytes > MAX_SEMANTIC_FRAME_BYTES) {
|
|
2649
|
-
this.fail(new Error("OMP RPC frame exceeds the semantic byte limit"));
|
|
2650
|
-
return;
|
|
2651
|
-
}
|
|
2652
|
-
if (this.lineParts.length >= MAX_LINE_PARTS || nextBytes > this.physicalFrameLimit) {
|
|
2653
|
-
this.discardedLineBytes = nextBytes;
|
|
2654
|
-
this.lineParts = [];
|
|
2655
|
-
this.lineBytes = 0;
|
|
2656
|
-
this.discardingLine = true;
|
|
2657
|
-
this.recordProtocolViolation("frame-limit", {
|
|
2658
|
-
reason: "physical-frame-limit",
|
|
2659
|
-
field: "frame.byteLength",
|
|
2660
|
-
expected: "within-byte-limit",
|
|
2661
|
-
actualType: "oversized",
|
|
2662
|
-
maxByteSize: nextBytes,
|
|
2663
|
-
limitBytes: this.physicalFrameLimit,
|
|
2664
|
-
});
|
|
2665
|
-
return;
|
|
2666
|
-
}
|
|
2667
|
-
this.lineParts.push(part);
|
|
2668
|
-
this.lineBytes = nextBytes;
|
|
2669
|
-
}
|
|
2670
|
-
|
|
2671
|
-
private completeLine(): void {
|
|
2672
|
-
if (this.lineBytes === 0) return;
|
|
2673
|
-
const line = Buffer.concat(this.lineParts, this.lineBytes);
|
|
2674
|
-
this.lineParts = [];
|
|
2675
|
-
this.lineBytes = 0;
|
|
2676
|
-
const payload = line.at(-1) === 13 ? line.subarray(0, -1) : line;
|
|
2677
|
-
if (payload.byteLength > MAX_SEMANTIC_FRAME_BYTES) {
|
|
2678
|
-
this.fail(new Error("OMP RPC frame exceeds the semantic byte limit"));
|
|
2679
|
-
return;
|
|
2680
|
-
}
|
|
2681
|
-
let decoded: unknown;
|
|
2682
|
-
try {
|
|
2683
|
-
decoded = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(payload));
|
|
2684
|
-
} catch {
|
|
2685
|
-
this.recordProtocolViolation("invalid-json", {
|
|
2686
|
-
reason: "json-decode",
|
|
2687
|
-
field: "frame",
|
|
2688
|
-
expected: "valid-json",
|
|
2689
|
-
actualType: "invalid-json",
|
|
2690
|
-
maxByteSize: payload.byteLength,
|
|
2691
|
-
});
|
|
2692
|
-
return;
|
|
2693
|
-
}
|
|
2694
|
-
this.receiveDecodedFrame(decoded, payload.byteLength);
|
|
2695
|
-
}
|
|
2696
|
-
|
|
2697
|
-
private receiveChunk(frame: ChunkFrame): void {
|
|
2698
|
-
if (
|
|
2699
|
-
frame.byteLength > MAX_SEMANTIC_FRAME_BYTES &&
|
|
2700
|
-
![...this.pending.values()].some(
|
|
2701
|
-
(pending) =>
|
|
2702
|
-
pending.command === "get_messages" || pending.command === "get_subagent_messages",
|
|
2703
|
-
)
|
|
2704
|
-
) {
|
|
2705
|
-
this.fail(new Error("OMP RPC frame exceeds the semantic byte limit"));
|
|
2706
|
-
return;
|
|
2707
|
-
}
|
|
2708
|
-
if (
|
|
2709
|
-
frame.index >= frame.count ||
|
|
2710
|
-
frame.byteLength > this.reassembledFrameLimit ||
|
|
2711
|
-
frame.data.length % 4 !== 0 ||
|
|
2712
|
-
!/^[A-Za-z0-9+/]*={0,2}$/u.test(frame.data)
|
|
2713
|
-
) {
|
|
2714
|
-
this.rejectChunk("chunk-metadata", {
|
|
2715
|
-
field: "chunk",
|
|
2716
|
-
expected: "valid-chunk-frame",
|
|
2717
|
-
actualType: "object",
|
|
2718
|
-
});
|
|
2719
|
-
return;
|
|
2720
|
-
}
|
|
2721
|
-
const decoded = Buffer.from(frame.data, "base64");
|
|
2722
|
-
if (decoded.byteLength > MAX_CHUNK_BYTES) {
|
|
2723
|
-
this.rejectChunk("chunk-size", {
|
|
2724
|
-
field: "chunk.data",
|
|
2725
|
-
expected: "within-byte-limit",
|
|
2726
|
-
actualType: "oversized",
|
|
2727
|
-
limitBytes: MAX_CHUNK_BYTES,
|
|
2728
|
-
});
|
|
2729
|
-
return;
|
|
2730
|
-
}
|
|
2731
|
-
if (!this.chunk) {
|
|
2732
|
-
if (frame.index !== 0) {
|
|
2733
|
-
this.rejectChunk("chunk-start-index", {
|
|
2734
|
-
field: "chunk.index",
|
|
2735
|
-
expected: "first-chunk-index-zero",
|
|
2736
|
-
actualType: "out-of-order",
|
|
2737
|
-
});
|
|
2738
|
-
return;
|
|
2739
|
-
}
|
|
2740
|
-
this.chunk = {
|
|
2741
|
-
id: frame.chunkId,
|
|
2742
|
-
count: frame.count,
|
|
2743
|
-
byteLength: frame.byteLength,
|
|
2744
|
-
parts: [],
|
|
2745
|
-
receivedBytes: 0,
|
|
2746
|
-
timer: setTimeout(
|
|
2747
|
-
() =>
|
|
2748
|
-
this.rejectChunk("chunk-timeout", {
|
|
2749
|
-
field: "chunk.sequence",
|
|
2750
|
-
expected: "chunk-before-deadline",
|
|
2751
|
-
actualType: "timeout",
|
|
2752
|
-
}),
|
|
2753
|
-
CHUNK_STALE_MS,
|
|
2754
|
-
),
|
|
2755
|
-
};
|
|
2756
|
-
}
|
|
2757
|
-
const chunk = this.chunk;
|
|
2758
|
-
if (
|
|
2759
|
-
chunk.id !== frame.chunkId ||
|
|
2760
|
-
chunk.count !== frame.count ||
|
|
2761
|
-
chunk.byteLength !== frame.byteLength ||
|
|
2762
|
-
chunk.parts.length !== frame.index ||
|
|
2763
|
-
chunk.receivedBytes + decoded.byteLength > chunk.byteLength ||
|
|
2764
|
-
chunk.receivedBytes + decoded.byteLength > this.reassembledFrameLimit
|
|
2765
|
-
) {
|
|
2766
|
-
this.rejectChunk("chunk-sequence", {
|
|
2767
|
-
field: "chunk.sequence",
|
|
2768
|
-
expected: "contiguous-chunk-sequence",
|
|
2769
|
-
actualType: "out-of-order",
|
|
2770
|
-
});
|
|
2771
|
-
return;
|
|
2772
|
-
}
|
|
2773
|
-
chunk.parts.push(decoded);
|
|
2774
|
-
chunk.receivedBytes += decoded.byteLength;
|
|
2775
|
-
if (chunk.parts.length !== chunk.count) return;
|
|
2776
|
-
if (chunk.receivedBytes !== chunk.byteLength) {
|
|
2777
|
-
this.rejectChunk("chunk-byte-count", {
|
|
2778
|
-
field: "chunk.byteLength",
|
|
2779
|
-
expected: "declared-chunk-byte-count",
|
|
2780
|
-
actualType: "mismatched",
|
|
2781
|
-
});
|
|
2782
|
-
return;
|
|
2783
|
-
}
|
|
2784
|
-
const reassembled = Buffer.concat(chunk.parts, chunk.receivedBytes);
|
|
2785
|
-
this.clearChunk();
|
|
2786
|
-
let decodedFrame: unknown;
|
|
2787
|
-
try {
|
|
2788
|
-
decodedFrame = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(reassembled));
|
|
2789
|
-
} catch {
|
|
2790
|
-
this.recordProtocolViolation("invalid-json", {
|
|
2791
|
-
reason: "chunk-json-decode",
|
|
2792
|
-
field: "chunk.data",
|
|
2793
|
-
expected: "valid-json",
|
|
2794
|
-
actualType: "invalid-json",
|
|
2795
|
-
maxByteSize: reassembled.byteLength,
|
|
2796
|
-
});
|
|
2797
|
-
return;
|
|
2798
|
-
}
|
|
2799
|
-
this.receiveDecodedFrame(decodedFrame, reassembled.byteLength);
|
|
180
|
+
return this.process.onEvent(listener);
|
|
2800
181
|
}
|
|
2801
182
|
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
const frame = value as Record<string, unknown>;
|
|
2805
|
-
const pending = typeof frame.id === "string" ? this.pending.get(frame.id) : undefined;
|
|
2806
|
-
if (
|
|
2807
|
-
frame.type === "response" &&
|
|
2808
|
-
(pending?.command === "get_messages" || pending?.command === "get_subagent_messages")
|
|
2809
|
-
) {
|
|
2810
|
-
this.receiveResponse(frame);
|
|
2811
|
-
return;
|
|
2812
|
-
}
|
|
2813
|
-
}
|
|
2814
|
-
if (rawByteLength > MAX_SEMANTIC_FRAME_BYTES) {
|
|
2815
|
-
this.fail(new Error("OMP RPC frame exceeds the semantic byte limit"));
|
|
2816
|
-
return;
|
|
2817
|
-
}
|
|
2818
|
-
if (this.receiveKnownResponse(value)) return;
|
|
2819
|
-
const sanitized = sanitizeLiveDisplayFrame(value);
|
|
2820
|
-
const frame = JsonObjectSchema.safeParse(sanitized);
|
|
2821
|
-
if (!frame.success) {
|
|
2822
|
-
this.recordProtocolViolation("invalid-envelope", {
|
|
2823
|
-
reason: "object-envelope",
|
|
2824
|
-
field: "frame",
|
|
2825
|
-
expected: "object-envelope",
|
|
2826
|
-
actualType: protocolDiagnosticActualType(sanitized),
|
|
2827
|
-
maxByteSize: rawByteLength,
|
|
2828
|
-
});
|
|
2829
|
-
return;
|
|
2830
|
-
}
|
|
2831
|
-
this.receiveFrame(frame.data, rawByteLength);
|
|
183
|
+
async getState(): Promise<OmpSessionState> {
|
|
184
|
+
return OmpSessionStateSchema.parse(await this.process.request({ type: "get_state" }));
|
|
2832
185
|
}
|
|
2833
186
|
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
const frame = value as Record<string, unknown>;
|
|
2837
|
-
if (
|
|
2838
|
-
frame.type !== "response" ||
|
|
2839
|
-
typeof frame.id !== "string" ||
|
|
2840
|
-
(!this.pending.has(frame.id) && !this.acceptedPromptIds.has(frame.id))
|
|
2841
|
-
) {
|
|
2842
|
-
return false;
|
|
2843
|
-
}
|
|
2844
|
-
this.receiveResponse(frame);
|
|
2845
|
-
return true;
|
|
187
|
+
async getSessionStats(): Promise<OmpSessionStats> {
|
|
188
|
+
return OmpSessionStatsSchema.parse(await this.process.request({ type: "get_session_stats" }));
|
|
2846
189
|
}
|
|
2847
190
|
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
const response = OmpResponseFrameSchema.safeParse(frame);
|
|
2852
|
-
if (!response.success) {
|
|
2853
|
-
const handledAcceptedFailure =
|
|
2854
|
-
rawId !== undefined && knownPending === undefined
|
|
2855
|
-
? this.emitAcceptedPromptFailure(rawId, frame)
|
|
2856
|
-
: false;
|
|
2857
|
-
if (!handledAcceptedFailure) {
|
|
2858
|
-
this.recordProtocolViolation("invalid-response", {
|
|
2859
|
-
reason: "response-schema",
|
|
2860
|
-
frameType: "response",
|
|
2861
|
-
field: "response",
|
|
2862
|
-
expected: "valid-response-frame",
|
|
2863
|
-
actualType: "object",
|
|
2864
|
-
});
|
|
2865
|
-
}
|
|
2866
|
-
if (rawId && knownPending) {
|
|
2867
|
-
this.takePending(rawId)?.reject(new Error("OMP RPC response is invalid"));
|
|
2868
|
-
}
|
|
2869
|
-
return;
|
|
2870
|
-
}
|
|
2871
|
-
const pending = this.pending.get(response.data.id);
|
|
2872
|
-
if (!pending) {
|
|
2873
|
-
if (!response.data.success) {
|
|
2874
|
-
this.emitAcceptedPromptFailure(response.data.id, response.data);
|
|
2875
|
-
}
|
|
2876
|
-
return;
|
|
191
|
+
async getMessages(signal?: AbortSignal): Promise<OmpMessage[]> {
|
|
192
|
+
if (!this.canReplayHistory) {
|
|
193
|
+
throw new Error("OMP history replay requires negotiated RPC protocol v2");
|
|
2877
194
|
}
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
const responseData = isHistory
|
|
2882
|
-
? sanitizeHistoryResponseData(response.data.data)
|
|
2883
|
-
: response.data.data;
|
|
2884
|
-
const boundedFrame =
|
|
2885
|
-
responseData === response.data.data ? frame : { ...frame, data: responseData };
|
|
2886
|
-
const responseItemLimit = isBranchHistory
|
|
2887
|
-
? 1_024
|
|
2888
|
-
: isHistory
|
|
2889
|
-
? 100_000
|
|
2890
|
-
: pending.command === "get_available_models"
|
|
2891
|
-
? MAX_MODEL_CATALOG_ITEMS
|
|
2892
|
-
: MAX_ARRAY_ITEMS;
|
|
2893
|
-
const responseByteLimit =
|
|
2894
|
-
isBranchHistory || isHistory
|
|
2895
|
-
? Math.min(MAX_REASSEMBLED_FRAME_BYTES, this.reassembledFrameLimit)
|
|
2896
|
-
: 2 * 1024 * 1024;
|
|
2897
|
-
// Model catalogs are truncated before publication, while the transport still enforces
|
|
2898
|
-
// aggregate byte and node budgets over the complete response.
|
|
2899
|
-
const responseNodeLimit = isBranchHistory
|
|
2900
|
-
? 4_096
|
|
2901
|
-
: isHistory
|
|
2902
|
-
? 400_000
|
|
2903
|
-
: pending.command === "get_available_models"
|
|
2904
|
-
? 16_384
|
|
2905
|
-
: 2_048;
|
|
2906
|
-
if (
|
|
2907
|
-
boundedJsonBytes(
|
|
2908
|
-
boundedFrame,
|
|
2909
|
-
responseByteLimit,
|
|
2910
|
-
responseItemLimit,
|
|
2911
|
-
MAX_IMAGE_DATA_LENGTH,
|
|
2912
|
-
responseNodeLimit,
|
|
2913
|
-
) === Number.POSITIVE_INFINITY
|
|
2914
|
-
) {
|
|
2915
|
-
this.takePending(response.data.id)?.reject(
|
|
2916
|
-
new Error("OMP RPC response exceeded command limits"),
|
|
195
|
+
if (this.messagePagingSupport === "unsupported") {
|
|
196
|
+
const legacy = OmpMessagesResultSchema.parse(
|
|
197
|
+
await this.requestWithSignal({ type: "get_messages" }, signal),
|
|
2917
198
|
);
|
|
2918
|
-
return;
|
|
2919
|
-
}
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
199
|
+
return legacy.messages;
|
|
200
|
+
}
|
|
201
|
+
let staleRestarts = 0;
|
|
202
|
+
let busyRetries = 0;
|
|
203
|
+
while (true) {
|
|
204
|
+
const messages: OmpMessage[] = [];
|
|
205
|
+
const seenCursors = new Set<string>();
|
|
206
|
+
const seenMessageIdentities = new Set<string>();
|
|
207
|
+
let pageCount = 0;
|
|
208
|
+
let cursor: string | undefined;
|
|
209
|
+
let totalMessages: number | undefined;
|
|
210
|
+
let aggregateBytes = 0;
|
|
211
|
+
let aggregateNodes = 0;
|
|
2923
212
|
try {
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
213
|
+
while (true) {
|
|
214
|
+
signal?.throwIfAborted();
|
|
215
|
+
let rawPage: unknown;
|
|
216
|
+
try {
|
|
217
|
+
rawPage = await this.requestWithSignal(
|
|
218
|
+
{
|
|
219
|
+
type: "get_messages_page",
|
|
220
|
+
...(cursor ? { cursor } : {}),
|
|
221
|
+
limit: OMP_MESSAGE_PAGE_LIMIT,
|
|
222
|
+
},
|
|
223
|
+
signal,
|
|
224
|
+
);
|
|
225
|
+
} catch (error) {
|
|
226
|
+
if (
|
|
227
|
+
error instanceof OmpRpcRequestRejectedError &&
|
|
228
|
+
error.code === "unsupported_command"
|
|
229
|
+
) {
|
|
230
|
+
this.messagePagingSupport = "unsupported";
|
|
231
|
+
const legacy = OmpMessagesResultSchema.parse(
|
|
232
|
+
await this.requestWithSignal({ type: "get_messages" }, signal),
|
|
233
|
+
);
|
|
234
|
+
return legacy.messages;
|
|
235
|
+
}
|
|
236
|
+
if (
|
|
237
|
+
error instanceof OmpRpcRequestRejectedError &&
|
|
238
|
+
error.code === "session_busy" &&
|
|
239
|
+
busyRetries < MAX_MESSAGE_PAGE_BUSY_RETRIES
|
|
240
|
+
) {
|
|
241
|
+
const delayMs = MESSAGE_PAGE_RETRY_BASE_MS * 2 ** busyRetries;
|
|
242
|
+
busyRetries += 1;
|
|
243
|
+
await this.waitForReplayRetry(delayMs, signal);
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
throw error;
|
|
247
|
+
}
|
|
248
|
+
pageCount += 1;
|
|
249
|
+
if (pageCount > MAX_MESSAGE_PAGES) {
|
|
250
|
+
throw new OmpRpcResponseLimitError(
|
|
251
|
+
"get_messages_page",
|
|
252
|
+
"items",
|
|
253
|
+
pageCount,
|
|
254
|
+
MAX_MESSAGE_PAGES,
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
const page = OmpMessagesPageResultSchema.parse(rawPage);
|
|
258
|
+
this.messagePagingSupport = "supported";
|
|
259
|
+
if (totalMessages !== undefined && page.totalMessages !== totalMessages) {
|
|
260
|
+
throw new Error("OMP message pagination returned an inconsistent total");
|
|
2929
261
|
}
|
|
2930
|
-
|
|
262
|
+
totalMessages ??= page.totalMessages;
|
|
263
|
+
const nextMessageCount = messages.length + page.messages.length;
|
|
264
|
+
if (nextMessageCount > totalMessages || nextMessageCount > MAX_REPLAY_MESSAGES) {
|
|
265
|
+
throw new OmpRpcResponseLimitError(
|
|
266
|
+
"get_messages_page",
|
|
267
|
+
"items",
|
|
268
|
+
nextMessageCount,
|
|
269
|
+
Math.min(totalMessages, MAX_REPLAY_MESSAGES),
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
const pageIdentities = new Set<string>();
|
|
273
|
+
for (const message of page.messages) {
|
|
274
|
+
const identity = message.entryId ?? message.id ?? message.responseId;
|
|
275
|
+
if (!identity) continue;
|
|
276
|
+
if (seenMessageIdentities.has(identity)) {
|
|
277
|
+
throw new Error("OMP message pagination repeated a message identity");
|
|
278
|
+
}
|
|
279
|
+
pageIdentities.add(identity);
|
|
280
|
+
}
|
|
281
|
+
const inspection = inspectJsonBounds(
|
|
282
|
+
page.messages,
|
|
283
|
+
MAX_REPLAY_BYTES,
|
|
284
|
+
MAX_REPLAY_MESSAGES,
|
|
285
|
+
MAX_IMAGE_DATA_LENGTH,
|
|
286
|
+
MAX_REPLAY_NODES,
|
|
287
|
+
);
|
|
288
|
+
if (inspection.violation) {
|
|
289
|
+
throw new OmpRpcResponseLimitError(
|
|
290
|
+
"get_messages_page",
|
|
291
|
+
inspection.violation.dimension,
|
|
292
|
+
inspection.violation.actual,
|
|
293
|
+
inspection.violation.limit,
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
aggregateBytes += inspection.bytes;
|
|
297
|
+
aggregateNodes += inspection.nodes;
|
|
298
|
+
if (aggregateBytes > MAX_REPLAY_BYTES) {
|
|
299
|
+
throw new OmpRpcResponseLimitError(
|
|
300
|
+
"get_messages_page",
|
|
301
|
+
"bytes",
|
|
302
|
+
aggregateBytes,
|
|
303
|
+
MAX_REPLAY_BYTES,
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
if (aggregateNodes > MAX_REPLAY_NODES) {
|
|
307
|
+
throw new OmpRpcResponseLimitError(
|
|
308
|
+
"get_messages_page",
|
|
309
|
+
"nodes",
|
|
310
|
+
aggregateNodes,
|
|
311
|
+
MAX_REPLAY_NODES,
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
for (const identity of pageIdentities) seenMessageIdentities.add(identity);
|
|
315
|
+
messages.push(...page.messages);
|
|
316
|
+
const nextCursor = page.nextCursor;
|
|
317
|
+
if (!nextCursor) {
|
|
318
|
+
if (messages.length !== totalMessages) {
|
|
319
|
+
throw new Error("OMP message pagination ended before the advertised total");
|
|
320
|
+
}
|
|
321
|
+
return messages;
|
|
322
|
+
}
|
|
323
|
+
if (page.messages.length === 0 || messages.length >= totalMessages) {
|
|
324
|
+
throw new Error("OMP message pagination did not make progress");
|
|
325
|
+
}
|
|
326
|
+
if (seenCursors.has(nextCursor)) {
|
|
327
|
+
throw new Error("OMP message pagination repeated a cursor");
|
|
328
|
+
}
|
|
329
|
+
seenCursors.add(nextCursor);
|
|
330
|
+
cursor = nextCursor;
|
|
2931
331
|
}
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
if (frame.success !== false || !this.acceptedPromptIds.delete(id)) return false;
|
|
2943
|
-
const error = typeof frame.error === "string" ? frame.error : undefined;
|
|
2944
|
-
const nativeError =
|
|
2945
|
-
error && utf8Bytes(error) <= MAX_RPC_ERROR_BYTES ? error : PROMPT_SCHEDULING_FAILURE;
|
|
2946
|
-
const code = typeof frame.code === "string" ? frame.code : undefined;
|
|
2947
|
-
const nativeCode = code && utf8Bytes(code) <= MAX_RPC_ERROR_CODE_BYTES ? code : undefined;
|
|
2948
|
-
this.emit({
|
|
2949
|
-
type: "prompt_error",
|
|
2950
|
-
id,
|
|
2951
|
-
error: nativeError,
|
|
2952
|
-
...(nativeCode ? { code: nativeCode } : {}),
|
|
2953
|
-
});
|
|
2954
|
-
return true;
|
|
2955
|
-
}
|
|
2956
|
-
|
|
2957
|
-
private takePending(id: string): PendingRequest | undefined {
|
|
2958
|
-
const pending = this.pending.get(id);
|
|
2959
|
-
if (!pending) return undefined;
|
|
2960
|
-
clearTimeout(pending.timer);
|
|
2961
|
-
this.pending.delete(id);
|
|
2962
|
-
return pending;
|
|
2963
|
-
}
|
|
2964
|
-
|
|
2965
|
-
private releaseQueuedWrite(id: string): void {
|
|
2966
|
-
const bytes = this.queuedWrites.get(id);
|
|
2967
|
-
if (bytes === undefined) return;
|
|
2968
|
-
this.queuedWrites.delete(id);
|
|
2969
|
-
this.pendingWriteBytes -= bytes;
|
|
2970
|
-
}
|
|
2971
|
-
|
|
2972
|
-
private receiveDegradedAgentEnd(value: unknown, onlyUnsafePayload: boolean): boolean {
|
|
2973
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
2974
|
-
const frame = value as Record<string, unknown>;
|
|
2975
|
-
if (frame.type !== "agent_end") return false;
|
|
2976
|
-
const envelope = OmpAgentEndEnvelopeSchema.safeParse(frame);
|
|
2977
|
-
if (!envelope.success) {
|
|
2978
|
-
this.fail(new Error("OMP emitted invalid terminal metadata"));
|
|
2979
|
-
return true;
|
|
2980
|
-
}
|
|
2981
|
-
const structuralFrame = mapRuntimeFrameDetails(frame, omitOptionalDetails);
|
|
2982
|
-
const messagesAreSafe =
|
|
2983
|
-
frame.messages === undefined ||
|
|
2984
|
-
(Array.isArray(frame.messages) &&
|
|
2985
|
-
frame.messages.length <= MAX_ARRAY_ITEMS &&
|
|
2986
|
-
boundedJsonBytes(
|
|
2987
|
-
structuralFrame.messages as unknown[],
|
|
2988
|
-
MAX_SEMANTIC_FRAME_BYTES,
|
|
2989
|
-
OMP_MAX_CONTENT_PARTS,
|
|
2990
|
-
MAX_IMAGE_DATA_LENGTH,
|
|
2991
|
-
4_096,
|
|
2992
|
-
) !== Number.POSITIVE_INFINITY);
|
|
2993
|
-
const payloadIsSafe =
|
|
2994
|
-
messagesAreSafe &&
|
|
2995
|
-
boundedJsonBytes(
|
|
2996
|
-
structuralFrame,
|
|
2997
|
-
MAX_SEMANTIC_FRAME_BYTES,
|
|
2998
|
-
OMP_MAX_CONTENT_PARTS,
|
|
2999
|
-
MAX_IMAGE_DATA_LENGTH,
|
|
3000
|
-
4_096,
|
|
3001
|
-
) !== Number.POSITIVE_INFINITY;
|
|
3002
|
-
if (onlyUnsafePayload && payloadIsSafe) return false;
|
|
3003
|
-
if (envelope.data.isTerminal === false) {
|
|
3004
|
-
this.fail(new Error("OMP emitted an invalid nonterminal agent_end payload"));
|
|
3005
|
-
return true;
|
|
3006
|
-
}
|
|
3007
|
-
const observedCount = Array.isArray(frame.messages)
|
|
3008
|
-
? frame.messages.length
|
|
3009
|
-
: Object.hasOwn(frame, "messages")
|
|
3010
|
-
? 1
|
|
3011
|
-
: undefined;
|
|
3012
|
-
const messageCount = Math.max(envelope.data.messageCount ?? 0, observedCount ?? 0, 1);
|
|
3013
|
-
this.emit({
|
|
3014
|
-
...envelope.data,
|
|
3015
|
-
messageCount,
|
|
3016
|
-
});
|
|
3017
|
-
this.streamedBlocks.clear();
|
|
3018
|
-
this.commandTextLength = 0;
|
|
3019
|
-
return true;
|
|
3020
|
-
}
|
|
3021
|
-
|
|
3022
|
-
private receiveFrame(frame: Record<string, unknown>, rawByteLength: number): void {
|
|
3023
|
-
const type = typeof frame.type === "string" && frame.type.length <= 64 ? frame.type : null;
|
|
3024
|
-
if (!type) {
|
|
3025
|
-
this.recordProtocolViolation("invalid-envelope", {
|
|
3026
|
-
reason: "missing-frame-type",
|
|
3027
|
-
field: "frame.type",
|
|
3028
|
-
expected: "bounded-frame-type",
|
|
3029
|
-
actualType: protocolDiagnosticActualType(frame.type),
|
|
3030
|
-
maxByteSize: rawByteLength,
|
|
3031
|
-
});
|
|
3032
|
-
return;
|
|
3033
|
-
}
|
|
3034
|
-
const safeFrame = mapRuntimeFrameDetails(frame, createOptionalMetadataSanitizer());
|
|
3035
|
-
if (this.receiveDegradedAgentEnd(safeFrame, true)) return;
|
|
3036
|
-
if (
|
|
3037
|
-
boundedJsonBytes(
|
|
3038
|
-
mapRuntimeFrameDetails(safeFrame, omitOptionalDetails),
|
|
3039
|
-
MAX_SEMANTIC_FRAME_BYTES,
|
|
3040
|
-
runtimeFrameCollectionLimit(safeFrame),
|
|
3041
|
-
MAX_IMAGE_DATA_LENGTH,
|
|
3042
|
-
4_096,
|
|
3043
|
-
) === Number.POSITIVE_INFINITY
|
|
3044
|
-
) {
|
|
3045
|
-
this.recordProtocolViolation("frame-limit", {
|
|
3046
|
-
reason: "semantic-frame-limit",
|
|
3047
|
-
field: "frame.byteLength",
|
|
3048
|
-
expected: "within-byte-limit",
|
|
3049
|
-
actualType: "oversized",
|
|
3050
|
-
maxByteSize: rawByteLength,
|
|
3051
|
-
limitBytes: MAX_SEMANTIC_FRAME_BYTES,
|
|
3052
|
-
});
|
|
3053
|
-
return;
|
|
3054
|
-
}
|
|
3055
|
-
if (type === "rpc_chunk") {
|
|
3056
|
-
const chunk = OmpChunkFrameSchema.safeParse(safeFrame);
|
|
3057
|
-
if (!chunk.success) {
|
|
3058
|
-
this.rejectChunk("chunk-schema", {
|
|
3059
|
-
field: "chunk",
|
|
3060
|
-
expected: "valid-chunk-frame",
|
|
3061
|
-
actualType: "object",
|
|
3062
|
-
});
|
|
3063
|
-
} else this.receiveChunk(chunk.data);
|
|
3064
|
-
return;
|
|
3065
|
-
}
|
|
3066
|
-
if (this.chunk) {
|
|
3067
|
-
this.clearChunk();
|
|
3068
|
-
this.recordProtocolViolation("interleaved-chunk", {
|
|
3069
|
-
reason: "frame-interleaved-with-chunk",
|
|
3070
|
-
field: "chunk.sequence",
|
|
3071
|
-
expected: "no-interleaved-frame",
|
|
3072
|
-
actualType: "interleaved",
|
|
3073
|
-
});
|
|
3074
|
-
}
|
|
3075
|
-
if (type === "rpc_frame_error") {
|
|
3076
|
-
this.recordProtocolViolation("remote-frame-error", {
|
|
3077
|
-
reason: "remote-frame-error",
|
|
3078
|
-
frameType: "rpc_frame_error",
|
|
3079
|
-
field: "frame",
|
|
3080
|
-
expected: "no-remote-frame-error",
|
|
3081
|
-
actualType: "remote-error",
|
|
3082
|
-
});
|
|
3083
|
-
return;
|
|
3084
|
-
}
|
|
3085
|
-
if (type === "ready") {
|
|
3086
|
-
if (this.readyReceived) {
|
|
3087
|
-
this.recordProtocolViolation("duplicate-ready", {
|
|
3088
|
-
reason: "ready-already-received",
|
|
3089
|
-
frameType: "ready",
|
|
3090
|
-
field: "ready",
|
|
3091
|
-
expected: "single-ready-frame",
|
|
3092
|
-
actualType: "duplicate",
|
|
3093
|
-
});
|
|
3094
|
-
return;
|
|
3095
|
-
}
|
|
3096
|
-
const ready = OmpReadyFrameSchema.safeParse(safeFrame);
|
|
3097
|
-
if (!ready.success) {
|
|
3098
|
-
this.recordProtocolViolation("invalid-ready", {
|
|
3099
|
-
reason: "ready-schema",
|
|
3100
|
-
frameType: "ready",
|
|
3101
|
-
field: "ready",
|
|
3102
|
-
expected: "valid-ready-frame",
|
|
3103
|
-
actualType: "object",
|
|
3104
|
-
});
|
|
3105
|
-
} else {
|
|
3106
|
-
this.readyReceived = true;
|
|
3107
|
-
this.resolveReady(ready.data);
|
|
3108
|
-
}
|
|
3109
|
-
return;
|
|
3110
|
-
}
|
|
3111
|
-
if (type === "response") {
|
|
3112
|
-
this.receiveResponse(safeFrame);
|
|
3113
|
-
return;
|
|
3114
|
-
}
|
|
3115
|
-
const event = OmpRuntimeEventSchema.safeParse(safeFrame);
|
|
3116
|
-
if (!event.success) {
|
|
3117
|
-
this.rejectMatchingToolApproval(safeFrame);
|
|
3118
|
-
if (type === "agent_end" && this.receiveDegradedAgentEnd(safeFrame, false)) return;
|
|
3119
|
-
this.recordProtocolViolation("invalid-event", {
|
|
3120
|
-
...invalidEventDiagnosticMetadata(safeFrame, type),
|
|
3121
|
-
maxByteSize: rawByteLength,
|
|
3122
|
-
});
|
|
3123
|
-
return;
|
|
3124
|
-
}
|
|
3125
|
-
if (!this.acceptEventState(event.data)) {
|
|
3126
|
-
this.recordProtocolViolation("invalid-event-state", {
|
|
3127
|
-
reason: "event-state-transition",
|
|
3128
|
-
eventType: event.data.type,
|
|
3129
|
-
field: "event.sequence",
|
|
3130
|
-
expected: "valid-event-state-transition",
|
|
3131
|
-
actualType: "out-of-order",
|
|
3132
|
-
maxByteSize: rawByteLength,
|
|
3133
|
-
});
|
|
3134
|
-
return;
|
|
3135
|
-
}
|
|
3136
|
-
this.emit(event.data);
|
|
3137
|
-
if (event.data.type === "prompt_result" && event.data.id) {
|
|
3138
|
-
this.acceptedPromptIds.delete(event.data.id);
|
|
3139
|
-
}
|
|
3140
|
-
if (event.data.type === "turn_end" || event.data.type === "agent_end") {
|
|
3141
|
-
this.turnActive = false;
|
|
3142
|
-
}
|
|
3143
|
-
if (
|
|
3144
|
-
event.data.type === "message_end" ||
|
|
3145
|
-
event.data.type === "turn_end" ||
|
|
3146
|
-
event.data.type === "agent_end"
|
|
3147
|
-
) {
|
|
3148
|
-
this.streamedBlocks.clear();
|
|
3149
|
-
}
|
|
3150
|
-
if (event.data.type === "turn_end") {
|
|
3151
|
-
this.commandTextLength = 0;
|
|
3152
|
-
this.activeToolCallIds.clear();
|
|
3153
|
-
}
|
|
3154
|
-
}
|
|
3155
|
-
|
|
3156
|
-
private rejectMatchingToolApproval(frame: Record<string, unknown>): void {
|
|
3157
|
-
if (frame.type !== "tool_approval_request") return;
|
|
3158
|
-
const { id, toolCallId } = frame;
|
|
3159
|
-
if (!isBoundedToolApprovalId(id) || !isBoundedToolApprovalId(toolCallId)) return;
|
|
3160
|
-
void this.sendFrame({
|
|
3161
|
-
type: "tool_approval_response",
|
|
3162
|
-
id,
|
|
3163
|
-
toolCallId,
|
|
3164
|
-
cancelled: true,
|
|
3165
|
-
}).catch(() => this.fail(new Error("OMP rejected tool approval could not be canceled")));
|
|
3166
|
-
}
|
|
3167
|
-
|
|
3168
|
-
private acceptEventState(event: z.infer<typeof OmpRuntimeEventSchema>): boolean {
|
|
3169
|
-
if (event.type === "turn_start") {
|
|
3170
|
-
this.turnActive = true;
|
|
3171
|
-
this.streamedBlocks.clear();
|
|
3172
|
-
this.commandTextLength = 0;
|
|
3173
|
-
this.activeToolCallIds.clear();
|
|
3174
|
-
return true;
|
|
3175
|
-
}
|
|
3176
|
-
if (event.type === "command_output") {
|
|
3177
|
-
const nextLength = this.commandTextLength + utf8Bytes(event.text ?? "");
|
|
3178
|
-
if (nextLength > MAX_STREAM_TEXT_LENGTH) return false;
|
|
3179
|
-
this.commandTextLength = nextLength;
|
|
3180
|
-
return true;
|
|
3181
|
-
}
|
|
3182
|
-
if (event.type === "tool_execution_start") {
|
|
3183
|
-
if (
|
|
3184
|
-
!this.activeToolCallIds.has(event.toolCallId) &&
|
|
3185
|
-
this.activeToolCallIds.size >= MAX_ACTIVE_TOOLS
|
|
3186
|
-
) {
|
|
3187
|
-
return false;
|
|
3188
|
-
}
|
|
3189
|
-
this.activeToolCallIds.add(event.toolCallId);
|
|
3190
|
-
return true;
|
|
3191
|
-
}
|
|
3192
|
-
if (event.type === "tool_execution_update") {
|
|
3193
|
-
return this.activeToolCallIds.has(event.toolCallId);
|
|
3194
|
-
}
|
|
3195
|
-
if (event.type === "tool_execution_end") {
|
|
3196
|
-
if (!this.activeToolCallIds.has(event.toolCallId)) return false;
|
|
3197
|
-
this.activeToolCallIds.delete(event.toolCallId);
|
|
3198
|
-
return true;
|
|
3199
|
-
}
|
|
3200
|
-
if (
|
|
3201
|
-
event.type !== "message_start" &&
|
|
3202
|
-
event.type !== "message_update" &&
|
|
3203
|
-
event.type !== "message_end"
|
|
3204
|
-
) {
|
|
3205
|
-
return true;
|
|
3206
|
-
}
|
|
3207
|
-
if (event.message.role !== "assistant") return true;
|
|
3208
|
-
const nextBlocks =
|
|
3209
|
-
event.type === "message_start" ? new Map<number, string>() : new Map(this.streamedBlocks);
|
|
3210
|
-
const content = event.message.content;
|
|
3211
|
-
if (typeof content === "string") {
|
|
3212
|
-
nextBlocks.set(0, content);
|
|
3213
|
-
} else if (Array.isArray(content)) {
|
|
3214
|
-
for (const [index, part] of content.entries()) {
|
|
3215
|
-
const text =
|
|
3216
|
-
part.type === "text" ? part.text : part.type === "thinking" ? part.thinking : undefined;
|
|
3217
|
-
if (text !== undefined) nextBlocks.set(index, text);
|
|
332
|
+
} catch (error) {
|
|
333
|
+
if (
|
|
334
|
+
error instanceof OmpRpcRequestRejectedError &&
|
|
335
|
+
error.code === "stale_cursor" &&
|
|
336
|
+
staleRestarts < MAX_MESSAGE_PAGE_STALE_RESTARTS
|
|
337
|
+
) {
|
|
338
|
+
staleRestarts += 1;
|
|
339
|
+
continue;
|
|
340
|
+
}
|
|
341
|
+
throw error;
|
|
3218
342
|
}
|
|
3219
343
|
}
|
|
3220
|
-
const update = event.type === "message_update" ? event.assistantMessageEvent : undefined;
|
|
3221
|
-
if (
|
|
3222
|
-
update?.contentIndex !== undefined &&
|
|
3223
|
-
update.delta !== undefined &&
|
|
3224
|
-
(content === undefined ||
|
|
3225
|
-
(Array.isArray(content) && content[update.contentIndex] === undefined))
|
|
3226
|
-
) {
|
|
3227
|
-
const current = nextBlocks.get(update.contentIndex) ?? "";
|
|
3228
|
-
nextBlocks.set(update.contentIndex, `${current}${update.delta}`);
|
|
3229
|
-
}
|
|
3230
|
-
let totalLength = 0;
|
|
3231
|
-
for (const text of nextBlocks.values()) {
|
|
3232
|
-
totalLength += utf8Bytes(text);
|
|
3233
|
-
if (totalLength > MAX_STREAM_TEXT_LENGTH) return false;
|
|
3234
|
-
}
|
|
3235
|
-
this.streamedBlocks.clear();
|
|
3236
|
-
for (const [index, text] of nextBlocks) this.streamedBlocks.set(index, text);
|
|
3237
|
-
return true;
|
|
3238
344
|
}
|
|
3239
345
|
|
|
3240
|
-
private
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
346
|
+
private async requestWithSignal(
|
|
347
|
+
command: Record<string, unknown>,
|
|
348
|
+
signal?: AbortSignal,
|
|
349
|
+
): Promise<unknown> {
|
|
350
|
+
signal?.throwIfAborted();
|
|
351
|
+
const request = this.process.request(command);
|
|
352
|
+
if (!signal) return request;
|
|
353
|
+
const aborted = Promise.withResolvers<never>();
|
|
354
|
+
const onAbort = () => aborted.reject(signal.reason);
|
|
355
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
356
|
+
try {
|
|
357
|
+
return await Promise.race([request, aborted.promise]);
|
|
358
|
+
} finally {
|
|
359
|
+
signal.removeEventListener("abort", onAbort);
|
|
360
|
+
void request.catch(() => undefined);
|
|
3246
361
|
}
|
|
3247
|
-
if (this.turnActive || this.acceptedPromptIds.size > 0) return "active-turn";
|
|
3248
|
-
return "idle";
|
|
3249
362
|
}
|
|
3250
363
|
|
|
3251
|
-
private
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
"category" | "reason" | "occurrenceCount" | "phase" | "frameType"
|
|
3256
|
-
> = {},
|
|
3257
|
-
): void {
|
|
3258
|
-
this.clearChunk();
|
|
3259
|
-
this.recordProtocolViolation("invalid-chunk", {
|
|
3260
|
-
reason,
|
|
3261
|
-
frameType: "rpc_chunk",
|
|
3262
|
-
...metadata,
|
|
3263
|
-
});
|
|
3264
|
-
}
|
|
3265
|
-
|
|
3266
|
-
private recordProtocolViolation(
|
|
3267
|
-
category: OmpProtocolViolationCategory,
|
|
3268
|
-
metadata: Omit<OmpProtocolViolationDiagnostic, "category" | "occurrenceCount" | "phase">,
|
|
3269
|
-
): void {
|
|
3270
|
-
const phase = this.protocolDiagnosticPhase();
|
|
3271
|
-
if (!this.protocolViolationTimer) {
|
|
3272
|
-
this.emitProtocolViolation({ category, occurrenceCount: 1, phase, ...metadata });
|
|
3273
|
-
this.protocolViolationTimer = setTimeout(
|
|
3274
|
-
() => this.flushProtocolViolations(),
|
|
3275
|
-
PROTOCOL_VIOLATION_COALESCE_MS,
|
|
3276
|
-
);
|
|
3277
|
-
return;
|
|
3278
|
-
}
|
|
3279
|
-
const key = `${category}:${metadata.reason}` as ProtocolViolationKey;
|
|
3280
|
-
const pending = this.pendingProtocolViolations.get(key);
|
|
3281
|
-
if (!pending) {
|
|
3282
|
-
this.pendingProtocolViolations.set(key, {
|
|
3283
|
-
category,
|
|
3284
|
-
occurrenceCount: 1,
|
|
3285
|
-
phase,
|
|
3286
|
-
...metadata,
|
|
3287
|
-
});
|
|
364
|
+
private async waitForReplayRetry(delayMs: number, signal?: AbortSignal): Promise<void> {
|
|
365
|
+
signal?.throwIfAborted();
|
|
366
|
+
if (!signal) {
|
|
367
|
+
await waitMs(delayMs);
|
|
3288
368
|
return;
|
|
3289
369
|
}
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
pending.reason = metadata.reason;
|
|
3294
|
-
pending.phase = phase;
|
|
3295
|
-
pending.frameType = metadata.frameType;
|
|
3296
|
-
pending.field = metadata.field;
|
|
3297
|
-
pending.expected = metadata.expected;
|
|
3298
|
-
pending.actualType = metadata.actualType;
|
|
3299
|
-
pending.limitBytes = metadata.limitBytes;
|
|
3300
|
-
}
|
|
3301
|
-
|
|
3302
|
-
private flushProtocolViolations(): void {
|
|
3303
|
-
if (this.protocolViolationTimer) clearTimeout(this.protocolViolationTimer);
|
|
3304
|
-
this.protocolViolationTimer = null;
|
|
3305
|
-
for (const diagnostic of this.pendingProtocolViolations.values()) {
|
|
3306
|
-
this.emitProtocolViolation(diagnostic);
|
|
3307
|
-
}
|
|
3308
|
-
this.pendingProtocolViolations.clear();
|
|
3309
|
-
}
|
|
3310
|
-
|
|
3311
|
-
private emitProtocolViolation(diagnostic: OmpProtocolViolationDiagnostic): void {
|
|
370
|
+
const aborted = Promise.withResolvers<never>();
|
|
371
|
+
const onAbort = () => aborted.reject(signal.reason);
|
|
372
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
3312
373
|
try {
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
// Diagnostics must never alter transport flow or cleanup.
|
|
3317
|
-
}
|
|
3318
|
-
}
|
|
3319
|
-
|
|
3320
|
-
private fail(error: Error): void {
|
|
3321
|
-
if (this.fatalError || this.closed) return;
|
|
3322
|
-
this.fatalError = error;
|
|
3323
|
-
this.rejectReady(error);
|
|
3324
|
-
this.failPending(error);
|
|
3325
|
-
this.emit({ type: "process_exit", error: error.message });
|
|
3326
|
-
void this.close().catch(() => undefined);
|
|
3327
|
-
}
|
|
3328
|
-
|
|
3329
|
-
private clearChunk(): void {
|
|
3330
|
-
const chunk = this.chunk;
|
|
3331
|
-
if (chunk) clearTimeout(chunk.timer);
|
|
3332
|
-
this.chunk = null;
|
|
3333
|
-
}
|
|
3334
|
-
|
|
3335
|
-
private failPending(error: Error): void {
|
|
3336
|
-
for (const pending of this.pending.values()) {
|
|
3337
|
-
clearTimeout(pending.timer);
|
|
3338
|
-
pending.reject(error);
|
|
3339
|
-
}
|
|
3340
|
-
this.pending.clear();
|
|
3341
|
-
for (const pending of this.pendingOneWayWrites.values()) {
|
|
3342
|
-
clearTimeout(pending.timer);
|
|
3343
|
-
pending.reject(error);
|
|
374
|
+
await Promise.race([waitMs(delayMs), aborted.promise]);
|
|
375
|
+
} finally {
|
|
376
|
+
signal.removeEventListener("abort", onAbort);
|
|
3344
377
|
}
|
|
3345
|
-
this.acceptedPromptIds.clear();
|
|
3346
|
-
this.pendingOneWayWrites.clear();
|
|
3347
|
-
this.queuedWrites.clear();
|
|
3348
|
-
this.pendingWriteBytes = 0;
|
|
3349
|
-
}
|
|
3350
|
-
|
|
3351
|
-
private emit(event: OmpRpcEvent): void {
|
|
3352
|
-
for (const listener of this.listeners) listener(event);
|
|
3353
|
-
}
|
|
3354
|
-
}
|
|
3355
|
-
|
|
3356
|
-
function validateReadyMetadata(frame: ReadyFrame): void {
|
|
3357
|
-
const metadata = [
|
|
3358
|
-
frame.protocolVersion,
|
|
3359
|
-
frame.supportedProtocolVersions,
|
|
3360
|
-
frame.maxFrameBytes,
|
|
3361
|
-
frame.maxReassembledFrameBytes,
|
|
3362
|
-
];
|
|
3363
|
-
if (metadata.every((value) => value === undefined)) {
|
|
3364
|
-
throw new OmpPublicError("OMP provider requires OMP RPC protocol v2");
|
|
3365
|
-
}
|
|
3366
|
-
if (metadata.some((value) => value === undefined)) {
|
|
3367
|
-
throw new Error("OMP ready frame contains incomplete protocol metadata");
|
|
3368
|
-
}
|
|
3369
|
-
if (
|
|
3370
|
-
frame.protocolVersion !== 1 ||
|
|
3371
|
-
!frame.supportedProtocolVersions?.includes(1) ||
|
|
3372
|
-
!frame.maxFrameBytes ||
|
|
3373
|
-
frame.maxFrameBytes > MAX_PHYSICAL_FRAME_BYTES ||
|
|
3374
|
-
!frame.maxReassembledFrameBytes ||
|
|
3375
|
-
frame.maxReassembledFrameBytes > MAX_REASSEMBLED_FRAME_BYTES ||
|
|
3376
|
-
frame.maxReassembledFrameBytes < frame.maxFrameBytes
|
|
3377
|
-
) {
|
|
3378
|
-
throw new Error("OMP ready frame advertises unsupported protocol limits");
|
|
3379
|
-
}
|
|
3380
|
-
if (frame.maxFrameBytes < MIN_HOST_TOOL_RESULT_FRAME_BYTES) {
|
|
3381
|
-
throw new Error("OMP ready frame cannot carry terminal host tool results");
|
|
3382
|
-
}
|
|
3383
|
-
if (!frame.supportedProtocolVersions.includes(2)) {
|
|
3384
|
-
throw new OmpPublicError("OMP provider requires OMP RPC protocol v2");
|
|
3385
|
-
}
|
|
3386
|
-
}
|
|
3387
|
-
|
|
3388
|
-
class OmpRpcSession implements OmpRuntimeSession {
|
|
3389
|
-
get maxHostToolFrameBytes(): number {
|
|
3390
|
-
return this.process.outboundFrameLimit;
|
|
3391
|
-
}
|
|
3392
|
-
get maxInputFrameBytes(): number {
|
|
3393
|
-
return this.process.outboundFrameLimit;
|
|
3394
|
-
}
|
|
3395
|
-
|
|
3396
|
-
constructor(
|
|
3397
|
-
private readonly process: OmpRpcProcess,
|
|
3398
|
-
private readonly removeAbortListener: () => void,
|
|
3399
|
-
readonly canReplayHistory: boolean,
|
|
3400
|
-
readonly inheritedRedactionValues: readonly string[],
|
|
3401
|
-
readonly supportsTypedToolApprovals: boolean,
|
|
3402
|
-
) {}
|
|
3403
|
-
|
|
3404
|
-
onEvent(listener: (event: OmpRpcEvent) => void): () => void {
|
|
3405
|
-
return this.process.onEvent(listener);
|
|
3406
|
-
}
|
|
3407
|
-
|
|
3408
|
-
async getState(): Promise<OmpSessionState> {
|
|
3409
|
-
return OmpSessionStateSchema.parse(await this.process.request({ type: "get_state" }));
|
|
3410
|
-
}
|
|
3411
|
-
|
|
3412
|
-
async getSessionStats(): Promise<OmpSessionStats> {
|
|
3413
|
-
return OmpSessionStatsSchema.parse(await this.process.request({ type: "get_session_stats" }));
|
|
3414
378
|
}
|
|
3415
379
|
|
|
3416
380
|
async compact(customInstructions?: string): Promise<OmpCompactionResult> {
|
|
@@ -3508,16 +472,6 @@ class OmpRpcSession implements OmpRuntimeSession {
|
|
|
3508
472
|
return result.data;
|
|
3509
473
|
}
|
|
3510
474
|
|
|
3511
|
-
async getMessages(): Promise<OmpMessage[]> {
|
|
3512
|
-
if (!this.canReplayHistory) {
|
|
3513
|
-
throw new Error("OMP history replay requires negotiated RPC protocol v2");
|
|
3514
|
-
}
|
|
3515
|
-
const result = OmpMessagesResultSchema.parse(
|
|
3516
|
-
await this.process.request({ type: "get_messages" }),
|
|
3517
|
-
);
|
|
3518
|
-
return result.messages;
|
|
3519
|
-
}
|
|
3520
|
-
|
|
3521
475
|
async setHostTools(tools: readonly OmpHostToolDefinition[]): Promise<string[]> {
|
|
3522
476
|
const safeTools = z.array(OmpHostToolDefinitionSchema).max(MAX_HOST_TOOLS).parse(tools);
|
|
3523
477
|
if (safeTools.length === 0) return [];
|
|
@@ -3544,7 +498,7 @@ class OmpRpcSession implements OmpRuntimeSession {
|
|
|
3544
498
|
const safeMessage = validateBoundedText(message, "prompt", MAX_TEXT_LENGTH);
|
|
3545
499
|
let acknowledgement: z.infer<typeof OmpPromptAckSchema> | undefined;
|
|
3546
500
|
const request = this.process.startRequest(
|
|
3547
|
-
|
|
501
|
+
buildOmpPromptRequest(safeMessage, images),
|
|
3548
502
|
undefined,
|
|
3549
503
|
(value) => {
|
|
3550
504
|
acknowledgement = OmpPromptAckSchema.parse(value) ?? {};
|
|
@@ -3558,7 +512,7 @@ class OmpRpcSession implements OmpRuntimeSession {
|
|
|
3558
512
|
|
|
3559
513
|
async steer(message: string, images: readonly OmpImage[] = []): Promise<void> {
|
|
3560
514
|
const safeMessage = validateBoundedText(message, "steer", MAX_TEXT_LENGTH);
|
|
3561
|
-
await this.process.
|
|
515
|
+
await this.process.request({
|
|
3562
516
|
type: "steer",
|
|
3563
517
|
message: safeMessage,
|
|
3564
518
|
...(images.length > 0 ? { images } : {}),
|
|
@@ -3566,7 +520,7 @@ class OmpRpcSession implements OmpRuntimeSession {
|
|
|
3566
520
|
}
|
|
3567
521
|
async followUp(message: string, images: readonly OmpImage[] = []): Promise<void> {
|
|
3568
522
|
const safeMessage = validateBoundedText(message, "follow-up", MAX_TEXT_LENGTH);
|
|
3569
|
-
await this.process.
|
|
523
|
+
await this.process.request({
|
|
3570
524
|
type: "follow_up",
|
|
3571
525
|
message: safeMessage,
|
|
3572
526
|
...(images.length > 0 ? { images } : {}),
|
|
@@ -3627,13 +581,14 @@ export class OmpRpcRuntime implements OmpRuntime {
|
|
|
3627
581
|
...transcript,
|
|
3628
582
|
messages: z
|
|
3629
583
|
.array(OmpMessageSchema)
|
|
3630
|
-
.max(
|
|
584
|
+
.max(MAX_REPLAY_MESSAGES)
|
|
3631
585
|
.parse(sanitizeMessageListMetadata(transcript.messages)),
|
|
3632
586
|
};
|
|
3633
587
|
}
|
|
3634
588
|
async readPersistedSubagentTranscript(options: {
|
|
3635
589
|
parentSessionFile: string;
|
|
3636
590
|
childTranscriptId: string;
|
|
591
|
+
sessionFile?: string;
|
|
3637
592
|
cwd: string;
|
|
3638
593
|
signal?: AbortSignal;
|
|
3639
594
|
}): Promise<OmpPersistedSubagentMessages> {
|
|
@@ -3642,12 +597,13 @@ export class OmpRpcRuntime implements OmpRuntime {
|
|
|
3642
597
|
options.childTranscriptId,
|
|
3643
598
|
options.cwd,
|
|
3644
599
|
options.signal,
|
|
600
|
+
options.sessionFile,
|
|
3645
601
|
);
|
|
3646
602
|
return {
|
|
3647
603
|
...transcript,
|
|
3648
604
|
messages: z
|
|
3649
605
|
.array(OmpMessageSchema)
|
|
3650
|
-
.max(
|
|
606
|
+
.max(MAX_REPLAY_MESSAGES)
|
|
3651
607
|
.parse(sanitizeMessageListMetadata(transcript.messages)),
|
|
3652
608
|
};
|
|
3653
609
|
}
|