@pellux/goodvibes-daemon-sdk 0.30.2 → 0.33.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/api-router.d.ts +4 -2
- package/dist/api-router.d.ts.map +1 -1
- package/dist/api-router.js +18 -10
- package/dist/artifact-upload.d.ts +9 -9
- package/dist/artifact-upload.d.ts.map +1 -1
- package/dist/artifact-upload.js +27 -19
- package/dist/auth-helpers.d.ts +9 -0
- package/dist/auth-helpers.d.ts.map +1 -0
- package/dist/auth-helpers.js +10 -0
- package/dist/automation.js +10 -10
- package/dist/channel-route-types.d.ts +22 -23
- package/dist/channel-route-types.d.ts.map +1 -1
- package/dist/channel-routes.d.ts.map +1 -1
- package/dist/channel-routes.js +37 -42
- package/dist/context.d.ts +27 -27
- package/dist/context.d.ts.map +1 -1
- package/dist/control-routes.d.ts +12 -13
- package/dist/control-routes.d.ts.map +1 -1
- package/dist/control-routes.js +55 -26
- package/dist/error-response.d.ts +10 -3
- package/dist/error-response.d.ts.map +1 -1
- package/dist/error-response.js +102 -11
- package/dist/http-policy.d.ts +3 -4
- package/dist/http-policy.d.ts.map +1 -1
- package/dist/http-policy.js +2 -1
- package/dist/index.d.ts +11 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/integration-routes.d.ts +1 -1
- package/dist/integration-routes.d.ts.map +1 -1
- package/dist/integration-routes.js +72 -33
- package/dist/knowledge-refinement-routes.d.ts.map +1 -1
- package/dist/knowledge-refinement-routes.js +22 -15
- package/dist/knowledge-route-types.d.ts +16 -15
- package/dist/knowledge-route-types.d.ts.map +1 -1
- package/dist/knowledge-routes.d.ts.map +1 -1
- package/dist/knowledge-routes.js +144 -89
- package/dist/media-route-types.d.ts +2 -1
- package/dist/media-route-types.d.ts.map +1 -1
- package/dist/media-routes.d.ts.map +1 -1
- package/dist/media-routes.js +119 -55
- package/dist/operator.d.ts +16 -0
- package/dist/operator.d.ts.map +1 -1
- package/dist/operator.js +105 -61
- package/dist/otlp-protobuf.d.ts.map +1 -1
- package/dist/otlp-protobuf.js +28 -10
- package/dist/remote-routes.d.ts +9 -3
- package/dist/remote-routes.d.ts.map +1 -1
- package/dist/remote-routes.js +259 -163
- package/dist/route-helpers.d.ts +13 -2
- package/dist/route-helpers.d.ts.map +1 -1
- package/dist/route-helpers.js +38 -1
- package/dist/runtime-automation-routes.d.ts.map +1 -1
- package/dist/runtime-automation-routes.js +88 -54
- package/dist/runtime-route-types.d.ts +87 -93
- package/dist/runtime-route-types.d.ts.map +1 -1
- package/dist/runtime-session-routes.d.ts +6 -4
- package/dist/runtime-session-routes.d.ts.map +1 -1
- package/dist/runtime-session-routes.js +132 -88
- package/dist/sessions.js +3 -3
- package/dist/system-route-types.d.ts +25 -24
- package/dist/system-route-types.d.ts.map +1 -1
- package/dist/system-routes.d.ts +1 -1
- package/dist/system-routes.d.ts.map +1 -1
- package/dist/system-routes.js +126 -92
- package/dist/tasks.d.ts.map +1 -1
- package/dist/tasks.js +2 -0
- package/dist/telemetry-routes.d.ts +14 -14
- package/dist/telemetry-routes.d.ts.map +1 -1
- package/dist/telemetry-routes.js +54 -29
- package/package.json +5 -4
package/dist/remote-routes.js
CHANGED
|
@@ -1,7 +1,114 @@
|
|
|
1
1
|
import { jsonErrorResponse } from './error-response.js';
|
|
2
|
-
import { serializableJsonResponse } from './route-helpers.js';
|
|
2
|
+
import { createRouteBodySchema, createRouteBodySchemaRegistry, readOptionalStringField, readStringArrayField, serializableJsonResponse, } from './route-helpers.js';
|
|
3
3
|
const MAX_REMOTE_RESULT_BYTES = 1_000_000;
|
|
4
4
|
const MAX_REMOTE_PAYLOAD_BYTES = 1_000_000;
|
|
5
|
+
const MAX_REMOTE_CAPABILITIES = 128;
|
|
6
|
+
const MAX_REMOTE_COMMANDS = 128;
|
|
7
|
+
const MAX_REMOTE_SCOPES = 128;
|
|
8
|
+
const remoteBodySchemas = createRouteBodySchemaRegistry({
|
|
9
|
+
pairRequest: createRouteBodySchema('POST /api/remote/pair', (body) => {
|
|
10
|
+
const label = readOptionalStringField(body, 'label');
|
|
11
|
+
if (!label)
|
|
12
|
+
return jsonErrorResponse({ error: 'Missing remote peer label' }, { status: 400 });
|
|
13
|
+
const requestedId = readOptionalStringField(body, 'requestedId');
|
|
14
|
+
const platform = readOptionalStringField(body, 'platform');
|
|
15
|
+
const deviceFamily = readOptionalStringField(body, 'deviceFamily');
|
|
16
|
+
const version = readOptionalStringField(body, 'version');
|
|
17
|
+
const clientMode = readOptionalStringField(body, 'clientMode');
|
|
18
|
+
const ttlMs = boundedPositiveNumber(body.ttlMs, 1_000, 86_400_000);
|
|
19
|
+
return {
|
|
20
|
+
peerKind: body.peerKind === 'device' ? 'device' : 'node',
|
|
21
|
+
...(requestedId ? { requestedId } : {}),
|
|
22
|
+
label,
|
|
23
|
+
...(platform ? { platform } : {}),
|
|
24
|
+
...(deviceFamily ? { deviceFamily } : {}),
|
|
25
|
+
...(version ? { version } : {}),
|
|
26
|
+
...(clientMode ? { clientMode } : {}),
|
|
27
|
+
capabilities: readStringArrayField(body, 'capabilities', MAX_REMOTE_CAPABILITIES) ?? [],
|
|
28
|
+
commands: readStringArrayField(body, 'commands', MAX_REMOTE_COMMANDS) ?? [],
|
|
29
|
+
metadata: readRemoteMetadata(body.metadata),
|
|
30
|
+
...(ttlMs !== undefined ? { ttlMs } : {}),
|
|
31
|
+
};
|
|
32
|
+
}),
|
|
33
|
+
pairVerify: createRouteBodySchema('POST /api/remote/pair/verify', (body) => {
|
|
34
|
+
const requestId = readOptionalStringField(body, 'requestId');
|
|
35
|
+
const challenge = readOptionalStringField(body, 'challenge');
|
|
36
|
+
if (!requestId || !challenge) {
|
|
37
|
+
return jsonErrorResponse({ error: 'Missing requestId or challenge' }, { status: 400 });
|
|
38
|
+
}
|
|
39
|
+
return { requestId, challenge, metadata: readRemoteMetadata(body.metadata) };
|
|
40
|
+
}),
|
|
41
|
+
peerHeartbeat: createRouteBodySchema('POST /api/remote/heartbeat', (body) => {
|
|
42
|
+
const capabilities = readStringArrayField(body, 'capabilities', MAX_REMOTE_CAPABILITIES);
|
|
43
|
+
const commands = readStringArrayField(body, 'commands', MAX_REMOTE_COMMANDS);
|
|
44
|
+
const version = readOptionalStringField(body, 'version');
|
|
45
|
+
const clientMode = readOptionalStringField(body, 'clientMode');
|
|
46
|
+
return {
|
|
47
|
+
...(capabilities ? { capabilities } : {}),
|
|
48
|
+
...(commands ? { commands } : {}),
|
|
49
|
+
...(version ? { version } : {}),
|
|
50
|
+
...(clientMode ? { clientMode } : {}),
|
|
51
|
+
metadata: readRemoteMetadata(body.metadata),
|
|
52
|
+
};
|
|
53
|
+
}),
|
|
54
|
+
workPull: createRouteBodySchema('POST /api/remote/work/pull', (body) => {
|
|
55
|
+
const maxItems = boundedPositiveNumber(body.maxItems, 1, 100);
|
|
56
|
+
const leaseMs = boundedPositiveNumber(body.leaseMs, 1_000, 3_600_000);
|
|
57
|
+
return {
|
|
58
|
+
...(maxItems !== undefined ? { maxItems } : {}),
|
|
59
|
+
...(leaseMs !== undefined ? { leaseMs } : {}),
|
|
60
|
+
};
|
|
61
|
+
}),
|
|
62
|
+
workComplete: createRouteBodySchema('POST /api/remote/work/:workId/complete', (body) => {
|
|
63
|
+
const error = readOptionalStringField(body, 'error');
|
|
64
|
+
return {
|
|
65
|
+
...(body.status === 'failed' || body.status === 'cancelled' || body.status === 'completed' ? { status: body.status } : {}),
|
|
66
|
+
result: body.result,
|
|
67
|
+
...(error ? { error } : {}),
|
|
68
|
+
metadata: readRemoteMetadata(body.metadata),
|
|
69
|
+
};
|
|
70
|
+
}),
|
|
71
|
+
operatorNote: createRouteBodySchema('POST /api/remote/operator-action', (body) => {
|
|
72
|
+
const note = readOptionalStringField(body, 'note');
|
|
73
|
+
const reason = readOptionalStringField(body, 'reason');
|
|
74
|
+
const label = readOptionalStringField(body, 'label');
|
|
75
|
+
const tokenId = readOptionalStringField(body, 'tokenId');
|
|
76
|
+
const scopes = readStringArrayField(body, 'scopes', MAX_REMOTE_SCOPES);
|
|
77
|
+
return {
|
|
78
|
+
...(note ? { note } : {}),
|
|
79
|
+
...(reason ? { reason } : {}),
|
|
80
|
+
...(label ? { label } : {}),
|
|
81
|
+
...(tokenId ? { tokenId } : {}),
|
|
82
|
+
...(typeof body.requeueClaimedWork === 'boolean' ? { requeueClaimedWork: body.requeueClaimedWork } : {}),
|
|
83
|
+
...(scopes ? { scopes } : {}),
|
|
84
|
+
};
|
|
85
|
+
}),
|
|
86
|
+
invoke: createRouteBodySchema('POST /api/remote/peers/:peerId/invoke', (body) => {
|
|
87
|
+
const command = readOptionalStringField(body, 'command');
|
|
88
|
+
if (!command)
|
|
89
|
+
return jsonErrorResponse({ error: 'Missing remote invoke command' }, { status: 400 });
|
|
90
|
+
const waitMs = boundedPositiveNumber(body.waitMs, 0, 300_000);
|
|
91
|
+
const timeoutMs = boundedPositiveNumber(body.timeoutMs, 1_000, 300_000);
|
|
92
|
+
const sessionId = readOptionalStringField(body, 'sessionId');
|
|
93
|
+
const routeId = readOptionalStringField(body, 'routeId');
|
|
94
|
+
const automationRunId = readOptionalStringField(body, 'automationRunId');
|
|
95
|
+
const automationJobId = readOptionalStringField(body, 'automationJobId');
|
|
96
|
+
const approvalId = readOptionalStringField(body, 'approvalId');
|
|
97
|
+
return {
|
|
98
|
+
command,
|
|
99
|
+
payload: body.payload,
|
|
100
|
+
priority: body.priority === 'high' || body.priority === 'default' ? body.priority : 'normal',
|
|
101
|
+
...(waitMs !== undefined ? { waitMs } : {}),
|
|
102
|
+
...(timeoutMs !== undefined ? { timeoutMs } : {}),
|
|
103
|
+
...(sessionId ? { sessionId } : {}),
|
|
104
|
+
...(routeId ? { routeId } : {}),
|
|
105
|
+
...(automationRunId ? { automationRunId } : {}),
|
|
106
|
+
...(automationJobId ? { automationJobId } : {}),
|
|
107
|
+
...(approvalId ? { approvalId } : {}),
|
|
108
|
+
metadata: readRemoteMetadata(body.metadata),
|
|
109
|
+
};
|
|
110
|
+
}),
|
|
111
|
+
});
|
|
5
112
|
export function createDaemonRemoteRouteHandlers(context) {
|
|
6
113
|
return {
|
|
7
114
|
getRemotePairRequests: () => Response.json({ requests: context.distributedRuntime.listPairRequests() }),
|
|
@@ -21,24 +128,13 @@ export async function handleRemotePairRequest(context, req) {
|
|
|
21
128
|
const body = await context.parseJsonBody(req);
|
|
22
129
|
if (body instanceof Response)
|
|
23
130
|
return body;
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
return Response.json({ error: 'Missing remote peer label' }, { status: 400 });
|
|
28
|
-
}
|
|
131
|
+
const input = remoteBodySchemas.pairRequest.parse(body);
|
|
132
|
+
if (input instanceof Response)
|
|
133
|
+
return input;
|
|
29
134
|
const created = await context.distributedRuntime.requestPairing({
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
label,
|
|
33
|
-
platform: typeof body.platform === 'string' ? body.platform : undefined,
|
|
34
|
-
deviceFamily: typeof body.deviceFamily === 'string' ? body.deviceFamily : undefined,
|
|
35
|
-
version: typeof body.version === 'string' ? body.version : undefined,
|
|
36
|
-
clientMode: typeof body.clientMode === 'string' ? body.clientMode : undefined,
|
|
37
|
-
capabilities: Array.isArray(body.capabilities) ? body.capabilities.filter((value) => typeof value === 'string') : [],
|
|
38
|
-
commands: Array.isArray(body.commands) ? body.commands.filter((value) => typeof value === 'string') : [],
|
|
39
|
-
metadata: readMetadataWithClientHintForwardedFor(req, body.metadata),
|
|
135
|
+
...input,
|
|
136
|
+
metadata: readMetadataWithClientHintForwardedFor(req, input.metadata),
|
|
40
137
|
requestedBy: 'remote',
|
|
41
|
-
ttlMs: boundedPositiveNumber(body.ttlMs, 1_000, 86_400_000),
|
|
42
138
|
});
|
|
43
139
|
return Response.json(created, { status: 201 });
|
|
44
140
|
}
|
|
@@ -46,17 +142,15 @@ export async function handleRemotePairVerify(context, req) {
|
|
|
46
142
|
const body = await context.parseJsonBody(req);
|
|
47
143
|
if (body instanceof Response)
|
|
48
144
|
return body;
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
const verified = await context.distributedRuntime.verifyPairRequest(requestId, challenge, {
|
|
55
|
-
metadata: readMetadataWithClientHintForwardedFor(req, body.metadata),
|
|
145
|
+
const input = remoteBodySchemas.pairVerify.parse(body);
|
|
146
|
+
if (input instanceof Response)
|
|
147
|
+
return input;
|
|
148
|
+
const verified = await context.distributedRuntime.verifyPairRequest(input.requestId, input.challenge, {
|
|
149
|
+
metadata: readMetadataWithClientHintForwardedFor(req, input.metadata),
|
|
56
150
|
});
|
|
57
151
|
return verified
|
|
58
152
|
? Response.json(verified)
|
|
59
|
-
:
|
|
153
|
+
: jsonErrorResponse({ error: 'Pair request not approved, expired, or invalid' }, { status: 404 });
|
|
60
154
|
}
|
|
61
155
|
export async function handleRemotePeerHeartbeat(context, req) {
|
|
62
156
|
const auth = await context.requireRemotePeer(req, 'remote:heartbeat');
|
|
@@ -65,12 +159,12 @@ export async function handleRemotePeerHeartbeat(context, req) {
|
|
|
65
159
|
const body = await context.parseJsonBody(req);
|
|
66
160
|
if (body instanceof Response)
|
|
67
161
|
return body;
|
|
162
|
+
const input = remoteBodySchemas.peerHeartbeat.parse(body);
|
|
163
|
+
if (input instanceof Response)
|
|
164
|
+
return input;
|
|
68
165
|
const peer = await context.distributedRuntime.heartbeatPeer(auth, {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
version: typeof body.version === 'string' ? body.version : undefined,
|
|
72
|
-
clientMode: typeof body.clientMode === 'string' ? body.clientMode : undefined,
|
|
73
|
-
metadata: readMetadataWithClientHintForwardedFor(req, body.metadata),
|
|
166
|
+
...input,
|
|
167
|
+
metadata: readMetadataWithClientHintForwardedFor(req, input.metadata),
|
|
74
168
|
});
|
|
75
169
|
return Response.json({ peer });
|
|
76
170
|
}
|
|
@@ -81,9 +175,12 @@ export async function handleRemotePeerWorkPull(context, req) {
|
|
|
81
175
|
const body = await context.parseJsonBody(req);
|
|
82
176
|
if (body instanceof Response)
|
|
83
177
|
return body;
|
|
178
|
+
const input = remoteBodySchemas.workPull.parse(body);
|
|
179
|
+
if (input instanceof Response)
|
|
180
|
+
return input;
|
|
84
181
|
const work = await context.distributedRuntime.claimWork(auth, {
|
|
85
|
-
maxItems:
|
|
86
|
-
leaseMs:
|
|
182
|
+
maxItems: input.maxItems,
|
|
183
|
+
leaseMs: input.leaseMs,
|
|
87
184
|
});
|
|
88
185
|
return Response.json({ work });
|
|
89
186
|
}
|
|
@@ -94,19 +191,24 @@ export async function handleRemotePeerWorkComplete(context, workId, req) {
|
|
|
94
191
|
const body = await context.parseJsonBody(req);
|
|
95
192
|
if (body instanceof Response)
|
|
96
193
|
return body;
|
|
97
|
-
const
|
|
194
|
+
const input = remoteBodySchemas.workComplete.parse(body);
|
|
195
|
+
if (input instanceof Response)
|
|
196
|
+
return input;
|
|
197
|
+
const result = validateRemoteJsonPayload(input.result, MAX_REMOTE_RESULT_BYTES, 'result');
|
|
98
198
|
if (result instanceof Response)
|
|
99
199
|
return result;
|
|
100
200
|
const work = await context.distributedRuntime.completeWork(auth, workId, {
|
|
101
|
-
status:
|
|
201
|
+
status: input.status,
|
|
102
202
|
result,
|
|
103
|
-
error:
|
|
104
|
-
metadata:
|
|
203
|
+
error: input.error,
|
|
204
|
+
metadata: input.metadata,
|
|
105
205
|
});
|
|
106
206
|
return work
|
|
107
207
|
? Response.json({ work })
|
|
108
|
-
:
|
|
208
|
+
: jsonErrorResponse({ error: 'Unknown or unclaimed remote work item' }, { status: 404 });
|
|
109
209
|
}
|
|
210
|
+
// Non-numeric input is omitted from downstream calls so services can apply
|
|
211
|
+
// their own defaults.
|
|
110
212
|
function boundedPositiveNumber(value, min, max) {
|
|
111
213
|
if (typeof value !== 'number' || !Number.isFinite(value))
|
|
112
214
|
return undefined;
|
|
@@ -115,118 +217,95 @@ function boundedPositiveNumber(value, min, max) {
|
|
|
115
217
|
function validateRemoteJsonPayload(value, maxBytes, field) {
|
|
116
218
|
const size = estimateJsonByteLengthWithinLimit(value ?? null, maxBytes);
|
|
117
219
|
if (size.kind === 'invalid') {
|
|
118
|
-
return
|
|
220
|
+
return jsonErrorResponse({
|
|
119
221
|
error: `Remote ${field} must be JSON-serializable`,
|
|
222
|
+
code: 'INVALID_REMOTE_JSON_PAYLOAD',
|
|
120
223
|
}, { status: 400 });
|
|
121
224
|
}
|
|
122
225
|
if (size.byteLength <= maxBytes)
|
|
123
226
|
return value;
|
|
124
|
-
return
|
|
125
|
-
error: `Remote ${field} exceeds ${maxBytes}
|
|
227
|
+
return jsonErrorResponse({
|
|
228
|
+
error: `Remote ${field} exceeds ${maxBytes} bytes after JSON encoding. Reduce payload size before retrying.`,
|
|
229
|
+
code: 'REMOTE_PAYLOAD_TOO_LARGE',
|
|
126
230
|
}, { status: 413 });
|
|
127
231
|
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
if (valueType === 'undefined' || valueType === 'function' || valueType === 'symbol')
|
|
143
|
-
return 4;
|
|
144
|
-
if (valueType !== 'object')
|
|
145
|
-
return undefined;
|
|
146
|
-
const objectValue = value;
|
|
147
|
-
if (seen.has(objectValue))
|
|
148
|
-
return undefined;
|
|
149
|
-
seen.add(objectValue);
|
|
150
|
-
try {
|
|
151
|
-
let total = 2;
|
|
152
|
-
if (Array.isArray(value)) {
|
|
153
|
-
for (let index = 0; index < value.length; index += 1) {
|
|
154
|
-
if (index > 0)
|
|
155
|
-
total += 1;
|
|
156
|
-
if (total > maxBytes)
|
|
157
|
-
return total;
|
|
158
|
-
const entrySize = measureJsonByteLength(value[index], maxBytes - total, seen);
|
|
159
|
-
if (entrySize === undefined)
|
|
160
|
-
return undefined;
|
|
161
|
-
total += entrySize;
|
|
162
|
-
if (total > maxBytes) {
|
|
163
|
-
return total;
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
return total;
|
|
232
|
+
/**
|
|
233
|
+
* Compute the exact JSON-encoded byte length of a string without allocating the encoded form.
|
|
234
|
+
* Walks each code unit and counts the bytes JSON.stringify would emit:
|
|
235
|
+
* - 2 bytes for the surrounding quotes
|
|
236
|
+
* - 2 bytes for short-escape characters (", \, \b, \t, \n, \f, \r)
|
|
237
|
+
* - 6 bytes for other control chars (\uXXXX)
|
|
238
|
+
* - 1–4 bytes per non-control code point (UTF-8 encoding length)
|
|
239
|
+
*/
|
|
240
|
+
function jsonStringByteLength(s) {
|
|
241
|
+
let len = 2; // surrounding quotes
|
|
242
|
+
for (let i = 0; i < s.length; i++) {
|
|
243
|
+
const c = s.charCodeAt(i);
|
|
244
|
+
if (c === 0x22 || c === 0x5c || c === 0x08 || c === 0x09 || c === 0x0a || c === 0x0c || c === 0x0d) {
|
|
245
|
+
len += 2; // \" \\ \b \t \n \f \r
|
|
167
246
|
}
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
const entryType = typeof entryValue;
|
|
171
|
-
if (entryType === 'undefined' || entryType === 'function' || entryType === 'symbol')
|
|
172
|
-
continue;
|
|
173
|
-
if (count > 0)
|
|
174
|
-
total += 1;
|
|
175
|
-
total += jsonStringByteLength(key, maxBytes - total) + 1;
|
|
176
|
-
if (total > maxBytes)
|
|
177
|
-
return total;
|
|
178
|
-
const entrySize = measureJsonByteLength(entryValue, maxBytes - total, seen);
|
|
179
|
-
if (entrySize === undefined)
|
|
180
|
-
return undefined;
|
|
181
|
-
total += entrySize;
|
|
182
|
-
count += 1;
|
|
183
|
-
if (total > maxBytes) {
|
|
184
|
-
return total;
|
|
185
|
-
}
|
|
247
|
+
else if (c < 0x20) {
|
|
248
|
+
len += 6; // \uXXXX
|
|
186
249
|
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
finally {
|
|
190
|
-
seen.delete(objectValue);
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
function jsonStringByteLength(value, maxBytes = Number.POSITIVE_INFINITY) {
|
|
194
|
-
let total = 2;
|
|
195
|
-
for (let index = 0; index < value.length; index += 1) {
|
|
196
|
-
const code = value.charCodeAt(index);
|
|
197
|
-
if (code === 0x22 || code === 0x5c || code === 0x08 || code === 0x0c || code === 0x0a || code === 0x0d || code === 0x09) {
|
|
198
|
-
total += 2;
|
|
250
|
+
else if (c < 0x80) {
|
|
251
|
+
len += 1;
|
|
199
252
|
}
|
|
200
|
-
else if (
|
|
201
|
-
|
|
253
|
+
else if (c < 0x800) {
|
|
254
|
+
len += 2;
|
|
202
255
|
}
|
|
203
|
-
else if (
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
total += 4;
|
|
207
|
-
index += 1;
|
|
208
|
-
}
|
|
209
|
-
else {
|
|
210
|
-
total += 6;
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
else if (code >= 0xdc00 && code <= 0xdfff) {
|
|
214
|
-
total += 6;
|
|
256
|
+
else if ((c & 0xfc00) === 0xd800 && i + 1 < s.length && (s.charCodeAt(i + 1) & 0xfc00) === 0xdc00) {
|
|
257
|
+
len += 4; // surrogate pair → 4-byte UTF-8
|
|
258
|
+
i++;
|
|
215
259
|
}
|
|
216
260
|
else {
|
|
217
|
-
|
|
261
|
+
len += 3;
|
|
218
262
|
}
|
|
219
|
-
if (total > maxBytes)
|
|
220
|
-
return total;
|
|
221
263
|
}
|
|
222
|
-
return
|
|
264
|
+
return len;
|
|
223
265
|
}
|
|
224
|
-
function
|
|
225
|
-
|
|
226
|
-
|
|
266
|
+
export function estimateJsonByteLengthWithinLimit(value, maxBytes) {
|
|
267
|
+
// walk the value with a counting replacer so we never allocate
|
|
268
|
+
// the full encoded string. The replacer throws a sentinel when the running
|
|
269
|
+
// byte total exceeds maxBytes, preventing peak allocation before the cap.
|
|
270
|
+
// switched from `val.length * 6` worst-case to exact
|
|
271
|
+
// jsonStringByteLength() count — same allocation profile, much tighter near the cap.
|
|
272
|
+
let byteCount = 0;
|
|
273
|
+
const OVER_LIMIT = Symbol('over-limit');
|
|
274
|
+
try {
|
|
275
|
+
JSON.stringify(value, (_key, val) => {
|
|
276
|
+
if (typeof val === 'string') {
|
|
277
|
+
byteCount += jsonStringByteLength(val);
|
|
278
|
+
}
|
|
279
|
+
else {
|
|
280
|
+
// null/number/boolean/object-open/array-open — add a small fixed cost per node.
|
|
281
|
+
byteCount += 16;
|
|
282
|
+
}
|
|
283
|
+
if (byteCount > maxBytes) {
|
|
284
|
+
// eslint-disable-next-line @typescript-eslint/no-throw-literal
|
|
285
|
+
throw OVER_LIMIT;
|
|
286
|
+
}
|
|
287
|
+
return val;
|
|
288
|
+
});
|
|
289
|
+
if (value === undefined)
|
|
290
|
+
return { kind: 'ok', byteLength: 4 }; // undefined → JSON undefined
|
|
291
|
+
// Result is an exact-count for strings; node-overhead remains a fixed approximation.
|
|
292
|
+
return { kind: 'ok', byteLength: Math.min(byteCount, maxBytes + 1) };
|
|
293
|
+
}
|
|
294
|
+
catch (err) {
|
|
295
|
+
if (err === OVER_LIMIT)
|
|
296
|
+
return { kind: 'ok', byteLength: maxBytes + 1 };
|
|
297
|
+
return { kind: 'invalid' };
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
function readRemoteMetadata(value) {
|
|
301
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
302
|
+
? value
|
|
227
303
|
: {};
|
|
304
|
+
}
|
|
305
|
+
function readMetadataWithClientHintForwardedFor(req, value) {
|
|
306
|
+
const metadata = { ...readRemoteMetadata(value) };
|
|
228
307
|
const clientHintForwardedFor = readClientHintForwardedFor(req);
|
|
229
|
-
return clientHintForwardedFor ? { ...metadata, clientHintForwardedFor } : metadata;
|
|
308
|
+
return clientHintForwardedFor ? { ...metadata, x_forwarded_for_untrusted: clientHintForwardedFor } : metadata;
|
|
230
309
|
}
|
|
231
310
|
function readClientHintForwardedFor(req) {
|
|
232
311
|
// x-forwarded-for is caller-controlled unless the daemon is explicitly behind
|
|
@@ -245,15 +324,18 @@ async function handleApproveRemotePairRequest(context, requestId, req) {
|
|
|
245
324
|
const body = await context.parseJsonBody(req);
|
|
246
325
|
if (body instanceof Response)
|
|
247
326
|
return body;
|
|
327
|
+
const input = remoteBodySchemas.operatorNote.parse(body);
|
|
328
|
+
if (input instanceof Response)
|
|
329
|
+
return input;
|
|
248
330
|
const approved = await context.distributedRuntime.approvePairRequest(requestId, {
|
|
249
331
|
actor: operatorActor(context, req),
|
|
250
|
-
note:
|
|
251
|
-
label:
|
|
252
|
-
metadata:
|
|
332
|
+
note: input.note,
|
|
333
|
+
label: input.label,
|
|
334
|
+
metadata: readRemoteMetadata(body.metadata),
|
|
253
335
|
});
|
|
254
336
|
return approved
|
|
255
337
|
? Response.json(approved)
|
|
256
|
-
:
|
|
338
|
+
: jsonErrorResponse({ error: 'Unknown remote pair request' }, { status: 404 });
|
|
257
339
|
}
|
|
258
340
|
async function handleRejectRemotePairRequest(context, requestId, req) {
|
|
259
341
|
const admin = context.requireAdmin(req);
|
|
@@ -262,13 +344,16 @@ async function handleRejectRemotePairRequest(context, requestId, req) {
|
|
|
262
344
|
const body = await context.parseJsonBody(req);
|
|
263
345
|
if (body instanceof Response)
|
|
264
346
|
return body;
|
|
347
|
+
const input = remoteBodySchemas.operatorNote.parse(body);
|
|
348
|
+
if (input instanceof Response)
|
|
349
|
+
return input;
|
|
265
350
|
const rejected = await context.distributedRuntime.rejectPairRequest(requestId, {
|
|
266
351
|
actor: operatorActor(context, req),
|
|
267
|
-
note:
|
|
352
|
+
note: input.note,
|
|
268
353
|
});
|
|
269
354
|
return rejected
|
|
270
355
|
? Response.json(rejected)
|
|
271
|
-
:
|
|
356
|
+
: jsonErrorResponse({ error: 'Unknown remote pair request' }, { status: 404 });
|
|
272
357
|
}
|
|
273
358
|
async function handleRotateRemotePeerToken(context, peerId, req) {
|
|
274
359
|
const admin = context.requireAdmin(req);
|
|
@@ -277,14 +362,17 @@ async function handleRotateRemotePeerToken(context, peerId, req) {
|
|
|
277
362
|
const body = await context.parseJsonBody(req);
|
|
278
363
|
if (body instanceof Response)
|
|
279
364
|
return body;
|
|
365
|
+
const input = remoteBodySchemas.operatorNote.parse(body);
|
|
366
|
+
if (input instanceof Response)
|
|
367
|
+
return input;
|
|
280
368
|
const rotated = await context.distributedRuntime.rotatePeerToken(peerId, {
|
|
281
369
|
actor: operatorActor(context, req),
|
|
282
|
-
label:
|
|
283
|
-
scopes:
|
|
370
|
+
label: input.label,
|
|
371
|
+
scopes: input.scopes,
|
|
284
372
|
});
|
|
285
373
|
return rotated
|
|
286
374
|
? Response.json(rotated)
|
|
287
|
-
:
|
|
375
|
+
: jsonErrorResponse({ error: 'Unknown distributed peer' }, { status: 404 });
|
|
288
376
|
}
|
|
289
377
|
async function handleRevokeRemotePeerToken(context, peerId, req) {
|
|
290
378
|
const admin = context.requireAdmin(req);
|
|
@@ -293,14 +381,17 @@ async function handleRevokeRemotePeerToken(context, peerId, req) {
|
|
|
293
381
|
const body = await context.parseJsonBody(req);
|
|
294
382
|
if (body instanceof Response)
|
|
295
383
|
return body;
|
|
384
|
+
const input = remoteBodySchemas.operatorNote.parse(body);
|
|
385
|
+
if (input instanceof Response)
|
|
386
|
+
return input;
|
|
296
387
|
const peer = await context.distributedRuntime.revokePeerToken(peerId, {
|
|
297
388
|
actor: operatorActor(context, req),
|
|
298
|
-
tokenId:
|
|
299
|
-
note:
|
|
389
|
+
tokenId: input.tokenId,
|
|
390
|
+
note: input.note,
|
|
300
391
|
});
|
|
301
392
|
return peer
|
|
302
393
|
? Response.json({ peer })
|
|
303
|
-
:
|
|
394
|
+
: jsonErrorResponse({ error: 'Unknown distributed peer' }, { status: 404 });
|
|
304
395
|
}
|
|
305
396
|
async function handleDisconnectRemotePeer(context, peerId, req) {
|
|
306
397
|
const admin = context.requireAdmin(req);
|
|
@@ -309,14 +400,17 @@ async function handleDisconnectRemotePeer(context, peerId, req) {
|
|
|
309
400
|
const body = await context.parseJsonBody(req);
|
|
310
401
|
if (body instanceof Response)
|
|
311
402
|
return body;
|
|
403
|
+
const input = remoteBodySchemas.operatorNote.parse(body);
|
|
404
|
+
if (input instanceof Response)
|
|
405
|
+
return input;
|
|
312
406
|
const peer = await context.distributedRuntime.disconnectPeer(peerId, {
|
|
313
407
|
actor: operatorActor(context, req),
|
|
314
|
-
note:
|
|
315
|
-
requeueClaimedWork:
|
|
408
|
+
note: input.note,
|
|
409
|
+
requeueClaimedWork: input.requeueClaimedWork,
|
|
316
410
|
});
|
|
317
411
|
return peer
|
|
318
412
|
? Response.json({ peer })
|
|
319
|
-
:
|
|
413
|
+
: jsonErrorResponse({ error: 'Unknown distributed peer' }, { status: 404 });
|
|
320
414
|
}
|
|
321
415
|
async function handleInvokeRemotePeer(context, peerId, req) {
|
|
322
416
|
const admin = context.requireAdmin(req);
|
|
@@ -325,28 +419,27 @@ async function handleInvokeRemotePeer(context, peerId, req) {
|
|
|
325
419
|
const body = await context.parseJsonBody(req);
|
|
326
420
|
if (body instanceof Response)
|
|
327
421
|
return body;
|
|
328
|
-
const
|
|
329
|
-
if (
|
|
330
|
-
return
|
|
331
|
-
|
|
332
|
-
const payload = validateRemoteJsonPayload(body.payload, MAX_REMOTE_PAYLOAD_BYTES, 'payload');
|
|
422
|
+
const input = remoteBodySchemas.invoke.parse(body);
|
|
423
|
+
if (input instanceof Response)
|
|
424
|
+
return input;
|
|
425
|
+
const payload = validateRemoteJsonPayload(input.payload, MAX_REMOTE_PAYLOAD_BYTES, 'payload');
|
|
333
426
|
if (payload instanceof Response)
|
|
334
427
|
return payload;
|
|
335
428
|
try {
|
|
336
429
|
const invoked = await context.distributedRuntime.invokePeer({
|
|
337
430
|
peerId,
|
|
338
|
-
command,
|
|
431
|
+
command: input.command,
|
|
339
432
|
payload,
|
|
340
|
-
priority:
|
|
433
|
+
priority: input.priority,
|
|
341
434
|
actor: operatorActor(context, req),
|
|
342
|
-
waitMs:
|
|
343
|
-
timeoutMs:
|
|
344
|
-
sessionId:
|
|
345
|
-
routeId:
|
|
346
|
-
automationRunId:
|
|
347
|
-
automationJobId:
|
|
348
|
-
approvalId:
|
|
349
|
-
metadata:
|
|
435
|
+
waitMs: input.waitMs,
|
|
436
|
+
timeoutMs: input.timeoutMs,
|
|
437
|
+
sessionId: input.sessionId,
|
|
438
|
+
routeId: input.routeId,
|
|
439
|
+
automationRunId: input.automationRunId,
|
|
440
|
+
automationJobId: input.automationJobId,
|
|
441
|
+
approvalId: input.approvalId,
|
|
442
|
+
metadata: input.metadata,
|
|
350
443
|
});
|
|
351
444
|
return Response.json(invoked, { status: 202 });
|
|
352
445
|
}
|
|
@@ -361,11 +454,14 @@ async function handleCancelRemoteWork(context, workId, req) {
|
|
|
361
454
|
const body = await context.parseJsonBody(req);
|
|
362
455
|
if (body instanceof Response)
|
|
363
456
|
return body;
|
|
457
|
+
const input = remoteBodySchemas.operatorNote.parse(body);
|
|
458
|
+
if (input instanceof Response)
|
|
459
|
+
return input;
|
|
364
460
|
const work = await context.distributedRuntime.cancelWork(workId, {
|
|
365
461
|
actor: operatorActor(context, req),
|
|
366
|
-
reason:
|
|
462
|
+
reason: input.reason,
|
|
367
463
|
});
|
|
368
464
|
return work
|
|
369
465
|
? Response.json({ work })
|
|
370
|
-
:
|
|
466
|
+
: jsonErrorResponse({ error: 'Unknown remote work item' }, { status: 404 });
|
|
371
467
|
}
|
package/dist/route-helpers.d.ts
CHANGED
|
@@ -1,16 +1,27 @@
|
|
|
1
1
|
export type JsonRecord = Record<string, unknown>;
|
|
2
|
+
export type JsonBody = JsonRecord;
|
|
3
|
+
export interface RouteBodySchema<T> {
|
|
4
|
+
readonly routeId: string;
|
|
5
|
+
readonly parse: (body: JsonRecord) => T | Response;
|
|
6
|
+
}
|
|
7
|
+
export declare function createRouteBodySchema<T>(routeId: string, parse: (body: JsonRecord) => T | Response): RouteBodySchema<T>;
|
|
8
|
+
export declare function createRouteBodySchemaRegistry<const TSchemaMap extends Record<string, RouteBodySchema<unknown>>>(schemas: TSchemaMap): TSchemaMap;
|
|
2
9
|
export declare function isJsonRecord(value: unknown): value is JsonRecord;
|
|
3
10
|
export declare function toSerializableJson(value: unknown, stack?: Map<object, string>, path?: string): unknown;
|
|
4
11
|
export declare function serializableJsonResponse(body: unknown, init?: ResponseInit): Response;
|
|
5
12
|
export interface BoundedIntegerOptions {
|
|
6
13
|
readonly fallback: number;
|
|
7
|
-
readonly min?: number;
|
|
8
|
-
readonly max?: number;
|
|
14
|
+
readonly min?: number | undefined;
|
|
15
|
+
readonly max?: number | undefined;
|
|
9
16
|
}
|
|
10
17
|
export declare function readBoundedInteger(raw: string | null, options: BoundedIntegerOptions): number;
|
|
11
18
|
export declare function readBoundedPositiveInteger(raw: string | null, fallback: number, max?: number): number;
|
|
19
|
+
export declare function readBoundedBodyInteger(value: unknown, fallback: number, max: number, min?: number): number;
|
|
12
20
|
export declare function readOptionalBoundedInteger(raw: string | null, min: number, max: number): number | undefined;
|
|
21
|
+
export declare function readOptionalStringField(body: JsonRecord, key: string): string | undefined;
|
|
22
|
+
export declare function readStringArrayField(body: JsonRecord, key: string, max?: number): string[] | undefined;
|
|
13
23
|
export declare function scopeMatches(granted: string, required: string): boolean;
|
|
24
|
+
export declare function hasAnyScope(grantedScopes: readonly string[] | undefined, requiredScopes: readonly string[]): boolean;
|
|
14
25
|
export declare function missingScopes(grantedScopes: readonly string[] | undefined, requiredScopes: readonly string[]): string[];
|
|
15
26
|
export type ChannelLifecycleAction = 'inspect' | 'setup' | 'retest' | 'connect' | 'disconnect' | 'start' | 'stop' | 'login' | 'logout' | 'wait_login';
|
|
16
27
|
export type ChannelConversationKind = 'direct' | 'group' | 'channel' | 'thread' | 'service';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"route-helpers.d.ts","sourceRoot":"","sources":["../src/route-helpers.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"route-helpers.d.ts","sourceRoot":"","sources":["../src/route-helpers.ts"],"names":[],"mappings":"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"}
|