@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,137 @@
1
+ const keys = [
2
+ "schema",
3
+ "eventId",
4
+ "event",
5
+ "occurredAt",
6
+ "projectId",
7
+ "sourceId",
8
+ "title",
9
+ "text"
10
+ ];
11
+ const events = new Set([
12
+ "task.event",
13
+ "execution.succeeded",
14
+ "execution.failed",
15
+ "notification.test"
16
+ ]);
17
+ const taskIds = [
18
+ "evt_type",
19
+ "task",
20
+ "taskEvent",
21
+ "timeline",
22
+ "timelineTruncated",
23
+ "timelineThroughSequence"
24
+ ];
25
+ export function assertNotificationMessage(value) {
26
+ if (!value || typeof value !== "object" || Array.isArray(value))
27
+ throw new Error("notification must be an object");
28
+ const message = value;
29
+ if (Object.keys(message).some((key) => !keys.includes(key) && !taskIds.includes(key)) ||
30
+ keys.some((key) => typeof message[key] !== "string")) {
31
+ throw new Error("notification contains missing or unsupported fields");
32
+ }
33
+ for (const key of keys) {
34
+ for (const scalar of message[key]) {
35
+ const code = scalar.codePointAt(0);
36
+ if (code >= 0xd800 && code <= 0xdfff)
37
+ throw new Error("notification contains an unpaired surrogate");
38
+ }
39
+ }
40
+ if (message.schema !== "octo.notification.v2" || !events.has(message.event))
41
+ throw new Error("unsupported notification schema or event");
42
+ if (message.event === "task.event") {
43
+ validateTaskNotification(message);
44
+ }
45
+ else if (taskIds.some((key) => key in message)) {
46
+ throw new Error("task fields require a task event");
47
+ }
48
+ for (const [key, max, min] of [
49
+ ["eventId", 256, 1],
50
+ ["projectId", 128, 1],
51
+ ["sourceId", 128, 1],
52
+ ["title", 256, 1],
53
+ ["text", 4096, 0]
54
+ ]) {
55
+ const length = Array.from(message[key]).length;
56
+ if (length < min || length > max)
57
+ throw new Error(`invalid notification ${key}`);
58
+ }
59
+ const timestamp = message.occurredAt;
60
+ if (!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/.test(timestamp) ||
61
+ !Number.isFinite(Date.parse(timestamp))) {
62
+ throw new Error("invalid notification occurredAt");
63
+ }
64
+ }
65
+ function object(value) {
66
+ if (!value || typeof value !== "object" || Array.isArray(value))
67
+ throw new Error("invalid task notification object");
68
+ return value;
69
+ }
70
+ function knownFields(value, required, optional = []) {
71
+ if (required.some((key) => !(key in value)) ||
72
+ Object.keys(value).some((key) => !required.includes(key) && !optional.includes(key)))
73
+ throw new Error("invalid task notification fields");
74
+ }
75
+ function eventType(value) {
76
+ return Number.isInteger(value) && Number(value) >= 101 && Number(value) <= 115;
77
+ }
78
+ function sequence(value) {
79
+ return Number.isSafeInteger(value) && Number(value) > 0;
80
+ }
81
+ function validateTaskNotification(message) {
82
+ const task = object(message.task), event = object(message.taskEvent);
83
+ knownFields(task, ["id", "title", "status", "row_version", "handler_epoch"], ["responsible_party", "current_handler"]);
84
+ knownFields(event, ["id", "sequence", "evt_type", "actor", "summary", "payload"], ["task_conversation_id", "source_run_id"]);
85
+ if (task.id !== message.sourceId ||
86
+ typeof task.title !== "string" ||
87
+ !["backlog", "todo", "in_progress", "done", "cancelled"].includes(String(task.status)) ||
88
+ !sequence(task.row_version) ||
89
+ !sequence(task.handler_epoch))
90
+ throw new Error("invalid task snapshot");
91
+ const actor = object(event.actor);
92
+ if (typeof actor.type !== "string" ||
93
+ typeof actor.id !== "string" ||
94
+ typeof event.summary !== "string")
95
+ throw new Error("invalid event actor or summary");
96
+ object(event.payload);
97
+ if (!eventType(message.evt_type) ||
98
+ message.evt_type !== event.evt_type ||
99
+ event.id !== message.eventId ||
100
+ !sequence(event.sequence) ||
101
+ message.timelineThroughSequence !== event.sequence)
102
+ throw new Error("invalid task event identity");
103
+ if (message.timelineTruncated !== undefined && typeof message.timelineTruncated !== "boolean")
104
+ throw new Error("invalid timeline truncation");
105
+ if (!Array.isArray(message.timeline) || message.timeline.length > 20)
106
+ throw new Error("invalid task timeline");
107
+ let previous = 0;
108
+ for (const value of message.timeline) {
109
+ const item = object(value);
110
+ knownFields(item, ["eventId", "sequence", "evt_type", "summary"]);
111
+ if (typeof item.eventId !== "string" ||
112
+ typeof item.summary !== "string" ||
113
+ !eventType(item.evt_type) ||
114
+ !sequence(item.sequence) ||
115
+ Number(item.sequence) <= previous ||
116
+ Number(item.sequence) > Number(event.sequence))
117
+ throw new Error("invalid task timeline order");
118
+ previous = Number(item.sequence);
119
+ }
120
+ checkScalars(message);
121
+ }
122
+ function checkScalars(value) {
123
+ if (typeof value === "string") {
124
+ for (const char of value) {
125
+ const code = char.codePointAt(0);
126
+ if (code >= 0xd800 && code <= 0xdfff)
127
+ throw new Error("notification contains an unpaired surrogate");
128
+ }
129
+ }
130
+ else if (Array.isArray(value))
131
+ value.forEach(checkScalars);
132
+ else if (value && typeof value === "object")
133
+ for (const [key, item] of Object.entries(value)) {
134
+ checkScalars(key);
135
+ checkScalars(item);
136
+ }
137
+ }
@@ -0,0 +1,9 @@
1
+ import type { AsyncUserInputQuestion, QuestionRequest, QuestionResponse } from "./generated/protocol-types.js";
2
+ /** 校验公开问题快照;不裁剪选项或把损坏的表单降为普通文字。 */
3
+ export declare function assertAsyncQuestions(value: unknown): asserts value is AsyncUserInputQuestion[];
4
+ /** 异步表单由交互事件授权回答;消息快照本身不代表仍有活动轮次。 */
5
+ export declare function assertQuestionRequest(value: unknown): asserts value is QuestionRequest;
6
+ /** 只校验响应形状;题目归属、漏题和排序由持有原始表单的 Adapter 核对。 */
7
+ export declare function assertQuestionResponse(value: unknown): asserts value is QuestionResponse;
8
+ /** 两条补充路径使用同一原题关联;只校验并编码数据,不解析正文或执行模板。 */
9
+ export declare function questionAnswerText(request: QuestionRequest, value: unknown): string;
@@ -0,0 +1,84 @@
1
+ import { assertJsonValue } from "./context.js";
2
+ const invalidResponse = () => Object.assign(new TypeError("INVALID_QUESTION_RESPONSE"), { code: "INVALID_QUESTION_RESPONSE" });
3
+ const record = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
4
+ const text = (value) => typeof value === "string" && value.trim().length > 0 && Array.from(value).length <= 4096;
5
+ const fields = (value, allowed) => Object.keys(value).every((key) => allowed.includes(key));
6
+ /** 校验公开问题快照;不裁剪选项或把损坏的表单降为普通文字。 */
7
+ export function assertAsyncQuestions(value) {
8
+ assertJsonValue(value);
9
+ if (!Array.isArray(value) ||
10
+ value.length > 50 ||
11
+ value.some((question) => !record(question) ||
12
+ !fields(question, ["title", "options"]) ||
13
+ !text(question.title) ||
14
+ !(question.options === null ||
15
+ (Array.isArray(question.options) &&
16
+ question.options.length <= 50 &&
17
+ question.options.every(text)))))
18
+ throw new TypeError("INVALID_ASYNC_QUESTIONS");
19
+ }
20
+ /** 异步表单由交互事件授权回答;消息快照本身不代表仍有活动轮次。 */
21
+ export function assertQuestionRequest(value) {
22
+ assertJsonValue(value);
23
+ if (!record(value) ||
24
+ !fields(value, ["schema", "messageId", "questions"]) ||
25
+ value.schema !== "octo.question-request/v1" ||
26
+ typeof value.messageId !== "string" ||
27
+ !value.messageId ||
28
+ !Array.isArray(value.questions) ||
29
+ !value.questions.length ||
30
+ value.questions.length > 50)
31
+ throw new TypeError("INVALID_QUESTION_REQUEST");
32
+ for (const [index, q] of value.questions.entries()) {
33
+ if (!record(q) ||
34
+ !fields(q, ["id", "title", "options", "allowCustom"]) ||
35
+ q.id !== `q:${index}` ||
36
+ q.allowCustom !== true)
37
+ throw new TypeError("INVALID_QUESTION_REQUEST");
38
+ assertAsyncQuestions([{ title: q.title, options: q.options }]);
39
+ }
40
+ }
41
+ /** 只校验响应形状;题目归属、漏题和排序由持有原始表单的 Adapter 核对。 */
42
+ export function assertQuestionResponse(value) {
43
+ try {
44
+ assertJsonValue(value);
45
+ }
46
+ catch {
47
+ throw invalidResponse();
48
+ }
49
+ if (!record(value) ||
50
+ !fields(value, ["schema", "answers"]) ||
51
+ value.schema !== "octo.question-response/v1" ||
52
+ !Array.isArray(value.answers) ||
53
+ !value.answers.length ||
54
+ value.answers.length > 50)
55
+ throw invalidResponse();
56
+ const ids = new Set();
57
+ for (const answer of value.answers) {
58
+ if (!record(answer) ||
59
+ !fields(answer, ["questionId", "text"]) ||
60
+ typeof answer.questionId !== "string" ||
61
+ !/^q:(0|[1-9][0-9]?)$/.test(answer.questionId) ||
62
+ ids.has(answer.questionId) ||
63
+ !text(answer.text))
64
+ throw invalidResponse();
65
+ ids.add(answer.questionId);
66
+ }
67
+ }
68
+ /** 两条补充路径使用同一原题关联;只校验并编码数据,不解析正文或执行模板。 */
69
+ export function questionAnswerText(request, value) {
70
+ assertQuestionRequest(request);
71
+ assertQuestionResponse(value);
72
+ if (value.answers.length !== request.questions.length ||
73
+ value.answers.some((answer) => !request.questions.some((q) => q.id === answer.questionId)))
74
+ throw invalidResponse();
75
+ return ("Answer to async user questions:\n" +
76
+ JSON.stringify({
77
+ messageId: request.messageId,
78
+ answers: request.questions.map((q) => ({
79
+ questionId: q.id,
80
+ title: q.title,
81
+ text: value.answers.find((a) => a.questionId === q.id).text
82
+ }))
83
+ }));
84
+ }
@@ -0,0 +1,20 @@
1
+ import { type CanonicalEvent } from "./index.js";
2
+ import type { ProtocolMode, RunProtocolErrorCode, HostInvokeOptions, HostRunReceipt, HostFrame, NativeHostFrame, CanonicalHostFrame, ControlHostFrame, HostControl, NativeInteractionResponse, AdapterProtocolSupport } from "./generated/protocol-types.js";
3
+ export type { ProtocolMode, RunProtocolErrorCode, HostInvokeOptions, HostRunReceipt, HostFrame, NativeHostFrame, CanonicalHostFrame, ControlHostFrame, HostControl, NativeInteractionResponse, AdapterProtocolSupport };
4
+ export declare const RUN_PROTOCOL_ERRORS: readonly ["PROTOCOL_MODE_INVALID", "PROTOCOL_MODE_UNSUPPORTED", "EXECUTION_PROTOCOL_MISMATCH", "EXECUTION_IDENTITY_CONFLICT", "NATIVE_METHOD_UNSUPPORTED", "NATIVE_RESPONSE_INVALID", "INTERACTION_NOT_ACTIVE", "INVALID_QUESTION_RESPONSE", "ASYNC_ANSWER_REJECTED", "OPERATION_OUTCOME_UNKNOWN", "HOST_EXECUTION_FAILED", "HOST_ENDED_WITHOUT_TERMINAL", "PROVIDER_MESSAGE_TOO_LARGE", "HOST_FRAME_TOO_LARGE", "STREAM_QUEUE_ITEM_TOO_LARGE", "STREAM_QUEUE_BYTES_EXCEEDED", "STREAM_QUEUE_ITEMS_EXCEEDED", "STREAM_GAP", "STREAM_EXPIRED", "REMOTE_NATIVE_UNSUPPORTED", "HOST_FRAME_INVALID", "HOST_CONTROL_INVALID", "HOST_RECEIPT_INVALID", "PROTOCOL_SUPPORT_INVALID"];
5
+ export declare function protocolError(code: (typeof RUN_PROTOCOL_ERRORS)[number], message?: string): Error & {
6
+ code: (typeof RUN_PROTOCOL_ERRORS)[number];
7
+ };
8
+ /** Only an omitted mode defaults. A present undefined, null or empty value is invalid. */
9
+ export declare function resolveHostInvokeOptions(value?: unknown): Readonly<Required<Pick<HostInvokeOptions, "protocolMode">> & HostInvokeOptions>;
10
+ export declare function assertAdapterProtocolSupport(value: unknown): asserts value is AdapterProtocolSupport;
11
+ export declare function assertProtocolSupport(support: AdapterProtocolSupport, options: HostInvokeOptions): void;
12
+ export declare function assertNativeInteractionResponse(value: unknown): asserts value is NativeInteractionResponse;
13
+ export declare function assertHostRunReceipt(value: unknown): asserts value is HostRunReceipt;
14
+ export declare function assertHostControl(value: unknown): asserts value is HostControl;
15
+ export declare function assertHostFrame(value: unknown): asserts value is HostFrame;
16
+ /** Subscription identity always comes from admission, never from omitted subscribe options. */
17
+ export declare function assertHostFrameIdentity(frame: HostFrame, receipt: HostRunReceipt): void;
18
+ export declare function isHostTerminal(frame: HostFrame): boolean;
19
+ /** A typed view of the selected canonical stream, never a payload cast or format guess. */
20
+ export declare function canonicalEvents(frames: AsyncIterable<HostFrame>): AsyncGenerator<CanonicalEvent>;
@@ -0,0 +1,294 @@
1
+ import { assertJsonValue } from "./context.js";
2
+ import { assertExecutionEvidence } from "./actmode.js";
3
+ import { assertCanonicalEvent, assertFailureEnvelope } from "./index.js";
4
+ import { safeCapacityEvidence, isCodexLocalFailure } from "./failure.js";
5
+ import { isStreamCapacityReason } from "./stream-limits.js";
6
+ export const RUN_PROTOCOL_ERRORS = [
7
+ "PROTOCOL_MODE_INVALID",
8
+ "PROTOCOL_MODE_UNSUPPORTED",
9
+ "EXECUTION_PROTOCOL_MISMATCH",
10
+ "EXECUTION_IDENTITY_CONFLICT",
11
+ "NATIVE_METHOD_UNSUPPORTED",
12
+ "NATIVE_RESPONSE_INVALID",
13
+ "INTERACTION_NOT_ACTIVE",
14
+ "INVALID_QUESTION_RESPONSE",
15
+ "ASYNC_ANSWER_REJECTED",
16
+ "OPERATION_OUTCOME_UNKNOWN",
17
+ "HOST_EXECUTION_FAILED",
18
+ "HOST_ENDED_WITHOUT_TERMINAL",
19
+ "PROVIDER_MESSAGE_TOO_LARGE",
20
+ "HOST_FRAME_TOO_LARGE",
21
+ "STREAM_QUEUE_ITEM_TOO_LARGE",
22
+ "STREAM_QUEUE_BYTES_EXCEEDED",
23
+ "STREAM_QUEUE_ITEMS_EXCEEDED",
24
+ "STREAM_GAP",
25
+ "STREAM_EXPIRED",
26
+ "REMOTE_NATIVE_UNSUPPORTED",
27
+ "HOST_FRAME_INVALID",
28
+ "HOST_CONTROL_INVALID",
29
+ "HOST_RECEIPT_INVALID",
30
+ "PROTOCOL_SUPPORT_INVALID"
31
+ ];
32
+ export function protocolError(code, message = code) {
33
+ return Object.assign(new Error(message), { code });
34
+ }
35
+ /** Only an omitted mode defaults. A present undefined, null or empty value is invalid. */
36
+ export function resolveHostInvokeOptions(value = {}) {
37
+ if (!isRecord(value) ||
38
+ Object.keys(value).some((key) => !["protocolMode", "protocol"].includes(key)))
39
+ throw protocolError("PROTOCOL_MODE_INVALID");
40
+ const mode = Object.hasOwn(value, "protocolMode") ? value.protocolMode : "native";
41
+ if (mode !== "native" && mode !== "canonical")
42
+ throw protocolError("PROTOCOL_MODE_INVALID");
43
+ if (Object.hasOwn(value, "protocol") && !nonemptyString(value.protocol, 128))
44
+ throw protocolError("EXECUTION_PROTOCOL_MISMATCH");
45
+ assertProtocolJson(value, "PROTOCOL_MODE_INVALID");
46
+ return Object.freeze({
47
+ protocolMode: mode,
48
+ ...(typeof value.protocol === "string" ? { protocol: value.protocol } : {})
49
+ });
50
+ }
51
+ export function assertAdapterProtocolSupport(value) {
52
+ assertProtocolJson(value, "PROTOCOL_SUPPORT_INVALID");
53
+ if (!isRecord(value) ||
54
+ !exactKeys(value, ["protocol", "modes", "nativeMethods"]) ||
55
+ !nonemptyString(value.protocol, 128) ||
56
+ !Array.isArray(value.modes) ||
57
+ !value.modes.length ||
58
+ new Set(value.modes).size !== value.modes.length ||
59
+ value.modes.some((mode) => mode !== "native" && mode !== "canonical") ||
60
+ !Array.isArray(value.nativeMethods) ||
61
+ new Set(value.nativeMethods).size !== value.nativeMethods.length ||
62
+ value.nativeMethods.some((method) => !nonemptyString(method, 256)))
63
+ throw protocolError("PROTOCOL_SUPPORT_INVALID");
64
+ if (!value.modes.includes("native") && value.nativeMethods.length)
65
+ throw protocolError("PROTOCOL_SUPPORT_INVALID");
66
+ }
67
+ export function assertProtocolSupport(support, options) {
68
+ assertAdapterProtocolSupport(support);
69
+ const resolved = resolveHostInvokeOptions(options);
70
+ if (!support.modes.includes(resolved.protocolMode))
71
+ throw protocolError("PROTOCOL_MODE_UNSUPPORTED");
72
+ if (resolved.protocol !== undefined && resolved.protocol !== support.protocol)
73
+ throw protocolError("EXECUTION_PROTOCOL_MISMATCH");
74
+ }
75
+ export function assertNativeInteractionResponse(value) {
76
+ assertProtocolJson(value, "NATIVE_RESPONSE_INVALID");
77
+ if (!isRecord(value) ||
78
+ Object.keys(value).some((key) => !["requestId", "payload"].includes(key)) ||
79
+ !requestId(value.requestId) ||
80
+ !isRecord(value.payload))
81
+ throw protocolError("NATIVE_RESPONSE_INVALID");
82
+ }
83
+ const IDENTITY_KEYS = [
84
+ "run_id",
85
+ "invocation_id",
86
+ "stream_id",
87
+ "agent_session_id",
88
+ "session_generation",
89
+ "protocol_mode",
90
+ "protocol"
91
+ ];
92
+ export function assertHostRunReceipt(value) {
93
+ assertProtocolJson(value, "HOST_RECEIPT_INVALID");
94
+ if (!isRecord(value) || !exactKeys(value, IDENTITY_KEYS) || !validIdentity(value))
95
+ throw protocolError("HOST_RECEIPT_INVALID");
96
+ }
97
+ export function assertHostControl(value) {
98
+ assertProtocolJson(value, "HOST_CONTROL_INVALID");
99
+ if (!isRecord(value))
100
+ throw protocolError("HOST_CONTROL_INVALID");
101
+ if (value.type === "evidence" &&
102
+ value.name === "execution" &&
103
+ exactKeys(value, ["type", "name", "payload"])) {
104
+ try {
105
+ assertExecutionEvidence(value.payload);
106
+ }
107
+ catch {
108
+ throw protocolError("HOST_CONTROL_INVALID");
109
+ }
110
+ return;
111
+ }
112
+ if (value.type === "evidence" &&
113
+ exactKeys(value, ["type", "name", "payload"]) &&
114
+ value.name === "session" &&
115
+ isRecord(value.payload)) {
116
+ return;
117
+ }
118
+ if (value.type === "state" &&
119
+ exactKeys(value, ["type", "state"]) &&
120
+ ["running", "completed", "failed", "cancelled", "lost"].includes(String(value.state)))
121
+ return;
122
+ if (value.type === "error" &&
123
+ exactKeys(value, ["type", "code", "message"], ["failure"]) &&
124
+ RUN_PROTOCOL_ERRORS.some((code) => code === value.code) &&
125
+ typeof value.message === "string") {
126
+ if (Object.hasOwn(value, "failure")) {
127
+ try {
128
+ assertFailureEnvelope(value.failure);
129
+ const evidence = safeCapacityEvidence(value.failure.params);
130
+ const localCodex = value.code === "HOST_EXECUTION_FAILED" && isCodexLocalFailure(value.failure);
131
+ if ((!localCodex &&
132
+ (!isStreamCapacityReason(value.code) || value.failure.reasonCode !== value.code)) ||
133
+ !exactKeys(value.failure, [
134
+ "schemaVersion",
135
+ "code",
136
+ "reasonCode",
137
+ "category",
138
+ "stage",
139
+ "messageKey",
140
+ "params",
141
+ "retryable",
142
+ "action",
143
+ "diagnosticId",
144
+ "source",
145
+ "occurredAt"
146
+ ], ["retryAfterMs", "cause"]) ||
147
+ !exactKeys(value.failure.source, ["component"], ["adapter"]) ||
148
+ !exactKeys(value.failure.action, ["type", "labelKey"]) ||
149
+ (value.failure.cause !== undefined &&
150
+ !exactKeys(value.failure.cause, ["reasonCode"], ["httpStatus"])) ||
151
+ Object.entries(value.failure.params).some(([key, entry]) => evidence[key] !== entry))
152
+ throw protocolError("HOST_CONTROL_INVALID");
153
+ }
154
+ catch {
155
+ throw protocolError("HOST_CONTROL_INVALID");
156
+ }
157
+ }
158
+ return;
159
+ }
160
+ if (value.type === "interaction" &&
161
+ exactKeys(value, ["type", "requestId", "status"], ["expiresAt", "responseRequired"]) &&
162
+ requestId(value.requestId) &&
163
+ ["pending", "resolved", "expired", "invalidated"].includes(String(value.status)) &&
164
+ (!Object.hasOwn(value, "expiresAt") || safeInteger(value.expiresAt, 1)) &&
165
+ (value.status !== "pending" || safeInteger(value.expiresAt, 1)) &&
166
+ (!Object.hasOwn(value, "responseRequired") ||
167
+ (value.status === "pending" && typeof value.responseRequired === "boolean")))
168
+ return;
169
+ if (value.type === "gap" &&
170
+ exactKeys(value, ["type", "requested_after_seq", "oldest_seq", "latest_seq"]) &&
171
+ safeInteger(value.requested_after_seq, 0) &&
172
+ safeInteger(value.oldest_seq, 1) &&
173
+ safeInteger(value.latest_seq, 0) &&
174
+ value.requested_after_seq + 1 < value.oldest_seq &&
175
+ value.oldest_seq <= value.latest_seq + 1)
176
+ return;
177
+ throw protocolError("HOST_CONTROL_INVALID");
178
+ }
179
+ export function assertHostFrame(value) {
180
+ assertProtocolJson(value, "HOST_FRAME_INVALID");
181
+ if (!isRecord(value) || !validIdentity(value) || !safeInteger(value.seq, 1))
182
+ throw protocolError("HOST_FRAME_INVALID");
183
+ const identity = [...IDENTITY_KEYS, "seq", "kind"];
184
+ if (value.kind === "native" &&
185
+ value.protocol_mode === "native" &&
186
+ exactKeys(value, [...identity, "messageKind", "payload"]) &&
187
+ ["response", "notification", "request", "message"].includes(String(value.messageKind)) &&
188
+ isRecord(value.payload))
189
+ return;
190
+ if (value.kind === "canonical" &&
191
+ value.protocol_mode === "canonical" &&
192
+ exactKeys(value, [...identity, "event"])) {
193
+ if (!isRecord(value.event) || !exactKeys(value.event, ["protocolVersion", "type", "payload"]))
194
+ throw protocolError("HOST_FRAME_INVALID");
195
+ try {
196
+ assertCanonicalEvent(value.event);
197
+ }
198
+ catch {
199
+ throw protocolError("HOST_FRAME_INVALID");
200
+ }
201
+ for (const key of ["messageId", "stepId", "role", "status", "text", "reason", "message"]) {
202
+ if (Object.hasOwn(value.event.payload, key) &&
203
+ typeof Reflect.get(value.event.payload, key) !== "string")
204
+ throw protocolError("HOST_FRAME_INVALID", `CanonicalEvent.payload.${key} must be a string`);
205
+ }
206
+ return;
207
+ }
208
+ if (value.kind === "control" && exactKeys(value, [...identity, "control"])) {
209
+ assertHostControl(value.control);
210
+ return;
211
+ }
212
+ throw protocolError("HOST_FRAME_INVALID");
213
+ }
214
+ /** Subscription identity always comes from admission, never from omitted subscribe options. */
215
+ export function assertHostFrameIdentity(frame, receipt) {
216
+ assertHostFrame(frame);
217
+ assertHostRunReceipt(receipt);
218
+ if (IDENTITY_KEYS.some((key) => Reflect.get(frame, key) !== Reflect.get(receipt, key)))
219
+ throw protocolError("EXECUTION_IDENTITY_CONFLICT");
220
+ }
221
+ export function isHostTerminal(frame) {
222
+ return (frame.kind === "control" &&
223
+ frame.control.type === "state" &&
224
+ ["completed", "failed", "cancelled", "lost"].includes(frame.control.state));
225
+ }
226
+ /** A typed view of the selected canonical stream, never a payload cast or format guess. */
227
+ export async function* canonicalEvents(frames) {
228
+ let identity;
229
+ let lastSeq;
230
+ for await (const frame of frames) {
231
+ assertHostFrame(frame);
232
+ if (frame.protocol_mode !== "canonical")
233
+ throw protocolError("EXECUTION_PROTOCOL_MISMATCH");
234
+ identity ??= {
235
+ run_id: frame.run_id,
236
+ invocation_id: frame.invocation_id,
237
+ stream_id: frame.stream_id,
238
+ agent_session_id: frame.agent_session_id,
239
+ session_generation: frame.session_generation,
240
+ protocol_mode: frame.protocol_mode,
241
+ protocol: frame.protocol
242
+ };
243
+ assertHostFrameIdentity(frame, identity);
244
+ if (lastSeq !== undefined && frame.seq !== lastSeq + 1)
245
+ throw protocolError("STREAM_GAP");
246
+ lastSeq = frame.seq;
247
+ if (frame.kind === "canonical") {
248
+ assertCanonicalEvent(frame.event);
249
+ yield frame.event;
250
+ }
251
+ else if (frame.kind === "control" && frame.control.type === "gap")
252
+ throw protocolError("STREAM_GAP");
253
+ else if (frame.kind === "control" && frame.control.type === "error")
254
+ throw Object.assign(new Error(frame.control.message), {
255
+ code: frame.control.code,
256
+ ...(frame.control.failure
257
+ ? {
258
+ ...safeCapacityEvidence(frame.control.failure.params),
259
+ failure: frame.control.failure
260
+ }
261
+ : {})
262
+ });
263
+ }
264
+ }
265
+ function validIdentity(value) {
266
+ return (["run_id", "invocation_id", "stream_id", "agent_session_id"].every((key) => nonemptyString(value[key], 256)) &&
267
+ safeInteger(value.session_generation, 0) &&
268
+ nonemptyString(value.protocol, 128) &&
269
+ (value.protocol_mode === "native" || value.protocol_mode === "canonical"));
270
+ }
271
+ function assertProtocolJson(value, code) {
272
+ try {
273
+ assertJsonValue(value);
274
+ }
275
+ catch {
276
+ throw protocolError(code);
277
+ }
278
+ }
279
+ function nonemptyString(value, max) {
280
+ return typeof value === "string" && value.length > 0 && Array.from(value).length <= max;
281
+ }
282
+ function safeInteger(value, min) {
283
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= min;
284
+ }
285
+ function requestId(value) {
286
+ return nonemptyString(value, 256) || (typeof value === "number" && Number.isSafeInteger(value));
287
+ }
288
+ function exactKeys(value, required, optional = []) {
289
+ return (required.every((key) => Object.hasOwn(value, key)) &&
290
+ Object.keys(value).every((key) => required.includes(key) || optional.includes(key)));
291
+ }
292
+ function isRecord(value) {
293
+ return value !== null && typeof value === "object" && !Array.isArray(value);
294
+ }
@@ -0,0 +1,3 @@
1
+ import type { ResolvedRuntimeSelection } from "./generated/protocol-types.js";
2
+ export declare const RUNTIME_SELECTION_SCHEMA: "octo.runtime-selection/v2";
3
+ export declare function assertResolvedRuntimeSelection(value: unknown): asserts value is ResolvedRuntimeSelection;
@@ -0,0 +1,68 @@
1
+ import { assertJsonValue } from "./context.js";
2
+ export const RUNTIME_SELECTION_SCHEMA = "octo.runtime-selection/v2";
3
+ export function assertResolvedRuntimeSelection(value) {
4
+ object(value, "RESOLVED_RUNTIME_SELECTION_REQUIRED");
5
+ assertJsonValue(value);
6
+ fields(value, [
7
+ "schema",
8
+ "selection_mode",
9
+ "explicit_runtime_id",
10
+ "authorized_candidates",
11
+ "offline_behavior",
12
+ "failover"
13
+ ]);
14
+ if (value.schema !== RUNTIME_SELECTION_SCHEMA ||
15
+ !["explicit", "candidates"].includes(value.selection_mode) ||
16
+ !["wait", "fail"].includes(value.offline_behavior) ||
17
+ !["candidates", "none"].includes(value.failover))
18
+ invalid();
19
+ if (value.selection_mode === "explicit")
20
+ text(value.explicit_runtime_id);
21
+ else if (Object.hasOwn(value, "explicit_runtime_id"))
22
+ invalid();
23
+ if (value.selection_mode === "explicit" && value.failover !== "none")
24
+ invalid();
25
+ if (!Array.isArray(value.authorized_candidates) ||
26
+ value.authorized_candidates.length === 0 ||
27
+ value.authorized_candidates.length > 64)
28
+ invalid();
29
+ const runtimeIds = new Set();
30
+ const priorities = new Set();
31
+ for (const candidate of value.authorized_candidates) {
32
+ object(candidate);
33
+ fields(candidate, ["runtime_id", "priority", "owner_subject_type", "owner_subject_id"]);
34
+ text(candidate.runtime_id);
35
+ text(candidate.owner_subject_type);
36
+ if (candidate.owner_subject_type !== "user" && candidate.owner_subject_type !== "teamspace")
37
+ invalid();
38
+ text(candidate.owner_subject_id);
39
+ positiveInteger(candidate.priority);
40
+ if (runtimeIds.has(candidate.runtime_id) || priorities.has(candidate.priority))
41
+ invalid();
42
+ runtimeIds.add(candidate.runtime_id);
43
+ priorities.add(candidate.priority);
44
+ }
45
+ if (value.selection_mode === "explicit" &&
46
+ (value.authorized_candidates.length !== 1 ||
47
+ !runtimeIds.has(value.explicit_runtime_id)))
48
+ invalid();
49
+ }
50
+ function invalid() {
51
+ throw new TypeError("RESOLVED_RUNTIME_SELECTION_INVALID");
52
+ }
53
+ function object(value, code = "RESOLVED_RUNTIME_SELECTION_INVALID") {
54
+ if (value === null || typeof value !== "object" || Array.isArray(value))
55
+ throw new TypeError(code);
56
+ }
57
+ function fields(value, allowed) {
58
+ if (Object.keys(value).some((key) => !allowed.includes(key)))
59
+ invalid();
60
+ }
61
+ function text(value) {
62
+ if (typeof value !== "string" || value.length === 0 || [...value].length > 191)
63
+ invalid();
64
+ }
65
+ function positiveInteger(value) {
66
+ if (!Number.isSafeInteger(value) || value < 1)
67
+ invalid();
68
+ }
@@ -0,0 +1,5 @@
1
+ import type { KernelStatus, ServiceProcessSnapshot } from "./generated/protocol-types.js";
2
+ /** status 的安全进程白名单与共享 Schema 对齐,拒绝任意 argv/env 扩展字段。 */
3
+ export declare function assertServiceProcessSnapshot(value: unknown): asserts value is ServiceProcessSnapshot;
4
+ /** 兼容没有服务快照的旧 status;有快照时完整校验后才能交给 doctor。 */
5
+ export declare function assertKernelStatus(value: unknown): asserts value is KernelStatus;