@mininglamp-oss/octo-runtime-client 0.0.0-stage → 1.1.11

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.
Files changed (87) hide show
  1. package/LICENSE +190 -0
  2. package/README.md +38 -2
  3. package/build-info.json +5 -0
  4. package/dist/async-question-reply.d.ts +30 -0
  5. package/dist/async-question-reply.js +199 -0
  6. package/dist/browser.d.ts +1 -0
  7. package/dist/browser.js +1 -0
  8. package/dist/client.d.ts +10 -0
  9. package/dist/client.js +465 -0
  10. package/dist/index.d.ts +6 -0
  11. package/dist/index.js +6 -0
  12. package/dist/internal.d.ts +4 -0
  13. package/dist/internal.js +5 -0
  14. package/dist/invocation-lifecycle.d.ts +59 -0
  15. package/dist/invocation-lifecycle.js +287 -0
  16. package/dist/notification.d.ts +1 -0
  17. package/dist/notification.js +1 -0
  18. package/dist/protocol.d.ts +1 -0
  19. package/dist/protocol.js +1 -0
  20. package/dist/runtime-path.d.ts +21 -0
  21. package/dist/runtime-path.js +138 -0
  22. package/dist/startup-catalog.d.ts +8 -0
  23. package/dist/startup-catalog.js +65 -0
  24. package/dist/startup-diagnostics.d.ts +82 -0
  25. package/dist/startup-diagnostics.js +371 -0
  26. package/dist/submission-error.d.ts +10 -0
  27. package/dist/submission-error.js +23 -0
  28. package/dist/types.d.ts +72 -0
  29. package/dist/types.js +10 -0
  30. package/node_modules/@mininglamp-oss/octo-runtime-protocol/dist/actmode.d.ts +4 -0
  31. package/node_modules/@mininglamp-oss/octo-runtime-protocol/dist/actmode.js +36 -0
  32. package/node_modules/@mininglamp-oss/octo-runtime-protocol/dist/adapter-metadata.d.ts +7 -0
  33. package/node_modules/@mininglamp-oss/octo-runtime-protocol/dist/adapter-metadata.js +1 -0
  34. package/node_modules/@mininglamp-oss/octo-runtime-protocol/dist/browser.d.ts +7 -0
  35. package/node_modules/@mininglamp-oss/octo-runtime-protocol/dist/browser.js +6 -0
  36. package/node_modules/@mininglamp-oss/octo-runtime-protocol/dist/context-catalog.d.ts +11 -0
  37. package/node_modules/@mininglamp-oss/octo-runtime-protocol/dist/context-catalog.js +33 -0
  38. package/node_modules/@mininglamp-oss/octo-runtime-protocol/dist/context-configuration.d.ts +5 -0
  39. package/node_modules/@mininglamp-oss/octo-runtime-protocol/dist/context-configuration.js +58 -0
  40. package/node_modules/@mininglamp-oss/octo-runtime-protocol/dist/context-hash.d.ts +2 -0
  41. package/node_modules/@mininglamp-oss/octo-runtime-protocol/dist/context-hash.js +5 -0
  42. package/node_modules/@mininglamp-oss/octo-runtime-protocol/dist/context.d.ts +29 -0
  43. package/node_modules/@mininglamp-oss/octo-runtime-protocol/dist/context.js +566 -0
  44. package/node_modules/@mininglamp-oss/octo-runtime-protocol/dist/execution-plan.d.ts +3 -0
  45. package/node_modules/@mininglamp-oss/octo-runtime-protocol/dist/execution-plan.js +80 -0
  46. package/node_modules/@mininglamp-oss/octo-runtime-protocol/dist/experience.d.ts +4 -0
  47. package/node_modules/@mininglamp-oss/octo-runtime-protocol/dist/experience.js +166 -0
  48. package/node_modules/@mininglamp-oss/octo-runtime-protocol/dist/failure.d.ts +82 -0
  49. package/node_modules/@mininglamp-oss/octo-runtime-protocol/dist/failure.js +562 -0
  50. package/node_modules/@mininglamp-oss/octo-runtime-protocol/dist/generated/protocol-types.d.ts +3022 -0
  51. package/node_modules/@mininglamp-oss/octo-runtime-protocol/dist/generated/protocol-types.js +6 -0
  52. package/node_modules/@mininglamp-oss/octo-runtime-protocol/dist/index.d.ts +124 -0
  53. package/node_modules/@mininglamp-oss/octo-runtime-protocol/dist/index.js +709 -0
  54. package/node_modules/@mininglamp-oss/octo-runtime-protocol/dist/invoke-input.d.ts +129 -0
  55. package/node_modules/@mininglamp-oss/octo-runtime-protocol/dist/invoke-input.js +177 -0
  56. package/node_modules/@mininglamp-oss/octo-runtime-protocol/dist/invoke-size.d.ts +3 -0
  57. package/node_modules/@mininglamp-oss/octo-runtime-protocol/dist/invoke-size.js +11 -0
  58. package/node_modules/@mininglamp-oss/octo-runtime-protocol/dist/json-size.d.ts +2 -0
  59. package/node_modules/@mininglamp-oss/octo-runtime-protocol/dist/json-size.js +91 -0
  60. package/node_modules/@mininglamp-oss/octo-runtime-protocol/dist/model-context.d.ts +3 -0
  61. package/node_modules/@mininglamp-oss/octo-runtime-protocol/dist/model-context.js +9 -0
  62. package/node_modules/@mininglamp-oss/octo-runtime-protocol/dist/multimodal.d.ts +15 -0
  63. package/node_modules/@mininglamp-oss/octo-runtime-protocol/dist/multimodal.js +166 -0
  64. package/node_modules/@mininglamp-oss/octo-runtime-protocol/dist/notification.d.ts +2 -0
  65. package/node_modules/@mininglamp-oss/octo-runtime-protocol/dist/notification.js +137 -0
  66. package/node_modules/@mininglamp-oss/octo-runtime-protocol/dist/questions.d.ts +9 -0
  67. package/node_modules/@mininglamp-oss/octo-runtime-protocol/dist/questions.js +84 -0
  68. package/node_modules/@mininglamp-oss/octo-runtime-protocol/dist/run-protocol.d.ts +20 -0
  69. package/node_modules/@mininglamp-oss/octo-runtime-protocol/dist/run-protocol.js +294 -0
  70. package/node_modules/@mininglamp-oss/octo-runtime-protocol/dist/runtime-selection.d.ts +3 -0
  71. package/node_modules/@mininglamp-oss/octo-runtime-protocol/dist/runtime-selection.js +68 -0
  72. package/node_modules/@mininglamp-oss/octo-runtime-protocol/dist/service-processes.d.ts +5 -0
  73. package/node_modules/@mininglamp-oss/octo-runtime-protocol/dist/service-processes.js +65 -0
  74. package/node_modules/@mininglamp-oss/octo-runtime-protocol/dist/stream-limits.d.ts +27 -0
  75. package/node_modules/@mininglamp-oss/octo-runtime-protocol/dist/stream-limits.js +56 -0
  76. package/node_modules/@mininglamp-oss/octo-runtime-protocol/package.json +15 -0
  77. package/package.json +55 -4
  78. package/schema/context-configuration-fixtures.json +1219 -0
  79. package/schema/context-fixtures.json +5988 -0
  80. package/schema/kernel-client.behavior.json +56 -0
  81. package/schema/kernel-ipc-fixtures.json +53 -0
  82. package/schema/kernel-ipc.schema.json +538 -0
  83. package/schema/native-invoke-fixtures.json +116 -0
  84. package/schema/protocol.schema.json +4525 -0
  85. package/schema/run-protocol-fixtures.json +761 -0
  86. package/schema/runtime-selection-fixtures.json +663 -0
  87. package/schema/session-name-fixtures.json +191 -0
