@rynx-ai/server 0.1.0 → 0.1.9
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/dist/browser-surface-control-queue.d.ts +42 -0
- package/dist/browser-surface-control-queue.js +187 -0
- package/dist/browser-surface-ws.d.ts +14 -0
- package/dist/browser-surface-ws.js +355 -0
- package/dist/control-api.d.ts +59 -4
- package/dist/control-api.js +2213 -132
- package/dist/control-web/assets/highlighted-body-OFNGDK62-e-w61YLy.js +1 -0
- package/dist/control-web/assets/index-BQ7XVBKO.css +32 -0
- package/dist/control-web/assets/index-ClpzuBJx.js +606 -0
- package/dist/control-web/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
- package/dist/control-web/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
- package/dist/control-web/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
- package/dist/control-web/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
- package/dist/control-web/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
- package/dist/control-web/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
- package/dist/control-web/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
- package/dist/control-web/assets/jetbrains-mono-cyrillic-wght-normal-D73BlboJ.woff2 +0 -0
- package/dist/control-web/assets/jetbrains-mono-greek-wght-normal-Bw9x6K1M.woff2 +0 -0
- package/dist/control-web/assets/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a.woff2 +0 -0
- package/dist/control-web/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2 +0 -0
- package/dist/control-web/assets/jetbrains-mono-vietnamese-wght-normal-Bt-aOZkq.woff2 +0 -0
- package/dist/control-web/assets/mermaid-GHXKKRXX-DZn7Hbr2.js +131 -0
- package/dist/control-web/assets/rynx-wordmark-LL1QRYHD.png +0 -0
- package/dist/control-web/favicon.png +0 -0
- package/dist/control-web/favicon.svg +38 -0
- package/dist/control-web/index.html +16 -0
- package/dist/control-web-dist.d.ts +1 -1
- package/dist/control-web-dist.js +19 -14
- package/dist/desktop-browser-host.d.ts +153 -0
- package/dist/desktop-browser-host.js +936 -0
- package/dist/direct-runtime-browser-surface-server.d.ts +74 -0
- package/dist/direct-runtime-browser-surface-server.js +821 -0
- package/dist/direct-runtime-server.d.ts +137 -0
- package/dist/direct-runtime-server.js +1931 -0
- package/dist/emulator-surface-ws.d.ts +6 -0
- package/dist/emulator-surface-ws.js +197 -0
- package/dist/machine-session-service.d.ts +182 -0
- package/dist/machine-session-service.js +559 -0
- package/dist/provider-cli-host.d.ts +10 -0
- package/dist/provider-cli-host.js +1 -0
- package/dist/remote-runtime-dispatcher.d.ts +94 -0
- package/dist/remote-runtime-dispatcher.js +380 -0
- package/dist/remote-runtime-session-projection.d.ts +19 -0
- package/dist/remote-runtime-session-projection.js +556 -0
- package/dist/remote-runtime.d.ts +22 -0
- package/dist/remote-runtime.js +32 -0
- package/dist/runtime-web-auth.d.ts +21 -0
- package/dist/runtime-web-auth.js +92 -0
- package/dist/server.d.ts +292 -10
- package/dist/server.js +314 -98
- package/dist/session-browser-service.d.ts +218 -0
- package/dist/session-browser-service.js +686 -0
- package/dist/session-browser-surface-coordinator.d.ts +23 -0
- package/dist/session-browser-surface-coordinator.js +563 -0
- package/dist/session-emulator-service.d.ts +167 -0
- package/dist/session-emulator-service.js +464 -0
- package/dist/session-runtime-index.d.ts +17 -0
- package/dist/session-runtime-index.js +86 -0
- package/dist/session-terminal-host.d.ts +46 -0
- package/dist/session-terminal-host.js +252 -0
- package/dist/terminal-ws.d.ts +9 -20
- package/dist/terminal-ws.js +420 -97
- package/package.json +9 -7
|
@@ -0,0 +1,556 @@
|
|
|
1
|
+
import { boundSessionInteractionRouting, sessionInteractionRoutingSkeletonBytes, SESSION_INTERACTION_STRUCTURAL_LIMITS, } from "@rynx-ai/protocol";
|
|
2
|
+
import { parseRemoteRuntimeSessionEventsFrame, parseRemoteRuntimeSessionListResult, parseRemoteRuntimeSessionSnapshotResult, REMOTE_RUNTIME_SESSION_MAX_EVENT_BYTES, REMOTE_RUNTIME_SESSION_MAX_ITEM_BYTES, REMOTE_RUNTIME_SESSION_MAX_PAYLOAD_BYTES, truncateRemoteRuntimeSessionPayload, } from "@rynx-ai/protocol/remote-runtime-rpc";
|
|
3
|
+
const MAX_RPC_RESULT_BYTES = 60 * 1_024;
|
|
4
|
+
const MAX_TRUNCATIONS = 64;
|
|
5
|
+
const MAX_TITLE_CHARS = 1_024;
|
|
6
|
+
const MAX_DESCRIPTION_CHARS = 8_192;
|
|
7
|
+
const MAX_PROJECTED_RUNTIME_BYTES = 10 * 1024;
|
|
8
|
+
export class RemoteRuntimeSessionProjectionError extends Error {
|
|
9
|
+
constructor(message, options) {
|
|
10
|
+
super(message, options);
|
|
11
|
+
this.name = "RemoteRuntimeSessionProjectionError";
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
/** Validate the list application result without truncating identity or summaries. */
|
|
15
|
+
export function projectRemoteRuntimeSessionListResult(value) {
|
|
16
|
+
if (isRecord(value) && value.directoryTruncated === true) {
|
|
17
|
+
throw new RemoteRuntimeSessionProjectionError("Remote Session directory exceeded its application scan bound");
|
|
18
|
+
}
|
|
19
|
+
const projected = parseRemoteRuntimeSessionListResult(value);
|
|
20
|
+
assertByteBudget(projected, MAX_RPC_RESULT_BYTES, "Remote Session list result");
|
|
21
|
+
return projected;
|
|
22
|
+
}
|
|
23
|
+
/** Project canonical history payloads into the bounded Direct Runtime DTO. */
|
|
24
|
+
export function projectRemoteRuntimeSessionSnapshotResult(value) {
|
|
25
|
+
const items = value.items.map((item) => projectRemoteRuntimeSessionItem(item));
|
|
26
|
+
const runtime = projectRuntimeSnapshot(value.runtime);
|
|
27
|
+
const candidate = {
|
|
28
|
+
sessionId: value.sessionId,
|
|
29
|
+
items,
|
|
30
|
+
runtime,
|
|
31
|
+
hasMore: value.hasMore,
|
|
32
|
+
...(value.nextAfterId === undefined ? {} : { nextAfterId: value.nextAfterId }),
|
|
33
|
+
};
|
|
34
|
+
const refs = [
|
|
35
|
+
...items.flatMap((item) => collectItemPayloadRefs(item)),
|
|
36
|
+
];
|
|
37
|
+
fitAggregate(candidate, refs, MAX_RPC_RESULT_BYTES, "Remote Session snapshot result");
|
|
38
|
+
for (const item of items) {
|
|
39
|
+
fitAggregate(item, collectItemPayloadRefs(item), REMOTE_RUNTIME_SESSION_MAX_ITEM_BYTES, `Remote Session item ${item.id}`);
|
|
40
|
+
}
|
|
41
|
+
try {
|
|
42
|
+
return parseRemoteRuntimeSessionSnapshotResult(candidate);
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
throw projectionError("Remote Session snapshot cannot be represented safely", error);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function projectRuntimeSnapshot(value) {
|
|
49
|
+
const runtime = structuredClone(value);
|
|
50
|
+
if (!isRecord(runtime) || !Array.isArray(runtime.pendingInteractions))
|
|
51
|
+
return runtime;
|
|
52
|
+
const pendingInteractions = runtime.pendingInteractions.map((entry) => {
|
|
53
|
+
if (!isRecord(entry) || !isRecord(entry.interaction))
|
|
54
|
+
return entry;
|
|
55
|
+
return {
|
|
56
|
+
responseId: entry.responseId,
|
|
57
|
+
interactionId: entry.interaction.interactionId,
|
|
58
|
+
};
|
|
59
|
+
});
|
|
60
|
+
if (!Array.isArray(runtime.activeResponseIds)) {
|
|
61
|
+
runtime.pendingInteractions = pendingInteractions;
|
|
62
|
+
return runtime;
|
|
63
|
+
}
|
|
64
|
+
const originalActive = runtime.activeResponseIds;
|
|
65
|
+
const projected = {
|
|
66
|
+
status: runtime.status,
|
|
67
|
+
activeResponseIds: [],
|
|
68
|
+
pendingInteractions: [],
|
|
69
|
+
};
|
|
70
|
+
const active = projected.activeResponseIds;
|
|
71
|
+
for (const responseId of originalActive) {
|
|
72
|
+
active.push(responseId);
|
|
73
|
+
if (byteLengthJson(projected) <= MAX_PROJECTED_RUNTIME_BYTES)
|
|
74
|
+
continue;
|
|
75
|
+
active.pop();
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
const pending = projected.pendingInteractions;
|
|
79
|
+
for (const entry of pendingInteractions) {
|
|
80
|
+
pending.push(entry);
|
|
81
|
+
if (byteLengthJson(projected) <= MAX_PROJECTED_RUNTIME_BYTES)
|
|
82
|
+
continue;
|
|
83
|
+
pending.pop();
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
if (active.length < originalActive.length) {
|
|
87
|
+
addOmission(projected, "activeResponseIds", originalActive.length, originalActive.length - active.length);
|
|
88
|
+
}
|
|
89
|
+
if (pending.length < pendingInteractions.length) {
|
|
90
|
+
addOmission(projected, "pendingInteractions", pendingInteractions.length, pendingInteractions.length - pending.length);
|
|
91
|
+
}
|
|
92
|
+
return projected;
|
|
93
|
+
}
|
|
94
|
+
/** Project one canonical durable item, preserving every identity and array entry. */
|
|
95
|
+
export function projectRemoteRuntimeSessionItem(item) {
|
|
96
|
+
const projected = structuredClone(item);
|
|
97
|
+
boundHistoricalInteractionRouting(projected);
|
|
98
|
+
const refs = collectItemPayloadRefs(projected);
|
|
99
|
+
boundPayloads(refs);
|
|
100
|
+
fitAggregate(projected, refs, REMOTE_RUNTIME_SESSION_MAX_ITEM_BYTES, `Remote Session item ${item.id}`);
|
|
101
|
+
validateProjectedItem(projected);
|
|
102
|
+
return projected;
|
|
103
|
+
}
|
|
104
|
+
function boundHistoricalInteractionRouting(item) {
|
|
105
|
+
if (item.type !== "interaction_request" || !isRecord(item.data))
|
|
106
|
+
return;
|
|
107
|
+
const interaction = item.data.interaction;
|
|
108
|
+
if (!isRecord(interaction) || !Array.isArray(interaction.fields) || !Array.isArray(interaction.actions)) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
let bounded;
|
|
112
|
+
try {
|
|
113
|
+
bounded = boundSessionInteractionRouting(interaction);
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
const originalEntries = interactionOptionCount(interaction);
|
|
119
|
+
const retainedEntries = interactionOptionCount(bounded);
|
|
120
|
+
if (retainedEntries === originalEntries)
|
|
121
|
+
return;
|
|
122
|
+
item.data.interaction = bounded;
|
|
123
|
+
addOmission(item, "data.interaction.fields[*].options", originalEntries, originalEntries - retainedEntries);
|
|
124
|
+
if (sessionInteractionRoutingSkeletonBytes(bounded) >
|
|
125
|
+
SESSION_INTERACTION_STRUCTURAL_LIMITS.routingSkeletonBytes) {
|
|
126
|
+
throw new RemoteRuntimeSessionProjectionError("Remote Session interaction routing identities exceed the canonical budget");
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
function interactionOptionCount(interaction) {
|
|
130
|
+
return interaction.fields.reduce((total, field) => total + (field.type === "select" ? field.options.length : 0), 0);
|
|
131
|
+
}
|
|
132
|
+
/** Project one live event and reject any event that can be proven cross-session. */
|
|
133
|
+
export function projectRemoteRuntimeSessionEvent(event, expectedSessionId) {
|
|
134
|
+
if (expectedSessionId !== undefined)
|
|
135
|
+
assertEventScope(event, expectedSessionId);
|
|
136
|
+
const projected = structuredClone(event);
|
|
137
|
+
projectNestedEventItems(projected);
|
|
138
|
+
const refs = collectEventPayloadRefs(projected);
|
|
139
|
+
boundPayloads(refs);
|
|
140
|
+
fitAggregate(projected, refs, REMOTE_RUNTIME_SESSION_MAX_EVENT_BYTES, "Remote Session event");
|
|
141
|
+
for (const item of eventItemRecords(projected)) {
|
|
142
|
+
fitAggregate(item, collectItemPayloadRefs(item), REMOTE_RUNTIME_SESSION_MAX_ITEM_BYTES, "Remote Session event item");
|
|
143
|
+
}
|
|
144
|
+
try {
|
|
145
|
+
const frame = parseRemoteRuntimeSessionEventsFrame({
|
|
146
|
+
type: "session.events.event",
|
|
147
|
+
subscriptionId: "projection",
|
|
148
|
+
event: projected,
|
|
149
|
+
});
|
|
150
|
+
if (frame.type !== "session.events.event")
|
|
151
|
+
throw new Error("unexpected event frame");
|
|
152
|
+
return frame.event;
|
|
153
|
+
}
|
|
154
|
+
catch (error) {
|
|
155
|
+
throw projectionError("Remote Session event cannot be represented safely", error);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
function validateProjectedItem(item) {
|
|
159
|
+
try {
|
|
160
|
+
parseRemoteRuntimeSessionSnapshotResult({
|
|
161
|
+
sessionId: item.sessionId,
|
|
162
|
+
items: [item],
|
|
163
|
+
runtime: { status: "idle", activeResponseIds: [], pendingInteractions: [] },
|
|
164
|
+
hasMore: false,
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
catch (error) {
|
|
168
|
+
throw projectionError(`Remote Session item ${item.id} cannot be represented safely`, error);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
function projectNestedEventItems(event) {
|
|
172
|
+
if (event.type === "response.output_item.done" ||
|
|
173
|
+
event.type === "session.input.consumed" ||
|
|
174
|
+
event.type === "session.interaction.requested" ||
|
|
175
|
+
event.type === "session.interaction.resolved" ||
|
|
176
|
+
event.type === "session.interaction.cancelled") {
|
|
177
|
+
if (isRecord(event.item)) {
|
|
178
|
+
event.item = projectRemoteRuntimeSessionItem(event.item);
|
|
179
|
+
}
|
|
180
|
+
// The durable interaction_request item is authoritative. Carrying the
|
|
181
|
+
// canonical top-level duplicate over Direct makes legal routing values
|
|
182
|
+
// impossible to fit in the fixed frame budget.
|
|
183
|
+
if (event.type === "session.interaction.requested")
|
|
184
|
+
delete event.interaction;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
function collectItemPayloadRefs(item) {
|
|
188
|
+
const refs = [];
|
|
189
|
+
const data = isRecord(item.data) ? item.data : undefined;
|
|
190
|
+
if (!data)
|
|
191
|
+
return refs;
|
|
192
|
+
switch (item.type) {
|
|
193
|
+
case "message":
|
|
194
|
+
collectContentParts(data.content, "data.content", item, refs);
|
|
195
|
+
break;
|
|
196
|
+
case "function_call":
|
|
197
|
+
addRef(data, "arguments", "data.arguments", item, refs, 16_384, true);
|
|
198
|
+
break;
|
|
199
|
+
case "function_call_output":
|
|
200
|
+
addRef(data, "output", "data.output", item, refs, 16_384, true);
|
|
201
|
+
break;
|
|
202
|
+
case "reasoning":
|
|
203
|
+
collectContentParts(data.summary, "data.summary", item, refs);
|
|
204
|
+
break;
|
|
205
|
+
case "terminal_command":
|
|
206
|
+
addRef(data, "command", "data.command", item, refs, 16_384, true);
|
|
207
|
+
addOptionalRef(data, "stdout", "data.stdout", item, refs, 16_384, true);
|
|
208
|
+
addOptionalRef(data, "stderr", "data.stderr", item, refs, 16_384, true);
|
|
209
|
+
break;
|
|
210
|
+
case "interaction_request":
|
|
211
|
+
collectInteraction(data.interaction, "data.interaction", item, refs);
|
|
212
|
+
break;
|
|
213
|
+
case "interaction_resolution":
|
|
214
|
+
collectInteractionResolution(data.resolution, "data.resolution", item, refs);
|
|
215
|
+
addOptionalRef(data, "reason", "data.reason", item, refs, MAX_DESCRIPTION_CHARS, true);
|
|
216
|
+
break;
|
|
217
|
+
case "error":
|
|
218
|
+
addRef(data, "message", "data.message", item, refs, 16_384, true);
|
|
219
|
+
break;
|
|
220
|
+
}
|
|
221
|
+
return refs;
|
|
222
|
+
}
|
|
223
|
+
function collectEventPayloadRefs(event) {
|
|
224
|
+
const refs = eventItemRecords(event).flatMap((item) => collectItemPayloadRefs(item));
|
|
225
|
+
switch (event.type) {
|
|
226
|
+
case "session.status":
|
|
227
|
+
addOptionalRef(event, "note", "note", event, refs, MAX_DESCRIPTION_CHARS, true);
|
|
228
|
+
break;
|
|
229
|
+
case "response.output_text.delta":
|
|
230
|
+
case "response.reasoning_summary_text.delta":
|
|
231
|
+
case "response.function_call_output.delta":
|
|
232
|
+
addRef(event, "delta", "delta", event, refs, 16_384, true);
|
|
233
|
+
break;
|
|
234
|
+
case "session.interaction.resolved":
|
|
235
|
+
collectInteractionResolution(event.resolution, "resolution", event, refs);
|
|
236
|
+
break;
|
|
237
|
+
case "session.interaction.cancelled":
|
|
238
|
+
addOptionalRef(event, "reason", "reason", event, refs, MAX_DESCRIPTION_CHARS, true);
|
|
239
|
+
break;
|
|
240
|
+
case "response.completed":
|
|
241
|
+
collectJsonStrings(event.usage, "usage", event, refs, 0);
|
|
242
|
+
break;
|
|
243
|
+
case "response.failed":
|
|
244
|
+
if (isRecord(event.error)) {
|
|
245
|
+
addRef(event.error, "message", "error.message", event, refs, 16_384, true);
|
|
246
|
+
}
|
|
247
|
+
break;
|
|
248
|
+
case "session.todos":
|
|
249
|
+
if (Array.isArray(event.todos)) {
|
|
250
|
+
event.todos.forEach((todo, index) => {
|
|
251
|
+
if (isRecord(todo)) {
|
|
252
|
+
addRef(todo, "subject", `todos[${index}].subject`, event, refs, MAX_DESCRIPTION_CHARS, true, "todos[*].subject");
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
break;
|
|
257
|
+
}
|
|
258
|
+
return refs;
|
|
259
|
+
}
|
|
260
|
+
function collectInteraction(value, prefix, owner, refs) {
|
|
261
|
+
if (!isRecord(value))
|
|
262
|
+
return;
|
|
263
|
+
addRef(value, "title", `${prefix}.title`, owner, refs, MAX_TITLE_CHARS, true);
|
|
264
|
+
addOptionalRef(value, "description", `${prefix}.description`, owner, refs, MAX_DESCRIPTION_CHARS, true);
|
|
265
|
+
if (isRecord(value.context)) {
|
|
266
|
+
const context = value.context;
|
|
267
|
+
addOptionalRef(context, "summary", `${prefix}.context.summary`, owner, refs, MAX_DESCRIPTION_CHARS, true);
|
|
268
|
+
addOptionalRef(context, "toolName", `${prefix}.context.toolName`, owner, refs, MAX_TITLE_CHARS, true);
|
|
269
|
+
addOptionalRef(context, "command", `${prefix}.context.command`, owner, refs, 16_384, true);
|
|
270
|
+
addOptionalRef(context, "cwd", `${prefix}.context.cwd`, owner, refs, 4_096, true);
|
|
271
|
+
addOptionalRef(context, "diff", `${prefix}.context.diff`, owner, refs, 16_384, true);
|
|
272
|
+
}
|
|
273
|
+
if (Array.isArray(value.fields)) {
|
|
274
|
+
value.fields.forEach((field, index) => {
|
|
275
|
+
if (!isRecord(field))
|
|
276
|
+
return;
|
|
277
|
+
const fieldPrefix = `${prefix}.fields[${index}]`;
|
|
278
|
+
addRef(field, "label", `${fieldPrefix}.label`, owner, refs, MAX_TITLE_CHARS, true, `${prefix}.fields[*].label`);
|
|
279
|
+
addOptionalRef(field, "description", `${fieldPrefix}.description`, owner, refs, MAX_DESCRIPTION_CHARS, true, `${prefix}.fields[*].description`);
|
|
280
|
+
addOptionalRef(field, "placeholder", `${fieldPrefix}.placeholder`, owner, refs, MAX_TITLE_CHARS, true, `${prefix}.fields[*].placeholder`);
|
|
281
|
+
if (Array.isArray(field.options)) {
|
|
282
|
+
field.options.forEach((option, optionIndex) => {
|
|
283
|
+
if (!isRecord(option))
|
|
284
|
+
return;
|
|
285
|
+
const optionPrefix = `${fieldPrefix}.options[${optionIndex}]`;
|
|
286
|
+
addRef(option, "label", `${optionPrefix}.label`, owner, refs, MAX_TITLE_CHARS, true, `${prefix}.fields[*].options[*].label`);
|
|
287
|
+
addOptionalRef(option, "description", `${optionPrefix}.description`, owner, refs, MAX_DESCRIPTION_CHARS, true, `${prefix}.fields[*].options[*].description`);
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
if (Array.isArray(value.actions)) {
|
|
293
|
+
value.actions.forEach((action, index) => {
|
|
294
|
+
if (!isRecord(action))
|
|
295
|
+
return;
|
|
296
|
+
const actionPrefix = `${prefix}.actions[${index}]`;
|
|
297
|
+
addRef(action, "label", `${actionPrefix}.label`, owner, refs, MAX_TITLE_CHARS, true, `${prefix}.actions[*].label`);
|
|
298
|
+
addOptionalRef(action, "description", `${actionPrefix}.description`, owner, refs, MAX_DESCRIPTION_CHARS, true, `${prefix}.actions[*].description`);
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
function collectInteractionResolution(value, prefix, owner, refs) {
|
|
303
|
+
if (!isRecord(value) || !isRecord(value.answers))
|
|
304
|
+
return;
|
|
305
|
+
for (const [key, answer] of Object.entries(value.answers)) {
|
|
306
|
+
if (typeof answer === "string") {
|
|
307
|
+
addRef(value.answers, key, `${prefix}.answers.${key}`, owner, refs, 16_384, true);
|
|
308
|
+
}
|
|
309
|
+
else if (Array.isArray(answer)) {
|
|
310
|
+
answer.forEach((entry, index) => {
|
|
311
|
+
if (typeof entry === "string") {
|
|
312
|
+
addArrayRef(answer, index, `${prefix}.answers.${key}[${index}]`, owner, refs, 16_384, true);
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
function collectContentParts(value, prefix, owner, refs) {
|
|
319
|
+
if (!Array.isArray(value))
|
|
320
|
+
return;
|
|
321
|
+
value.forEach((part, index) => {
|
|
322
|
+
if (isRecord(part))
|
|
323
|
+
addRef(part, "text", `${prefix}[${index}].text`, owner, refs, 16_384, true);
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
function collectJsonStrings(value, prefix, owner, refs, depth) {
|
|
327
|
+
if (depth > 8)
|
|
328
|
+
return;
|
|
329
|
+
if (Array.isArray(value)) {
|
|
330
|
+
value.forEach((entry, index) => {
|
|
331
|
+
if (typeof entry === "string") {
|
|
332
|
+
addArrayRef(value, index, `${prefix}[${index}]`, owner, refs, 16_384, true);
|
|
333
|
+
}
|
|
334
|
+
else {
|
|
335
|
+
collectJsonStrings(entry, `${prefix}[${index}]`, owner, refs, depth + 1);
|
|
336
|
+
}
|
|
337
|
+
});
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
if (!isRecord(value))
|
|
341
|
+
return;
|
|
342
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
343
|
+
if (typeof entry === "string") {
|
|
344
|
+
addRef(value, key, `${prefix}.${key}`, owner, refs, 16_384, true);
|
|
345
|
+
}
|
|
346
|
+
else {
|
|
347
|
+
collectJsonStrings(entry, `${prefix}.${key}`, owner, refs, depth + 1);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
function addOptionalRef(container, key, field, owner, refs, maxChars, allowEmpty, metadataField) {
|
|
352
|
+
if (container[key] !== undefined) {
|
|
353
|
+
addRef(container, key, field, owner, refs, maxChars, allowEmpty, metadataField);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
function addArrayRef(container, index, field, owner, refs, maxChars, allowEmpty) {
|
|
357
|
+
addRef(container, String(index), field, owner, refs, maxChars, allowEmpty);
|
|
358
|
+
}
|
|
359
|
+
function addRef(container, key, field, owner, refs, maxChars, allowEmpty, metadataField = field) {
|
|
360
|
+
const value = container[key];
|
|
361
|
+
if (typeof value !== "string")
|
|
362
|
+
return;
|
|
363
|
+
const existing = existingOriginalBytes(owner, metadataField);
|
|
364
|
+
const currentBytes = byteLength(value);
|
|
365
|
+
if (existing !== undefined && existing < currentBytes) {
|
|
366
|
+
throw new RemoteRuntimeSessionProjectionError(`invalid truncation metadata for ${field}`);
|
|
367
|
+
}
|
|
368
|
+
refs.push({
|
|
369
|
+
container,
|
|
370
|
+
key,
|
|
371
|
+
field,
|
|
372
|
+
metadataField,
|
|
373
|
+
owner,
|
|
374
|
+
originalUtf8Bytes: existing ?? currentBytes,
|
|
375
|
+
maxChars,
|
|
376
|
+
allowEmpty,
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
function boundPayloads(refs) {
|
|
380
|
+
for (const ref of refs) {
|
|
381
|
+
const current = currentText(ref);
|
|
382
|
+
let next = safeCharacterPrefix(current, ref.maxChars);
|
|
383
|
+
next = truncateRemoteRuntimeSessionPayload(next, REMOTE_RUNTIME_SESSION_MAX_PAYLOAD_BYTES).text;
|
|
384
|
+
if (next !== current)
|
|
385
|
+
applyTruncation(ref, next);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
function fitAggregate(value, refs, maxBytes, field) {
|
|
389
|
+
let size = byteLengthJson(value);
|
|
390
|
+
let attempts = 0;
|
|
391
|
+
while (size > maxBytes) {
|
|
392
|
+
const candidates = refs
|
|
393
|
+
.map((ref) => ({ ref, bytes: byteLength(currentText(ref)), jsonBytes: byteLengthJson(currentText(ref)) }))
|
|
394
|
+
.filter(({ ref, bytes }) => bytes > minimumBytes(ref))
|
|
395
|
+
.sort((left, right) => right.jsonBytes - left.jsonBytes || left.ref.field.localeCompare(right.ref.field));
|
|
396
|
+
const candidate = candidates[0];
|
|
397
|
+
if (!candidate) {
|
|
398
|
+
throw new RemoteRuntimeSessionProjectionError(`${field} exceeds ${maxBytes} bytes without truncatable payload`);
|
|
399
|
+
}
|
|
400
|
+
const overflow = size - maxBytes;
|
|
401
|
+
const target = Math.max(minimumBytes(candidate.ref), candidate.bytes - Math.max(1, overflow));
|
|
402
|
+
const next = truncateRemoteRuntimeSessionPayload(currentText(candidate.ref), target).text;
|
|
403
|
+
if (next === currentText(candidate.ref)) {
|
|
404
|
+
throw new RemoteRuntimeSessionProjectionError(`${field} cannot make projection progress`);
|
|
405
|
+
}
|
|
406
|
+
applyTruncation(candidate.ref, next);
|
|
407
|
+
const nextSize = byteLengthJson(value);
|
|
408
|
+
if (nextSize >= size && attempts > refs.length) {
|
|
409
|
+
throw new RemoteRuntimeSessionProjectionError(`${field} truncation metadata exceeds its savings`);
|
|
410
|
+
}
|
|
411
|
+
size = nextSize;
|
|
412
|
+
attempts += 1;
|
|
413
|
+
if (attempts > 10_000)
|
|
414
|
+
throw new RemoteRuntimeSessionProjectionError(`${field} projection did not converge`);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
function applyTruncation(ref, text) {
|
|
418
|
+
if (!ref.allowEmpty && text.length === 0) {
|
|
419
|
+
throw new RemoteRuntimeSessionProjectionError(`${ref.field} cannot be projected to an empty value`);
|
|
420
|
+
}
|
|
421
|
+
ref.container[ref.key] = text;
|
|
422
|
+
const truncations = truncationList(ref.owner);
|
|
423
|
+
const existing = truncations.find((entry) => entry.field === ref.metadataField);
|
|
424
|
+
if (existing) {
|
|
425
|
+
existing.originalUtf8Bytes = Math.max(existing.originalUtf8Bytes, ref.originalUtf8Bytes);
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
if (truncations.length >= MAX_TRUNCATIONS) {
|
|
429
|
+
throw new RemoteRuntimeSessionProjectionError(`projection exceeds ${MAX_TRUNCATIONS} truncation records`);
|
|
430
|
+
}
|
|
431
|
+
if (ref.metadataField.length === 0 ||
|
|
432
|
+
ref.metadataField.length > 256 ||
|
|
433
|
+
/[\u0000-\u001f\u007f]/.test(ref.metadataField)) {
|
|
434
|
+
throw new RemoteRuntimeSessionProjectionError("projection field path is not representable");
|
|
435
|
+
}
|
|
436
|
+
truncations.push({ field: ref.metadataField, originalUtf8Bytes: ref.originalUtf8Bytes });
|
|
437
|
+
ref.owner.truncations = truncations;
|
|
438
|
+
}
|
|
439
|
+
function addOmission(owner, field, originalEntries, omittedEntries) {
|
|
440
|
+
if (omittedEntries <= 0)
|
|
441
|
+
return;
|
|
442
|
+
const omissions = omissionList(owner);
|
|
443
|
+
const existing = omissions.find((entry) => entry.field === field);
|
|
444
|
+
if (existing) {
|
|
445
|
+
existing.originalEntries = Math.max(existing.originalEntries, originalEntries);
|
|
446
|
+
existing.omittedEntries = Math.max(existing.omittedEntries, omittedEntries);
|
|
447
|
+
}
|
|
448
|
+
else {
|
|
449
|
+
omissions.push({ field, originalEntries, omittedEntries, reason: "wire_budget" });
|
|
450
|
+
}
|
|
451
|
+
owner.omissions = omissions;
|
|
452
|
+
}
|
|
453
|
+
function omissionList(owner) {
|
|
454
|
+
if (owner.omissions === undefined)
|
|
455
|
+
return [];
|
|
456
|
+
if (!Array.isArray(owner.omissions)) {
|
|
457
|
+
throw new RemoteRuntimeSessionProjectionError("invalid existing omission metadata");
|
|
458
|
+
}
|
|
459
|
+
return owner.omissions.map((entry) => {
|
|
460
|
+
if (!isRecord(entry) ||
|
|
461
|
+
typeof entry.field !== "string" ||
|
|
462
|
+
!Number.isSafeInteger(entry.originalEntries) ||
|
|
463
|
+
entry.originalEntries <= 0 ||
|
|
464
|
+
!Number.isSafeInteger(entry.omittedEntries) ||
|
|
465
|
+
entry.omittedEntries <= 0 ||
|
|
466
|
+
entry.omittedEntries > entry.originalEntries ||
|
|
467
|
+
entry.reason !== "wire_budget") {
|
|
468
|
+
throw new RemoteRuntimeSessionProjectionError("invalid existing omission metadata");
|
|
469
|
+
}
|
|
470
|
+
return entry;
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
function truncationList(owner) {
|
|
474
|
+
if (owner.truncations === undefined)
|
|
475
|
+
return [];
|
|
476
|
+
if (!Array.isArray(owner.truncations)) {
|
|
477
|
+
throw new RemoteRuntimeSessionProjectionError("invalid existing truncation metadata");
|
|
478
|
+
}
|
|
479
|
+
return owner.truncations.map((entry) => {
|
|
480
|
+
if (!isRecord(entry) ||
|
|
481
|
+
typeof entry.field !== "string" ||
|
|
482
|
+
!Number.isSafeInteger(entry.originalUtf8Bytes) ||
|
|
483
|
+
entry.originalUtf8Bytes <= 0) {
|
|
484
|
+
throw new RemoteRuntimeSessionProjectionError("invalid existing truncation metadata");
|
|
485
|
+
}
|
|
486
|
+
return entry;
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
function existingOriginalBytes(owner, field) {
|
|
490
|
+
return truncationList(owner).find((entry) => entry.field === field)?.originalUtf8Bytes;
|
|
491
|
+
}
|
|
492
|
+
function currentText(ref) {
|
|
493
|
+
const value = ref.container[ref.key];
|
|
494
|
+
if (typeof value !== "string") {
|
|
495
|
+
throw new RemoteRuntimeSessionProjectionError(`${ref.field} changed during projection`);
|
|
496
|
+
}
|
|
497
|
+
return value;
|
|
498
|
+
}
|
|
499
|
+
function minimumBytes(ref) {
|
|
500
|
+
if (ref.allowEmpty)
|
|
501
|
+
return 0;
|
|
502
|
+
const current = currentText(ref);
|
|
503
|
+
const first = [...current][0];
|
|
504
|
+
return first === undefined ? 0 : byteLength(first);
|
|
505
|
+
}
|
|
506
|
+
function safeCharacterPrefix(value, maxChars) {
|
|
507
|
+
if (value.length <= maxChars)
|
|
508
|
+
return value;
|
|
509
|
+
let end = maxChars;
|
|
510
|
+
const code = value.charCodeAt(end - 1);
|
|
511
|
+
if (code >= 0xd800 && code <= 0xdbff)
|
|
512
|
+
end -= 1;
|
|
513
|
+
return value.slice(0, end);
|
|
514
|
+
}
|
|
515
|
+
function eventItemRecords(event) {
|
|
516
|
+
return isRecord(event.item) ? [event.item] : [];
|
|
517
|
+
}
|
|
518
|
+
function assertEventScope(event, expectedSessionId) {
|
|
519
|
+
let actual;
|
|
520
|
+
switch (event.type) {
|
|
521
|
+
case "session.status":
|
|
522
|
+
case "response.created":
|
|
523
|
+
case "session.interaction.requested":
|
|
524
|
+
case "session.interaction.resolved":
|
|
525
|
+
case "session.interaction.cancelled":
|
|
526
|
+
case "session.todos":
|
|
527
|
+
case "session.rotated":
|
|
528
|
+
actual = event.sessionId;
|
|
529
|
+
break;
|
|
530
|
+
case "response.output_item.done":
|
|
531
|
+
case "session.input.consumed":
|
|
532
|
+
actual = event.item.sessionId;
|
|
533
|
+
break;
|
|
534
|
+
}
|
|
535
|
+
if (actual !== undefined && actual !== expectedSessionId) {
|
|
536
|
+
throw new RemoteRuntimeSessionProjectionError("Remote Session event belongs to another session");
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
function assertByteBudget(value, maxBytes, field) {
|
|
540
|
+
const size = byteLengthJson(value);
|
|
541
|
+
if (size > maxBytes) {
|
|
542
|
+
throw new RemoteRuntimeSessionProjectionError(`${field} exceeds ${maxBytes} bytes`);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
function byteLength(value) {
|
|
546
|
+
return Buffer.byteLength(value, "utf8");
|
|
547
|
+
}
|
|
548
|
+
function byteLengthJson(value) {
|
|
549
|
+
return Buffer.byteLength(JSON.stringify(value), "utf8");
|
|
550
|
+
}
|
|
551
|
+
function isRecord(value) {
|
|
552
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
553
|
+
}
|
|
554
|
+
function projectionError(message, cause) {
|
|
555
|
+
return new RemoteRuntimeSessionProjectionError(message, { cause });
|
|
556
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type DaemonStatus } from "@rynx-ai/protocol/remote-runtime";
|
|
2
|
+
/** Encoding-neutral application snapshot for one daemon Runtime incarnation. */
|
|
3
|
+
export interface DaemonRuntimeSnapshot {
|
|
4
|
+
daemonId: string;
|
|
5
|
+
daemonInstanceId: string;
|
|
6
|
+
coreProtocolVersion: number;
|
|
7
|
+
minimumCompatibleCoreVersion: number;
|
|
8
|
+
semanticCapabilities: readonly string[];
|
|
9
|
+
startedAt: Date;
|
|
10
|
+
}
|
|
11
|
+
/** Transport-independent application port exposed through Local or Direct bindings. */
|
|
12
|
+
export interface DaemonRuntimeHost {
|
|
13
|
+
status(): DaemonRuntimeSnapshot;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Freeze the boot snapshot at daemon startup while returning defensive copies
|
|
17
|
+
* to adapters. A future live capability registry may replace this factory
|
|
18
|
+
* without changing the host contract or boot identity fields.
|
|
19
|
+
*/
|
|
20
|
+
export declare function createDaemonRuntimeHost(input: DaemonRuntimeSnapshot): DaemonRuntimeHost;
|
|
21
|
+
/** Map the application snapshot onto the current JSON Core wire DTO. */
|
|
22
|
+
export declare function encodeDaemonStatus(status: DaemonRuntimeSnapshot): DaemonStatus;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { parseDaemonStatus, } from "@rynx-ai/protocol/remote-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Freeze the boot snapshot at daemon startup while returning defensive copies
|
|
4
|
+
* to adapters. A future live capability registry may replace this factory
|
|
5
|
+
* without changing the host contract or boot identity fields.
|
|
6
|
+
*/
|
|
7
|
+
export function createDaemonRuntimeHost(input) {
|
|
8
|
+
const dto = encodeDaemonStatus(input);
|
|
9
|
+
const status = {
|
|
10
|
+
...dto,
|
|
11
|
+
semanticCapabilities: [...dto.semanticCapabilities],
|
|
12
|
+
startedAt: new Date(dto.startedAt),
|
|
13
|
+
};
|
|
14
|
+
return {
|
|
15
|
+
status: () => ({
|
|
16
|
+
...status,
|
|
17
|
+
semanticCapabilities: [...status.semanticCapabilities],
|
|
18
|
+
startedAt: new Date(status.startedAt),
|
|
19
|
+
}),
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/** Map the application snapshot onto the current JSON Core wire DTO. */
|
|
23
|
+
export function encodeDaemonStatus(status) {
|
|
24
|
+
if (!(status.startedAt instanceof Date) || !Number.isFinite(status.startedAt.getTime())) {
|
|
25
|
+
throw new Error("invalid daemon Runtime status: startedAt must be a valid Date");
|
|
26
|
+
}
|
|
27
|
+
return parseDaemonStatus({
|
|
28
|
+
...status,
|
|
29
|
+
semanticCapabilities: [...status.semanticCapabilities],
|
|
30
|
+
startedAt: status.startedAt.toISOString(),
|
|
31
|
+
});
|
|
32
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { IncomingMessage } from "node:http";
|
|
2
|
+
export declare const RUNTIME_WEB_CAPABILITY_HEADER = "x-rynx-runtime-capability";
|
|
3
|
+
export declare const RUNTIME_WEB_CAPABILITY_QUERY = "capability";
|
|
4
|
+
/**
|
|
5
|
+
* Process-lifetime CSRF capability shared by the Runtime HTTP BFF and its
|
|
6
|
+
* browser WebSocket upgrades. It is not a daemon-management credential.
|
|
7
|
+
*/
|
|
8
|
+
export declare const RUNTIME_WEB_CAPABILITY: string;
|
|
9
|
+
export interface RuntimeWebAuthorizationFailure {
|
|
10
|
+
status: 401 | 403;
|
|
11
|
+
error: "loopback_only" | "invalid_host" | "cross_origin" | "invalid_runtime_capability";
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Authorize a browser request before any Runtime lease or terminal is opened.
|
|
15
|
+
* Socket address and the original Host header are both checked to resist DNS
|
|
16
|
+
* rebinding; mutation metadata and the capability provide the CSRF fence.
|
|
17
|
+
*/
|
|
18
|
+
export declare function authorizeRuntimeWebRequest(request: IncomingMessage, options?: {
|
|
19
|
+
mutation?: boolean;
|
|
20
|
+
capability?: string;
|
|
21
|
+
}): RuntimeWebAuthorizationFailure | undefined;
|