@@ -0,0 +1,709 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { assertAsyncQuestions, assertQuestionRequest } from "./questions.js";
3
+ export { assertAsyncQuestions, assertQuestionRequest, assertQuestionResponse, questionAnswerText } from "./questions.js";
4
+ import { assertSessionName } from "./invoke-input.js";
5
+ export { assertJsonByteLimit } from "./json-size.js";
6
+ export { INVOKE_REQUEST_MAX_BYTES, assertInvokeRequestSize } from "./invoke-size.js";
7
+ export * from "./stream-limits.js";
8
+ export { assertKernelStatus, assertServiceProcessSnapshot } from "./service-processes.js";
9
+ export * from "./run-protocol.js";
10
+ export * from "./invoke-input.js";
11
+ export * from "./model-context.js";
12
+ export * from "./execution-plan.js";
13
+ export { contextHash } from "./context-hash.js";
14
+ export { assertJsonValue } from "./context.js";
15
+ import { assertExperiencePayload } from "./experience.js";
16
+ export { PROVIDER_DIAGNOSTIC_MAX_BYTES } from "./experience.js";
17
+ import { assertModelInputParts, assertImageRecoveryParts, hasImageInput } from "./multimodal.js";
18
+ export { IMAGE_INPUT_LIMITS, IMAGE_INPUT_MIME_TYPES, assertModelInputParts, providerInputParts, hasImageInput, modelPartsFromInput } from "./multimodal.js";
19
+ import { assertAgentSource, assertApprovalMode, assertContextEnvelope, assertContextEnvs, assertJsonValue, MAX_RUN_TIMEOUT_MS, contextItem } from "./context.js";
20
+ export { APPROVAL_MODES, CONTEXT_SCHEMA, CONTEXT_MAX_BYTES, CONTEXT_MAX_ITEMS, DEFAULT_RUN_TIMEOUT_MS, MAX_RUN_TIMEOUT_MS, RUNTIME_RUN_CONTRACT, assertAgentSource, assertApprovalMode, assertContextEnvelope, assertContextEnvs, assertMcpServers, assertContextMcp, assertResolvedExecutionPlan, canonicalContext, contextItem, mergeContext } from "./context.js";
21
+ export { RUNTIME_SELECTION_SCHEMA, assertResolvedRuntimeSelection } from "./runtime-selection.js";
22
+ export { CONTEXT_TYPE_CATALOG, contextTypeDefinition, findContextTypeDefinition } from "./context-catalog.js";
23
+ export { assertContextConfiguration, resolveContextConfiguration } from "./context-configuration.js";
24
+ export { assertNotificationMessage } from "./notification.js";
25
+ export { FAILURE_CATALOG, classifyFailure, isCodexLocalFailureReason, isCodexLocalFailure, failureMessage, failurePayload, isFailureEnvelope } from "./failure.js";
26
+ /**
27
+ * The three terminal states a Run may reach.
28
+ *
29
+ * `finished` means normal completion, `error` means execution failed, and
30
+ * `stopped` means an explicit cancellation or an aborted stream. The Adapter
31
+ * Host guarantees every invocation delivers exactly one of these upstream.
32
+ */
33
+ export const PROTOCOL_VERSION = 2;
34
+ export function canonicalTerminalStatus(value) {
35
+ if (value === "completed" ||
36
+ value === "success" ||
37
+ value === "succeeded" ||
38
+ value === undefined ||
39
+ value === "")
40
+ return "completed";
41
+ if (value === "cancelled" ||
42
+ value === "stopped" ||
43
+ value === "interrupted" ||
44
+ value === "canceled")
45
+ return "cancelled";
46
+ return "failed";
47
+ }
48
+ export const TERMINAL_EVENT_TYPES = ["run.completed", "run.failed", "run.cancelled"];
49
+ /**
50
+ * The stable event set the Core can persist and upper-layer UIs can consume.
51
+ *
52
+ * Rough lifecycle: Run queued/running -> message, tool, surface and usage
53
+ * updates -> a Run terminal. Provider-native event names must be translated
54
+ * inside the adapter and must never leak through to this level.
55
+ */
56
+ export { contextActmode, assertExecutionEvidence } from "./actmode.js";
57
+ import { assertExecutionEvidence } from "./actmode.js";
58
+ export const CANONICAL_EVENT_TYPES = [
59
+ "session.update",
60
+ "execution.update",
61
+ "usage.update",
62
+ "model_step.started",
63
+ "model_step.finished",
64
+ "message.create",
65
+ "message.delta",
66
+ "message.update",
67
+ "tool_call.create",
68
+ "tool_call.delta",
69
+ "tool_call.update",
70
+ "activity.started",
71
+ "activity.updated",
72
+ "activity.finished",
73
+ "plan.updated",
74
+ "tool_call.progress",
75
+ "run.notice",
76
+ "interaction.requested",
77
+ "interaction.resolved",
78
+ "run.queued",
79
+ "run.started",
80
+ ...TERMINAL_EVENT_TYPES
81
+ ];
82
+ export const INTERACTION_RESPONSE_OUTCOMES = [
83
+ "accepted",
84
+ "declined",
85
+ "answered",
86
+ "cancelled"
87
+ ];
88
+ /** Provider adapters must use this accessor so hidden context is sent exactly once when present. */
89
+ export function providerInputText(request) {
90
+ return request.modelInput;
91
+ }
92
+ /** Inject trusted instructions only when starting a provider session, including a fresh restart. */
93
+ export function initialControlInstructions(request, freshSession = !request.providerSession?.sessionId) {
94
+ return freshSession ? request.controlInstructions : "";
95
+ }
96
+ /** Exact alternate input for a provider that could not resume its session. */
97
+ export function freshFallbackInputText(request) {
98
+ if (request.freshFallbackImagesUnavailable)
99
+ throw new TypeError("ARTIFACT_UNAVAILABLE: historical images cannot be restored");
100
+ return request.freshFallbackModelInput ?? providerInputText(request);
101
+ }
102
+ /** Runtime validation at the Agent Work plugin boundary; JSON schema remains the wire source of truth. */
103
+ export function assertChannelScope(value) {
104
+ if (!isRecord(value))
105
+ throw new TypeError("channel context scope must be an object");
106
+ for (const key of ["tenant_id", "endpoint_id", "channel", "bot_id", "conversation_id"]) {
107
+ if (typeof value[key] !== "string" || value[key].length === 0) {
108
+ throw new TypeError(`channel context scope.${key} must be a non-empty string`);
109
+ }
110
+ }
111
+ if (!(typeof value.account_id === "string" || value.account_id === null)) {
112
+ throw new TypeError("channel context scope.account_id must be a string or null");
113
+ }
114
+ if (value.thread_id !== undefined &&
115
+ value.thread_id !== null &&
116
+ typeof value.thread_id !== "string") {
117
+ throw new TypeError("channel context scope.thread_id must be a string, null, or omitted");
118
+ }
119
+ }
120
+ export function assertChannelMessageFact(value) {
121
+ if (!isRecord(value))
122
+ throw new TypeError("channel context fact must be an object");
123
+ if (typeof value.message_id !== "string" || value.message_id.length === 0) {
124
+ throw new TypeError("channel context fact.message_id must be a non-empty string");
125
+ }
126
+ if (value.message_seq !== undefined &&
127
+ (!Number.isSafeInteger(value.message_seq) || Number(value.message_seq) < 0)) {
128
+ throw new TypeError("channel context fact.message_seq must be a non-negative safe integer when present");
129
+ }
130
+ const sender = value.sender;
131
+ if (!isRecord(sender) || typeof sender.id !== "string" || sender.id.length === 0) {
132
+ throw new TypeError("channel context fact.sender.id must be a non-empty string");
133
+ }
134
+ if (typeof sender.kind !== "string" || sender.kind.length === 0) {
135
+ throw new TypeError("channel context fact.sender.kind must be a non-empty string");
136
+ }
137
+ if (sender.label !== undefined && typeof sender.label !== "string") {
138
+ throw new TypeError("channel context fact.sender.label must be a string when present");
139
+ }
140
+ if (!Number.isSafeInteger(value.occurred_at_ms) || Number(value.occurred_at_ms) <= 0) {
141
+ throw new TypeError("channel context fact.occurred_at_ms must be a positive safe integer");
142
+ }
143
+ if (!Array.isArray(value.parts) ||
144
+ value.parts.length === 0 ||
145
+ value.parts.some((part) => !isRecord(part) ||
146
+ typeof part.type !== "string" ||
147
+ part.type.length === 0 ||
148
+ (part.text !== undefined && typeof part.text !== "string"))) {
149
+ throw new TypeError("channel context fact.parts must contain at least one typed object");
150
+ }
151
+ if (value.flags !== undefined &&
152
+ (!isRecord(value.flags) ||
153
+ (value.flags.self !== undefined && typeof value.flags.self !== "boolean") ||
154
+ (value.flags.control !== undefined && typeof value.flags.control !== "boolean"))) {
155
+ throw new TypeError("channel context fact.flags must contain optional boolean self/control values");
156
+ }
157
+ }
158
+ export function assertChannelContextAppend(value) {
159
+ if (!isRecord(value))
160
+ throw new TypeError("channel context append must be an object");
161
+ if (typeof value.agent_id !== "string" ||
162
+ value.agent_id.length < 1 ||
163
+ value.agent_id.length > 96) {
164
+ throw new TypeError("channel context append.agent_id must contain 1..96 characters");
165
+ }
166
+ assertChannelScope(value.scope);
167
+ if (!Array.isArray(value.facts) || value.facts.length < 1 || value.facts.length > 100) {
168
+ throw new TypeError("channel context append.facts must contain 1..100 facts");
169
+ }
170
+ for (const fact of value.facts)
171
+ assertChannelMessageFact(fact);
172
+ if (value.gap_after_seq !== undefined &&
173
+ (!Number.isSafeInteger(value.gap_after_seq) || Number(value.gap_after_seq) < 0)) {
174
+ throw new TypeError("channel context append.gap_after_seq must be a non-negative safe integer when present");
175
+ }
176
+ }
177
+ export function assertContextSyncResult(value) {
178
+ if (!isRecord(value))
179
+ throw new TypeError("context sync result must be an object");
180
+ assertChannelScope(value.scope);
181
+ if (!Array.isArray(value.facts) || value.facts.length > 100) {
182
+ throw new TypeError("context sync result.facts must contain at most 100 facts");
183
+ }
184
+ for (const fact of value.facts)
185
+ assertChannelMessageFact(fact);
186
+ const result = value;
187
+ if (typeof result.request_id !== "string" || result.request_id.length === 0) {
188
+ throw new TypeError("context sync result.request_id must be a non-empty string");
189
+ }
190
+ if (!["complete", "partial", "unavailable"].includes(result.completeness)) {
191
+ throw new TypeError("context sync result.completeness is invalid");
192
+ }
193
+ if (result.completeness === "unavailable" && result.facts.length !== 0) {
194
+ throw new TypeError("unavailable context sync result must not contain facts");
195
+ }
196
+ if (result.through_seq !== undefined &&
197
+ (!Number.isSafeInteger(result.through_seq) || Number(result.through_seq) < 0)) {
198
+ throw new TypeError("context sync result.through_seq must be a non-negative safe integer when present");
199
+ }
200
+ }
201
+ export function assertChannelContextDescriptor(value) {
202
+ if (!isRecord(value))
203
+ throw new TypeError("channel context descriptor must be an object");
204
+ assertChannelScope(value.scope);
205
+ if (typeof value.consumer_key !== "string" || value.consumer_key.length === 0) {
206
+ throw new TypeError("channel context descriptor.consumer_key must be a non-empty string");
207
+ }
208
+ if (!Number.isSafeInteger(value.session_generation) || Number(value.session_generation) < 1) {
209
+ throw new TypeError("channel context descriptor.session_generation must be a positive safe integer");
210
+ }
211
+ if (typeof value.trigger_message_id !== "string" || value.trigger_message_id.length === 0) {
212
+ throw new TypeError("channel context descriptor.trigger_message_id must be a non-empty string");
213
+ }
214
+ assertChannelMessageFact(value.trigger_fact);
215
+ if (value.trigger_fact.message_id !== value.trigger_message_id) {
216
+ throw new TypeError("channel context descriptor trigger_fact.message_id must match trigger_message_id");
217
+ }
218
+ if (value.trigger_seq !== undefined &&
219
+ (!Number.isSafeInteger(value.trigger_seq) || Number(value.trigger_seq) < 0)) {
220
+ throw new TypeError("channel context descriptor.trigger_seq must be a non-negative safe integer when present");
221
+ }
222
+ const hasTriggerSeq = value.trigger_seq !== undefined;
223
+ const factHasTriggerSeq = value.trigger_fact.message_seq !== undefined;
224
+ if (hasTriggerSeq !== factHasTriggerSeq || value.trigger_fact.message_seq !== value.trigger_seq) {
225
+ throw new TypeError("channel context descriptor trigger_fact.message_seq must match trigger_seq");
226
+ }
227
+ }
228
+ export const TERMINAL_EVENTS = new Set(TERMINAL_EVENT_TYPES);
229
+ export const EVENT_TYPES = new Set(CANONICAL_EVENT_TYPES);
230
+ export const MESSAGE_EXPOSURES = new Set([
231
+ "public_reasoning",
232
+ "public_summary",
233
+ "public_plan",
234
+ "private_reasoning",
235
+ "unknown"
236
+ ]);
237
+ export const MESSAGE_CHANNELS = new Set([
238
+ "analysis",
239
+ "commentary",
240
+ "final"
241
+ ]);
242
+ /**
243
+ * Validate an adapter event and stamp it with the ID, sequence, timestamp and
244
+ * Run ownership that persistence requires. Returns a frozen object so callers
245
+ * cannot mutate it after append.
246
+ */
247
+ export function envelope(run, seq, event) {
248
+ assertCanonicalEvent(event);
249
+ // TypeScript loses the indexed type/payload relationship when a generic union is spread.
250
+ const value = {
251
+ protocolVersion: PROTOCOL_VERSION,
252
+ id: randomUUID(),
253
+ seq,
254
+ tsMs: Date.now(),
255
+ agentSessionId: run.agentSessionId,
256
+ runId: run.id,
257
+ type: event.type,
258
+ payload: event.payload ?? {}
259
+ };
260
+ return Object.freeze(value);
261
+ }
262
+ /** Assert the minimal runtime shape of a canonical event at the dynamic adapter boundary. */
263
+ export function assertCanonicalEvent(event) {
264
+ // Adapters are a plugin boundary, so runtime validation cannot rely on TypeScript's compile-time types alone.
265
+ if (!isRecord(event) ||
266
+ typeof event.type !== "string" ||
267
+ !EVENT_TYPES.has(event.type)) {
268
+ throw new TypeError(`unsupported canonical event: ${isRecord(event) ? String(event.type) : "undefined"}`);
269
+ }
270
+ if (event.payload !== undefined && !isRecord(event.payload)) {
271
+ throw new TypeError("event.payload must be an object");
272
+ }
273
+ if (event.protocolVersion !== PROTOCOL_VERSION)
274
+ throw new TypeError("canonical protocolVersion must be 2");
275
+ const payload = isRecord(event.payload) ? event.payload : {};
276
+ if (event.type.startsWith("message.")) {
277
+ if (payload.delivery !== undefined && payload.delivery !== "async")
278
+ throw new TypeError("INVALID_MESSAGE_DELIVERY");
279
+ if (payload.questions !== undefined) {
280
+ if (payload.delivery !== "async")
281
+ throw new TypeError("INVALID_MESSAGE_DELIVERY");
282
+ assertAsyncQuestions(payload.questions);
283
+ }
284
+ }
285
+ if (event.type === "execution.update")
286
+ assertExecutionEvidence(payload);
287
+ assertExperiencePayload(event.type, payload);
288
+ if (event.type === "run.failed" && payload.failure !== undefined) {
289
+ assertFailureEnvelope(payload.failure);
290
+ }
291
+ if (event.type.startsWith("message.")) {
292
+ if (payload.channel !== undefined &&
293
+ (typeof payload.channel !== "string" ||
294
+ !MESSAGE_CHANNELS.has(payload.channel))) {
295
+ throw new TypeError("message event payload.channel must be analysis, commentary, or final when present");
296
+ }
297
+ if (payload.channel === "analysis")
298
+ assertAnalysisExposure(payload);
299
+ }
300
+ if (event.type === "model_step.started" || event.type === "model_step.finished") {
301
+ requireEventString(payload, "stepId", event.type);
302
+ const timestampKey = event.type === "model_step.started" ? "startedAtMs" : "endedAtMs";
303
+ if (payload[timestampKey] !== undefined &&
304
+ (!Number.isSafeInteger(payload[timestampKey]) || Number(payload[timestampKey]) <= 0)) {
305
+ throw new TypeError(`${event.type} payload.${timestampKey} must be a positive safe integer when present`);
306
+ }
307
+ }
308
+ if (event.type === "model_step.finished") {
309
+ requireEventString(payload, "status", event.type);
310
+ if (payload.finishReason !== undefined && typeof payload.finishReason !== "string") {
311
+ throw new TypeError("model_step.finished payload.finishReason must be a string when present");
312
+ }
313
+ if (payload.publicText !== undefined)
314
+ throw new TypeError("model_step.finished payload.publicText is forbidden");
315
+ if (!["completed", "failed", "cancelled"].includes(String(payload.status)))
316
+ throw new TypeError("model_step.finished payload.status is invalid");
317
+ }
318
+ if (event.type === "tool_call.create") {
319
+ requireEventString(payload, "id", event.type);
320
+ requireEventString(payload, "name", event.type);
321
+ if (!isRecord(payload.args))
322
+ throw new TypeError("tool_call.create payload.args must be an object");
323
+ if (payload.status !== "running")
324
+ throw new TypeError("tool_call.create payload.status must be running");
325
+ }
326
+ if (event.type === "tool_call.delta") {
327
+ requireEventString(payload, "id", event.type);
328
+ const hasArgs = typeof payload.argsDelta === "string";
329
+ const hasOutput = typeof payload.outputDelta === "string" || typeof payload.outputSnapshot === "string";
330
+ if (!hasArgs && !hasOutput) {
331
+ throw new TypeError("tool_call.delta requires argsDelta, outputDelta or outputSnapshot");
332
+ }
333
+ }
334
+ if (event.type === "tool_call.update") {
335
+ requireEventString(payload, "id", event.type);
336
+ requireEventString(payload, "status", event.type);
337
+ if (!["completed", "failed", "cancelled"].includes(String(payload.status))) {
338
+ throw new TypeError("tool_call.update payload.status must be terminal");
339
+ }
340
+ }
341
+ if (event.type === "interaction.requested") {
342
+ requireEventString(payload, "interactionId", event.type);
343
+ requireEventString(payload, "executionInstanceId", event.type);
344
+ if (payload.delivery !== "async" || payload.expiresAt !== undefined)
345
+ requireEventString(payload, "expiresAt", event.type);
346
+ if (Array.from(String(payload.interactionId)).length > 191 ||
347
+ Array.from(String(payload.executionInstanceId)).length > 191 ||
348
+ (payload.expiresAt !== undefined && !isInteractionTimestamp(payload.expiresAt)))
349
+ throw new TypeError("interaction.requested instance or expiry is invalid");
350
+ if (payload.kind !== "approval" &&
351
+ payload.kind !== "question" &&
352
+ payload.kind !== "elicitation") {
353
+ throw new TypeError("interaction.requested payload.kind is unsupported");
354
+ }
355
+ if (!isRecord(payload.request))
356
+ throw new TypeError("interaction.requested payload.request must be an object");
357
+ if (payload.delivery !== undefined) {
358
+ if (payload.delivery !== "async" || payload.kind !== "question")
359
+ throw new TypeError("INVALID_INTERACTION_DELIVERY");
360
+ assertQuestionRequest(payload.request);
361
+ }
362
+ if ((Object.hasOwn(payload, "responseRequired") &&
363
+ typeof payload.responseRequired !== "boolean") ||
364
+ (payload.responseRequired === false && payload.kind !== "approval"))
365
+ throw new TypeError("interaction.requested responseRequired is invalid");
366
+ if (payload.kind === "approval")
367
+ assertApprovalRequest(payload.request);
368
+ }
369
+ if (event.type === "interaction.resolved") {
370
+ requireEventString(payload, "interactionId", event.type);
371
+ requireEventString(payload, "executionInstanceId", event.type);
372
+ if (Array.from(String(payload.interactionId)).length > 191 ||
373
+ Array.from(String(payload.executionInstanceId)).length > 191)
374
+ throw new TypeError("interaction.resolved instance is invalid");
375
+ if (!["resolved", "expired", "invalidated"].includes(String(payload.status)))
376
+ throw new TypeError("interaction.resolved payload.status is invalid");
377
+ if (payload.status === "resolved") {
378
+ if (!INTERACTION_RESPONSE_OUTCOMES.includes(payload.outcome))
379
+ throw new TypeError("interaction.resolved payload.outcome is invalid");
380
+ }
381
+ else
382
+ requireEventString(payload, "reasonCode", event.type);
383
+ }
384
+ }
385
+ function isInteractionTimestamp(value) {
386
+ const matched = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.\d+)?(Z|[+-]\d{2}:\d{2})$/.exec(value);
387
+ if (!matched)
388
+ return false;
389
+ const [, year, month, day, hour, minute, second, offset] = matched;
390
+ const calendar = new Date(0);
391
+ calendar.setUTCFullYear(Number(year), Number(month) - 1, Number(day));
392
+ if (calendar.getUTCFullYear() !== Number(year) ||
393
+ calendar.getUTCMonth() !== Number(month) - 1 ||
394
+ calendar.getUTCDate() !== Number(day))
395
+ return false;
396
+ if (Number(hour) > 23 || Number(minute) > 59 || Number(second) > 59)
397
+ return false;
398
+ if (offset !== "Z" && (Number(offset.slice(1, 3)) > 23 || Number(offset.slice(4)) > 59))
399
+ return false;
400
+ return Number.isFinite(Date.parse(value));
401
+ }
402
+ export function assertApprovalRequest(value) {
403
+ assertJsonValue(value);
404
+ if (!isRecord(value) || value.schema !== "octo.approval-request/v1")
405
+ throw new TypeError("INVALID_APPROVAL_REQUEST");
406
+ const allowed = [
407
+ "schema",
408
+ "policy",
409
+ "operation",
410
+ "requestedPermissions",
411
+ "reason",
412
+ "allowedOutcomes",
413
+ "grantScope",
414
+ "providerDetails"
415
+ ];
416
+ if (Object.keys(value).some((property) => !allowed.includes(property)))
417
+ throw new TypeError("INVALID_APPROVAL_REQUEST");
418
+ if (Buffer.byteLength(JSON.stringify(value), "utf8") > 65_536)
419
+ throw new TypeError("INVALID_APPROVAL_REQUEST");
420
+ if (!isRecord(value.policy) ||
421
+ Object.keys(value.policy).some((property) => !["mode", "hash"].includes(property)))
422
+ throw new TypeError("INVALID_APPROVAL_REQUEST");
423
+ assertApprovalMode(value.policy.mode);
424
+ if (typeof value.policy.hash !== "string" || !/^sha256:[a-f0-9]{64}$/.test(value.policy.hash))
425
+ throw new TypeError("INVALID_APPROVAL_REQUEST");
426
+ if (!isRecord(value.operation) ||
427
+ ![
428
+ "command_execution",
429
+ "file_change",
430
+ "tool_execution",
431
+ "permission_grant",
432
+ "context_access"
433
+ ].includes(String(value.operation.kind)))
434
+ throw new TypeError("INVALID_APPROVAL_REQUEST");
435
+ if (Object.keys(value.operation).some((property) => !["kind", "summary", "toolCallId", "details"].includes(property)) ||
436
+ typeof value.operation.summary !== "string" ||
437
+ !value.operation.summary ||
438
+ value.operation.summary.length > 8192)
439
+ throw new TypeError("INVALID_APPROVAL_REQUEST");
440
+ if (value.operation.toolCallId !== undefined &&
441
+ (typeof value.operation.toolCallId !== "string" || !value.operation.toolCallId))
442
+ throw new TypeError("INVALID_APPROVAL_REQUEST");
443
+ if (value.grantScope !== "request" && value.grantScope !== "provider")
444
+ throw new TypeError("APPROVAL_GRANT_SCOPE_UNSUPPORTED");
445
+ if (value.grantScope === "provider") {
446
+ const details = value.providerDetails;
447
+ if (!isRecord(details) ||
448
+ details.provider !== "openclaw" ||
449
+ details.method !== "exec.approval.resolve" ||
450
+ !Array.isArray(details.options) ||
451
+ !details.options.length ||
452
+ !details.options.some((option) => isRecord(option) && option.optionId === "allow-always" && option.kind === "allow_always") ||
453
+ details.options.some((option) => !isRecord(option) ||
454
+ typeof option.optionId !== "string" ||
455
+ typeof option.kind !== "string" ||
456
+ !["allow-once:allow_once", "allow-always:allow_always", "deny:reject_once"].includes(`${option.optionId}:${option.kind}`)))
457
+ throw new TypeError("APPROVAL_GRANT_SCOPE_UNSUPPORTED");
458
+ }
459
+ if (!Array.isArray(value.requestedPermissions))
460
+ throw new TypeError("INVALID_APPROVAL_REQUEST");
461
+ if (value.requestedPermissions.length)
462
+ throw new TypeError("APPROVAL_PERMISSION_UNSUPPORTED");
463
+ if (!Array.isArray(value.allowedOutcomes) ||
464
+ !value.allowedOutcomes.length ||
465
+ new Set(value.allowedOutcomes).size !== value.allowedOutcomes.length ||
466
+ value.allowedOutcomes.some((outcome) => !["accepted", "declined", "cancelled"].includes(String(outcome))))
467
+ throw new TypeError("INVALID_APPROVAL_REQUEST");
468
+ if (value.reason !== undefined && typeof value.reason !== "string")
469
+ throw new TypeError("INVALID_APPROVAL_REQUEST");
470
+ for (const details of [value.providerDetails, value.operation.details]) {
471
+ if (details === undefined)
472
+ continue;
473
+ const displayFields = [
474
+ "provider",
475
+ "method",
476
+ "itemId",
477
+ "approvalId",
478
+ "reason",
479
+ "command",
480
+ "cwd",
481
+ "grantRoot",
482
+ "files",
483
+ "toolCallId",
484
+ "title",
485
+ "description",
486
+ "options"
487
+ ];
488
+ if (!isRecord(details) ||
489
+ Object.keys(details).some((property) => !displayFields.includes(property)))
490
+ throw new TypeError("APPROVAL_DISPLAY_FIELD_UNSUPPORTED");
491
+ }
492
+ }
493
+ /** Validate a response before it crosses the process or provider boundary. */
494
+ export function assertInteractionResponse(value) {
495
+ if (!isRecord(value))
496
+ throw new TypeError("interaction response must be an object");
497
+ if (typeof value.interactionId !== "string" ||
498
+ value.interactionId.length === 0 ||
499
+ Array.from(value.interactionId).length > 191) {
500
+ throw new TypeError("interaction response interactionId must be 1-191 characters");
501
+ }
502
+ if (typeof value.outcome !== "string" ||
503
+ !INTERACTION_RESPONSE_OUTCOMES.includes(value.outcome)) {
504
+ throw new TypeError("interaction response outcome is unsupported");
505
+ }
506
+ }
507
+ /** Validate a structured failure at the dynamic adapter boundary. */
508
+ export function assertFailureEnvelope(value) {
509
+ if (!isRecord(value))
510
+ throw new TypeError("failure must be an object");
511
+ if (value.schemaVersion !== 1)
512
+ throw new TypeError("failure.schemaVersion must be 1");
513
+ if (typeof value.code !== "string" || !/^OH-[A-Z]{3}-\d{4}$/.test(value.code)) {
514
+ throw new TypeError("failure.code must be a global OH error code");
515
+ }
516
+ for (const key of [
517
+ "reasonCode",
518
+ "category",
519
+ "stage",
520
+ "messageKey",
521
+ "diagnosticId",
522
+ "occurredAt"
523
+ ]) {
524
+ if (typeof value[key] !== "string" || value[key].length === 0) {
525
+ throw new TypeError(`failure.${key} must be a non-empty string`);
526
+ }
527
+ }
528
+ if (!/^diag_[0-9a-f]{32}$/.test(value.diagnosticId)) {
529
+ throw new TypeError("failure.diagnosticId must be a non-identifying diagnostic ID");
530
+ }
531
+ if (Number.isNaN(Date.parse(value.occurredAt)))
532
+ throw new TypeError("failure.occurredAt must be an ISO timestamp");
533
+ if (!isRecord(value.params))
534
+ throw new TypeError("failure.params must be an object");
535
+ if (typeof value.retryable !== "boolean")
536
+ throw new TypeError("failure.retryable must be a boolean");
537
+ if (value.retryAfterMs !== undefined &&
538
+ (!Number.isSafeInteger(value.retryAfterMs) || Number(value.retryAfterMs) < 0)) {
539
+ throw new TypeError("failure.retryAfterMs must be a non-negative safe integer");
540
+ }
541
+ if (!isRecord(value.action) ||
542
+ typeof value.action.type !== "string" ||
543
+ typeof value.action.labelKey !== "string") {
544
+ throw new TypeError("failure.action must be a catalog action");
545
+ }
546
+ if (!isRecord(value.source) ||
547
+ (value.source.component !== "octo-runtime" && value.source.component !== "octo-agent-worker")) {
548
+ throw new TypeError("failure.source.component must be an approved failure producer");
549
+ }
550
+ if (value.cause !== undefined &&
551
+ (!isRecord(value.cause) || typeof value.cause.reasonCode !== "string")) {
552
+ throw new TypeError("failure.cause must contain reasonCode");
553
+ }
554
+ }
555
+ /** Assert the execution identifiers and path fields that must be present before the Host calls an adapter. */
556
+ export function assertInvokeRequest(request) {
557
+ if (!isRecord(request))
558
+ throw new TypeError("InvokeRequest must be an object");
559
+ if (request.sessionName !== undefined)
560
+ assertSessionName(request.sessionName);
561
+ if (request.protocolVersion !== PROTOCOL_VERSION)
562
+ throw new TypeError("InvokeRequest.protocolVersion must be 2");
563
+ if ("appendInstructions" in request)
564
+ throw new TypeError("LEGACY_RUN_CONTRACT_REJECTED");
565
+ assertAgentSource(request.agentSource);
566
+ assertContextEnvelope(request.context);
567
+ if (request.executionEnvOverrides !== undefined) {
568
+ assertContextEnvs(request.executionEnvOverrides);
569
+ if (Buffer.byteLength(JSON.stringify(request.executionEnvOverrides)) > 65_536)
570
+ throw new TypeError("EXECUTION_ENV_TOO_LARGE");
571
+ }
572
+ assertApprovalMode(request.approvalMode);
573
+ const modelChoices = contextItem(request.context, "octo.execution", "model")?.payload;
574
+ const approvalPolicy = contextItem(request.context, "octo.security", "approval_policy")?.payload;
575
+ if (approvalPolicy && approvalPolicy.mode !== request.approvalMode)
576
+ throw new TypeError("CONTEXT_PLAN_MISMATCH");
577
+ const agentConfig = request.agentConfig;
578
+ if (isRecord(agentConfig) &&
579
+ ["autoApprove", "dangerouslyBypassPermissions", "permissionMode"].some((key) => key in agentConfig)) {
580
+ throw new TypeError("LEGACY_PERMISSION_CONFIG_REJECTED");
581
+ }
582
+ if (isRecord(agentConfig) && "thinking" in agentConfig)
583
+ throw new TypeError("LEGACY_REASONING_CONFIG_REJECTED");
584
+ for (const selection of [
585
+ { name: "selection", property: "id", config: "model" },
586
+ { name: "reasoning", property: "effort", config: "effort" }
587
+ ]) {
588
+ const selected = modelChoices?.[selection.name];
589
+ if (selected &&
590
+ selected[selection.property] !==
591
+ (isRecord(agentConfig) ? agentConfig[selection.config] : undefined))
592
+ throw new TypeError("CONTEXT_PLAN_MISMATCH");
593
+ }
594
+ const workspace = contextItem(request.context, "octo.execution", "workspace")?.payload;
595
+ if (workspace?.mode === "fixed" && workspace.path !== request.workdir)
596
+ throw new TypeError("CONTEXT_PLAN_MISMATCH");
597
+ if (["skills", "skillRoots", "providerStateDir", "mcpServers"].some((key) => Object.hasOwn(request, key)))
598
+ throw new TypeError("LOCAL_PLUGIN_CONFIGURATION_REQUIRED");
599
+ if (typeof request.controlInstructions !== "string")
600
+ throw new TypeError("InvokeRequest.controlInstructions must be a string");
601
+ if (request.runtimeLimits !== undefined)
602
+ assertRuntimeLimits(request.runtimeLimits);
603
+ for (const key of ["invocationId", "agentSessionId", "runId", "prompt"]) {
604
+ if (typeof request[key] !== "string" || request[key].length === 0) {
605
+ throw new TypeError(`InvokeRequest.${key} must be a non-empty string`);
606
+ }
607
+ }
608
+ const providerWorkspace = contextItem(request.context, "octo.execution", "adapter")?.payload.type === "openclaw";
609
+ if (typeof request.workdir !== "string" ||
610
+ (!request.workdir && request.agentSource !== "preset" && !providerWorkspace))
611
+ throw new TypeError("InvokeRequest.workdir is required for provisioned execution");
612
+ if (typeof request.modelInput !== "string" || request.modelInput.length === 0) {
613
+ throw new TypeError("InvokeRequest.modelInput must be a non-empty string");
614
+ }
615
+ if (request.modelInputParts !== undefined)
616
+ assertModelInputParts(request.modelInputParts);
617
+ if (request.freshFallbackModelInputParts !== undefined)
618
+ assertModelInputParts(request.freshFallbackModelInputParts);
619
+ if (request.freshFallbackImagesUnavailable !== undefined &&
620
+ typeof request.freshFallbackImagesUnavailable !== "boolean")
621
+ throw new TypeError("INPUT_PARTS_INVALID");
622
+ const imageInput = {
623
+ modelInputParts: request.modelInputParts,
624
+ freshFallbackModelInputParts: request.freshFallbackModelInputParts,
625
+ freshFallbackModelInput: typeof request.freshFallbackModelInput === "string"
626
+ ? request.freshFallbackModelInput
627
+ : undefined
628
+ };
629
+ if (hasImageInput(imageInput) &&
630
+ request.freshFallbackModelInput !== undefined &&
631
+ request.freshFallbackModelInputParts === undefined) {
632
+ throw new TypeError("IMAGE_RECOVERY_INPUT_REQUIRED");
633
+ }
634
+ assertImageRecoveryParts(imageInput);
635
+ if (request.freshFallbackModelInput !== undefined &&
636
+ (typeof request.freshFallbackModelInput !== "string" ||
637
+ request.freshFallbackModelInput.length === 0)) {
638
+ throw new TypeError("InvokeRequest.freshFallbackModelInput must be a non-empty string when present");
639
+ }
640
+ }
641
+ export function assertRuntimeLimits(value) {
642
+ if (!isRecord(value))
643
+ throw new TypeError("runtimeLimits must be an object");
644
+ const bounds = {
645
+ firstFrameTimeoutMs: 1_800_000,
646
+ modelIdleTimeoutMs: 3_600_000,
647
+ modelIdleWarningMs: 3_600_000,
648
+ toolTimeoutMs: 86_400_000,
649
+ toolWallTimeoutMs: 86_400_000,
650
+ runTimeoutMs: MAX_RUN_TIMEOUT_MS,
651
+ cancelGraceMs: 60_000
652
+ };
653
+ for (const [key, setting] of Object.entries(value)) {
654
+ if (key === "enforcement" || key === "modelIdlePolicy") {
655
+ if (setting !== "enforce" && setting !== "observe")
656
+ throw new TypeError(`invalid runtimeLimits.${key}`);
657
+ }
658
+ else if (!(key in bounds) ||
659
+ typeof setting !== "number" ||
660
+ !Number.isSafeInteger(setting) ||
661
+ setting < 100 ||
662
+ setting > bounds[key]) {
663
+ throw new TypeError(`invalid runtimeLimits.${key}`);
664
+ }
665
+ }
666
+ if (value.enforcement === "observe" &&
667
+ ["modelIdlePolicy", "modelIdleWarningMs", "toolWallTimeoutMs"].some((key) => value[key] !== undefined)) {
668
+ throw new TypeError("legacy enforcement=observe cannot be combined with watchdog v2 settings; use modelIdlePolicy=observe with enforcement=enforce");
669
+ }
670
+ }
671
+ /** Type guard: whether an event ends the current Run. */
672
+ export function isTerminal(event) {
673
+ return TERMINAL_EVENTS.has(event.type);
674
+ }
675
+ /** Missing or malformed analysis metadata is never treated as public. */
676
+ export function messageExposure(event) {
677
+ if (!event.type.startsWith("message.") || event.payload.channel !== "analysis")
678
+ return "unknown";
679
+ const exposure = event.payload.exposure;
680
+ return typeof exposure === "string" && MESSAGE_EXPOSURES.has(exposure) ? exposure : "unknown";
681
+ }
682
+ function isRecord(value) {
683
+ return value !== null && typeof value === "object" && !Array.isArray(value);
684
+ }
685
+ function requireEventString(payload, key, eventType) {
686
+ if (typeof payload[key] !== "string" || payload[key].length === 0) {
687
+ throw new TypeError(`${eventType} payload.${key} must be a non-empty string`);
688
+ }
689
+ }
690
+ function assertAnalysisExposure(payload) {
691
+ if (payload.exposure !== undefined &&
692
+ (typeof payload.exposure !== "string" ||
693
+ !MESSAGE_EXPOSURES.has(payload.exposure))) {
694
+ throw new TypeError("analysis message payload.exposure is invalid");
695
+ }
696
+ if (payload.exposure !== undefined && !isRecord(payload.provenance)) {
697
+ throw new TypeError("analysis message with explicit exposure requires payload.provenance");
698
+ }
699
+ if (payload.provenance === undefined)
700
+ return;
701
+ if (!isRecord(payload.provenance)) {
702
+ throw new TypeError("analysis message payload.provenance must be an object");
703
+ }
704
+ for (const key of ["provider", "nativeEvent"]) {
705
+ if (typeof payload.provenance[key] !== "string" || payload.provenance[key].length === 0) {
706
+ throw new TypeError(`analysis message payload.provenance.${key} must be a non-empty string`);
707
+ }
708
+ }
709
+ }