@masons/agent-network 0.6.15 → 0.6.16
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/_vendor/runtime-adapter-client/conformance.d.ts +8 -0
- package/dist/_vendor/runtime-adapter-client/conformance.d.ts.map +1 -0
- package/dist/_vendor/runtime-adapter-client/conformance.js +75 -0
- package/dist/_vendor/runtime-adapter-client/icm.d.ts +6 -0
- package/dist/_vendor/runtime-adapter-client/icm.d.ts.map +1 -0
- package/dist/_vendor/runtime-adapter-client/icm.js +86 -0
- package/dist/_vendor/runtime-adapter-client/index.d.ts +7 -0
- package/dist/_vendor/runtime-adapter-client/index.d.ts.map +1 -0
- package/dist/_vendor/runtime-adapter-client/index.js +6 -0
- package/dist/_vendor/runtime-adapter-client/runtime-adapter-api.d.ts +300 -0
- package/dist/_vendor/runtime-adapter-client/runtime-adapter-api.d.ts.map +1 -0
- package/dist/_vendor/runtime-adapter-client/runtime-adapter-api.js +3112 -0
- package/dist/_vendor/runtime-adapter-client/send-admission.d.ts +89 -0
- package/dist/_vendor/runtime-adapter-client/send-admission.d.ts.map +1 -0
- package/dist/_vendor/runtime-adapter-client/send-admission.js +169 -0
- package/dist/_vendor/runtime-adapter-client/types.d.ts +632 -0
- package/dist/_vendor/runtime-adapter-client/types.d.ts.map +1 -0
- package/dist/_vendor/runtime-adapter-client/types.js +33 -0
- package/dist/_vendor/runtime-adapter-client/work-target.d.ts +5 -0
- package/dist/_vendor/runtime-adapter-client/work-target.d.ts.map +1 -0
- package/dist/_vendor/runtime-adapter-client/work-target.js +47 -0
- package/dist/channel.d.ts.map +1 -1
- package/dist/channel.js +105 -29
- package/dist/connector-client.d.ts +2 -1
- package/dist/connector-client.d.ts.map +1 -1
- package/dist/connector-client.js +6 -1
- package/dist/plugin.d.ts +1 -1
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +15 -8
- package/dist/services-retained-recipient.d.ts +58 -0
- package/dist/services-retained-recipient.d.ts.map +1 -0
- package/dist/services-retained-recipient.js +324 -0
- package/dist/turn-context.d.ts +2 -0
- package/dist/turn-context.d.ts.map +1 -1
- package/dist/turn-context.js +9 -0
- package/dist/types.d.ts +5 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +3 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +3 -3
|
@@ -0,0 +1,3112 @@
|
|
|
1
|
+
import { findPeerVisibleRuntimeMetadataKeys } from "./conformance.js";
|
|
2
|
+
import { isRuntimeDebugIdentifierTitle, parseRuntimeSessionDisplayMetadata, parseRuntimeSessionIdentityKey, } from "./icm.js";
|
|
3
|
+
import { ADAPTER_RUNTIME_PROCESSING_STATES, RUNTIME_ASSIGNMENT_FAILURE_CATEGORIES, } from "./types.js";
|
|
4
|
+
import { parseRuntimeWorkTargetRef, sameRuntimeWorkTargetRef, } from "./work-target.js";
|
|
5
|
+
export const TARGET_MESSAGING_MEDIA_TYPE = "application/vnd.masons.icm-messaging-v1+json";
|
|
6
|
+
export class RuntimeAdapterApiError extends Error {
|
|
7
|
+
status;
|
|
8
|
+
code;
|
|
9
|
+
constructor(status, code, message) {
|
|
10
|
+
super(message);
|
|
11
|
+
this.name = "RuntimeAdapterApiError";
|
|
12
|
+
this.status = status;
|
|
13
|
+
this.code = code;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
export class RuntimeAdapterNetworkError extends Error {
|
|
17
|
+
code;
|
|
18
|
+
detail;
|
|
19
|
+
requestKind;
|
|
20
|
+
constructor(code, detail, requestKind) {
|
|
21
|
+
super(`runtime adapter ${code}: ${detail}`);
|
|
22
|
+
this.name = "RuntimeAdapterNetworkError";
|
|
23
|
+
this.code = code;
|
|
24
|
+
this.detail = detail;
|
|
25
|
+
this.requestKind = requestKind;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
const DEFAULT_REQUEST_TIMEOUT_MS = {
|
|
29
|
+
register_runtime_endpoint: 15_000,
|
|
30
|
+
heartbeat_runtime_endpoint: 10_000,
|
|
31
|
+
transition_runtime_endpoint_state: 15_000,
|
|
32
|
+
update_runtime_endpoint_display_metadata: 15_000,
|
|
33
|
+
unregister_runtime_endpoint: 15_000,
|
|
34
|
+
list_owned_runtime_endpoints: 20_000,
|
|
35
|
+
get_owned_runtime_endpoint: 20_000,
|
|
36
|
+
list_runtime_claim_candidates: 30_000,
|
|
37
|
+
read_runtime_session_conversation_history: 30_000,
|
|
38
|
+
dispatch_runtime_undispatched: 15_000,
|
|
39
|
+
init_runtime_owner_action_handoff: 15_000,
|
|
40
|
+
poll_runtime_owner_action_handoff: 15_000,
|
|
41
|
+
claim_runtime_self_assignment: 15_000,
|
|
42
|
+
release_runtime_self_assignment: 15_000,
|
|
43
|
+
settle_runtime_mailbox_item: 15_000,
|
|
44
|
+
dismiss_runtime_undispatched: 15_000,
|
|
45
|
+
create_runtime_assignment_reply_intent: 15_000,
|
|
46
|
+
create_runtime_outbound_message_intent: 15_000,
|
|
47
|
+
list_runtime_directed_session_targets: 15_000,
|
|
48
|
+
submit_runtime_target_send: 15_000,
|
|
49
|
+
read_runtime_target_attempt_outcome: 15_000,
|
|
50
|
+
read_runtime_target_message_outcome: 15_000,
|
|
51
|
+
acquire_node_recipient_message: 10_000,
|
|
52
|
+
submit_node_recipient_message_admission_evidence: 15_000,
|
|
53
|
+
acquire_services_retained_node_recipient_message: 10_000,
|
|
54
|
+
consume_services_retained_node_recipient_message: 15_000,
|
|
55
|
+
acquire_exact_target_presentation: 10_000,
|
|
56
|
+
submit_exact_target_recipient_admission_evidence: 15_000,
|
|
57
|
+
submit_exact_target_presentation_evidence: 15_000,
|
|
58
|
+
release_exact_target_presentation: 15_000,
|
|
59
|
+
emit_runtime_processing_state: 15_000,
|
|
60
|
+
};
|
|
61
|
+
export async function registerRuntimeEndpoint(cfg, runtimeKey, params) {
|
|
62
|
+
const res = await apiFetch(cfg, "register_runtime_endpoint", `${baseUrl(cfg)}/runtime-endpoints`, {
|
|
63
|
+
method: "POST",
|
|
64
|
+
headers: jsonHeaders(runtimeKey),
|
|
65
|
+
body: JSON.stringify(params),
|
|
66
|
+
});
|
|
67
|
+
if (!res.ok)
|
|
68
|
+
return handleError(res);
|
|
69
|
+
return readRegisterRuntimeEndpointResponse(await res.json().catch(() => null));
|
|
70
|
+
}
|
|
71
|
+
export async function heartbeatRuntimeEndpoint(cfg, runtimeKey, endpointId) {
|
|
72
|
+
const res = await apiFetch(cfg, "heartbeat_runtime_endpoint", `${baseUrl(cfg)}/runtime-endpoints/${encodeURIComponent(endpointId)}/heartbeat`, {
|
|
73
|
+
method: "POST",
|
|
74
|
+
headers: authHeaders(runtimeKey),
|
|
75
|
+
});
|
|
76
|
+
if (!res.ok)
|
|
77
|
+
return handleError(res);
|
|
78
|
+
return ((await res.json().catch(() => ({}))) ?? {});
|
|
79
|
+
}
|
|
80
|
+
export async function transitionRuntimeEndpointState(cfg, runtimeKey, endpointId, params) {
|
|
81
|
+
const res = await apiFetch(cfg, "transition_runtime_endpoint_state", `${baseUrl(cfg)}/runtime-endpoints/${encodeURIComponent(endpointId)}/state`, {
|
|
82
|
+
method: "POST",
|
|
83
|
+
headers: jsonHeaders(runtimeKey),
|
|
84
|
+
body: JSON.stringify(params),
|
|
85
|
+
});
|
|
86
|
+
if (!res.ok)
|
|
87
|
+
return handleError(res);
|
|
88
|
+
return ((await res.json().catch(() => ({}))) ??
|
|
89
|
+
{});
|
|
90
|
+
}
|
|
91
|
+
export async function updateRuntimeEndpointDisplayMetadata(cfg, runtimeKey, endpointId, params) {
|
|
92
|
+
const res = await apiFetch(cfg, "update_runtime_endpoint_display_metadata", `${baseUrl(cfg)}/runtime-endpoints/${encodeURIComponent(endpointId)}/display-metadata`, {
|
|
93
|
+
method: "POST",
|
|
94
|
+
headers: jsonHeaders(runtimeKey),
|
|
95
|
+
body: JSON.stringify(params),
|
|
96
|
+
});
|
|
97
|
+
if (!res.ok)
|
|
98
|
+
return handleError(res);
|
|
99
|
+
}
|
|
100
|
+
export async function unregisterRuntimeEndpoint(cfg, runtimeKey, endpointId, asNodeId) {
|
|
101
|
+
const query = asNodeId
|
|
102
|
+
? `?${new URLSearchParams({ as: asNodeId }).toString()}`
|
|
103
|
+
: "";
|
|
104
|
+
const res = await apiFetch(cfg, "unregister_runtime_endpoint", `${baseUrl(cfg)}/runtime-endpoints/${encodeURIComponent(endpointId)}${query}`, {
|
|
105
|
+
method: "DELETE",
|
|
106
|
+
headers: authHeaders(runtimeKey),
|
|
107
|
+
});
|
|
108
|
+
if (!res.ok)
|
|
109
|
+
return handleError(res);
|
|
110
|
+
return ((await res.json().catch(() => ({}))) ??
|
|
111
|
+
{});
|
|
112
|
+
}
|
|
113
|
+
export async function listOwnedRuntimeEndpoints(cfg, runtimeKey, asNodeId, options = {}) {
|
|
114
|
+
const query = new URLSearchParams({ as: asNodeId }).toString();
|
|
115
|
+
const res = await apiFetch(cfg, "list_owned_runtime_endpoints", `${baseUrl(cfg)}/runtime-endpoints?${query}`, {
|
|
116
|
+
headers: endpointReadHeaders(runtimeKey, options),
|
|
117
|
+
});
|
|
118
|
+
if (!res.ok)
|
|
119
|
+
return handleError(res);
|
|
120
|
+
return readListOwnedRuntimeEndpointsResponse(await res.json().catch(() => null));
|
|
121
|
+
}
|
|
122
|
+
export async function getOwnedRuntimeEndpoint(cfg, runtimeKey, endpointId, asNodeId, options = {}) {
|
|
123
|
+
const query = new URLSearchParams({ as: asNodeId }).toString();
|
|
124
|
+
const res = await apiFetch(cfg, "get_owned_runtime_endpoint", `${baseUrl(cfg)}/runtime-endpoints/${encodeURIComponent(endpointId)}?${query}`, {
|
|
125
|
+
headers: endpointReadHeaders(runtimeKey, options),
|
|
126
|
+
});
|
|
127
|
+
if (!res.ok)
|
|
128
|
+
return handleError(res);
|
|
129
|
+
return readOwnedRuntimeEndpointView(await res.json().catch(() => null));
|
|
130
|
+
}
|
|
131
|
+
export async function listRuntimeClaimCandidates(cfg, runtimeKey, params) {
|
|
132
|
+
const res = await apiFetch(cfg, "list_runtime_claim_candidates", `${baseUrl(cfg)}/runtime/claim-candidates`, {
|
|
133
|
+
method: "POST",
|
|
134
|
+
headers: jsonHeaders(runtimeKey),
|
|
135
|
+
body: JSON.stringify({
|
|
136
|
+
type: "runtime_claim_candidates",
|
|
137
|
+
version: 1,
|
|
138
|
+
endpoint_id: params.endpoint_id,
|
|
139
|
+
endpoint_nonce: params.endpoint_nonce,
|
|
140
|
+
...(params.limit !== undefined && { limit: params.limit }),
|
|
141
|
+
}),
|
|
142
|
+
});
|
|
143
|
+
if (!res.ok) {
|
|
144
|
+
const error = await readErrorEnvelope(res);
|
|
145
|
+
throw new RuntimeAdapterApiError(res.status, error.code ?? "claim_candidates_failed", error.message ?? res.statusText);
|
|
146
|
+
}
|
|
147
|
+
const raw = (await res.json().catch(() => null));
|
|
148
|
+
const rawItems = typeof raw === "object" &&
|
|
149
|
+
raw !== null &&
|
|
150
|
+
!Array.isArray(raw) &&
|
|
151
|
+
Array.isArray(raw.items)
|
|
152
|
+
? raw.items
|
|
153
|
+
: null;
|
|
154
|
+
if (!rawItems) {
|
|
155
|
+
throw new RuntimeAdapterApiError(502, "invalid_claim_candidates_response", "invalid claim-candidates response");
|
|
156
|
+
}
|
|
157
|
+
const items = [];
|
|
158
|
+
for (const rawItem of rawItems) {
|
|
159
|
+
const item = parseRuntimeClaimCandidate(rawItem);
|
|
160
|
+
if (!item) {
|
|
161
|
+
throw new RuntimeAdapterApiError(502, "invalid_claim_candidates_item", "invalid claim-candidates item");
|
|
162
|
+
}
|
|
163
|
+
items.push(item);
|
|
164
|
+
}
|
|
165
|
+
return { items };
|
|
166
|
+
}
|
|
167
|
+
export async function readRuntimeSessionConversationHistory(cfg, runtimeKey, params) {
|
|
168
|
+
const res = await apiFetch(cfg, "read_runtime_session_conversation_history", `${baseUrl(cfg)}/runtime/session-conversation-history`, {
|
|
169
|
+
method: "POST",
|
|
170
|
+
headers: jsonHeaders(runtimeKey),
|
|
171
|
+
body: JSON.stringify({
|
|
172
|
+
type: "runtime_session_conversation_history",
|
|
173
|
+
version: 1,
|
|
174
|
+
endpoint_id: params.endpoint_id,
|
|
175
|
+
endpoint_nonce: params.endpoint_nonce,
|
|
176
|
+
conversation_ref: params.conversation_ref,
|
|
177
|
+
...(params.limit !== undefined && { limit: params.limit }),
|
|
178
|
+
...(params.before !== undefined && { before: params.before }),
|
|
179
|
+
...(params.after !== undefined && { after: params.after }),
|
|
180
|
+
}),
|
|
181
|
+
});
|
|
182
|
+
if (!res.ok) {
|
|
183
|
+
const error = await readErrorEnvelope(res);
|
|
184
|
+
throw new RuntimeAdapterApiError(res.status, error.code ?? "runtime_conversation_history_failed", error.message ?? res.statusText);
|
|
185
|
+
}
|
|
186
|
+
const raw = (await res.json().catch(() => null));
|
|
187
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
188
|
+
throw new RuntimeAdapterApiError(502, "invalid_runtime_conversation_history_response", "invalid runtime conversation history response");
|
|
189
|
+
}
|
|
190
|
+
const body = raw;
|
|
191
|
+
const conversationRef = parseRuntimeConversationRef(body.conversation_ref);
|
|
192
|
+
const rawItems = Array.isArray(body.items) ? body.items : null;
|
|
193
|
+
if (!conversationRef || !rawItems || typeof body.has_more !== "boolean") {
|
|
194
|
+
throw new RuntimeAdapterApiError(502, "invalid_runtime_conversation_history_response", "invalid runtime conversation history response");
|
|
195
|
+
}
|
|
196
|
+
if (conversationRef.kind !== params.conversation_ref.kind ||
|
|
197
|
+
conversationRef.id !== params.conversation_ref.id) {
|
|
198
|
+
throw new RuntimeAdapterApiError(502, "runtime_conversation_history_ref_mismatch", "runtime conversation history response did not match the requested conversation_ref");
|
|
199
|
+
}
|
|
200
|
+
const items = [];
|
|
201
|
+
for (const rawItem of rawItems) {
|
|
202
|
+
const item = parseRuntimeConversationHistoryMessage(rawItem);
|
|
203
|
+
if (!item) {
|
|
204
|
+
throw new RuntimeAdapterApiError(502, "invalid_runtime_conversation_history_item", "invalid runtime conversation history item");
|
|
205
|
+
}
|
|
206
|
+
items.push(item);
|
|
207
|
+
}
|
|
208
|
+
return {
|
|
209
|
+
conversation_ref: conversationRef,
|
|
210
|
+
items,
|
|
211
|
+
has_more: body.has_more,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
export async function dispatchRuntimeUndispatched(cfg, ownerAccessToken, params) {
|
|
215
|
+
const res = await apiFetch(cfg, "dispatch_runtime_undispatched", `${baseUrl(cfg)}/runtime/dispatch`, {
|
|
216
|
+
method: "POST",
|
|
217
|
+
headers: jsonHeaders(ownerAccessToken),
|
|
218
|
+
body: JSON.stringify({
|
|
219
|
+
undispatched_id: params.undispatched_id,
|
|
220
|
+
target_endpoint_id: params.target_endpoint_id,
|
|
221
|
+
...(params.target_ref !== undefined && {
|
|
222
|
+
target_ref: params.target_ref,
|
|
223
|
+
}),
|
|
224
|
+
...(params.idempotency_key !== undefined && {
|
|
225
|
+
idempotency_key: params.idempotency_key,
|
|
226
|
+
}),
|
|
227
|
+
}),
|
|
228
|
+
});
|
|
229
|
+
if (!res.ok)
|
|
230
|
+
return handleError(res);
|
|
231
|
+
return readRuntimeDispatchUndispatchedResponse(await res.json().catch(() => null));
|
|
232
|
+
}
|
|
233
|
+
export async function initRuntimeOwnerActionHandoff(cfg, runtimeKey, params) {
|
|
234
|
+
const res = await apiFetch(cfg, "init_runtime_owner_action_handoff", `${baseUrl(cfg)}/runtime/owner-action-handoffs/init`, {
|
|
235
|
+
method: "POST",
|
|
236
|
+
headers: jsonHeaders(runtimeKey),
|
|
237
|
+
body: JSON.stringify({
|
|
238
|
+
type: "runtime_owner_action_handoff_init",
|
|
239
|
+
version: 1,
|
|
240
|
+
action: "dispatch_undispatched",
|
|
241
|
+
undispatched_id: params.undispatched_id,
|
|
242
|
+
target_endpoint_id: params.target_endpoint_id,
|
|
243
|
+
endpoint_nonce: params.endpoint_nonce,
|
|
244
|
+
target_ref: params.target_ref,
|
|
245
|
+
...(params.selection_hint !== undefined && {
|
|
246
|
+
selection_hint: params.selection_hint,
|
|
247
|
+
}),
|
|
248
|
+
}),
|
|
249
|
+
});
|
|
250
|
+
if (!res.ok)
|
|
251
|
+
return handleError(res);
|
|
252
|
+
return readRuntimeOwnerActionHandoffInitResponse(await res.json().catch(() => null));
|
|
253
|
+
}
|
|
254
|
+
export async function pollRuntimeOwnerActionHandoff(cfg, runtimeKey, params) {
|
|
255
|
+
const res = await apiFetch(cfg, "poll_runtime_owner_action_handoff", `${baseUrl(cfg)}/runtime/owner-action-handoffs/${encodeURIComponent(params.handoff_session_id)}/poll`, {
|
|
256
|
+
method: "POST",
|
|
257
|
+
headers: jsonHeaders(runtimeKey),
|
|
258
|
+
body: JSON.stringify({
|
|
259
|
+
type: "runtime_owner_action_handoff_poll",
|
|
260
|
+
version: 1,
|
|
261
|
+
target_endpoint_id: params.target_endpoint_id,
|
|
262
|
+
endpoint_nonce: params.endpoint_nonce,
|
|
263
|
+
}),
|
|
264
|
+
});
|
|
265
|
+
if (!res.ok)
|
|
266
|
+
return handleError(res);
|
|
267
|
+
return readRuntimeOwnerActionHandoffPollResponse(await res.json().catch(() => null));
|
|
268
|
+
}
|
|
269
|
+
export async function claimRuntimeSelfAssignment(cfg, runtimeKey, params) {
|
|
270
|
+
let res;
|
|
271
|
+
try {
|
|
272
|
+
res = await apiFetch(cfg, "claim_runtime_self_assignment", `${baseUrl(cfg)}/runtime/self-claim`, {
|
|
273
|
+
method: "POST",
|
|
274
|
+
headers: jsonHeaders(runtimeKey),
|
|
275
|
+
body: JSON.stringify({
|
|
276
|
+
undispatched_id: params.undispatched_id,
|
|
277
|
+
target_endpoint_id: params.target_endpoint_id,
|
|
278
|
+
endpoint_nonce: params.endpoint_nonce,
|
|
279
|
+
...(params.expected_target_ref !== undefined && {
|
|
280
|
+
expected_target_ref: params.expected_target_ref,
|
|
281
|
+
}),
|
|
282
|
+
...(params.recover_existing !== undefined && {
|
|
283
|
+
recover_existing: params.recover_existing,
|
|
284
|
+
}),
|
|
285
|
+
}),
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
catch (err) {
|
|
289
|
+
return {
|
|
290
|
+
ok: false,
|
|
291
|
+
terminal: false,
|
|
292
|
+
detail: networkDetail(err),
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
if (res.status === 200 || res.status === 201) {
|
|
296
|
+
const outcome = await readRuntimeSelfClaimSuccess(res, params);
|
|
297
|
+
if (!outcome.ok) {
|
|
298
|
+
const releaseParams = buildPostCasReleaseParams(outcome.postCasInvalidClaim, params.endpoint_nonce);
|
|
299
|
+
if (releaseParams) {
|
|
300
|
+
const releaseOutcome = await releaseRuntimeSelfAssignment(cfg, runtimeKey, releaseParams);
|
|
301
|
+
if (!releaseOutcome.ok) {
|
|
302
|
+
return {
|
|
303
|
+
...outcome,
|
|
304
|
+
detail: `${outcome.detail ?? "invalid self-claim response"}; release_failed: ${releaseOutcome.detail ?? releaseOutcome.status ?? "unknown"}`,
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
return outcome;
|
|
310
|
+
}
|
|
311
|
+
if ([401, 403, 404, 409, 422].includes(res.status)) {
|
|
312
|
+
return {
|
|
313
|
+
ok: false,
|
|
314
|
+
terminal: true,
|
|
315
|
+
status: res.status,
|
|
316
|
+
detail: await readErrorDetail(res),
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
return {
|
|
320
|
+
ok: false,
|
|
321
|
+
terminal: false,
|
|
322
|
+
status: res.status,
|
|
323
|
+
detail: await readErrorDetail(res),
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
export async function releaseRuntimeSelfAssignment(cfg, runtimeKey, params) {
|
|
327
|
+
let res;
|
|
328
|
+
try {
|
|
329
|
+
res = await apiFetch(cfg, "release_runtime_self_assignment", `${baseUrl(cfg)}/runtime/self-claim/release`, {
|
|
330
|
+
method: "POST",
|
|
331
|
+
headers: jsonHeaders(runtimeKey),
|
|
332
|
+
body: JSON.stringify(params),
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
catch (err) {
|
|
336
|
+
return transientNetworkOutcome(err);
|
|
337
|
+
}
|
|
338
|
+
if (res.status === 204)
|
|
339
|
+
return { ok: true };
|
|
340
|
+
if ([401, 403, 404, 409, 422].includes(res.status)) {
|
|
341
|
+
return terminalHttpOutcome(res);
|
|
342
|
+
}
|
|
343
|
+
return transientHttpOutcome(res);
|
|
344
|
+
}
|
|
345
|
+
export async function settleRuntimeMailboxItem(cfg, runtimeKey, params) {
|
|
346
|
+
let res;
|
|
347
|
+
try {
|
|
348
|
+
res = await apiFetch(cfg, "settle_runtime_mailbox_item", `${baseUrl(cfg)}/runtime/self-settle`, {
|
|
349
|
+
method: "POST",
|
|
350
|
+
headers: jsonHeaders(runtimeKey),
|
|
351
|
+
body: JSON.stringify(params),
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
catch (err) {
|
|
355
|
+
return selfSettleFailureOutcome(transientNetworkOutcome(err));
|
|
356
|
+
}
|
|
357
|
+
if (res.ok) {
|
|
358
|
+
const body = await res.json().catch(() => null);
|
|
359
|
+
const parsed = readRuntimeSelfSettleResponse(body, params);
|
|
360
|
+
if (!parsed.ok) {
|
|
361
|
+
return {
|
|
362
|
+
ok: false,
|
|
363
|
+
terminal: true,
|
|
364
|
+
detail: parsed.detail,
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
return { ok: true, settle: parsed.settle };
|
|
368
|
+
}
|
|
369
|
+
if ([401, 403, 404, 409, 422].includes(res.status)) {
|
|
370
|
+
return selfSettleFailureOutcome(await terminalHttpOutcome(res));
|
|
371
|
+
}
|
|
372
|
+
return selfSettleFailureOutcome(await transientHttpOutcome(res));
|
|
373
|
+
}
|
|
374
|
+
export async function dismissRuntimeUndispatched(cfg, ownerAccessToken, params) {
|
|
375
|
+
const res = await apiFetch(cfg, "dismiss_runtime_undispatched", `${baseUrl(cfg)}/runtime/undispatched-dismiss`, {
|
|
376
|
+
method: "POST",
|
|
377
|
+
headers: jsonHeaders(ownerAccessToken),
|
|
378
|
+
body: JSON.stringify(params),
|
|
379
|
+
});
|
|
380
|
+
if (!res.ok)
|
|
381
|
+
return handleError(res);
|
|
382
|
+
return readRuntimeUndispatchedDismissResponse(await res.json().catch(() => null), params);
|
|
383
|
+
}
|
|
384
|
+
function selfSettleFailureOutcome(outcome) {
|
|
385
|
+
if (!outcome.ok)
|
|
386
|
+
return outcome;
|
|
387
|
+
return {
|
|
388
|
+
ok: false,
|
|
389
|
+
terminal: false,
|
|
390
|
+
detail: "runtime self-settle completed without a response body",
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
export async function createRuntimeAssignmentReplyIntent(cfg, runtimeKey, params) {
|
|
394
|
+
const res = await apiFetch(cfg, "create_runtime_assignment_reply_intent", `${baseUrl(cfg)}/runtime/assignment-reply`, {
|
|
395
|
+
method: "POST",
|
|
396
|
+
headers: jsonHeaders(runtimeKey),
|
|
397
|
+
body: JSON.stringify({
|
|
398
|
+
type: "runtime_assignment_reply_request",
|
|
399
|
+
version: 1,
|
|
400
|
+
endpoint_id: params.endpoint_id,
|
|
401
|
+
endpoint_nonce: params.endpoint_nonce,
|
|
402
|
+
source_message_id: params.source_message_id,
|
|
403
|
+
assignment_id: params.assignment_id,
|
|
404
|
+
reply_request_id: params.reply_request_id,
|
|
405
|
+
content: params.content,
|
|
406
|
+
...(params.content_type !== undefined && {
|
|
407
|
+
content_type: params.content_type,
|
|
408
|
+
}),
|
|
409
|
+
...(params.metadata !== undefined && { metadata: params.metadata }),
|
|
410
|
+
}),
|
|
411
|
+
});
|
|
412
|
+
if (!res.ok)
|
|
413
|
+
return handleError(res);
|
|
414
|
+
return readRuntimeAssignmentReplyIntentResponse(await res.json().catch(() => null));
|
|
415
|
+
}
|
|
416
|
+
export async function createRuntimeOutboundMessageIntent(cfg, runtimeKey, params) {
|
|
417
|
+
const res = await apiFetch(cfg, "create_runtime_outbound_message_intent", `${baseUrl(cfg)}/runtime/outbound-message`, {
|
|
418
|
+
method: "POST",
|
|
419
|
+
headers: jsonHeaders(runtimeKey),
|
|
420
|
+
body: JSON.stringify({
|
|
421
|
+
type: "runtime_outbound_message_request",
|
|
422
|
+
version: 1,
|
|
423
|
+
endpoint_id: params.endpoint_id,
|
|
424
|
+
endpoint_nonce: params.endpoint_nonce,
|
|
425
|
+
send_request_id: params.send_request_id,
|
|
426
|
+
to: params.to,
|
|
427
|
+
content: params.content,
|
|
428
|
+
...(params.content_type !== undefined && {
|
|
429
|
+
content_type: params.content_type,
|
|
430
|
+
}),
|
|
431
|
+
...(params.metadata !== undefined && { metadata: params.metadata }),
|
|
432
|
+
}),
|
|
433
|
+
});
|
|
434
|
+
if (!res.ok)
|
|
435
|
+
return handleError(res);
|
|
436
|
+
return readRuntimeOutboundMessageIntentResponse(await res.json().catch(() => null));
|
|
437
|
+
}
|
|
438
|
+
export async function listRuntimeDirectedSessionTargets(cfg, runtimeKey, params) {
|
|
439
|
+
const res = await apiFetch(cfg, "list_runtime_directed_session_targets", `${baseUrl(cfg)}/runtime/member-send`, {
|
|
440
|
+
method: "POST",
|
|
441
|
+
headers: jsonHeaders(runtimeKey),
|
|
442
|
+
body: JSON.stringify({
|
|
443
|
+
type: "list_runtime_session_targets",
|
|
444
|
+
version: 1,
|
|
445
|
+
...params,
|
|
446
|
+
}),
|
|
447
|
+
});
|
|
448
|
+
if (!res.ok)
|
|
449
|
+
return handleError(res);
|
|
450
|
+
const parsed = readRuntimeDirectedSessionTargetPage(await res.json().catch(() => null));
|
|
451
|
+
if (!parsed) {
|
|
452
|
+
throw new RuntimeAdapterApiError(502, "invalid_directed_session_page", "invalid bounded directed Session page response");
|
|
453
|
+
}
|
|
454
|
+
return parsed;
|
|
455
|
+
}
|
|
456
|
+
export async function listRuntimeDirectedSessionActionTargets(cfg, runtimeKey, params) {
|
|
457
|
+
const res = await apiFetch(cfg, "list_runtime_directed_session_targets", `${baseUrl(cfg)}/runtime/member-send`, {
|
|
458
|
+
method: "POST",
|
|
459
|
+
headers: jsonHeaders(runtimeKey),
|
|
460
|
+
body: JSON.stringify({
|
|
461
|
+
type: "list_runtime_session_targets",
|
|
462
|
+
version: 2,
|
|
463
|
+
...params,
|
|
464
|
+
}),
|
|
465
|
+
});
|
|
466
|
+
if (!res.ok)
|
|
467
|
+
return handleError(res);
|
|
468
|
+
const parsed = readRuntimeDirectedSessionActionTargetPage(await res.json().catch(() => null));
|
|
469
|
+
if (!parsed) {
|
|
470
|
+
throw new RuntimeAdapterApiError(502, "invalid_directed_session_action_projection", "invalid directed Session action target projection response");
|
|
471
|
+
}
|
|
472
|
+
return parsed;
|
|
473
|
+
}
|
|
474
|
+
export async function resolveRuntimeDirectedSessionTargets(cfg, runtimeKey, params) {
|
|
475
|
+
const res = await apiFetch(cfg, "list_runtime_directed_session_targets", `${baseUrl(cfg)}/runtime/member-send`, {
|
|
476
|
+
method: "POST",
|
|
477
|
+
headers: jsonHeaders(runtimeKey),
|
|
478
|
+
body: JSON.stringify({
|
|
479
|
+
type: "list_runtime_session_targets",
|
|
480
|
+
version: 3,
|
|
481
|
+
...params,
|
|
482
|
+
}),
|
|
483
|
+
});
|
|
484
|
+
if (!res.ok)
|
|
485
|
+
return handleError(res);
|
|
486
|
+
const parsed = readRuntimeDirectedSessionResolutionResponse(await res.json().catch(() => null), params.query);
|
|
487
|
+
if (!parsed) {
|
|
488
|
+
throw new RuntimeAdapterApiError(502, "invalid_directed_session_resolution", "invalid directed Session target resolution response");
|
|
489
|
+
}
|
|
490
|
+
return parsed;
|
|
491
|
+
}
|
|
492
|
+
export async function submitRuntimeTargetSend(cfg, runtimeKey, params) {
|
|
493
|
+
const res = await apiFetch(cfg, "submit_runtime_target_send", `${baseUrl(cfg)}/messages`, {
|
|
494
|
+
method: "POST",
|
|
495
|
+
headers: targetMessagingHeaders(runtimeKey),
|
|
496
|
+
body: JSON.stringify(params),
|
|
497
|
+
});
|
|
498
|
+
if (!res.ok)
|
|
499
|
+
return handleError(res);
|
|
500
|
+
return readRuntimeTargetSendOutcomeOrThrow(await res.json().catch(() => null));
|
|
501
|
+
}
|
|
502
|
+
export async function readRuntimeTargetAttemptOutcome(cfg, runtimeKey, attemptRef) {
|
|
503
|
+
const res = await apiFetch(cfg, "read_runtime_target_attempt_outcome", `${baseUrl(cfg)}/sending-attempts/${encodeURIComponent(attemptRef)}/outcome`, {
|
|
504
|
+
method: "GET",
|
|
505
|
+
headers: targetMessagingReadHeaders(runtimeKey),
|
|
506
|
+
});
|
|
507
|
+
if (!res.ok)
|
|
508
|
+
return handleError(res);
|
|
509
|
+
const outcome = readRuntimeTargetSendOutcomeOrThrow(await res.json().catch(() => null));
|
|
510
|
+
if ("attempt_ref" in outcome && outcome.attempt_ref !== attemptRef) {
|
|
511
|
+
throw new RuntimeAdapterApiError(502, "invalid_target_attempt_outcome", "invalid TARGET attempt outcome response");
|
|
512
|
+
}
|
|
513
|
+
return outcome;
|
|
514
|
+
}
|
|
515
|
+
export async function readRuntimeTargetMessageOutcome(cfg, runtimeKey, messageRef) {
|
|
516
|
+
const res = await apiFetch(cfg, "read_runtime_target_message_outcome", `${baseUrl(cfg)}/messages/${encodeURIComponent(messageRef)}/outcome`, {
|
|
517
|
+
method: "GET",
|
|
518
|
+
headers: targetMessagingReadHeaders(runtimeKey),
|
|
519
|
+
});
|
|
520
|
+
if (!res.ok)
|
|
521
|
+
return handleError(res);
|
|
522
|
+
const outcome = parseRuntimeTargetMessageOutcome(await res.json().catch(() => null));
|
|
523
|
+
if (!outcome || outcome.message_ref !== messageRef) {
|
|
524
|
+
throw new RuntimeAdapterApiError(502, "invalid_target_message_outcome", "invalid TARGET Message outcome response");
|
|
525
|
+
}
|
|
526
|
+
return outcome;
|
|
527
|
+
}
|
|
528
|
+
function readRuntimeTargetSendOutcomeOrThrow(raw) {
|
|
529
|
+
const parsed = parseRuntimeTargetAttemptOutcome(raw) ??
|
|
530
|
+
parseRuntimeTargetMessageOutcome(raw);
|
|
531
|
+
if (!parsed) {
|
|
532
|
+
throw new RuntimeAdapterApiError(502, "invalid_target_send_outcome", "invalid TARGET send outcome response");
|
|
533
|
+
}
|
|
534
|
+
return parsed;
|
|
535
|
+
}
|
|
536
|
+
function parseRuntimeTargetAttemptOutcome(raw) {
|
|
537
|
+
if (!isRecord(raw) ||
|
|
538
|
+
!isNonEmptyString(raw.attempt_ref) ||
|
|
539
|
+
!isNonEmptyString(raw.projection_etag) ||
|
|
540
|
+
!isCanonicalIsoInstantValue(raw.outcome_at)) {
|
|
541
|
+
return null;
|
|
542
|
+
}
|
|
543
|
+
const common = {
|
|
544
|
+
attempt_ref: raw.attempt_ref,
|
|
545
|
+
projection_etag: raw.projection_etag,
|
|
546
|
+
outcome_at: raw.outcome_at,
|
|
547
|
+
};
|
|
548
|
+
if (raw.outcome === "refused") {
|
|
549
|
+
if (raw.refusal_code === "remote_definitive_refusal") {
|
|
550
|
+
if (!hasExactKeys(raw, [
|
|
551
|
+
"attempt_ref",
|
|
552
|
+
"local_outcome_recorded_at",
|
|
553
|
+
"outcome",
|
|
554
|
+
"outcome_at",
|
|
555
|
+
"projection_etag",
|
|
556
|
+
"refusal_code",
|
|
557
|
+
"remote_refusal_decided_at",
|
|
558
|
+
]) ||
|
|
559
|
+
!isCanonicalIsoInstantValue(raw.remote_refusal_decided_at) ||
|
|
560
|
+
!isCanonicalIsoInstantValue(raw.local_outcome_recorded_at) ||
|
|
561
|
+
raw.outcome_at !== raw.local_outcome_recorded_at) {
|
|
562
|
+
return null;
|
|
563
|
+
}
|
|
564
|
+
return {
|
|
565
|
+
...common,
|
|
566
|
+
outcome: "refused",
|
|
567
|
+
refusal_code: "remote_definitive_refusal",
|
|
568
|
+
remote_refusal_decided_at: raw.remote_refusal_decided_at,
|
|
569
|
+
local_outcome_recorded_at: raw.local_outcome_recorded_at,
|
|
570
|
+
};
|
|
571
|
+
}
|
|
572
|
+
if (!hasExactKeys(raw, [
|
|
573
|
+
"attempt_ref",
|
|
574
|
+
"outcome",
|
|
575
|
+
"outcome_at",
|
|
576
|
+
"projection_etag",
|
|
577
|
+
"refusal_code",
|
|
578
|
+
]) ||
|
|
579
|
+
![
|
|
580
|
+
"not_authorized",
|
|
581
|
+
"identity_not_admissible",
|
|
582
|
+
"invalid_target",
|
|
583
|
+
"invalid_declaration",
|
|
584
|
+
"capability_not_current",
|
|
585
|
+
].includes(raw.refusal_code)) {
|
|
586
|
+
return null;
|
|
587
|
+
}
|
|
588
|
+
return {
|
|
589
|
+
...common,
|
|
590
|
+
outcome: "refused",
|
|
591
|
+
refusal_code: raw.refusal_code,
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
if (raw.outcome === "placement_pending" ||
|
|
595
|
+
raw.outcome === "placement_lapsed") {
|
|
596
|
+
if (!hasExactKeys(raw, [
|
|
597
|
+
"attempt_ref",
|
|
598
|
+
"outcome",
|
|
599
|
+
"outcome_at",
|
|
600
|
+
"placement_deadline",
|
|
601
|
+
"projection_etag",
|
|
602
|
+
]) ||
|
|
603
|
+
!isCanonicalIsoInstantValue(raw.placement_deadline) ||
|
|
604
|
+
(raw.outcome === "placement_lapsed" &&
|
|
605
|
+
Date.parse(raw.outcome_at) < Date.parse(raw.placement_deadline))) {
|
|
606
|
+
return null;
|
|
607
|
+
}
|
|
608
|
+
return {
|
|
609
|
+
...common,
|
|
610
|
+
outcome: raw.outcome,
|
|
611
|
+
placement_deadline: raw.placement_deadline,
|
|
612
|
+
};
|
|
613
|
+
}
|
|
614
|
+
if (raw.outcome === "dispatch_pending" ||
|
|
615
|
+
raw.outcome === "acceptance_unconfirmed") {
|
|
616
|
+
if (!hasExactKeys(raw, [
|
|
617
|
+
"attempt_ref",
|
|
618
|
+
"outcome",
|
|
619
|
+
"outcome_at",
|
|
620
|
+
"projection_etag",
|
|
621
|
+
])) {
|
|
622
|
+
return null;
|
|
623
|
+
}
|
|
624
|
+
return { ...common, outcome: raw.outcome };
|
|
625
|
+
}
|
|
626
|
+
return null;
|
|
627
|
+
}
|
|
628
|
+
function parseRuntimeTargetMessageOutcome(raw) {
|
|
629
|
+
if (!isRecord(raw) ||
|
|
630
|
+
!isNonEmptyString(raw.message_ref) ||
|
|
631
|
+
!isNonEmptyString(raw.conversation_ref) ||
|
|
632
|
+
!isNonEmptyString(raw.projection_etag) ||
|
|
633
|
+
raw.placement !== "placed" ||
|
|
634
|
+
!isCanonicalIsoInstantValue(raw.outcome_at)) {
|
|
635
|
+
return null;
|
|
636
|
+
}
|
|
637
|
+
const common = {
|
|
638
|
+
message_ref: raw.message_ref,
|
|
639
|
+
conversation_ref: raw.conversation_ref,
|
|
640
|
+
projection_etag: raw.projection_etag,
|
|
641
|
+
placement: "placed",
|
|
642
|
+
outcome_at: raw.outcome_at,
|
|
643
|
+
};
|
|
644
|
+
if (raw.custody === "held") {
|
|
645
|
+
if (!hasExactKeys(raw, [
|
|
646
|
+
"conversation_ref",
|
|
647
|
+
"custody",
|
|
648
|
+
"hold_deadline",
|
|
649
|
+
"message_ref",
|
|
650
|
+
"outcome_at",
|
|
651
|
+
"placement",
|
|
652
|
+
"projection_etag",
|
|
653
|
+
]) ||
|
|
654
|
+
!isCanonicalIsoInstantValue(raw.hold_deadline)) {
|
|
655
|
+
return null;
|
|
656
|
+
}
|
|
657
|
+
return { ...common, custody: "held", hold_deadline: raw.hold_deadline };
|
|
658
|
+
}
|
|
659
|
+
if (raw.custody === "lapsed") {
|
|
660
|
+
if (!hasExactKeys(raw, [
|
|
661
|
+
"conversation_ref",
|
|
662
|
+
"custody",
|
|
663
|
+
"hold_deadline",
|
|
664
|
+
"lapsed_at",
|
|
665
|
+
"message_ref",
|
|
666
|
+
"outcome_at",
|
|
667
|
+
"placement",
|
|
668
|
+
"projection_etag",
|
|
669
|
+
]) ||
|
|
670
|
+
!isCanonicalIsoInstantValue(raw.hold_deadline) ||
|
|
671
|
+
!isCanonicalIsoInstantValue(raw.lapsed_at) ||
|
|
672
|
+
raw.outcome_at !== raw.lapsed_at ||
|
|
673
|
+
Date.parse(raw.lapsed_at) < Date.parse(raw.hold_deadline)) {
|
|
674
|
+
return null;
|
|
675
|
+
}
|
|
676
|
+
return {
|
|
677
|
+
...common,
|
|
678
|
+
custody: "lapsed",
|
|
679
|
+
hold_deadline: raw.hold_deadline,
|
|
680
|
+
lapsed_at: raw.lapsed_at,
|
|
681
|
+
};
|
|
682
|
+
}
|
|
683
|
+
if (raw.custody !== "taken")
|
|
684
|
+
return null;
|
|
685
|
+
if (hasExactKeys(raw, [
|
|
686
|
+
"conversation_ref",
|
|
687
|
+
"custody",
|
|
688
|
+
"custody_decided_at",
|
|
689
|
+
"hold_deadline",
|
|
690
|
+
"message_ref",
|
|
691
|
+
"outcome_at",
|
|
692
|
+
"placement",
|
|
693
|
+
"projection_etag",
|
|
694
|
+
]) &&
|
|
695
|
+
isCanonicalIsoInstantValue(raw.hold_deadline) &&
|
|
696
|
+
isCanonicalIsoInstantValue(raw.custody_decided_at) &&
|
|
697
|
+
raw.outcome_at === raw.custody_decided_at) {
|
|
698
|
+
return {
|
|
699
|
+
...common,
|
|
700
|
+
custody: "taken",
|
|
701
|
+
hold_deadline: raw.hold_deadline,
|
|
702
|
+
custody_decided_at: raw.custody_decided_at,
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
if (hasExactKeys(raw, [
|
|
706
|
+
"conversation_ref",
|
|
707
|
+
"custody",
|
|
708
|
+
"local_outcome_recorded_at",
|
|
709
|
+
"message_ref",
|
|
710
|
+
"outcome_at",
|
|
711
|
+
"placement",
|
|
712
|
+
"projection_etag",
|
|
713
|
+
"remote_custody_decided_at",
|
|
714
|
+
]) &&
|
|
715
|
+
isCanonicalIsoInstantValue(raw.remote_custody_decided_at) &&
|
|
716
|
+
isCanonicalIsoInstantValue(raw.local_outcome_recorded_at) &&
|
|
717
|
+
raw.outcome_at === raw.local_outcome_recorded_at) {
|
|
718
|
+
return {
|
|
719
|
+
...common,
|
|
720
|
+
custody: "taken",
|
|
721
|
+
remote_custody_decided_at: raw.remote_custody_decided_at,
|
|
722
|
+
local_outcome_recorded_at: raw.local_outcome_recorded_at,
|
|
723
|
+
};
|
|
724
|
+
}
|
|
725
|
+
return null;
|
|
726
|
+
}
|
|
727
|
+
function readRuntimeDirectedSessionTargetDirectory(raw) {
|
|
728
|
+
if (!isRecord(raw) || !Array.isArray(raw.nodes)) {
|
|
729
|
+
return null;
|
|
730
|
+
}
|
|
731
|
+
const nodes = [];
|
|
732
|
+
for (const rawNode of raw.nodes) {
|
|
733
|
+
const nodeLabelOrigin = isRecord(rawNode)
|
|
734
|
+
? readRuntimeDirectedSessionLabelOrigin(rawNode.display)
|
|
735
|
+
: null;
|
|
736
|
+
if (!isRecord(rawNode) ||
|
|
737
|
+
!isNonEmptyString(rawNode.node_ref) ||
|
|
738
|
+
!isRecord(rawNode.display) ||
|
|
739
|
+
typeof rawNode.display.label !== "string" ||
|
|
740
|
+
nodeLabelOrigin === null ||
|
|
741
|
+
![
|
|
742
|
+
"exact_sessions_complete",
|
|
743
|
+
"exact_sessions_not_applicable",
|
|
744
|
+
"exact_sessions_incomplete",
|
|
745
|
+
].includes(rawNode.coverage) ||
|
|
746
|
+
!Array.isArray(rawNode.sessions)) {
|
|
747
|
+
return null;
|
|
748
|
+
}
|
|
749
|
+
const sessions = [];
|
|
750
|
+
for (const rawSession of rawNode.sessions) {
|
|
751
|
+
const sessionLabelOrigin = isRecord(rawSession)
|
|
752
|
+
? readRuntimeDirectedSessionLabelOrigin(rawSession.display)
|
|
753
|
+
: null;
|
|
754
|
+
if (!isRecord(rawSession) ||
|
|
755
|
+
!isRecord(rawSession.display) ||
|
|
756
|
+
typeof rawSession.display.label !== "string" ||
|
|
757
|
+
sessionLabelOrigin === null ||
|
|
758
|
+
(rawSession.display.host_label !== undefined &&
|
|
759
|
+
typeof rawSession.display.host_label !== "string") ||
|
|
760
|
+
typeof rawSession.target_ref !== "string") {
|
|
761
|
+
return null;
|
|
762
|
+
}
|
|
763
|
+
sessions.push({
|
|
764
|
+
display: {
|
|
765
|
+
label: rawSession.display.label,
|
|
766
|
+
label_origin: sessionLabelOrigin,
|
|
767
|
+
...(typeof rawSession.display.host_label === "string" && {
|
|
768
|
+
host_label: rawSession.display.host_label,
|
|
769
|
+
}),
|
|
770
|
+
},
|
|
771
|
+
target_ref: rawSession.target_ref,
|
|
772
|
+
});
|
|
773
|
+
}
|
|
774
|
+
nodes.push({
|
|
775
|
+
node_ref: rawNode.node_ref,
|
|
776
|
+
display: {
|
|
777
|
+
label: rawNode.display.label,
|
|
778
|
+
label_origin: nodeLabelOrigin,
|
|
779
|
+
},
|
|
780
|
+
coverage: rawNode.coverage,
|
|
781
|
+
sessions,
|
|
782
|
+
});
|
|
783
|
+
}
|
|
784
|
+
return { nodes };
|
|
785
|
+
}
|
|
786
|
+
function readRuntimeDirectedSessionTargetPage(raw) {
|
|
787
|
+
if (!isRecord(raw) ||
|
|
788
|
+
raw.status !== "exact_session_target_page" ||
|
|
789
|
+
raw.contract_version !== 1 ||
|
|
790
|
+
readRuntimeTargetSendSource(raw.acting_source) === null ||
|
|
791
|
+
!isRecord(raw.traversal) ||
|
|
792
|
+
raw.traversal.query_kind !== "all_owned_nodes" ||
|
|
793
|
+
typeof raw.traversal.traversal_ref !== "string" ||
|
|
794
|
+
!isPositiveInteger(raw.traversal.page) ||
|
|
795
|
+
raw.traversal.page_bound !== 12 ||
|
|
796
|
+
!isNonNegativeInteger(raw.traversal.entry_count) ||
|
|
797
|
+
raw.traversal.entry_count > 12 ||
|
|
798
|
+
!isNonNegativeInteger(raw.traversal.choice_count) ||
|
|
799
|
+
raw.traversal.choice_count > 12 ||
|
|
800
|
+
typeof raw.traversal.complete !== "boolean" ||
|
|
801
|
+
typeof raw.traversal.expires_at !== "string" ||
|
|
802
|
+
!isCanonicalIsoInstant(raw.traversal.expires_at) ||
|
|
803
|
+
(raw.traversal.continuation !== undefined &&
|
|
804
|
+
typeof raw.traversal.continuation !== "string") ||
|
|
805
|
+
raw.traversal.complete === (raw.traversal.continuation !== undefined) ||
|
|
806
|
+
!Array.isArray(raw.nodes) ||
|
|
807
|
+
raw.nodes.length > 12) {
|
|
808
|
+
return null;
|
|
809
|
+
}
|
|
810
|
+
for (const rawNode of raw.nodes) {
|
|
811
|
+
if (!isRecord(rawNode) ||
|
|
812
|
+
!isRecord(rawNode.display) ||
|
|
813
|
+
rawNode.display.label_origin === undefined ||
|
|
814
|
+
!Array.isArray(rawNode.sessions) ||
|
|
815
|
+
rawNode.sessions.some((rawSession) => !isRecord(rawSession) ||
|
|
816
|
+
!isRecord(rawSession.display) ||
|
|
817
|
+
rawSession.display.label_origin === undefined)) {
|
|
818
|
+
return null;
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
const directory = readRuntimeDirectedSessionTargetDirectory({
|
|
822
|
+
nodes: raw.nodes,
|
|
823
|
+
});
|
|
824
|
+
if (!directory)
|
|
825
|
+
return null;
|
|
826
|
+
const targetRefs = new Set();
|
|
827
|
+
const visibleTargets = new Set();
|
|
828
|
+
let choiceCount = 0;
|
|
829
|
+
let zeroSessionNodeCount = 0;
|
|
830
|
+
for (const node of directory.nodes) {
|
|
831
|
+
if (node.sessions.length === 0)
|
|
832
|
+
zeroSessionNodeCount += 1;
|
|
833
|
+
for (const session of node.sessions) {
|
|
834
|
+
choiceCount += 1;
|
|
835
|
+
if (targetRefs.has(session.target_ref))
|
|
836
|
+
return null;
|
|
837
|
+
targetRefs.add(session.target_ref);
|
|
838
|
+
const visibleTarget = JSON.stringify([
|
|
839
|
+
node.display.label,
|
|
840
|
+
session.display.label,
|
|
841
|
+
session.display.host_label ?? null,
|
|
842
|
+
]);
|
|
843
|
+
if (visibleTargets.has(visibleTarget))
|
|
844
|
+
return null;
|
|
845
|
+
visibleTargets.add(visibleTarget);
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
if (choiceCount !== raw.traversal.choice_count ||
|
|
849
|
+
choiceCount + zeroSessionNodeCount !== raw.traversal.entry_count) {
|
|
850
|
+
return null;
|
|
851
|
+
}
|
|
852
|
+
return {
|
|
853
|
+
status: "exact_session_target_page",
|
|
854
|
+
contract_version: 1,
|
|
855
|
+
acting_source: readRuntimeTargetSendSource(raw.acting_source),
|
|
856
|
+
traversal: {
|
|
857
|
+
query_kind: "all_owned_nodes",
|
|
858
|
+
traversal_ref: raw.traversal.traversal_ref,
|
|
859
|
+
page: raw.traversal.page,
|
|
860
|
+
page_bound: 12,
|
|
861
|
+
entry_count: raw.traversal.entry_count,
|
|
862
|
+
choice_count: raw.traversal.choice_count,
|
|
863
|
+
complete: raw.traversal.complete,
|
|
864
|
+
expires_at: raw.traversal.expires_at,
|
|
865
|
+
...(typeof raw.traversal.continuation === "string" && {
|
|
866
|
+
continuation: raw.traversal.continuation,
|
|
867
|
+
}),
|
|
868
|
+
},
|
|
869
|
+
nodes: directory.nodes,
|
|
870
|
+
};
|
|
871
|
+
}
|
|
872
|
+
function readRuntimeDirectedSessionActionTargetPage(raw) {
|
|
873
|
+
if (!isRecord(raw) ||
|
|
874
|
+
raw.status !== "exact_session_action_target_projection" ||
|
|
875
|
+
raw.contract_version !== 2 ||
|
|
876
|
+
readRuntimeTargetSendSource(raw.acting_source) === null ||
|
|
877
|
+
!isRecord(raw.current_online) ||
|
|
878
|
+
raw.current_online.status !== "unavailable" ||
|
|
879
|
+
raw.current_online.code !== "receiver_evidence_provider_unavailable" ||
|
|
880
|
+
!isRecord(raw.recent_conversations) ||
|
|
881
|
+
raw.recent_conversations.status !== "available" ||
|
|
882
|
+
typeof raw.recent_conversations.traversal_ref !== "string" ||
|
|
883
|
+
!isPositiveInteger(raw.recent_conversations.page) ||
|
|
884
|
+
raw.recent_conversations.page_bound !== 12 ||
|
|
885
|
+
!isNonNegativeInteger(raw.recent_conversations.entry_count) ||
|
|
886
|
+
raw.recent_conversations.entry_count > 12 ||
|
|
887
|
+
!isNonNegativeInteger(raw.recent_conversations.choice_count) ||
|
|
888
|
+
raw.recent_conversations.choice_count > 12 ||
|
|
889
|
+
typeof raw.recent_conversations.complete !== "boolean" ||
|
|
890
|
+
typeof raw.recent_conversations.expires_at !== "string" ||
|
|
891
|
+
!isCanonicalIsoInstant(raw.recent_conversations.expires_at) ||
|
|
892
|
+
(raw.recent_conversations.continuation !== undefined &&
|
|
893
|
+
typeof raw.recent_conversations.continuation !== "string") ||
|
|
894
|
+
raw.recent_conversations.complete ===
|
|
895
|
+
(raw.recent_conversations.continuation !== undefined) ||
|
|
896
|
+
!Array.isArray(raw.recent_conversations.rows) ||
|
|
897
|
+
raw.recent_conversations.rows.length > 12 ||
|
|
898
|
+
!isRecord(raw.directory_access) ||
|
|
899
|
+
raw.directory_access.status !== "available" ||
|
|
900
|
+
raw.directory_access.contract_version !== 1) {
|
|
901
|
+
return null;
|
|
902
|
+
}
|
|
903
|
+
const rows = [];
|
|
904
|
+
const targetRefs = new Set();
|
|
905
|
+
const visibleTargets = new Set();
|
|
906
|
+
let choiceCount = 0;
|
|
907
|
+
for (const rawRow of raw.recent_conversations.rows) {
|
|
908
|
+
const nodeOrigin = isRecord(rawRow)
|
|
909
|
+
? readRuntimeDirectedSessionLabelOrigin(isRecord(rawRow.node) ? rawRow.node.display : null)
|
|
910
|
+
: null;
|
|
911
|
+
const sessionOrigin = isRecord(rawRow)
|
|
912
|
+
? readRuntimeDirectedSessionLabelOrigin(isRecord(rawRow.session) ? rawRow.session.display : null)
|
|
913
|
+
: null;
|
|
914
|
+
if (!isRecord(rawRow) ||
|
|
915
|
+
!isRecord(rawRow.node) ||
|
|
916
|
+
!isNonEmptyString(rawRow.node.node_ref) ||
|
|
917
|
+
!isRecord(rawRow.node.display) ||
|
|
918
|
+
typeof rawRow.node.display.label !== "string" ||
|
|
919
|
+
nodeOrigin === null ||
|
|
920
|
+
!isRecord(rawRow.session) ||
|
|
921
|
+
!isRecord(rawRow.session.display) ||
|
|
922
|
+
typeof rawRow.session.display.label !== "string" ||
|
|
923
|
+
sessionOrigin === null ||
|
|
924
|
+
(rawRow.session.display.host_label !== undefined &&
|
|
925
|
+
typeof rawRow.session.display.host_label !== "string") ||
|
|
926
|
+
typeof rawRow.last_message_at !== "string" ||
|
|
927
|
+
!isCanonicalIsoInstant(rawRow.last_message_at) ||
|
|
928
|
+
!Array.isArray(rawRow.routes) ||
|
|
929
|
+
rawRow.routes.length > 1 ||
|
|
930
|
+
rawRow.routes.some((route) => route !== "exact_target_store") ||
|
|
931
|
+
(rawRow.target_ref !== undefined &&
|
|
932
|
+
!isNonEmptyString(rawRow.target_ref)) ||
|
|
933
|
+
(rawRow.routes.length === 1) !== isNonEmptyString(rawRow.target_ref)) {
|
|
934
|
+
return null;
|
|
935
|
+
}
|
|
936
|
+
if (isNonEmptyString(rawRow.target_ref)) {
|
|
937
|
+
choiceCount += 1;
|
|
938
|
+
if (targetRefs.has(rawRow.target_ref))
|
|
939
|
+
return null;
|
|
940
|
+
targetRefs.add(rawRow.target_ref);
|
|
941
|
+
}
|
|
942
|
+
const visibleTarget = JSON.stringify([
|
|
943
|
+
rawRow.node.display.label,
|
|
944
|
+
rawRow.session.display.label,
|
|
945
|
+
rawRow.session.display.host_label ?? null,
|
|
946
|
+
]);
|
|
947
|
+
if (visibleTargets.has(visibleTarget))
|
|
948
|
+
return null;
|
|
949
|
+
visibleTargets.add(visibleTarget);
|
|
950
|
+
rows.push({
|
|
951
|
+
node: {
|
|
952
|
+
node_ref: rawRow.node.node_ref,
|
|
953
|
+
display: {
|
|
954
|
+
label: rawRow.node.display.label,
|
|
955
|
+
label_origin: nodeOrigin,
|
|
956
|
+
},
|
|
957
|
+
},
|
|
958
|
+
session: {
|
|
959
|
+
display: {
|
|
960
|
+
label: rawRow.session.display.label,
|
|
961
|
+
label_origin: sessionOrigin,
|
|
962
|
+
...(typeof rawRow.session.display.host_label === "string" && {
|
|
963
|
+
host_label: rawRow.session.display.host_label,
|
|
964
|
+
}),
|
|
965
|
+
},
|
|
966
|
+
},
|
|
967
|
+
last_message_at: rawRow.last_message_at,
|
|
968
|
+
routes: rawRow.routes.length === 1 ? ["exact_target_store"] : [],
|
|
969
|
+
...(typeof rawRow.target_ref === "string" && {
|
|
970
|
+
target_ref: rawRow.target_ref,
|
|
971
|
+
}),
|
|
972
|
+
});
|
|
973
|
+
}
|
|
974
|
+
if (rows.length !== raw.recent_conversations.entry_count ||
|
|
975
|
+
choiceCount !== raw.recent_conversations.choice_count) {
|
|
976
|
+
return null;
|
|
977
|
+
}
|
|
978
|
+
return {
|
|
979
|
+
status: "exact_session_action_target_projection",
|
|
980
|
+
contract_version: 2,
|
|
981
|
+
acting_source: readRuntimeTargetSendSource(raw.acting_source),
|
|
982
|
+
current_online: {
|
|
983
|
+
status: "unavailable",
|
|
984
|
+
code: "receiver_evidence_provider_unavailable",
|
|
985
|
+
},
|
|
986
|
+
recent_conversations: {
|
|
987
|
+
status: "available",
|
|
988
|
+
traversal_ref: raw.recent_conversations.traversal_ref,
|
|
989
|
+
page: raw.recent_conversations.page,
|
|
990
|
+
page_bound: 12,
|
|
991
|
+
entry_count: raw.recent_conversations.entry_count,
|
|
992
|
+
choice_count: raw.recent_conversations.choice_count,
|
|
993
|
+
complete: raw.recent_conversations.complete,
|
|
994
|
+
expires_at: raw.recent_conversations.expires_at,
|
|
995
|
+
...(typeof raw.recent_conversations.continuation === "string" && {
|
|
996
|
+
continuation: raw.recent_conversations.continuation,
|
|
997
|
+
}),
|
|
998
|
+
rows,
|
|
999
|
+
},
|
|
1000
|
+
directory_access: { status: "available", contract_version: 1 },
|
|
1001
|
+
};
|
|
1002
|
+
}
|
|
1003
|
+
function readRuntimeDirectedSessionResolutionNode(raw) {
|
|
1004
|
+
if (!isRecord(raw) ||
|
|
1005
|
+
!hasExactKeys(raw, ["node_ref", "display", "coverage"]) ||
|
|
1006
|
+
!isNonEmptyString(raw.node_ref) ||
|
|
1007
|
+
!isRecord(raw.display) ||
|
|
1008
|
+
!hasExactKeys(raw.display, ["label", "label_origin"]) ||
|
|
1009
|
+
!isNonEmptyString(raw.display.label) ||
|
|
1010
|
+
![
|
|
1011
|
+
"exact_sessions_complete",
|
|
1012
|
+
"exact_sessions_not_applicable",
|
|
1013
|
+
"exact_sessions_incomplete",
|
|
1014
|
+
].includes(raw.coverage)) {
|
|
1015
|
+
return null;
|
|
1016
|
+
}
|
|
1017
|
+
const labelOrigin = readRuntimeDirectedSessionLabelOrigin(raw.display);
|
|
1018
|
+
if (labelOrigin === null)
|
|
1019
|
+
return null;
|
|
1020
|
+
return {
|
|
1021
|
+
node_ref: raw.node_ref,
|
|
1022
|
+
display: { label: raw.display.label, label_origin: labelOrigin },
|
|
1023
|
+
coverage: raw.coverage,
|
|
1024
|
+
};
|
|
1025
|
+
}
|
|
1026
|
+
function readRuntimeDirectedSessionResolutionSession(raw) {
|
|
1027
|
+
if (!isRecord(raw) ||
|
|
1028
|
+
!hasExactKeys(raw, ["display", "target_ref"]) ||
|
|
1029
|
+
!isRecord(raw.display) ||
|
|
1030
|
+
!hasExactKeys(raw.display, raw.display.host_label === undefined
|
|
1031
|
+
? ["label", "label_origin"]
|
|
1032
|
+
: ["label", "label_origin", "host_label"]) ||
|
|
1033
|
+
!isNonEmptyString(raw.display.label) ||
|
|
1034
|
+
(raw.display.host_label !== undefined &&
|
|
1035
|
+
!isNonEmptyString(raw.display.host_label)) ||
|
|
1036
|
+
!isNonEmptyString(raw.target_ref)) {
|
|
1037
|
+
return null;
|
|
1038
|
+
}
|
|
1039
|
+
const labelOrigin = readRuntimeDirectedSessionLabelOrigin(raw.display);
|
|
1040
|
+
if (labelOrigin === null)
|
|
1041
|
+
return null;
|
|
1042
|
+
return {
|
|
1043
|
+
display: {
|
|
1044
|
+
label: raw.display.label,
|
|
1045
|
+
label_origin: labelOrigin,
|
|
1046
|
+
...(typeof raw.display.host_label === "string" && {
|
|
1047
|
+
host_label: raw.display.host_label,
|
|
1048
|
+
}),
|
|
1049
|
+
},
|
|
1050
|
+
target_ref: raw.target_ref,
|
|
1051
|
+
};
|
|
1052
|
+
}
|
|
1053
|
+
function readRuntimeDirectedSessionResolutionTraversal(raw) {
|
|
1054
|
+
if (!isRecord(raw) ||
|
|
1055
|
+
!hasExactKeys(raw, raw.continuation === undefined
|
|
1056
|
+
? [
|
|
1057
|
+
"traversal_ref",
|
|
1058
|
+
"page",
|
|
1059
|
+
"page_bound",
|
|
1060
|
+
"entry_count",
|
|
1061
|
+
"choice_count",
|
|
1062
|
+
"complete",
|
|
1063
|
+
"expires_at",
|
|
1064
|
+
]
|
|
1065
|
+
: [
|
|
1066
|
+
"traversal_ref",
|
|
1067
|
+
"page",
|
|
1068
|
+
"page_bound",
|
|
1069
|
+
"entry_count",
|
|
1070
|
+
"choice_count",
|
|
1071
|
+
"complete",
|
|
1072
|
+
"expires_at",
|
|
1073
|
+
"continuation",
|
|
1074
|
+
]) ||
|
|
1075
|
+
!isNonEmptyString(raw.traversal_ref) ||
|
|
1076
|
+
!isPositiveInteger(raw.page) ||
|
|
1077
|
+
raw.page_bound !== 12 ||
|
|
1078
|
+
!isNonNegativeInteger(raw.entry_count) ||
|
|
1079
|
+
raw.entry_count > 12 ||
|
|
1080
|
+
!isNonNegativeInteger(raw.choice_count) ||
|
|
1081
|
+
raw.choice_count > 12 ||
|
|
1082
|
+
typeof raw.complete !== "boolean" ||
|
|
1083
|
+
!isCanonicalIsoInstantValue(raw.expires_at) ||
|
|
1084
|
+
(raw.continuation !== undefined && !isNonEmptyString(raw.continuation)) ||
|
|
1085
|
+
raw.complete === (raw.continuation !== undefined)) {
|
|
1086
|
+
return null;
|
|
1087
|
+
}
|
|
1088
|
+
return {
|
|
1089
|
+
traversal_ref: raw.traversal_ref,
|
|
1090
|
+
page: raw.page,
|
|
1091
|
+
page_bound: 12,
|
|
1092
|
+
entry_count: raw.entry_count,
|
|
1093
|
+
choice_count: raw.choice_count,
|
|
1094
|
+
complete: raw.complete,
|
|
1095
|
+
expires_at: raw.expires_at,
|
|
1096
|
+
...(typeof raw.continuation === "string" && {
|
|
1097
|
+
continuation: raw.continuation,
|
|
1098
|
+
}),
|
|
1099
|
+
};
|
|
1100
|
+
}
|
|
1101
|
+
function readRuntimeDirectedSessionResolutionResponse(raw, expectedQuery) {
|
|
1102
|
+
if (!isRecord(raw) ||
|
|
1103
|
+
raw.contract_version !== 3 ||
|
|
1104
|
+
readRuntimeTargetSendSource(raw.acting_source) === null) {
|
|
1105
|
+
return null;
|
|
1106
|
+
}
|
|
1107
|
+
const actingSource = readRuntimeTargetSendSource(raw.acting_source);
|
|
1108
|
+
if (raw.status === "runtime_node_target_resolution") {
|
|
1109
|
+
if (expectedQuery.kind !== "node_match" ||
|
|
1110
|
+
!hasExactKeys(raw, [
|
|
1111
|
+
"status",
|
|
1112
|
+
"contract_version",
|
|
1113
|
+
"acting_source",
|
|
1114
|
+
"query",
|
|
1115
|
+
"outcome",
|
|
1116
|
+
"nodes",
|
|
1117
|
+
]) ||
|
|
1118
|
+
!isRecord(raw.query) ||
|
|
1119
|
+
!hasExactKeys(raw.query, ["kind", "match", "handle"]) ||
|
|
1120
|
+
raw.query.kind !== "node_match" ||
|
|
1121
|
+
raw.query.match !== "canonical_handle_exact" ||
|
|
1122
|
+
raw.query.handle !== expectedQuery.match.handle ||
|
|
1123
|
+
(raw.outcome !== "zero" && raw.outcome !== "one") ||
|
|
1124
|
+
!Array.isArray(raw.nodes) ||
|
|
1125
|
+
raw.nodes.length !== (raw.outcome === "one" ? 1 : 0)) {
|
|
1126
|
+
return null;
|
|
1127
|
+
}
|
|
1128
|
+
const nodes = raw.nodes.map(readRuntimeDirectedSessionResolutionNode);
|
|
1129
|
+
if (nodes.some((node) => node === null))
|
|
1130
|
+
return null;
|
|
1131
|
+
return {
|
|
1132
|
+
status: "runtime_node_target_resolution",
|
|
1133
|
+
contract_version: 3,
|
|
1134
|
+
acting_source: actingSource,
|
|
1135
|
+
query: {
|
|
1136
|
+
kind: "node_match",
|
|
1137
|
+
match: "canonical_handle_exact",
|
|
1138
|
+
handle: raw.query.handle,
|
|
1139
|
+
},
|
|
1140
|
+
outcome: raw.outcome,
|
|
1141
|
+
nodes: nodes,
|
|
1142
|
+
};
|
|
1143
|
+
}
|
|
1144
|
+
if (raw.status === "runtime_node_session_target_resolution") {
|
|
1145
|
+
if (expectedQuery.kind !== "node_sessions" ||
|
|
1146
|
+
expectedQuery.match === undefined ||
|
|
1147
|
+
!hasExactKeys(raw, [
|
|
1148
|
+
"status",
|
|
1149
|
+
"contract_version",
|
|
1150
|
+
"acting_source",
|
|
1151
|
+
"query",
|
|
1152
|
+
"node",
|
|
1153
|
+
"outcome",
|
|
1154
|
+
"sessions",
|
|
1155
|
+
]) ||
|
|
1156
|
+
!isRecord(raw.query) ||
|
|
1157
|
+
!hasExactKeys(raw.query, ["kind", "match", "node_ref", "label"]) ||
|
|
1158
|
+
raw.query.kind !== "node_sessions" ||
|
|
1159
|
+
raw.query.match !== "approved_display_exact" ||
|
|
1160
|
+
!isNonEmptyString(raw.query.node_ref) ||
|
|
1161
|
+
raw.query.node_ref !== expectedQuery.node_ref ||
|
|
1162
|
+
raw.query.label !== expectedQuery.match.label ||
|
|
1163
|
+
!["not_applicable", "zero", "one"].includes(raw.outcome) ||
|
|
1164
|
+
!Array.isArray(raw.sessions) ||
|
|
1165
|
+
raw.sessions.length > 12) {
|
|
1166
|
+
return null;
|
|
1167
|
+
}
|
|
1168
|
+
const node = readRuntimeDirectedSessionResolutionNode(raw.node);
|
|
1169
|
+
const sessions = raw.sessions.map(readRuntimeDirectedSessionResolutionSession);
|
|
1170
|
+
const validCount = ((raw.outcome === "not_applicable" || raw.outcome === "zero") &&
|
|
1171
|
+
sessions.length === 0) ||
|
|
1172
|
+
(raw.outcome === "one" && sessions.length === 1);
|
|
1173
|
+
if (!node ||
|
|
1174
|
+
sessions.some((session) => session === null) ||
|
|
1175
|
+
!validCount ||
|
|
1176
|
+
raw.query.node_ref !== node.node_ref ||
|
|
1177
|
+
(raw.outcome === "not_applicable") !==
|
|
1178
|
+
(node.coverage === "exact_sessions_not_applicable") ||
|
|
1179
|
+
(raw.outcome !== "not_applicable" &&
|
|
1180
|
+
node.coverage !== "exact_sessions_complete")) {
|
|
1181
|
+
return null;
|
|
1182
|
+
}
|
|
1183
|
+
const targetRefs = new Set(sessions.map((session) => session?.target_ref));
|
|
1184
|
+
if (targetRefs.size !== sessions.length)
|
|
1185
|
+
return null;
|
|
1186
|
+
return {
|
|
1187
|
+
status: "runtime_node_session_target_resolution",
|
|
1188
|
+
contract_version: 3,
|
|
1189
|
+
acting_source: actingSource,
|
|
1190
|
+
query: {
|
|
1191
|
+
kind: "node_sessions",
|
|
1192
|
+
match: "approved_display_exact",
|
|
1193
|
+
node_ref: raw.query.node_ref,
|
|
1194
|
+
label: raw.query.label,
|
|
1195
|
+
},
|
|
1196
|
+
node,
|
|
1197
|
+
outcome: raw.outcome,
|
|
1198
|
+
sessions: sessions,
|
|
1199
|
+
};
|
|
1200
|
+
}
|
|
1201
|
+
if (raw.status === "runtime_node_target_page") {
|
|
1202
|
+
if (expectedQuery.kind !== "browse_nodes" ||
|
|
1203
|
+
!hasExactKeys(raw, [
|
|
1204
|
+
"status",
|
|
1205
|
+
"contract_version",
|
|
1206
|
+
"acting_source",
|
|
1207
|
+
"query",
|
|
1208
|
+
"traversal",
|
|
1209
|
+
"nodes",
|
|
1210
|
+
]) ||
|
|
1211
|
+
!isRecord(raw.query) ||
|
|
1212
|
+
!hasExactKeys(raw.query, ["kind"]) ||
|
|
1213
|
+
raw.query.kind !== "browse_nodes" ||
|
|
1214
|
+
!Array.isArray(raw.nodes) ||
|
|
1215
|
+
raw.nodes.length > 12) {
|
|
1216
|
+
return null;
|
|
1217
|
+
}
|
|
1218
|
+
const traversal = readRuntimeDirectedSessionResolutionTraversal(raw.traversal);
|
|
1219
|
+
const nodes = raw.nodes.map(readRuntimeDirectedSessionResolutionNode);
|
|
1220
|
+
if (!traversal ||
|
|
1221
|
+
nodes.some((node) => node === null) ||
|
|
1222
|
+
traversal.entry_count !== nodes.length ||
|
|
1223
|
+
traversal.choice_count !== nodes.length ||
|
|
1224
|
+
new Set(nodes.map((node) => node?.node_ref)).size !== nodes.length) {
|
|
1225
|
+
return null;
|
|
1226
|
+
}
|
|
1227
|
+
return {
|
|
1228
|
+
status: "runtime_node_target_page",
|
|
1229
|
+
contract_version: 3,
|
|
1230
|
+
acting_source: actingSource,
|
|
1231
|
+
query: { kind: "browse_nodes" },
|
|
1232
|
+
traversal,
|
|
1233
|
+
nodes: nodes,
|
|
1234
|
+
};
|
|
1235
|
+
}
|
|
1236
|
+
if (raw.status === "runtime_node_session_target_page") {
|
|
1237
|
+
if (expectedQuery.kind !== "node_sessions" ||
|
|
1238
|
+
expectedQuery.match !== undefined ||
|
|
1239
|
+
!hasExactKeys(raw, [
|
|
1240
|
+
"status",
|
|
1241
|
+
"contract_version",
|
|
1242
|
+
"acting_source",
|
|
1243
|
+
"query",
|
|
1244
|
+
"node",
|
|
1245
|
+
"traversal",
|
|
1246
|
+
"sessions",
|
|
1247
|
+
]) ||
|
|
1248
|
+
!isRecord(raw.query) ||
|
|
1249
|
+
!hasExactKeys(raw.query, ["kind", "node_ref"]) ||
|
|
1250
|
+
raw.query.kind !== "node_sessions" ||
|
|
1251
|
+
!isNonEmptyString(raw.query.node_ref) ||
|
|
1252
|
+
raw.query.node_ref !== expectedQuery.node_ref ||
|
|
1253
|
+
!Array.isArray(raw.sessions) ||
|
|
1254
|
+
raw.sessions.length > 12) {
|
|
1255
|
+
return null;
|
|
1256
|
+
}
|
|
1257
|
+
const node = readRuntimeDirectedSessionResolutionNode(raw.node);
|
|
1258
|
+
const traversal = readRuntimeDirectedSessionResolutionTraversal(raw.traversal);
|
|
1259
|
+
const sessions = raw.sessions.map(readRuntimeDirectedSessionResolutionSession);
|
|
1260
|
+
if (!node ||
|
|
1261
|
+
!traversal ||
|
|
1262
|
+
sessions.some((session) => session === null) ||
|
|
1263
|
+
traversal.entry_count !== sessions.length ||
|
|
1264
|
+
traversal.choice_count !== sessions.length ||
|
|
1265
|
+
new Set(sessions.map((session) => session?.target_ref)).size !==
|
|
1266
|
+
sessions.length ||
|
|
1267
|
+
raw.query.node_ref !== node.node_ref ||
|
|
1268
|
+
node.coverage === "exact_sessions_incomplete" ||
|
|
1269
|
+
(node.coverage === "exact_sessions_not_applicable" &&
|
|
1270
|
+
(sessions.length !== 0 || !traversal.complete))) {
|
|
1271
|
+
return null;
|
|
1272
|
+
}
|
|
1273
|
+
return {
|
|
1274
|
+
status: "runtime_node_session_target_page",
|
|
1275
|
+
contract_version: 3,
|
|
1276
|
+
acting_source: actingSource,
|
|
1277
|
+
query: { kind: "node_sessions", node_ref: raw.query.node_ref },
|
|
1278
|
+
node,
|
|
1279
|
+
traversal,
|
|
1280
|
+
sessions: sessions,
|
|
1281
|
+
};
|
|
1282
|
+
}
|
|
1283
|
+
return null;
|
|
1284
|
+
}
|
|
1285
|
+
function readRuntimeDirectedSessionLabelOrigin(display) {
|
|
1286
|
+
if (!isRecord(display))
|
|
1287
|
+
return null;
|
|
1288
|
+
const labelOrigin = display.label_origin;
|
|
1289
|
+
if (typeof labelOrigin !== "string")
|
|
1290
|
+
return null;
|
|
1291
|
+
switch (labelOrigin) {
|
|
1292
|
+
case "approved_display":
|
|
1293
|
+
case "approved_display_with_query_discriminator":
|
|
1294
|
+
case "query_discriminator_only":
|
|
1295
|
+
return labelOrigin;
|
|
1296
|
+
default:
|
|
1297
|
+
return null;
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
function readRuntimeTargetSendSource(value) {
|
|
1301
|
+
if (!isRecord(value))
|
|
1302
|
+
return null;
|
|
1303
|
+
if (value.kind === "node" &&
|
|
1304
|
+
hasExactKeys(value, ["kind", "node_ref"]) &&
|
|
1305
|
+
isNonEmptyString(value.node_ref)) {
|
|
1306
|
+
return { kind: "node", node_ref: value.node_ref };
|
|
1307
|
+
}
|
|
1308
|
+
if (value.kind === "exact_session" &&
|
|
1309
|
+
hasExactKeys(value, ["kind", "session_ref"]) &&
|
|
1310
|
+
isNonEmptyString(value.session_ref)) {
|
|
1311
|
+
return { kind: "exact_session", session_ref: value.session_ref };
|
|
1312
|
+
}
|
|
1313
|
+
return null;
|
|
1314
|
+
}
|
|
1315
|
+
function isCanonicalIsoInstant(value) {
|
|
1316
|
+
const parsed = new Date(value);
|
|
1317
|
+
return Number.isFinite(parsed.getTime()) && parsed.toISOString() === value;
|
|
1318
|
+
}
|
|
1319
|
+
function isCanonicalIsoInstantValue(value) {
|
|
1320
|
+
return typeof value === "string" && isCanonicalIsoInstant(value);
|
|
1321
|
+
}
|
|
1322
|
+
export async function emitRuntimeProcessingState(cfg, runtimeKey, event) {
|
|
1323
|
+
const invalidAdapterEvent = validateAdapterProcessingStateEvent(event);
|
|
1324
|
+
if (invalidAdapterEvent) {
|
|
1325
|
+
return {
|
|
1326
|
+
ok: false,
|
|
1327
|
+
terminal: true,
|
|
1328
|
+
detail: invalidAdapterEvent,
|
|
1329
|
+
};
|
|
1330
|
+
}
|
|
1331
|
+
let res;
|
|
1332
|
+
try {
|
|
1333
|
+
res = await apiFetch(cfg, "emit_runtime_processing_state", `${baseUrl(cfg)}/runtime/processing-state`, {
|
|
1334
|
+
method: "POST",
|
|
1335
|
+
headers: jsonHeaders(runtimeKey),
|
|
1336
|
+
body: JSON.stringify(event),
|
|
1337
|
+
});
|
|
1338
|
+
}
|
|
1339
|
+
catch (err) {
|
|
1340
|
+
return transientNetworkOutcome(err);
|
|
1341
|
+
}
|
|
1342
|
+
if (res.status === 204)
|
|
1343
|
+
return { ok: true };
|
|
1344
|
+
if ([401, 403, 409, 422].includes(res.status)) {
|
|
1345
|
+
return terminalHttpOutcome(res);
|
|
1346
|
+
}
|
|
1347
|
+
return transientHttpOutcome(res);
|
|
1348
|
+
}
|
|
1349
|
+
export async function acquireNodeRecipientMessage(cfg, runtimeKey) {
|
|
1350
|
+
const res = await apiFetch(cfg, "acquire_node_recipient_message", `${baseUrl(cfg)}/runtime/node-recipient/messages/acquire`, {
|
|
1351
|
+
method: "POST",
|
|
1352
|
+
headers: jsonHeaders(runtimeKey),
|
|
1353
|
+
body: JSON.stringify({
|
|
1354
|
+
type: "runtime_node_recipient_message_acquire",
|
|
1355
|
+
version: 1,
|
|
1356
|
+
}),
|
|
1357
|
+
});
|
|
1358
|
+
const raw = await res.json().catch(() => null);
|
|
1359
|
+
if (res.ok || res.status === 409) {
|
|
1360
|
+
return readNodeRecipientMessageAcquireResponse(raw, res.status);
|
|
1361
|
+
}
|
|
1362
|
+
return handleError(res);
|
|
1363
|
+
}
|
|
1364
|
+
export async function submitNodeRecipientMessageAdmissionEvidence(cfg, runtimeKey, event) {
|
|
1365
|
+
const invalidEvent = validateNodeRecipientMessageAdmissionEvent(event);
|
|
1366
|
+
if (invalidEvent) {
|
|
1367
|
+
return { ok: false, terminal: true, detail: invalidEvent };
|
|
1368
|
+
}
|
|
1369
|
+
let res;
|
|
1370
|
+
try {
|
|
1371
|
+
res = await apiFetch(cfg, "submit_node_recipient_message_admission_evidence", `${baseUrl(cfg)}/runtime/node-recipient/messages/admission-evidence`, {
|
|
1372
|
+
method: "POST",
|
|
1373
|
+
headers: jsonHeaders(runtimeKey),
|
|
1374
|
+
body: JSON.stringify(event),
|
|
1375
|
+
});
|
|
1376
|
+
}
|
|
1377
|
+
catch (error) {
|
|
1378
|
+
return transientNetworkOutcome(error);
|
|
1379
|
+
}
|
|
1380
|
+
if (res.ok) {
|
|
1381
|
+
const body = await res.json().catch(() => null);
|
|
1382
|
+
if (!isRecord(body) ||
|
|
1383
|
+
!hasExactKeys(body, ["type", "version", "status", "admitted_at"]) ||
|
|
1384
|
+
body.type !== "runtime_node_recipient_message_admission_evidence" ||
|
|
1385
|
+
body.version !== 1 ||
|
|
1386
|
+
(body.status !== "admitted" && body.status !== "standing") ||
|
|
1387
|
+
!isNonNegativeInteger(body.admitted_at)) {
|
|
1388
|
+
return invalidWireResponse("invalid_node_recipient_message_admission_response", "invalid node-recipient message admission response");
|
|
1389
|
+
}
|
|
1390
|
+
return {
|
|
1391
|
+
ok: true,
|
|
1392
|
+
status: body.status,
|
|
1393
|
+
admittedAt: body.admitted_at,
|
|
1394
|
+
};
|
|
1395
|
+
}
|
|
1396
|
+
if (res.status === 409 || res.status === 503) {
|
|
1397
|
+
const body = await res.json().catch(() => null);
|
|
1398
|
+
if (!isRecord(body) ||
|
|
1399
|
+
!hasExactKeys(body, [
|
|
1400
|
+
"type",
|
|
1401
|
+
"version",
|
|
1402
|
+
"status",
|
|
1403
|
+
"reason",
|
|
1404
|
+
"retryable",
|
|
1405
|
+
]) ||
|
|
1406
|
+
body.type !== "runtime_node_recipient_message_admission_evidence" ||
|
|
1407
|
+
body.version !== 1 ||
|
|
1408
|
+
![
|
|
1409
|
+
"retryable",
|
|
1410
|
+
"attempt_superseded",
|
|
1411
|
+
"message_terminal",
|
|
1412
|
+
"rejected",
|
|
1413
|
+
].includes(body.status) ||
|
|
1414
|
+
!isNonEmptyString(body.reason) ||
|
|
1415
|
+
!validNodeRecipientMessageAdmissionFailure(body.status, body.reason) ||
|
|
1416
|
+
body.retryable !== (body.status === "retryable") ||
|
|
1417
|
+
(body.status === "retryable" && res.status !== 503) ||
|
|
1418
|
+
(body.status !== "retryable" && res.status !== 409)) {
|
|
1419
|
+
return invalidWireResponse("invalid_node_recipient_message_admission_response", "invalid node-recipient message admission response");
|
|
1420
|
+
}
|
|
1421
|
+
return {
|
|
1422
|
+
ok: false,
|
|
1423
|
+
providerStatus: body.status,
|
|
1424
|
+
reason: body.reason,
|
|
1425
|
+
terminal: body.status !== "retryable",
|
|
1426
|
+
status: res.status,
|
|
1427
|
+
};
|
|
1428
|
+
}
|
|
1429
|
+
if ([401, 403, 422].includes(res.status))
|
|
1430
|
+
return terminalHttpOutcome(res);
|
|
1431
|
+
return transientHttpOutcome(res);
|
|
1432
|
+
}
|
|
1433
|
+
export async function acquireServicesRetainedNodeRecipientMessage(cfg, runtimeKey, params) {
|
|
1434
|
+
if (!isNonEmptyString(params.endpoint_id) ||
|
|
1435
|
+
!isNonEmptyString(params.endpoint_nonce)) {
|
|
1436
|
+
throw new RuntimeAdapterApiError(422, "invalid_services_retained_node_recipient_acquire", "endpoint_id and endpoint_nonce are required");
|
|
1437
|
+
}
|
|
1438
|
+
const res = await apiFetch(cfg, "acquire_services_retained_node_recipient_message", `${baseUrl(cfg)}/runtime/node-recipient/messages/acquire`, {
|
|
1439
|
+
method: "POST",
|
|
1440
|
+
headers: jsonHeaders(runtimeKey),
|
|
1441
|
+
body: JSON.stringify({
|
|
1442
|
+
type: "runtime_node_recipient_message_acquire",
|
|
1443
|
+
version: 2,
|
|
1444
|
+
endpoint_id: params.endpoint_id,
|
|
1445
|
+
endpoint_nonce: params.endpoint_nonce,
|
|
1446
|
+
}),
|
|
1447
|
+
});
|
|
1448
|
+
const raw = await res.json().catch(() => null);
|
|
1449
|
+
if (res.ok || res.status === 409 || res.status === 503) {
|
|
1450
|
+
return readServicesRetainedNodeRecipientAcquireResponse(raw, res.status);
|
|
1451
|
+
}
|
|
1452
|
+
return handleError(res);
|
|
1453
|
+
}
|
|
1454
|
+
export async function consumeServicesRetainedNodeRecipientMessage(cfg, runtimeKey, event) {
|
|
1455
|
+
const rawEvent = event;
|
|
1456
|
+
if (!hasExactKeys(rawEvent, [
|
|
1457
|
+
"type",
|
|
1458
|
+
"version",
|
|
1459
|
+
"endpoint_id",
|
|
1460
|
+
"endpoint_nonce",
|
|
1461
|
+
"message_ref",
|
|
1462
|
+
"offer_ref",
|
|
1463
|
+
]) ||
|
|
1464
|
+
event.type !== "runtime_node_recipient_message_consumption" ||
|
|
1465
|
+
event.version !== 1 ||
|
|
1466
|
+
!isNonEmptyString(event.endpoint_id) ||
|
|
1467
|
+
!isNonEmptyString(event.endpoint_nonce) ||
|
|
1468
|
+
!isNonEmptyString(event.message_ref) ||
|
|
1469
|
+
!isNonEmptyString(event.offer_ref)) {
|
|
1470
|
+
return {
|
|
1471
|
+
ok: false,
|
|
1472
|
+
terminal: true,
|
|
1473
|
+
detail: "services-retained node-recipient consumption does not match the v1 contract",
|
|
1474
|
+
};
|
|
1475
|
+
}
|
|
1476
|
+
let res;
|
|
1477
|
+
try {
|
|
1478
|
+
res = await apiFetch(cfg, "consume_services_retained_node_recipient_message", `${baseUrl(cfg)}/runtime/node-recipient/messages/consumption`, {
|
|
1479
|
+
method: "POST",
|
|
1480
|
+
headers: jsonHeaders(runtimeKey),
|
|
1481
|
+
body: JSON.stringify(event),
|
|
1482
|
+
});
|
|
1483
|
+
}
|
|
1484
|
+
catch (error) {
|
|
1485
|
+
return transientNetworkOutcome(error);
|
|
1486
|
+
}
|
|
1487
|
+
const body = await res.json().catch(() => null);
|
|
1488
|
+
if (res.ok) {
|
|
1489
|
+
if (!isRecord(body) ||
|
|
1490
|
+
!hasExactKeys(body, ["type", "version", "status", "consumed_at"]) ||
|
|
1491
|
+
body.type !== "runtime_node_recipient_message_consumption" ||
|
|
1492
|
+
body.version !== 1 ||
|
|
1493
|
+
(body.status !== "consumed" && body.status !== "standing") ||
|
|
1494
|
+
!isNonNegativeInteger(body.consumed_at)) {
|
|
1495
|
+
return invalidWireResponse("invalid_services_retained_node_recipient_consumption_response", "invalid services-retained node-recipient consumption response");
|
|
1496
|
+
}
|
|
1497
|
+
return {
|
|
1498
|
+
ok: true,
|
|
1499
|
+
status: body.status,
|
|
1500
|
+
consumedAt: body.consumed_at,
|
|
1501
|
+
};
|
|
1502
|
+
}
|
|
1503
|
+
if (res.status === 409 || res.status === 503) {
|
|
1504
|
+
if (!isRecord(body) ||
|
|
1505
|
+
!hasExactKeys(body, [
|
|
1506
|
+
"type",
|
|
1507
|
+
"version",
|
|
1508
|
+
"status",
|
|
1509
|
+
"reason",
|
|
1510
|
+
"retryable",
|
|
1511
|
+
]) ||
|
|
1512
|
+
body.type !== "runtime_node_recipient_message_consumption" ||
|
|
1513
|
+
body.version !== 1 ||
|
|
1514
|
+
!["retryable", "rejected", "message_terminal"].includes(body.status) ||
|
|
1515
|
+
!isNonEmptyString(body.reason) ||
|
|
1516
|
+
!validServicesRetainedNodeRecipientConsumptionFailure(body.status, body.reason) ||
|
|
1517
|
+
body.retryable !== (body.status === "retryable") ||
|
|
1518
|
+
(body.status === "retryable" && res.status !== 503) ||
|
|
1519
|
+
(body.status !== "retryable" && res.status !== 409)) {
|
|
1520
|
+
return invalidWireResponse("invalid_services_retained_node_recipient_consumption_response", "invalid services-retained node-recipient consumption response");
|
|
1521
|
+
}
|
|
1522
|
+
return {
|
|
1523
|
+
ok: false,
|
|
1524
|
+
providerStatus: body.status,
|
|
1525
|
+
reason: body.reason,
|
|
1526
|
+
terminal: body.status !== "retryable",
|
|
1527
|
+
status: res.status,
|
|
1528
|
+
};
|
|
1529
|
+
}
|
|
1530
|
+
if ([401, 403, 422].includes(res.status))
|
|
1531
|
+
return terminalHttpOutcome(res);
|
|
1532
|
+
return transientHttpOutcome(res);
|
|
1533
|
+
}
|
|
1534
|
+
export async function acquireExactTargetPresentation(cfg, runtimeKey, params) {
|
|
1535
|
+
const version = params.version ?? 1;
|
|
1536
|
+
const res = await apiFetch(cfg, "acquire_exact_target_presentation", `${baseUrl(cfg)}/runtime/processing-state/presentation-acquire`, {
|
|
1537
|
+
method: "POST",
|
|
1538
|
+
headers: jsonHeaders(runtimeKey),
|
|
1539
|
+
body: JSON.stringify({
|
|
1540
|
+
type: params.eligible_grains !== undefined
|
|
1541
|
+
? "runtime_exact_target_presentation_filtered_acquire"
|
|
1542
|
+
: "runtime_exact_target_presentation_acquire",
|
|
1543
|
+
version,
|
|
1544
|
+
endpoint_id: params.endpoint_id,
|
|
1545
|
+
endpoint_nonce: params.endpoint_nonce,
|
|
1546
|
+
...(params.eligible_grains !== undefined && {
|
|
1547
|
+
eligible_grains: params.eligible_grains,
|
|
1548
|
+
}),
|
|
1549
|
+
}),
|
|
1550
|
+
});
|
|
1551
|
+
if (!res.ok)
|
|
1552
|
+
return handleError(res);
|
|
1553
|
+
return readExactTargetPresentationAcquireResponse(await res.json().catch(() => null), version, params.eligible_grains !== undefined);
|
|
1554
|
+
}
|
|
1555
|
+
export async function submitExactTargetRecipientAdmissionEvidence(cfg, runtimeKey, event) {
|
|
1556
|
+
const invalidEvent = validateExactTargetRecipientAdmissionEvidenceEvent(event);
|
|
1557
|
+
if (invalidEvent) {
|
|
1558
|
+
return {
|
|
1559
|
+
ok: false,
|
|
1560
|
+
terminal: true,
|
|
1561
|
+
detail: invalidEvent,
|
|
1562
|
+
};
|
|
1563
|
+
}
|
|
1564
|
+
let res;
|
|
1565
|
+
try {
|
|
1566
|
+
res = await apiFetch(cfg, "submit_exact_target_recipient_admission_evidence", `${baseUrl(cfg)}/runtime/processing-state/recipient-admission-evidence`, {
|
|
1567
|
+
method: "POST",
|
|
1568
|
+
headers: jsonHeaders(runtimeKey),
|
|
1569
|
+
body: JSON.stringify(event),
|
|
1570
|
+
});
|
|
1571
|
+
}
|
|
1572
|
+
catch (err) {
|
|
1573
|
+
return transientNetworkOutcome(err);
|
|
1574
|
+
}
|
|
1575
|
+
if (res.ok) {
|
|
1576
|
+
const body = (await res.json().catch(() => null));
|
|
1577
|
+
if (body?.type !== "runtime_exact_target_recipient_admission_evidence" ||
|
|
1578
|
+
body.version !== 1 ||
|
|
1579
|
+
(body.status !== "admitted" && body.status !== "standing") ||
|
|
1580
|
+
!isNonNegativeInteger(body.admitted_at)) {
|
|
1581
|
+
return invalidWireResponse("invalid_exact_target_recipient_admission_response", "invalid exact-target recipient-admission response");
|
|
1582
|
+
}
|
|
1583
|
+
return {
|
|
1584
|
+
ok: true,
|
|
1585
|
+
status: body.status,
|
|
1586
|
+
admittedAt: body.admitted_at,
|
|
1587
|
+
};
|
|
1588
|
+
}
|
|
1589
|
+
if (res.status === 409 || res.status === 503) {
|
|
1590
|
+
const body = (await res.json().catch(() => null));
|
|
1591
|
+
if (body?.type !== "runtime_exact_target_recipient_admission_evidence" ||
|
|
1592
|
+
body.version !== 1 ||
|
|
1593
|
+
![
|
|
1594
|
+
"retryable",
|
|
1595
|
+
"attempt_superseded",
|
|
1596
|
+
"message_terminal",
|
|
1597
|
+
"rejected",
|
|
1598
|
+
].includes(body.status) ||
|
|
1599
|
+
!isNonEmptyString(body.reason) ||
|
|
1600
|
+
!validExactTargetRecipientAdmissionFailure(body.status, body.reason) ||
|
|
1601
|
+
body.retryable !== (body.status === "retryable") ||
|
|
1602
|
+
(body.status === "retryable" && res.status !== 503) ||
|
|
1603
|
+
(body.status !== "retryable" && res.status !== 409)) {
|
|
1604
|
+
return invalidWireResponse("invalid_exact_target_recipient_admission_response", "invalid exact-target recipient-admission response");
|
|
1605
|
+
}
|
|
1606
|
+
return {
|
|
1607
|
+
ok: false,
|
|
1608
|
+
providerStatus: body.status,
|
|
1609
|
+
reason: body.reason,
|
|
1610
|
+
terminal: body.status !== "retryable",
|
|
1611
|
+
status: res.status,
|
|
1612
|
+
};
|
|
1613
|
+
}
|
|
1614
|
+
if ([401, 403, 422].includes(res.status)) {
|
|
1615
|
+
return terminalHttpOutcome(res);
|
|
1616
|
+
}
|
|
1617
|
+
return transientHttpOutcome(res);
|
|
1618
|
+
}
|
|
1619
|
+
export async function submitExactTargetPresentationEvidence(cfg, runtimeKey, event) {
|
|
1620
|
+
const invalidEvent = validateExactTargetPresentationEvidenceEvent(event);
|
|
1621
|
+
if (invalidEvent) {
|
|
1622
|
+
return {
|
|
1623
|
+
ok: false,
|
|
1624
|
+
terminal: true,
|
|
1625
|
+
detail: invalidEvent,
|
|
1626
|
+
};
|
|
1627
|
+
}
|
|
1628
|
+
let res;
|
|
1629
|
+
try {
|
|
1630
|
+
res = await apiFetch(cfg, "submit_exact_target_presentation_evidence", `${baseUrl(cfg)}/runtime/processing-state/presentation-evidence`, {
|
|
1631
|
+
method: "POST",
|
|
1632
|
+
headers: jsonHeaders(runtimeKey),
|
|
1633
|
+
body: JSON.stringify(event),
|
|
1634
|
+
});
|
|
1635
|
+
}
|
|
1636
|
+
catch (err) {
|
|
1637
|
+
return transientNetworkOutcome(err);
|
|
1638
|
+
}
|
|
1639
|
+
if (res.status === 204)
|
|
1640
|
+
return { ok: true };
|
|
1641
|
+
if ([401, 403, 409, 422].includes(res.status)) {
|
|
1642
|
+
return terminalHttpOutcome(res);
|
|
1643
|
+
}
|
|
1644
|
+
return transientHttpOutcome(res);
|
|
1645
|
+
}
|
|
1646
|
+
export async function releaseExactTargetPresentation(cfg, runtimeKey, event) {
|
|
1647
|
+
let res;
|
|
1648
|
+
try {
|
|
1649
|
+
res = await apiFetch(cfg, "release_exact_target_presentation", `${baseUrl(cfg)}/runtime/processing-state/presentation-release`, {
|
|
1650
|
+
method: "POST",
|
|
1651
|
+
headers: jsonHeaders(runtimeKey),
|
|
1652
|
+
body: JSON.stringify(event),
|
|
1653
|
+
});
|
|
1654
|
+
}
|
|
1655
|
+
catch (err) {
|
|
1656
|
+
return transientNetworkOutcome(err);
|
|
1657
|
+
}
|
|
1658
|
+
if (res.ok) {
|
|
1659
|
+
const body = (await res.json().catch(() => null));
|
|
1660
|
+
return { ok: true, released: body?.released === true };
|
|
1661
|
+
}
|
|
1662
|
+
if ([401, 403, 409, 422].includes(res.status)) {
|
|
1663
|
+
return terminalHttpOutcome(res);
|
|
1664
|
+
}
|
|
1665
|
+
return transientHttpOutcome(res);
|
|
1666
|
+
}
|
|
1667
|
+
function getFetch(cfg) {
|
|
1668
|
+
return cfg.fetch ?? fetch;
|
|
1669
|
+
}
|
|
1670
|
+
async function apiFetch(cfg, requestKind, url, init = {}) {
|
|
1671
|
+
const timeoutMs = requestTimeoutMs(cfg, requestKind);
|
|
1672
|
+
const controller = new AbortController();
|
|
1673
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
1674
|
+
try {
|
|
1675
|
+
return await getFetch(cfg)(url, { ...init, signal: controller.signal });
|
|
1676
|
+
}
|
|
1677
|
+
catch (err) {
|
|
1678
|
+
throw toNetworkError(err, requestKind, timeoutMs);
|
|
1679
|
+
}
|
|
1680
|
+
finally {
|
|
1681
|
+
clearTimeout(timer);
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1684
|
+
function requestTimeoutMs(cfg, requestKind) {
|
|
1685
|
+
const override = cfg.requestTimeoutMs?.[requestKind];
|
|
1686
|
+
if (override !== undefined && Number.isFinite(override) && override > 0) {
|
|
1687
|
+
return override;
|
|
1688
|
+
}
|
|
1689
|
+
return DEFAULT_REQUEST_TIMEOUT_MS[requestKind];
|
|
1690
|
+
}
|
|
1691
|
+
function toNetworkError(err, requestKind, timeoutMs) {
|
|
1692
|
+
if (err instanceof Error && err.name === "AbortError") {
|
|
1693
|
+
return new RuntimeAdapterNetworkError("timeout", `timed out after ${timeoutMs}ms`, requestKind);
|
|
1694
|
+
}
|
|
1695
|
+
if (err instanceof TypeError && err.message === "fetch failed") {
|
|
1696
|
+
const cause = err.cause;
|
|
1697
|
+
const code = causeCode(cause);
|
|
1698
|
+
const detail = cause instanceof Error && cause.message
|
|
1699
|
+
? cause.message
|
|
1700
|
+
: code || "fetch failed";
|
|
1701
|
+
return new RuntimeAdapterNetworkError(isTimeoutCode(code) ? "timeout" : "network", detail, requestKind);
|
|
1702
|
+
}
|
|
1703
|
+
return err;
|
|
1704
|
+
}
|
|
1705
|
+
function causeCode(cause) {
|
|
1706
|
+
if (cause && typeof cause === "object" && "code" in cause) {
|
|
1707
|
+
const code = cause.code;
|
|
1708
|
+
if (typeof code === "string")
|
|
1709
|
+
return code;
|
|
1710
|
+
}
|
|
1711
|
+
return "";
|
|
1712
|
+
}
|
|
1713
|
+
function isTimeoutCode(code) {
|
|
1714
|
+
return (code === "ETIMEDOUT" ||
|
|
1715
|
+
code === "UND_ERR_CONNECT_TIMEOUT" ||
|
|
1716
|
+
code === "UND_ERR_HEADERS_TIMEOUT" ||
|
|
1717
|
+
code === "UND_ERR_BODY_TIMEOUT");
|
|
1718
|
+
}
|
|
1719
|
+
function baseUrl(cfg) {
|
|
1720
|
+
const trimmed = cfg.apiHost.replace(/\/+$/, "");
|
|
1721
|
+
const origin = /^https?:\/\//.test(trimmed) ? trimmed : `https://${trimmed}`;
|
|
1722
|
+
return `${origin}/v1`;
|
|
1723
|
+
}
|
|
1724
|
+
async function handleError(res) {
|
|
1725
|
+
const error = await readErrorEnvelope(res);
|
|
1726
|
+
throw new RuntimeAdapterApiError(res.status, error.code ?? "unknown", error.message ?? res.statusText);
|
|
1727
|
+
}
|
|
1728
|
+
function authHeaders(runtimeKey) {
|
|
1729
|
+
return { Authorization: `Bearer ${runtimeKey}` };
|
|
1730
|
+
}
|
|
1731
|
+
function jsonHeaders(runtimeKey) {
|
|
1732
|
+
return {
|
|
1733
|
+
...authHeaders(runtimeKey),
|
|
1734
|
+
"Content-Type": "application/json",
|
|
1735
|
+
};
|
|
1736
|
+
}
|
|
1737
|
+
function targetMessagingHeaders(runtimeKey) {
|
|
1738
|
+
return {
|
|
1739
|
+
...authHeaders(runtimeKey),
|
|
1740
|
+
Accept: TARGET_MESSAGING_MEDIA_TYPE,
|
|
1741
|
+
"Content-Type": TARGET_MESSAGING_MEDIA_TYPE,
|
|
1742
|
+
};
|
|
1743
|
+
}
|
|
1744
|
+
function targetMessagingReadHeaders(runtimeKey) {
|
|
1745
|
+
return {
|
|
1746
|
+
...authHeaders(runtimeKey),
|
|
1747
|
+
Accept: TARGET_MESSAGING_MEDIA_TYPE,
|
|
1748
|
+
};
|
|
1749
|
+
}
|
|
1750
|
+
function endpointReadHeaders(runtimeKey, options) {
|
|
1751
|
+
return {
|
|
1752
|
+
...authHeaders(runtimeKey),
|
|
1753
|
+
...(options.endpointNonce !== undefined && {
|
|
1754
|
+
"X-Endpoint-Nonce": options.endpointNonce,
|
|
1755
|
+
}),
|
|
1756
|
+
};
|
|
1757
|
+
}
|
|
1758
|
+
function validateAdapterProcessingStateEvent(event) {
|
|
1759
|
+
if (!isRecord(event))
|
|
1760
|
+
return "processing-state event must be an object";
|
|
1761
|
+
const raw = event;
|
|
1762
|
+
if (raw.type !== undefined && raw.type !== "runtime_processing_state") {
|
|
1763
|
+
return 'type must be "runtime_processing_state"';
|
|
1764
|
+
}
|
|
1765
|
+
if (raw.version !== undefined && raw.version !== 1) {
|
|
1766
|
+
return "version must be 1";
|
|
1767
|
+
}
|
|
1768
|
+
if (raw.agent_id !== undefined && !isNonEmptyString(raw.agent_id)) {
|
|
1769
|
+
return "agent_id must be a non-empty string";
|
|
1770
|
+
}
|
|
1771
|
+
if (!isNonEmptyString(raw.source_message_id)) {
|
|
1772
|
+
return "source_message_id must be a non-empty string";
|
|
1773
|
+
}
|
|
1774
|
+
if (!isNonEmptyString(raw.assignment_id)) {
|
|
1775
|
+
return "assignment_id must be a non-empty string";
|
|
1776
|
+
}
|
|
1777
|
+
if (!isNonEmptyString(raw.state_event_id)) {
|
|
1778
|
+
return "state_event_id must be a non-empty string";
|
|
1779
|
+
}
|
|
1780
|
+
if (!isPositiveInteger(raw.state_sequence)) {
|
|
1781
|
+
return "state_sequence must be a positive integer";
|
|
1782
|
+
}
|
|
1783
|
+
if (parseRuntimeWorkTargetRef(raw.target_ref) === null) {
|
|
1784
|
+
return "target_ref must be a valid RuntimeWorkTargetRef";
|
|
1785
|
+
}
|
|
1786
|
+
if (!ADAPTER_RUNTIME_PROCESSING_STATES.includes(raw.state)) {
|
|
1787
|
+
return `state is not adapter-emittable: ${String(raw.state)}`;
|
|
1788
|
+
}
|
|
1789
|
+
if (!isUnixMs(raw.occurred_at)) {
|
|
1790
|
+
return "occurred_at must be a unix timestamp in milliseconds";
|
|
1791
|
+
}
|
|
1792
|
+
if (raw.undispatched_id !== undefined &&
|
|
1793
|
+
!isNonEmptyString(raw.undispatched_id)) {
|
|
1794
|
+
return "undispatched_id must be a non-empty string";
|
|
1795
|
+
}
|
|
1796
|
+
if (raw.retryable !== undefined && typeof raw.retryable !== "boolean") {
|
|
1797
|
+
return "retryable must be a boolean";
|
|
1798
|
+
}
|
|
1799
|
+
if (raw.state === "processing_failed" && raw.retryable === undefined) {
|
|
1800
|
+
return "retryable is required for processing_failed";
|
|
1801
|
+
}
|
|
1802
|
+
if (raw.reason_code !== undefined && !isNonEmptyString(raw.reason_code)) {
|
|
1803
|
+
return "reason_code must be a non-empty string";
|
|
1804
|
+
}
|
|
1805
|
+
if (raw.state === "waiting_for_runtime_capability" &&
|
|
1806
|
+
raw.reason_code === undefined) {
|
|
1807
|
+
return "reason_code is required for waiting_for_runtime_capability";
|
|
1808
|
+
}
|
|
1809
|
+
if (raw.detail !== undefined && typeof raw.detail !== "string") {
|
|
1810
|
+
return "detail must be a string";
|
|
1811
|
+
}
|
|
1812
|
+
if ("reply_request_id" in raw ||
|
|
1813
|
+
"outbound_message_id" in raw ||
|
|
1814
|
+
"broker_projection_token" in raw) {
|
|
1815
|
+
return "adapter processing-state events must not carry reply or broker projection fields";
|
|
1816
|
+
}
|
|
1817
|
+
return undefined;
|
|
1818
|
+
}
|
|
1819
|
+
function validateExactTargetPresentationEvidenceEvent(event) {
|
|
1820
|
+
if (!isRecord(event))
|
|
1821
|
+
return "presentation evidence event must be an object";
|
|
1822
|
+
const raw = event;
|
|
1823
|
+
for (const field of [
|
|
1824
|
+
"state_event_id",
|
|
1825
|
+
"state_sequence",
|
|
1826
|
+
"state",
|
|
1827
|
+
"occurred_at",
|
|
1828
|
+
"undispatched_id",
|
|
1829
|
+
"retryable",
|
|
1830
|
+
"reason_code",
|
|
1831
|
+
"detail",
|
|
1832
|
+
"reply_request_id",
|
|
1833
|
+
"outbound_message_id",
|
|
1834
|
+
"broker_projection_token",
|
|
1835
|
+
]) {
|
|
1836
|
+
if (raw[field] !== undefined) {
|
|
1837
|
+
return `presentation evidence event must not include ${field}`;
|
|
1838
|
+
}
|
|
1839
|
+
}
|
|
1840
|
+
if (raw.type !== "runtime_exact_target_presentation_evidence") {
|
|
1841
|
+
return 'type must be "runtime_exact_target_presentation_evidence"';
|
|
1842
|
+
}
|
|
1843
|
+
if (raw.version !== 1) {
|
|
1844
|
+
return "version must be 1";
|
|
1845
|
+
}
|
|
1846
|
+
if (!isNonEmptyString(raw.source_message_id)) {
|
|
1847
|
+
return "source_message_id must be a non-empty string";
|
|
1848
|
+
}
|
|
1849
|
+
if (!isNonEmptyString(raw.endpoint_id)) {
|
|
1850
|
+
return "endpoint_id must be a non-empty string";
|
|
1851
|
+
}
|
|
1852
|
+
if (!isNonEmptyString(raw.endpoint_nonce)) {
|
|
1853
|
+
return "endpoint_nonce must be a non-empty string";
|
|
1854
|
+
}
|
|
1855
|
+
if (!isNonEmptyString(raw.assignment_id)) {
|
|
1856
|
+
return "assignment_id must be a non-empty string";
|
|
1857
|
+
}
|
|
1858
|
+
if (parseRuntimeWorkTargetRef(raw.target_ref) === null) {
|
|
1859
|
+
return "target_ref must be a valid RuntimeWorkTargetRef";
|
|
1860
|
+
}
|
|
1861
|
+
if (!isNonEmptyString(raw.presentation_confirmation_token) ||
|
|
1862
|
+
!/^etp1_[A-Za-z0-9_-]{43}$/.test(raw.presentation_confirmation_token)) {
|
|
1863
|
+
return "presentation_confirmation_token must be a valid v1 token";
|
|
1864
|
+
}
|
|
1865
|
+
if (!isRecord(raw.presentation_evidence)) {
|
|
1866
|
+
return "presentation_evidence must be an object";
|
|
1867
|
+
}
|
|
1868
|
+
const evidence = raw.presentation_evidence;
|
|
1869
|
+
if (evidence.version !== 1 ||
|
|
1870
|
+
!["full", "partial", "pointer_only"].includes(evidence.disposition) ||
|
|
1871
|
+
!isNonEmptyString(evidence.evidence_contract_id) ||
|
|
1872
|
+
!isNonNegativeInteger(evidence.evidence_contract_generation) ||
|
|
1873
|
+
!isNonEmptyString(evidence.envelope_digest) ||
|
|
1874
|
+
!/^[A-Za-z0-9_-]{43}$/.test(evidence.envelope_digest) ||
|
|
1875
|
+
!isNonEmptyString(evidence.role) ||
|
|
1876
|
+
!isNonEmptyString(evidence.boundary_kind) ||
|
|
1877
|
+
!isNonEmptyString(evidence.boundary_ref) ||
|
|
1878
|
+
!isNonNegativeInteger(evidence.boundary_position) ||
|
|
1879
|
+
!isNonEmptyString(evidence.authority_incarnation) ||
|
|
1880
|
+
!isNonNegativeInteger(evidence.authority_sequence) ||
|
|
1881
|
+
!isUnixMs(evidence.observed_at)) {
|
|
1882
|
+
return "presentation_evidence does not match the v1 contract";
|
|
1883
|
+
}
|
|
1884
|
+
return undefined;
|
|
1885
|
+
}
|
|
1886
|
+
function validateExactTargetRecipientAdmissionEvidenceEvent(event) {
|
|
1887
|
+
if (!isRecord(event)) {
|
|
1888
|
+
return "recipient admission evidence event must be an object";
|
|
1889
|
+
}
|
|
1890
|
+
const raw = event;
|
|
1891
|
+
if (raw.type !== "runtime_exact_target_recipient_admission_evidence") {
|
|
1892
|
+
return 'type must be "runtime_exact_target_recipient_admission_evidence"';
|
|
1893
|
+
}
|
|
1894
|
+
if (raw.version !== 1)
|
|
1895
|
+
return "version must be 1";
|
|
1896
|
+
if (!isNonEmptyString(raw.endpoint_id)) {
|
|
1897
|
+
return "endpoint_id must be a non-empty string";
|
|
1898
|
+
}
|
|
1899
|
+
if (!isNonEmptyString(raw.endpoint_nonce)) {
|
|
1900
|
+
return "endpoint_nonce must be a non-empty string";
|
|
1901
|
+
}
|
|
1902
|
+
if (!isNonEmptyString(raw.source_message_id)) {
|
|
1903
|
+
return "source_message_id must be a non-empty string";
|
|
1904
|
+
}
|
|
1905
|
+
if (!isNonEmptyString(raw.assignment_id)) {
|
|
1906
|
+
return "assignment_id must be a non-empty string";
|
|
1907
|
+
}
|
|
1908
|
+
if (parseRuntimeWorkTargetRef(raw.target_ref) === null) {
|
|
1909
|
+
return "target_ref must be a valid RuntimeWorkTargetRef";
|
|
1910
|
+
}
|
|
1911
|
+
if (!isNonEmptyString(raw.presentation_confirmation_token) ||
|
|
1912
|
+
!/^etp1_[A-Za-z0-9_-]{43}$/.test(raw.presentation_confirmation_token)) {
|
|
1913
|
+
return "presentation_confirmation_token must be a valid v1 token";
|
|
1914
|
+
}
|
|
1915
|
+
if (!isRecord(raw.recipient_admission_evidence)) {
|
|
1916
|
+
return "recipient_admission_evidence must be an object";
|
|
1917
|
+
}
|
|
1918
|
+
const evidence = raw.recipient_admission_evidence;
|
|
1919
|
+
if (evidence.version !== 1 ||
|
|
1920
|
+
!isNonEmptyString(evidence.evidence_contract_id) ||
|
|
1921
|
+
!isNonNegativeInteger(evidence.evidence_contract_generation) ||
|
|
1922
|
+
typeof evidence.envelope_digest !== "string" ||
|
|
1923
|
+
!/^[A-Za-z0-9_-]{43}$/.test(evidence.envelope_digest) ||
|
|
1924
|
+
!isNonEmptyString(evidence.authority_incarnation) ||
|
|
1925
|
+
evidence.authority_sequence !== 1) {
|
|
1926
|
+
return "recipient_admission_evidence does not match the v1 contract";
|
|
1927
|
+
}
|
|
1928
|
+
return undefined;
|
|
1929
|
+
}
|
|
1930
|
+
function validateNodeRecipientMessageAdmissionEvent(event) {
|
|
1931
|
+
if (!isRecord(event)) {
|
|
1932
|
+
return "node-recipient message admission event must be an object";
|
|
1933
|
+
}
|
|
1934
|
+
const raw = event;
|
|
1935
|
+
if (!hasExactKeys(raw, [
|
|
1936
|
+
"type",
|
|
1937
|
+
"version",
|
|
1938
|
+
"message_ref",
|
|
1939
|
+
"offer_ref",
|
|
1940
|
+
"confirmation_token",
|
|
1941
|
+
"recipient_admission_evidence",
|
|
1942
|
+
]) ||
|
|
1943
|
+
raw.type !== "runtime_node_recipient_message_admission_evidence" ||
|
|
1944
|
+
raw.version !== 1 ||
|
|
1945
|
+
!isNonEmptyString(raw.message_ref) ||
|
|
1946
|
+
!isNonEmptyString(raw.offer_ref) ||
|
|
1947
|
+
!isNonEmptyString(raw.confirmation_token) ||
|
|
1948
|
+
!/^etp1_[A-Za-z0-9_-]{43}$/.test(raw.confirmation_token) ||
|
|
1949
|
+
!isRecord(raw.recipient_admission_evidence)) {
|
|
1950
|
+
return "node-recipient message admission event does not match the v1 contract";
|
|
1951
|
+
}
|
|
1952
|
+
const evidence = raw.recipient_admission_evidence;
|
|
1953
|
+
if (!hasExactKeys(evidence, [
|
|
1954
|
+
"version",
|
|
1955
|
+
"evidence_contract_id",
|
|
1956
|
+
"evidence_contract_generation",
|
|
1957
|
+
"envelope_digest",
|
|
1958
|
+
"authority_incarnation",
|
|
1959
|
+
"authority_sequence",
|
|
1960
|
+
]) ||
|
|
1961
|
+
evidence.version !== 1 ||
|
|
1962
|
+
!isNonEmptyString(evidence.evidence_contract_id) ||
|
|
1963
|
+
!isNonNegativeInteger(evidence.evidence_contract_generation) ||
|
|
1964
|
+
typeof evidence.envelope_digest !== "string" ||
|
|
1965
|
+
!/^[A-Za-z0-9_-]{43}$/.test(evidence.envelope_digest) ||
|
|
1966
|
+
!isNonEmptyString(evidence.authority_incarnation) ||
|
|
1967
|
+
evidence.authority_sequence !== 1) {
|
|
1968
|
+
return "recipient_admission_evidence does not match the v1 contract";
|
|
1969
|
+
}
|
|
1970
|
+
return undefined;
|
|
1971
|
+
}
|
|
1972
|
+
function validNodeRecipientMessageAdmissionFailure(status, reason) {
|
|
1973
|
+
switch (status) {
|
|
1974
|
+
case "retryable":
|
|
1975
|
+
return reason === "processing_conflict";
|
|
1976
|
+
case "attempt_superseded":
|
|
1977
|
+
return ["attempt_missing", "attempt_expired"].includes(reason);
|
|
1978
|
+
case "message_terminal":
|
|
1979
|
+
return reason === "message_terminal";
|
|
1980
|
+
case "rejected":
|
|
1981
|
+
return [
|
|
1982
|
+
"invalid_input",
|
|
1983
|
+
"attempt_conflict",
|
|
1984
|
+
"target_mismatch",
|
|
1985
|
+
"evidence_conflict",
|
|
1986
|
+
].includes(reason);
|
|
1987
|
+
default:
|
|
1988
|
+
return false;
|
|
1989
|
+
}
|
|
1990
|
+
}
|
|
1991
|
+
function validServicesRetainedNodeRecipientConsumptionFailure(status, reason) {
|
|
1992
|
+
switch (status) {
|
|
1993
|
+
case "retryable":
|
|
1994
|
+
return ["endpoint_unavailable", "processing_conflict"].includes(reason);
|
|
1995
|
+
case "message_terminal":
|
|
1996
|
+
return reason === "message_terminal";
|
|
1997
|
+
case "rejected":
|
|
1998
|
+
return [
|
|
1999
|
+
"invalid_input",
|
|
2000
|
+
"node_addressing_class_unavailable",
|
|
2001
|
+
"capability_missing",
|
|
2002
|
+
"attempt_missing",
|
|
2003
|
+
"attempt_conflict",
|
|
2004
|
+
"target_mismatch",
|
|
2005
|
+
].includes(reason);
|
|
2006
|
+
default:
|
|
2007
|
+
return false;
|
|
2008
|
+
}
|
|
2009
|
+
}
|
|
2010
|
+
function validExactTargetRecipientAdmissionFailure(status, reason) {
|
|
2011
|
+
switch (status) {
|
|
2012
|
+
case "retryable":
|
|
2013
|
+
return ["endpoint_unavailable", "processing_conflict"].includes(reason);
|
|
2014
|
+
case "attempt_superseded":
|
|
2015
|
+
return ["attempt_missing", "attempt_expired"].includes(reason);
|
|
2016
|
+
case "message_terminal":
|
|
2017
|
+
return reason === "message_terminal";
|
|
2018
|
+
case "rejected":
|
|
2019
|
+
return [
|
|
2020
|
+
"invalid_input",
|
|
2021
|
+
"source_missing",
|
|
2022
|
+
"attempt_conflict",
|
|
2023
|
+
"target_mismatch",
|
|
2024
|
+
"evidence_conflict",
|
|
2025
|
+
].includes(reason);
|
|
2026
|
+
default:
|
|
2027
|
+
return false;
|
|
2028
|
+
}
|
|
2029
|
+
}
|
|
2030
|
+
function isNonEmptyString(value) {
|
|
2031
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
2032
|
+
}
|
|
2033
|
+
function isPositiveInteger(value) {
|
|
2034
|
+
return typeof value === "number" && Number.isInteger(value) && value > 0;
|
|
2035
|
+
}
|
|
2036
|
+
function isNonNegativeInteger(value) {
|
|
2037
|
+
return typeof value === "number" && Number.isInteger(value) && value >= 0;
|
|
2038
|
+
}
|
|
2039
|
+
function isUnixMs(value) {
|
|
2040
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0;
|
|
2041
|
+
}
|
|
2042
|
+
function readRemoteMessageEnvelope(raw, expectedMessageRef) {
|
|
2043
|
+
if (!isRecord(raw) ||
|
|
2044
|
+
!hasExactKeys(raw, [
|
|
2045
|
+
"type",
|
|
2046
|
+
"sender",
|
|
2047
|
+
"message_ref",
|
|
2048
|
+
"content",
|
|
2049
|
+
...(raw.sent_at === undefined ? [] : ["sent_at"]),
|
|
2050
|
+
]) ||
|
|
2051
|
+
raw.type !== "masons.remote_message.v1" ||
|
|
2052
|
+
!isRecord(raw.sender) ||
|
|
2053
|
+
!hasExactKeys(raw.sender, [
|
|
2054
|
+
...(raw.sender.display_name === undefined ? [] : ["display_name"]),
|
|
2055
|
+
...(raw.sender.handle === undefined ? [] : ["handle"]),
|
|
2056
|
+
]) ||
|
|
2057
|
+
!isNonEmptyString(raw.message_ref) ||
|
|
2058
|
+
(expectedMessageRef !== undefined &&
|
|
2059
|
+
raw.message_ref !== expectedMessageRef) ||
|
|
2060
|
+
typeof raw.content !== "string" ||
|
|
2061
|
+
(raw.sender.display_name !== undefined &&
|
|
2062
|
+
typeof raw.sender.display_name !== "string") ||
|
|
2063
|
+
(raw.sender.handle !== undefined &&
|
|
2064
|
+
typeof raw.sender.handle !== "string") ||
|
|
2065
|
+
(raw.sent_at !== undefined && !isCanonicalIsoInstantValue(raw.sent_at))) {
|
|
2066
|
+
return null;
|
|
2067
|
+
}
|
|
2068
|
+
return {
|
|
2069
|
+
type: "masons.remote_message.v1",
|
|
2070
|
+
sender: {
|
|
2071
|
+
...(typeof raw.sender.display_name === "string" && {
|
|
2072
|
+
display_name: raw.sender.display_name,
|
|
2073
|
+
}),
|
|
2074
|
+
...(typeof raw.sender.handle === "string" && {
|
|
2075
|
+
handle: raw.sender.handle,
|
|
2076
|
+
}),
|
|
2077
|
+
},
|
|
2078
|
+
message_ref: raw.message_ref,
|
|
2079
|
+
content: raw.content,
|
|
2080
|
+
...(typeof raw.sent_at === "string" && { sent_at: raw.sent_at }),
|
|
2081
|
+
};
|
|
2082
|
+
}
|
|
2083
|
+
function readNodeRecipientMessageAcquireResponse(raw, httpStatus) {
|
|
2084
|
+
const invalid = () => invalidWireResponse("invalid_node_recipient_message_acquire_response", "invalid node-recipient message acquire response");
|
|
2085
|
+
if (!isRecord(raw) ||
|
|
2086
|
+
raw.type !== "runtime_node_recipient_message_acquire" ||
|
|
2087
|
+
raw.version !== 1) {
|
|
2088
|
+
return invalid();
|
|
2089
|
+
}
|
|
2090
|
+
if (raw.status === "empty" &&
|
|
2091
|
+
httpStatus === 200 &&
|
|
2092
|
+
hasExactKeys(raw, ["type", "version", "status"])) {
|
|
2093
|
+
return {
|
|
2094
|
+
type: "runtime_node_recipient_message_acquire",
|
|
2095
|
+
version: 1,
|
|
2096
|
+
status: "empty",
|
|
2097
|
+
};
|
|
2098
|
+
}
|
|
2099
|
+
if (raw.status === "rejected" &&
|
|
2100
|
+
httpStatus === 409 &&
|
|
2101
|
+
raw.reason === "node_addressing_class_unavailable" &&
|
|
2102
|
+
raw.retryable === false &&
|
|
2103
|
+
hasExactKeys(raw, ["type", "version", "status", "reason", "retryable"])) {
|
|
2104
|
+
return {
|
|
2105
|
+
type: "runtime_node_recipient_message_acquire",
|
|
2106
|
+
version: 1,
|
|
2107
|
+
status: "rejected",
|
|
2108
|
+
reason: "node_addressing_class_unavailable",
|
|
2109
|
+
retryable: false,
|
|
2110
|
+
};
|
|
2111
|
+
}
|
|
2112
|
+
const envelope = readRemoteMessageEnvelope(raw.envelope, raw.message_ref);
|
|
2113
|
+
if (httpStatus !== 200 ||
|
|
2114
|
+
(raw.status !== "offered" && raw.status !== "standing") ||
|
|
2115
|
+
!hasExactKeys(raw, [
|
|
2116
|
+
"type",
|
|
2117
|
+
"version",
|
|
2118
|
+
"status",
|
|
2119
|
+
"message_ref",
|
|
2120
|
+
"offer_ref",
|
|
2121
|
+
"confirmation_token",
|
|
2122
|
+
"recipient_admission_contract",
|
|
2123
|
+
"evidence_authority_incarnation",
|
|
2124
|
+
"content_type",
|
|
2125
|
+
"envelope_digest",
|
|
2126
|
+
"offered_at",
|
|
2127
|
+
"expires_at",
|
|
2128
|
+
"envelope",
|
|
2129
|
+
]) ||
|
|
2130
|
+
!isNonEmptyString(raw.message_ref) ||
|
|
2131
|
+
!isNonEmptyString(raw.offer_ref) ||
|
|
2132
|
+
!isNonEmptyString(raw.confirmation_token) ||
|
|
2133
|
+
!/^etp1_[A-Za-z0-9_-]{43}$/.test(raw.confirmation_token) ||
|
|
2134
|
+
!isRecord(raw.recipient_admission_contract) ||
|
|
2135
|
+
!hasExactKeys(raw.recipient_admission_contract, ["id", "generation"]) ||
|
|
2136
|
+
!isNonEmptyString(raw.recipient_admission_contract.id) ||
|
|
2137
|
+
!isNonNegativeInteger(raw.recipient_admission_contract.generation) ||
|
|
2138
|
+
!isNonEmptyString(raw.evidence_authority_incarnation) ||
|
|
2139
|
+
!isNonEmptyString(raw.content_type) ||
|
|
2140
|
+
typeof raw.envelope_digest !== "string" ||
|
|
2141
|
+
!/^[A-Za-z0-9_-]{43}$/.test(raw.envelope_digest) ||
|
|
2142
|
+
!isCanonicalIsoInstantValue(raw.offered_at) ||
|
|
2143
|
+
!isCanonicalIsoInstantValue(raw.expires_at) ||
|
|
2144
|
+
Date.parse(raw.offered_at) >= Date.parse(raw.expires_at) ||
|
|
2145
|
+
envelope === null) {
|
|
2146
|
+
return invalid();
|
|
2147
|
+
}
|
|
2148
|
+
return {
|
|
2149
|
+
type: "runtime_node_recipient_message_acquire",
|
|
2150
|
+
version: 1,
|
|
2151
|
+
status: raw.status,
|
|
2152
|
+
message_ref: raw.message_ref,
|
|
2153
|
+
offer_ref: raw.offer_ref,
|
|
2154
|
+
confirmation_token: raw.confirmation_token,
|
|
2155
|
+
recipient_admission_contract: {
|
|
2156
|
+
id: raw.recipient_admission_contract.id,
|
|
2157
|
+
generation: raw.recipient_admission_contract.generation,
|
|
2158
|
+
},
|
|
2159
|
+
evidence_authority_incarnation: raw.evidence_authority_incarnation,
|
|
2160
|
+
content_type: raw.content_type,
|
|
2161
|
+
envelope_digest: raw.envelope_digest,
|
|
2162
|
+
offered_at: raw.offered_at,
|
|
2163
|
+
expires_at: raw.expires_at,
|
|
2164
|
+
envelope,
|
|
2165
|
+
};
|
|
2166
|
+
}
|
|
2167
|
+
function readServicesRetainedNodeRecipientAcquireResponse(raw, httpStatus) {
|
|
2168
|
+
const invalid = () => invalidWireResponse("invalid_services_retained_node_recipient_acquire_response", "invalid services-retained node-recipient acquire response");
|
|
2169
|
+
if (!isRecord(raw) ||
|
|
2170
|
+
raw.type !== "runtime_node_recipient_message_acquire" ||
|
|
2171
|
+
raw.version !== 2) {
|
|
2172
|
+
return invalid();
|
|
2173
|
+
}
|
|
2174
|
+
if (raw.status === "empty" &&
|
|
2175
|
+
httpStatus === 200 &&
|
|
2176
|
+
hasExactKeys(raw, ["type", "version", "status"])) {
|
|
2177
|
+
return {
|
|
2178
|
+
type: "runtime_node_recipient_message_acquire",
|
|
2179
|
+
version: 2,
|
|
2180
|
+
status: "empty",
|
|
2181
|
+
};
|
|
2182
|
+
}
|
|
2183
|
+
if (raw.status === "rejected") {
|
|
2184
|
+
const reason = raw.reason;
|
|
2185
|
+
const retryable = reason === "endpoint_unavailable";
|
|
2186
|
+
if (![
|
|
2187
|
+
"node_addressing_class_unavailable",
|
|
2188
|
+
"endpoint_unavailable",
|
|
2189
|
+
"capability_missing",
|
|
2190
|
+
"message_terminal",
|
|
2191
|
+
].includes(reason) ||
|
|
2192
|
+
raw.retryable !== retryable ||
|
|
2193
|
+
httpStatus !== (retryable ? 503 : 409) ||
|
|
2194
|
+
!hasExactKeys(raw, ["type", "version", "status", "reason", "retryable"])) {
|
|
2195
|
+
return invalid();
|
|
2196
|
+
}
|
|
2197
|
+
return {
|
|
2198
|
+
type: "runtime_node_recipient_message_acquire",
|
|
2199
|
+
version: 2,
|
|
2200
|
+
status: "rejected",
|
|
2201
|
+
reason: reason,
|
|
2202
|
+
retryable,
|
|
2203
|
+
};
|
|
2204
|
+
}
|
|
2205
|
+
const envelope = readRemoteMessageEnvelope(raw.envelope, raw.message_ref);
|
|
2206
|
+
if (httpStatus !== 200 ||
|
|
2207
|
+
raw.status !== "retained" ||
|
|
2208
|
+
!hasExactKeys(raw, [
|
|
2209
|
+
"type",
|
|
2210
|
+
"version",
|
|
2211
|
+
"status",
|
|
2212
|
+
"message_ref",
|
|
2213
|
+
"offer_ref",
|
|
2214
|
+
"recipient_admission_contract",
|
|
2215
|
+
"content_type",
|
|
2216
|
+
"envelope_digest",
|
|
2217
|
+
"retained_at",
|
|
2218
|
+
"envelope",
|
|
2219
|
+
]) ||
|
|
2220
|
+
!isNonEmptyString(raw.message_ref) ||
|
|
2221
|
+
!isNonEmptyString(raw.offer_ref) ||
|
|
2222
|
+
!isRecord(raw.recipient_admission_contract) ||
|
|
2223
|
+
!hasExactKeys(raw.recipient_admission_contract, ["id", "generation"]) ||
|
|
2224
|
+
raw.recipient_admission_contract.id !==
|
|
2225
|
+
"target-node-recipient-services-retained-v1" ||
|
|
2226
|
+
raw.recipient_admission_contract.generation !== 1 ||
|
|
2227
|
+
!isNonEmptyString(raw.content_type) ||
|
|
2228
|
+
typeof raw.envelope_digest !== "string" ||
|
|
2229
|
+
!/^[A-Za-z0-9_-]{43}$/.test(raw.envelope_digest) ||
|
|
2230
|
+
!isCanonicalIsoInstantValue(raw.retained_at) ||
|
|
2231
|
+
envelope === null) {
|
|
2232
|
+
return invalid();
|
|
2233
|
+
}
|
|
2234
|
+
return {
|
|
2235
|
+
type: "runtime_node_recipient_message_acquire",
|
|
2236
|
+
version: 2,
|
|
2237
|
+
status: "retained",
|
|
2238
|
+
message_ref: raw.message_ref,
|
|
2239
|
+
offer_ref: raw.offer_ref,
|
|
2240
|
+
recipient_admission_contract: {
|
|
2241
|
+
id: "target-node-recipient-services-retained-v1",
|
|
2242
|
+
generation: 1,
|
|
2243
|
+
},
|
|
2244
|
+
content_type: raw.content_type,
|
|
2245
|
+
envelope_digest: raw.envelope_digest,
|
|
2246
|
+
retained_at: raw.retained_at,
|
|
2247
|
+
envelope,
|
|
2248
|
+
};
|
|
2249
|
+
}
|
|
2250
|
+
function readExactTargetPresentationAcquireResponse(raw, expectedVersion, filtered = false) {
|
|
2251
|
+
const invalid = () => invalidWireResponse("invalid_exact_target_presentation_acquire_response", "invalid exact-target presentation acquire response");
|
|
2252
|
+
const expectedType = filtered
|
|
2253
|
+
? "runtime_exact_target_presentation_filtered_acquire"
|
|
2254
|
+
: "runtime_exact_target_presentation_acquire";
|
|
2255
|
+
if (!isRecord(raw) ||
|
|
2256
|
+
raw.type !== expectedType ||
|
|
2257
|
+
raw.version !== expectedVersion) {
|
|
2258
|
+
return invalid();
|
|
2259
|
+
}
|
|
2260
|
+
if (raw.status === "empty") {
|
|
2261
|
+
return {
|
|
2262
|
+
type: "runtime_exact_target_presentation_acquire",
|
|
2263
|
+
version: expectedVersion,
|
|
2264
|
+
status: "empty",
|
|
2265
|
+
};
|
|
2266
|
+
}
|
|
2267
|
+
const envelope = readRemoteMessageEnvelope(raw.envelope);
|
|
2268
|
+
if ((raw.status !== "bound" && raw.status !== "standing") ||
|
|
2269
|
+
!isNonEmptyString(raw.source_message_id) ||
|
|
2270
|
+
!isNonEmptyString(raw.assignment_id) ||
|
|
2271
|
+
!isNonEmptyString(raw.confirmation_token) ||
|
|
2272
|
+
!/^etp1_[A-Za-z0-9_-]{43}$/.test(raw.confirmation_token) ||
|
|
2273
|
+
!isRecord(raw.evidence_contract) ||
|
|
2274
|
+
!isNonEmptyString(raw.evidence_contract.id) ||
|
|
2275
|
+
!isNonNegativeInteger(raw.evidence_contract.generation) ||
|
|
2276
|
+
!isNonEmptyString(raw.evidence_contract.role) ||
|
|
2277
|
+
!isNonEmptyString(raw.evidence_contract.boundary_kind) ||
|
|
2278
|
+
!isNonEmptyString(raw.evidence_authority_incarnation) ||
|
|
2279
|
+
!isNonEmptyString(raw.content_type) ||
|
|
2280
|
+
typeof raw.envelope_digest !== "string" ||
|
|
2281
|
+
!/^[A-Za-z0-9_-]{43}$/.test(raw.envelope_digest) ||
|
|
2282
|
+
!isNonEmptyString(raw.assigned_at) ||
|
|
2283
|
+
Number.isNaN(Date.parse(raw.assigned_at)) ||
|
|
2284
|
+
!isNonEmptyString(raw.expires_at) ||
|
|
2285
|
+
Number.isNaN(Date.parse(raw.expires_at)) ||
|
|
2286
|
+
(expectedVersion === 1 && raw.recipient_admission_contract !== undefined) ||
|
|
2287
|
+
(expectedVersion === 2 &&
|
|
2288
|
+
(!isRecord(raw.recipient_admission_contract) ||
|
|
2289
|
+
!isNonEmptyString(raw.recipient_admission_contract.id) ||
|
|
2290
|
+
!isNonNegativeInteger(raw.recipient_admission_contract.generation))) ||
|
|
2291
|
+
envelope === null) {
|
|
2292
|
+
return invalid();
|
|
2293
|
+
}
|
|
2294
|
+
const targetRef = parseRuntimeWorkTargetRef(raw.target_ref);
|
|
2295
|
+
if (targetRef === null)
|
|
2296
|
+
return invalid();
|
|
2297
|
+
const response = {
|
|
2298
|
+
type: "runtime_exact_target_presentation_acquire",
|
|
2299
|
+
status: raw.status,
|
|
2300
|
+
source_message_id: raw.source_message_id,
|
|
2301
|
+
assignment_id: raw.assignment_id,
|
|
2302
|
+
target_ref: targetRef,
|
|
2303
|
+
confirmation_token: raw.confirmation_token,
|
|
2304
|
+
evidence_contract: {
|
|
2305
|
+
id: raw.evidence_contract.id,
|
|
2306
|
+
generation: raw.evidence_contract.generation,
|
|
2307
|
+
role: raw.evidence_contract.role,
|
|
2308
|
+
boundary_kind: raw.evidence_contract.boundary_kind,
|
|
2309
|
+
},
|
|
2310
|
+
evidence_authority_incarnation: raw.evidence_authority_incarnation,
|
|
2311
|
+
content_type: raw.content_type,
|
|
2312
|
+
envelope_digest: raw.envelope_digest,
|
|
2313
|
+
assigned_at: raw.assigned_at,
|
|
2314
|
+
expires_at: raw.expires_at,
|
|
2315
|
+
envelope,
|
|
2316
|
+
};
|
|
2317
|
+
if (expectedVersion === 1)
|
|
2318
|
+
return { ...response, version: 1 };
|
|
2319
|
+
if (!isRecord(raw.recipient_admission_contract))
|
|
2320
|
+
return invalid();
|
|
2321
|
+
return {
|
|
2322
|
+
...response,
|
|
2323
|
+
version: 2,
|
|
2324
|
+
recipient_admission_contract: {
|
|
2325
|
+
id: raw.recipient_admission_contract.id,
|
|
2326
|
+
generation: raw.recipient_admission_contract.generation,
|
|
2327
|
+
},
|
|
2328
|
+
};
|
|
2329
|
+
}
|
|
2330
|
+
function readRegisterRuntimeEndpointResponse(raw) {
|
|
2331
|
+
if (!isRecord(raw)) {
|
|
2332
|
+
return invalidWireResponse("invalid_register_runtime_endpoint_response", "invalid runtime endpoint registration response");
|
|
2333
|
+
}
|
|
2334
|
+
if (typeof raw.endpoint_id !== "string" ||
|
|
2335
|
+
typeof raw.node_id !== "string" ||
|
|
2336
|
+
typeof raw.display_label !== "string" ||
|
|
2337
|
+
typeof raw.heartbeat_after_seconds !== "number" ||
|
|
2338
|
+
typeof raw.lease_ttl_seconds !== "number" ||
|
|
2339
|
+
typeof raw.soft_retention_seconds !== "number" ||
|
|
2340
|
+
typeof raw.registered_at !== "string") {
|
|
2341
|
+
return invalidWireResponse("invalid_register_runtime_endpoint_response", "invalid runtime endpoint registration response");
|
|
2342
|
+
}
|
|
2343
|
+
const runtimeCapabilities = readOptionalStringArray(raw.runtime_capabilities);
|
|
2344
|
+
const sessionNaming = raw.session_naming === undefined
|
|
2345
|
+
? undefined
|
|
2346
|
+
: parseRuntimeSessionNaming(raw.session_naming);
|
|
2347
|
+
if (runtimeCapabilities === null ||
|
|
2348
|
+
sessionNaming === null ||
|
|
2349
|
+
(raw.execution_surface !== undefined &&
|
|
2350
|
+
typeof raw.execution_surface !== "string") ||
|
|
2351
|
+
(raw.background_exchange_mode !== undefined &&
|
|
2352
|
+
typeof raw.background_exchange_mode !== "string") ||
|
|
2353
|
+
(raw.natural_boundary_presentation_contract_id !== undefined &&
|
|
2354
|
+
typeof raw.natural_boundary_presentation_contract_id !== "string") ||
|
|
2355
|
+
(raw.node_recipient_admission_contract_id !== undefined &&
|
|
2356
|
+
typeof raw.node_recipient_admission_contract_id !== "string")) {
|
|
2357
|
+
return invalidWireResponse("invalid_register_runtime_endpoint_response", "invalid runtime endpoint registration response");
|
|
2358
|
+
}
|
|
2359
|
+
return {
|
|
2360
|
+
endpoint_id: raw.endpoint_id,
|
|
2361
|
+
node_id: raw.node_id,
|
|
2362
|
+
display_label: raw.display_label,
|
|
2363
|
+
...(sessionNaming !== undefined && { session_naming: sessionNaming }),
|
|
2364
|
+
...(runtimeCapabilities !== undefined && {
|
|
2365
|
+
runtime_capabilities: runtimeCapabilities,
|
|
2366
|
+
}),
|
|
2367
|
+
...(typeof raw.execution_surface === "string" && {
|
|
2368
|
+
execution_surface: raw.execution_surface,
|
|
2369
|
+
}),
|
|
2370
|
+
...(typeof raw.background_exchange_mode === "string" && {
|
|
2371
|
+
background_exchange_mode: raw.background_exchange_mode,
|
|
2372
|
+
}),
|
|
2373
|
+
...(typeof raw.natural_boundary_presentation_contract_id === "string" && {
|
|
2374
|
+
natural_boundary_presentation_contract_id: raw.natural_boundary_presentation_contract_id,
|
|
2375
|
+
}),
|
|
2376
|
+
...(typeof raw.node_recipient_admission_contract_id === "string" && {
|
|
2377
|
+
node_recipient_admission_contract_id: raw.node_recipient_admission_contract_id,
|
|
2378
|
+
}),
|
|
2379
|
+
heartbeat_after_seconds: raw.heartbeat_after_seconds,
|
|
2380
|
+
lease_ttl_seconds: raw.lease_ttl_seconds,
|
|
2381
|
+
soft_retention_seconds: raw.soft_retention_seconds,
|
|
2382
|
+
registered_at: raw.registered_at,
|
|
2383
|
+
};
|
|
2384
|
+
}
|
|
2385
|
+
function readListOwnedRuntimeEndpointsResponse(raw) {
|
|
2386
|
+
const rawItems = isRecord(raw) && Array.isArray(raw.items)
|
|
2387
|
+
? raw.items
|
|
2388
|
+
: isRecord(raw) && Array.isArray(raw.endpoints)
|
|
2389
|
+
? raw.endpoints
|
|
2390
|
+
: null;
|
|
2391
|
+
if (!isRecord(raw) || !isNonNegativeInteger(raw.total) || rawItems === null) {
|
|
2392
|
+
return invalidWireResponse("invalid_list_runtime_endpoints_response", "invalid runtime endpoint list response");
|
|
2393
|
+
}
|
|
2394
|
+
return {
|
|
2395
|
+
total: raw.total,
|
|
2396
|
+
items: rawItems.map((item) => readOwnedRuntimeEndpointView(item)),
|
|
2397
|
+
};
|
|
2398
|
+
}
|
|
2399
|
+
function readOwnedRuntimeEndpointView(raw) {
|
|
2400
|
+
if (!isRecord(raw)) {
|
|
2401
|
+
return invalidWireResponse("invalid_runtime_endpoint_view", "invalid runtime endpoint view");
|
|
2402
|
+
}
|
|
2403
|
+
if (typeof raw.endpoint_id !== "string" ||
|
|
2404
|
+
typeof raw.node_id !== "string" ||
|
|
2405
|
+
typeof raw.runtime_kind !== "string" ||
|
|
2406
|
+
typeof raw.display_label !== "string" ||
|
|
2407
|
+
typeof raw.registered_at !== "string" ||
|
|
2408
|
+
typeof raw.is_self !== "boolean") {
|
|
2409
|
+
return invalidWireResponse("invalid_runtime_endpoint_view", "invalid runtime endpoint view");
|
|
2410
|
+
}
|
|
2411
|
+
const runtimeCapabilities = readOptionalStringArray(raw.runtime_capabilities);
|
|
2412
|
+
const runtimeSessionId = typeof raw.runtime_session_id === "string"
|
|
2413
|
+
? raw.runtime_session_id
|
|
2414
|
+
: undefined;
|
|
2415
|
+
const runtimeSessionIdentityKey = raw.runtime_session_identity_key === undefined
|
|
2416
|
+
? undefined
|
|
2417
|
+
: parseRuntimeSessionIdentityKey(raw.runtime_session_identity_key);
|
|
2418
|
+
const runtimeSessionDisplay = raw.runtime_session_display === undefined
|
|
2419
|
+
? undefined
|
|
2420
|
+
: parseRuntimeSessionDisplayMetadata(raw.runtime_session_display);
|
|
2421
|
+
const sessionNaming = raw.session_naming === undefined
|
|
2422
|
+
? undefined
|
|
2423
|
+
: parseRuntimeSessionNaming(raw.session_naming);
|
|
2424
|
+
const workTarget = raw.runtime_work_target_ref === undefined
|
|
2425
|
+
? undefined
|
|
2426
|
+
: parseRuntimeWorkTargetRef(raw.runtime_work_target_ref);
|
|
2427
|
+
if (runtimeCapabilities === null || sessionNaming === null) {
|
|
2428
|
+
return invalidWireResponse("invalid_runtime_endpoint_view", "invalid runtime endpoint view");
|
|
2429
|
+
}
|
|
2430
|
+
if (raw.runtime_session_identity_key !== undefined &&
|
|
2431
|
+
runtimeSessionIdentityKey === null) {
|
|
2432
|
+
return invalidWireResponse("invalid_runtime_endpoint_view", "invalid runtime endpoint view");
|
|
2433
|
+
}
|
|
2434
|
+
if (raw.runtime_session_display !== undefined &&
|
|
2435
|
+
runtimeSessionDisplay === null) {
|
|
2436
|
+
return invalidWireResponse("invalid_runtime_endpoint_view", "invalid runtime endpoint view");
|
|
2437
|
+
}
|
|
2438
|
+
const validRuntimeSessionIdentityKey = runtimeSessionIdentityKey === null ? undefined : runtimeSessionIdentityKey;
|
|
2439
|
+
const validRuntimeSessionDisplay = runtimeSessionDisplay === null ? undefined : runtimeSessionDisplay;
|
|
2440
|
+
if (validRuntimeSessionDisplay !== undefined &&
|
|
2441
|
+
isRuntimeDebugIdentifierTitle(validRuntimeSessionDisplay.title, [
|
|
2442
|
+
raw.endpoint_id,
|
|
2443
|
+
runtimeSessionId,
|
|
2444
|
+
validRuntimeSessionIdentityKey,
|
|
2445
|
+
])) {
|
|
2446
|
+
return invalidWireResponse("invalid_runtime_endpoint_view", "invalid runtime endpoint view");
|
|
2447
|
+
}
|
|
2448
|
+
if (raw.runtime_work_target_ref !== undefined && workTarget === null) {
|
|
2449
|
+
return invalidWireResponse("invalid_runtime_endpoint_view", "invalid runtime endpoint view");
|
|
2450
|
+
}
|
|
2451
|
+
const runtimeWorkTarget = workTarget === null ? undefined : workTarget;
|
|
2452
|
+
return {
|
|
2453
|
+
endpoint_id: raw.endpoint_id,
|
|
2454
|
+
node_id: raw.node_id,
|
|
2455
|
+
...(typeof raw.node_handle === "string" && {
|
|
2456
|
+
node_handle: raw.node_handle,
|
|
2457
|
+
}),
|
|
2458
|
+
runtime_kind: raw.runtime_kind,
|
|
2459
|
+
...(runtimeSessionId !== undefined && {
|
|
2460
|
+
runtime_session_id: runtimeSessionId,
|
|
2461
|
+
}),
|
|
2462
|
+
...(validRuntimeSessionIdentityKey !== undefined && {
|
|
2463
|
+
runtime_session_identity_key: validRuntimeSessionIdentityKey,
|
|
2464
|
+
}),
|
|
2465
|
+
display_label: raw.display_label,
|
|
2466
|
+
...(typeof raw.session_name === "string" && {
|
|
2467
|
+
session_name: raw.session_name,
|
|
2468
|
+
}),
|
|
2469
|
+
...(typeof raw.task_hint === "string" && { task_hint: raw.task_hint }),
|
|
2470
|
+
...(sessionNaming !== undefined && { session_naming: sessionNaming }),
|
|
2471
|
+
...(validRuntimeSessionDisplay !== undefined && {
|
|
2472
|
+
runtime_session_display: validRuntimeSessionDisplay,
|
|
2473
|
+
}),
|
|
2474
|
+
...(isWorkspaceMetadata(raw.workspace) && { workspace: raw.workspace }),
|
|
2475
|
+
...(typeof raw.tracking_ref === "string" && {
|
|
2476
|
+
tracking_ref: raw.tracking_ref,
|
|
2477
|
+
}),
|
|
2478
|
+
...(isEndpointFingerprint(raw.fingerprint) && {
|
|
2479
|
+
fingerprint: raw.fingerprint,
|
|
2480
|
+
}),
|
|
2481
|
+
...(runtimeCapabilities !== undefined && {
|
|
2482
|
+
runtime_capabilities: runtimeCapabilities,
|
|
2483
|
+
}),
|
|
2484
|
+
...(typeof raw.execution_surface === "string" && {
|
|
2485
|
+
execution_surface: raw.execution_surface,
|
|
2486
|
+
}),
|
|
2487
|
+
...(typeof raw.background_exchange_mode === "string" && {
|
|
2488
|
+
background_exchange_mode: raw.background_exchange_mode,
|
|
2489
|
+
}),
|
|
2490
|
+
...(typeof raw.natural_boundary_presentation_contract_id === "string" && {
|
|
2491
|
+
natural_boundary_presentation_contract_id: raw.natural_boundary_presentation_contract_id,
|
|
2492
|
+
}),
|
|
2493
|
+
...(typeof raw.node_recipient_admission_contract_id === "string" && {
|
|
2494
|
+
node_recipient_admission_contract_id: raw.node_recipient_admission_contract_id,
|
|
2495
|
+
}),
|
|
2496
|
+
...(runtimeWorkTarget !== undefined && {
|
|
2497
|
+
runtime_work_target_ref: runtimeWorkTarget,
|
|
2498
|
+
}),
|
|
2499
|
+
registered_at: raw.registered_at,
|
|
2500
|
+
...(typeof raw.retired_at === "string" && { retired_at: raw.retired_at }),
|
|
2501
|
+
is_self: raw.is_self,
|
|
2502
|
+
};
|
|
2503
|
+
}
|
|
2504
|
+
function parseRuntimeClaimCandidate(raw) {
|
|
2505
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
2506
|
+
return null;
|
|
2507
|
+
}
|
|
2508
|
+
const item = raw;
|
|
2509
|
+
if (typeof item.undispatched_id !== "string" ||
|
|
2510
|
+
typeof item.source_message_id !== "string" ||
|
|
2511
|
+
typeof item.sender_address !== "string" ||
|
|
2512
|
+
typeof item.content_preview !== "string" ||
|
|
2513
|
+
typeof item.reason !== "string" ||
|
|
2514
|
+
typeof item.claim_policy !== "string" ||
|
|
2515
|
+
typeof item.arrived_at !== "number") {
|
|
2516
|
+
return null;
|
|
2517
|
+
}
|
|
2518
|
+
const conversationRef = parseRuntimeConversationRef(item.conversation_ref);
|
|
2519
|
+
return {
|
|
2520
|
+
undispatched_id: item.undispatched_id,
|
|
2521
|
+
source_message_id: item.source_message_id,
|
|
2522
|
+
sender_address: item.sender_address,
|
|
2523
|
+
content_preview: item.content_preview,
|
|
2524
|
+
reason: item.reason,
|
|
2525
|
+
claim_policy: item.claim_policy,
|
|
2526
|
+
...(typeof item.target_endpoint_id_hint === "string" && {
|
|
2527
|
+
target_endpoint_id_hint: item.target_endpoint_id_hint,
|
|
2528
|
+
}),
|
|
2529
|
+
...(typeof item.in_reply_to === "string" && {
|
|
2530
|
+
in_reply_to: item.in_reply_to,
|
|
2531
|
+
}),
|
|
2532
|
+
...(typeof item.origin_scid === "string" && {
|
|
2533
|
+
origin_scid: item.origin_scid,
|
|
2534
|
+
}),
|
|
2535
|
+
...(typeof item.origin_ncid === "string" && {
|
|
2536
|
+
origin_ncid: item.origin_ncid,
|
|
2537
|
+
}),
|
|
2538
|
+
...(conversationRef !== null && {
|
|
2539
|
+
conversation_ref: conversationRef,
|
|
2540
|
+
}),
|
|
2541
|
+
...(typeof item.active_assignment_id === "string" && {
|
|
2542
|
+
active_assignment_id: item.active_assignment_id,
|
|
2543
|
+
}),
|
|
2544
|
+
...(typeof item.active_target_endpoint_id === "string" && {
|
|
2545
|
+
active_target_endpoint_id: item.active_target_endpoint_id,
|
|
2546
|
+
}),
|
|
2547
|
+
arrived_at: item.arrived_at,
|
|
2548
|
+
};
|
|
2549
|
+
}
|
|
2550
|
+
function parseRuntimeConversationRef(raw) {
|
|
2551
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
2552
|
+
return null;
|
|
2553
|
+
}
|
|
2554
|
+
const ref = raw;
|
|
2555
|
+
if (ref.kind === "session_conversation" &&
|
|
2556
|
+
typeof ref.id === "string" &&
|
|
2557
|
+
ref.id.length > 0) {
|
|
2558
|
+
return { kind: "session_conversation", id: ref.id };
|
|
2559
|
+
}
|
|
2560
|
+
return null;
|
|
2561
|
+
}
|
|
2562
|
+
function parseRuntimeConversationHistoryMessage(raw) {
|
|
2563
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
2564
|
+
return null;
|
|
2565
|
+
}
|
|
2566
|
+
const item = raw;
|
|
2567
|
+
if (typeof item.message_id !== "string" ||
|
|
2568
|
+
typeof item.source_node_id !== "string" ||
|
|
2569
|
+
typeof item.target_node_id !== "string" ||
|
|
2570
|
+
typeof item.timestamp !== "string") {
|
|
2571
|
+
return null;
|
|
2572
|
+
}
|
|
2573
|
+
return {
|
|
2574
|
+
message_id: item.message_id,
|
|
2575
|
+
source_node_id: item.source_node_id,
|
|
2576
|
+
target_node_id: item.target_node_id,
|
|
2577
|
+
timestamp: item.timestamp,
|
|
2578
|
+
...(typeof item.content === "string" && { content: item.content }),
|
|
2579
|
+
...(typeof item.content_type === "string" && {
|
|
2580
|
+
content_type: item.content_type,
|
|
2581
|
+
}),
|
|
2582
|
+
};
|
|
2583
|
+
}
|
|
2584
|
+
function readRuntimeDispatchUndispatchedResponse(raw) {
|
|
2585
|
+
if (!isRecord(raw)) {
|
|
2586
|
+
return invalidWireResponse("invalid_runtime_dispatch_response", "invalid runtime dispatch response");
|
|
2587
|
+
}
|
|
2588
|
+
if (typeof raw.idempotency_key !== "string" ||
|
|
2589
|
+
typeof raw.emitted_at !== "string" ||
|
|
2590
|
+
typeof raw.accepted !== "boolean" ||
|
|
2591
|
+
(raw.delivery_mode !== undefined && typeof raw.delivery_mode !== "string")) {
|
|
2592
|
+
return invalidWireResponse("invalid_runtime_dispatch_response", "invalid runtime dispatch response");
|
|
2593
|
+
}
|
|
2594
|
+
return {
|
|
2595
|
+
idempotency_key: raw.idempotency_key,
|
|
2596
|
+
emitted_at: raw.emitted_at,
|
|
2597
|
+
accepted: raw.accepted,
|
|
2598
|
+
...(typeof raw.delivery_mode === "string" && {
|
|
2599
|
+
delivery_mode: raw.delivery_mode,
|
|
2600
|
+
}),
|
|
2601
|
+
};
|
|
2602
|
+
}
|
|
2603
|
+
function readRuntimeSelfSettleResponse(raw, params) {
|
|
2604
|
+
if (!isRecord(raw)) {
|
|
2605
|
+
return { ok: false, detail: "invalid runtime self-settle response" };
|
|
2606
|
+
}
|
|
2607
|
+
if (raw.settled !== true ||
|
|
2608
|
+
!isNonEmptyString(raw.undispatched_id) ||
|
|
2609
|
+
raw.undispatched_id !== params.undispatched_id ||
|
|
2610
|
+
raw.remove_reason !== "handled_no_reply") {
|
|
2611
|
+
return { ok: false, detail: "invalid runtime self-settle response" };
|
|
2612
|
+
}
|
|
2613
|
+
return {
|
|
2614
|
+
ok: true,
|
|
2615
|
+
settle: {
|
|
2616
|
+
settled: true,
|
|
2617
|
+
undispatched_id: raw.undispatched_id,
|
|
2618
|
+
remove_reason: "handled_no_reply",
|
|
2619
|
+
},
|
|
2620
|
+
};
|
|
2621
|
+
}
|
|
2622
|
+
function readRuntimeUndispatchedDismissResponse(raw, params) {
|
|
2623
|
+
const expectedReason = params.reason ?? "owner_dismissed";
|
|
2624
|
+
if (!isRecord(raw)) {
|
|
2625
|
+
return invalidWireResponse("invalid_runtime_undispatched_dismiss_response", "invalid runtime undispatched-dismiss response");
|
|
2626
|
+
}
|
|
2627
|
+
if (raw.dismissed !== true ||
|
|
2628
|
+
!isNonEmptyString(raw.undispatched_id) ||
|
|
2629
|
+
raw.undispatched_id !== params.undispatched_id ||
|
|
2630
|
+
raw.remove_reason !== expectedReason ||
|
|
2631
|
+
(raw.ttl_ms !== undefined &&
|
|
2632
|
+
(typeof raw.ttl_ms !== "number" ||
|
|
2633
|
+
!Number.isFinite(raw.ttl_ms) ||
|
|
2634
|
+
raw.ttl_ms < 0))) {
|
|
2635
|
+
return invalidWireResponse("invalid_runtime_undispatched_dismiss_response", "invalid runtime undispatched-dismiss response");
|
|
2636
|
+
}
|
|
2637
|
+
return {
|
|
2638
|
+
dismissed: true,
|
|
2639
|
+
undispatched_id: raw.undispatched_id,
|
|
2640
|
+
remove_reason: expectedReason,
|
|
2641
|
+
...(typeof raw.ttl_ms === "number" && { ttl_ms: raw.ttl_ms }),
|
|
2642
|
+
};
|
|
2643
|
+
}
|
|
2644
|
+
function readRuntimeAssignmentReplyIntentResponse(raw) {
|
|
2645
|
+
if (!isRecord(raw) || raw.ok !== true) {
|
|
2646
|
+
return invalidWireResponse("invalid_runtime_assignment_reply_intent_response", "invalid runtime assignment reply intent response");
|
|
2647
|
+
}
|
|
2648
|
+
if (raw.status === "committed" &&
|
|
2649
|
+
hasExactKeys(raw, [
|
|
2650
|
+
"ok",
|
|
2651
|
+
"status",
|
|
2652
|
+
"assignment_id",
|
|
2653
|
+
"source_message_id",
|
|
2654
|
+
"reply_request_id",
|
|
2655
|
+
"outbound_message_id",
|
|
2656
|
+
]) &&
|
|
2657
|
+
isNonEmptyString(raw.assignment_id) &&
|
|
2658
|
+
isNonEmptyString(raw.source_message_id) &&
|
|
2659
|
+
isNonEmptyString(raw.reply_request_id) &&
|
|
2660
|
+
isNonEmptyString(raw.outbound_message_id)) {
|
|
2661
|
+
return {
|
|
2662
|
+
ok: true,
|
|
2663
|
+
status: "committed",
|
|
2664
|
+
assignment_id: raw.assignment_id,
|
|
2665
|
+
source_message_id: raw.source_message_id,
|
|
2666
|
+
reply_request_id: raw.reply_request_id,
|
|
2667
|
+
outbound_message_id: raw.outbound_message_id,
|
|
2668
|
+
};
|
|
2669
|
+
}
|
|
2670
|
+
if (raw.status === "already_emitted" &&
|
|
2671
|
+
isNonEmptyString(raw.assignment_id) &&
|
|
2672
|
+
isNonEmptyString(raw.source_message_id) &&
|
|
2673
|
+
isNonEmptyString(raw.reply_request_id) &&
|
|
2674
|
+
isNonEmptyString(raw.outbound_message_id) &&
|
|
2675
|
+
(raw.ack_status === undefined ||
|
|
2676
|
+
raw.ack_status === "delivered" ||
|
|
2677
|
+
raw.ack_status === "stored")) {
|
|
2678
|
+
return {
|
|
2679
|
+
ok: true,
|
|
2680
|
+
status: "already_emitted",
|
|
2681
|
+
assignment_id: raw.assignment_id,
|
|
2682
|
+
source_message_id: raw.source_message_id,
|
|
2683
|
+
reply_request_id: raw.reply_request_id,
|
|
2684
|
+
outbound_message_id: raw.outbound_message_id,
|
|
2685
|
+
...(raw.ack_status !== undefined && { ack_status: raw.ack_status }),
|
|
2686
|
+
};
|
|
2687
|
+
}
|
|
2688
|
+
if (raw.status !== "pending" ||
|
|
2689
|
+
!isNonEmptyString(raw.assignment_id) ||
|
|
2690
|
+
!isNonEmptyString(raw.source_message_id) ||
|
|
2691
|
+
!isNonEmptyString(raw.reply_request_id) ||
|
|
2692
|
+
!isNonEmptyString(raw.outbound_message_id) ||
|
|
2693
|
+
!isNonEmptyString(raw.gateway_receipt_token)) {
|
|
2694
|
+
return invalidWireResponse("invalid_runtime_assignment_reply_intent_response", "invalid runtime assignment reply intent response");
|
|
2695
|
+
}
|
|
2696
|
+
const gatewayMessage = readRuntimeAssignmentReplyGatewayMessage(raw.gateway_message);
|
|
2697
|
+
if (gatewayMessage === null) {
|
|
2698
|
+
return invalidWireResponse("invalid_runtime_assignment_reply_gateway_message", "invalid runtime assignment reply gateway message");
|
|
2699
|
+
}
|
|
2700
|
+
if (gatewayMessage.messageId !== raw.outbound_message_id ||
|
|
2701
|
+
gatewayMessage.assignmentReplyReceiptToken !== raw.gateway_receipt_token) {
|
|
2702
|
+
return invalidWireResponse("runtime_assignment_reply_intent_mismatch", "runtime assignment reply intent fields do not match");
|
|
2703
|
+
}
|
|
2704
|
+
return {
|
|
2705
|
+
ok: true,
|
|
2706
|
+
status: "pending",
|
|
2707
|
+
assignment_id: raw.assignment_id,
|
|
2708
|
+
source_message_id: raw.source_message_id,
|
|
2709
|
+
reply_request_id: raw.reply_request_id,
|
|
2710
|
+
outbound_message_id: raw.outbound_message_id,
|
|
2711
|
+
gateway_receipt_token: raw.gateway_receipt_token,
|
|
2712
|
+
gateway_message: gatewayMessage,
|
|
2713
|
+
};
|
|
2714
|
+
}
|
|
2715
|
+
function readRuntimeAssignmentReplyGatewayMessage(raw) {
|
|
2716
|
+
if (!isRecord(raw))
|
|
2717
|
+
return null;
|
|
2718
|
+
const metadata = isRecord(raw.metadata) ? raw.metadata : null;
|
|
2719
|
+
if (raw.event !== "SEND_MESSAGE" ||
|
|
2720
|
+
!isNonEmptyString(raw.messageId) ||
|
|
2721
|
+
!isNonEmptyString(raw.to) ||
|
|
2722
|
+
!isNonEmptyString(raw.content) ||
|
|
2723
|
+
!isNonEmptyString(raw.contentType) ||
|
|
2724
|
+
metadata === null ||
|
|
2725
|
+
!isNonEmptyString(raw.assignmentReplyReceiptToken)) {
|
|
2726
|
+
return null;
|
|
2727
|
+
}
|
|
2728
|
+
if (findPeerVisibleRuntimeMetadataKeys(metadata).length > 0)
|
|
2729
|
+
return null;
|
|
2730
|
+
if (findGatewayLocalReceiptMetadataKeys(metadata).length > 0)
|
|
2731
|
+
return null;
|
|
2732
|
+
return {
|
|
2733
|
+
event: "SEND_MESSAGE",
|
|
2734
|
+
messageId: raw.messageId,
|
|
2735
|
+
to: raw.to,
|
|
2736
|
+
content: raw.content,
|
|
2737
|
+
contentType: raw.contentType,
|
|
2738
|
+
metadata,
|
|
2739
|
+
assignmentReplyReceiptToken: raw.assignmentReplyReceiptToken,
|
|
2740
|
+
};
|
|
2741
|
+
}
|
|
2742
|
+
function readRuntimeOutboundMessageIntentResponse(raw) {
|
|
2743
|
+
if (!isRecord(raw) || raw.ok !== true) {
|
|
2744
|
+
return invalidWireResponse("invalid_runtime_outbound_message_intent_response", "invalid runtime outbound message intent response");
|
|
2745
|
+
}
|
|
2746
|
+
if (raw.status === "already_emitted" &&
|
|
2747
|
+
isNonEmptyString(raw.send_request_id) &&
|
|
2748
|
+
isNonEmptyString(raw.outbound_message_id) &&
|
|
2749
|
+
isNonEmptyString(raw.correlation_id) &&
|
|
2750
|
+
(raw.ack_status === undefined ||
|
|
2751
|
+
raw.ack_status === "delivered" ||
|
|
2752
|
+
raw.ack_status === "stored")) {
|
|
2753
|
+
return {
|
|
2754
|
+
ok: true,
|
|
2755
|
+
status: "already_emitted",
|
|
2756
|
+
send_request_id: raw.send_request_id,
|
|
2757
|
+
outbound_message_id: raw.outbound_message_id,
|
|
2758
|
+
correlation_id: raw.correlation_id,
|
|
2759
|
+
...(raw.ack_status !== undefined && { ack_status: raw.ack_status }),
|
|
2760
|
+
};
|
|
2761
|
+
}
|
|
2762
|
+
if (raw.status !== "pending" ||
|
|
2763
|
+
!isNonEmptyString(raw.send_request_id) ||
|
|
2764
|
+
!isNonEmptyString(raw.outbound_message_id) ||
|
|
2765
|
+
!isNonEmptyString(raw.correlation_id) ||
|
|
2766
|
+
!isNonEmptyString(raw.gateway_receipt_token)) {
|
|
2767
|
+
return invalidWireResponse("invalid_runtime_outbound_message_intent_response", "invalid runtime outbound message intent response");
|
|
2768
|
+
}
|
|
2769
|
+
const gatewayMessage = readRuntimeOutboundGatewayMessage(raw.gateway_message);
|
|
2770
|
+
if (gatewayMessage === null) {
|
|
2771
|
+
return invalidWireResponse("invalid_runtime_outbound_gateway_message", "invalid runtime outbound gateway message");
|
|
2772
|
+
}
|
|
2773
|
+
if (gatewayMessage.messageId !== raw.outbound_message_id ||
|
|
2774
|
+
gatewayMessage.runtimeOutboundReceiptToken !== raw.gateway_receipt_token ||
|
|
2775
|
+
gatewayMessage.metadata.correlation_id !== raw.correlation_id) {
|
|
2776
|
+
return invalidWireResponse("runtime_outbound_message_intent_mismatch", "runtime outbound message intent fields do not match");
|
|
2777
|
+
}
|
|
2778
|
+
return {
|
|
2779
|
+
ok: true,
|
|
2780
|
+
status: "pending",
|
|
2781
|
+
send_request_id: raw.send_request_id,
|
|
2782
|
+
outbound_message_id: raw.outbound_message_id,
|
|
2783
|
+
correlation_id: raw.correlation_id,
|
|
2784
|
+
gateway_receipt_token: raw.gateway_receipt_token,
|
|
2785
|
+
gateway_message: gatewayMessage,
|
|
2786
|
+
};
|
|
2787
|
+
}
|
|
2788
|
+
function readRuntimeOutboundGatewayMessage(raw) {
|
|
2789
|
+
if (!isRecord(raw))
|
|
2790
|
+
return null;
|
|
2791
|
+
const metadata = isRecord(raw.metadata) ? raw.metadata : null;
|
|
2792
|
+
if (raw.event !== "SEND_MESSAGE" ||
|
|
2793
|
+
!isNonEmptyString(raw.messageId) ||
|
|
2794
|
+
!isNonEmptyString(raw.to) ||
|
|
2795
|
+
!isNonEmptyString(raw.content) ||
|
|
2796
|
+
!isNonEmptyString(raw.contentType) ||
|
|
2797
|
+
metadata === null ||
|
|
2798
|
+
!isNonEmptyString(raw.runtimeOutboundReceiptToken)) {
|
|
2799
|
+
return null;
|
|
2800
|
+
}
|
|
2801
|
+
if (findPeerVisibleRuntimeMetadataKeys(metadata).length > 0)
|
|
2802
|
+
return null;
|
|
2803
|
+
if (findGatewayLocalReceiptMetadataKeys(metadata).length > 0)
|
|
2804
|
+
return null;
|
|
2805
|
+
return {
|
|
2806
|
+
event: "SEND_MESSAGE",
|
|
2807
|
+
messageId: raw.messageId,
|
|
2808
|
+
to: raw.to,
|
|
2809
|
+
content: raw.content,
|
|
2810
|
+
contentType: raw.contentType,
|
|
2811
|
+
metadata,
|
|
2812
|
+
runtimeOutboundReceiptToken: raw.runtimeOutboundReceiptToken,
|
|
2813
|
+
};
|
|
2814
|
+
}
|
|
2815
|
+
function findGatewayLocalReceiptMetadataKeys(value) {
|
|
2816
|
+
const keys = new Set();
|
|
2817
|
+
visitGatewayLocalReceiptMetadata(value, keys);
|
|
2818
|
+
return [...keys].sort();
|
|
2819
|
+
}
|
|
2820
|
+
function visitGatewayLocalReceiptMetadata(value, keys) {
|
|
2821
|
+
if (Array.isArray(value)) {
|
|
2822
|
+
for (const item of value)
|
|
2823
|
+
visitGatewayLocalReceiptMetadata(item, keys);
|
|
2824
|
+
return;
|
|
2825
|
+
}
|
|
2826
|
+
if (!value || typeof value !== "object")
|
|
2827
|
+
return;
|
|
2828
|
+
for (const [key, child] of Object.entries(value)) {
|
|
2829
|
+
if (key === "assignmentReplyReceiptToken" ||
|
|
2830
|
+
key === "assignment_reply_receipt_token" ||
|
|
2831
|
+
key === "runtimeOutboundReceiptToken" ||
|
|
2832
|
+
key === "runtime_outbound_receipt_token" ||
|
|
2833
|
+
key === "gatewayReceiptToken" ||
|
|
2834
|
+
key === "gateway_receipt_token") {
|
|
2835
|
+
keys.add(key);
|
|
2836
|
+
}
|
|
2837
|
+
visitGatewayLocalReceiptMetadata(child, keys);
|
|
2838
|
+
}
|
|
2839
|
+
}
|
|
2840
|
+
function readRuntimeOwnerActionHandoffInitResponse(raw) {
|
|
2841
|
+
if (!isRecord(raw)) {
|
|
2842
|
+
return invalidWireResponse("invalid_runtime_owner_action_handoff_init_response", "invalid runtime owner-action handoff init response");
|
|
2843
|
+
}
|
|
2844
|
+
if (typeof raw.handoff_session_id !== "string" ||
|
|
2845
|
+
typeof raw.approve_url !== "string" ||
|
|
2846
|
+
typeof raw.expires_at !== "string") {
|
|
2847
|
+
return invalidWireResponse("invalid_runtime_owner_action_handoff_init_response", "invalid runtime owner-action handoff init response");
|
|
2848
|
+
}
|
|
2849
|
+
return {
|
|
2850
|
+
handoff_session_id: raw.handoff_session_id,
|
|
2851
|
+
approve_url: raw.approve_url,
|
|
2852
|
+
expires_at: raw.expires_at,
|
|
2853
|
+
};
|
|
2854
|
+
}
|
|
2855
|
+
function readRuntimeOwnerActionHandoffPollResponse(raw) {
|
|
2856
|
+
if (!isRecord(raw) || typeof raw.status !== "string") {
|
|
2857
|
+
return invalidWireResponse("invalid_runtime_owner_action_handoff_poll_response", "invalid runtime owner-action handoff poll response");
|
|
2858
|
+
}
|
|
2859
|
+
if (raw.status === "pending")
|
|
2860
|
+
return { status: "pending" };
|
|
2861
|
+
if (raw.status === "completed") {
|
|
2862
|
+
if (raw.dispatch === undefined) {
|
|
2863
|
+
return invalidWireResponse("invalid_runtime_owner_action_handoff_poll_response", "invalid runtime owner-action handoff poll response");
|
|
2864
|
+
}
|
|
2865
|
+
let dispatch;
|
|
2866
|
+
try {
|
|
2867
|
+
dispatch = readRuntimeDispatchUndispatchedResponse(raw.dispatch);
|
|
2868
|
+
}
|
|
2869
|
+
catch (err) {
|
|
2870
|
+
if (err instanceof RuntimeAdapterApiError) {
|
|
2871
|
+
return invalidWireResponse("invalid_runtime_owner_action_handoff_poll_response", "invalid runtime owner-action handoff poll response");
|
|
2872
|
+
}
|
|
2873
|
+
throw err;
|
|
2874
|
+
}
|
|
2875
|
+
return {
|
|
2876
|
+
status: "completed",
|
|
2877
|
+
dispatch,
|
|
2878
|
+
};
|
|
2879
|
+
}
|
|
2880
|
+
if (raw.status === "failed") {
|
|
2881
|
+
if (typeof raw.error !== "string") {
|
|
2882
|
+
return invalidWireResponse("invalid_runtime_owner_action_handoff_poll_response", "invalid runtime owner-action handoff poll response");
|
|
2883
|
+
}
|
|
2884
|
+
return {
|
|
2885
|
+
status: "failed",
|
|
2886
|
+
error: raw.error,
|
|
2887
|
+
...(typeof raw.message === "string" && { message: raw.message }),
|
|
2888
|
+
...(typeof raw.retryable === "boolean" && {
|
|
2889
|
+
retryable: raw.retryable,
|
|
2890
|
+
}),
|
|
2891
|
+
...(typeof raw.dispatch_status_code === "number" && {
|
|
2892
|
+
dispatch_status_code: raw.dispatch_status_code,
|
|
2893
|
+
}),
|
|
2894
|
+
};
|
|
2895
|
+
}
|
|
2896
|
+
return invalidWireResponse("invalid_runtime_owner_action_handoff_poll_response", "invalid runtime owner-action handoff poll response");
|
|
2897
|
+
}
|
|
2898
|
+
async function readRuntimeSelfClaimSuccess(res, params) {
|
|
2899
|
+
const rawBody = (await res.json().catch(() => null));
|
|
2900
|
+
const body = rawBody !== null && typeof rawBody === "object" && !Array.isArray(rawBody)
|
|
2901
|
+
? rawBody
|
|
2902
|
+
: null;
|
|
2903
|
+
const targetRef = parseRuntimeWorkTargetRef(body?.target_ref);
|
|
2904
|
+
const assignmentId = body?.assignment_id;
|
|
2905
|
+
const undispatchedId = body?.undispatched_id;
|
|
2906
|
+
const sourceMessageId = body?.source_message_id;
|
|
2907
|
+
const brokerProjectionToken = body?.broker_projection_token;
|
|
2908
|
+
const assignedAt = body?.assigned_at;
|
|
2909
|
+
const claimed = body?.claimed;
|
|
2910
|
+
const recovered = body?.recovered;
|
|
2911
|
+
const message = body?.message;
|
|
2912
|
+
if (targetRef !== null &&
|
|
2913
|
+
targetMatchesClaimParams(targetRef, params) &&
|
|
2914
|
+
typeof assignmentId === "string" &&
|
|
2915
|
+
typeof undispatchedId === "string" &&
|
|
2916
|
+
typeof sourceMessageId === "string" &&
|
|
2917
|
+
typeof brokerProjectionToken === "string" &&
|
|
2918
|
+
typeof assignedAt === "number" &&
|
|
2919
|
+
typeof claimed === "boolean" &&
|
|
2920
|
+
(recovered === undefined || typeof recovered === "boolean") &&
|
|
2921
|
+
typeof message === "object" &&
|
|
2922
|
+
message !== null &&
|
|
2923
|
+
typeof message.from === "string" &&
|
|
2924
|
+
typeof message.content === "string" &&
|
|
2925
|
+
typeof message.contentType === "string" &&
|
|
2926
|
+
typeof message.sourceMessageId === "string" &&
|
|
2927
|
+
message.sourceMessageId === sourceMessageId &&
|
|
2928
|
+
undispatchedId === params.undispatched_id &&
|
|
2929
|
+
(message.metadata === undefined ||
|
|
2930
|
+
(typeof message.metadata === "object" &&
|
|
2931
|
+
message.metadata !== null &&
|
|
2932
|
+
!Array.isArray(message.metadata)))) {
|
|
2933
|
+
const claim = {
|
|
2934
|
+
assignment_id: assignmentId,
|
|
2935
|
+
undispatched_id: undispatchedId,
|
|
2936
|
+
source_message_id: sourceMessageId,
|
|
2937
|
+
target_ref: targetRef,
|
|
2938
|
+
broker_projection_token: brokerProjectionToken,
|
|
2939
|
+
assigned_at: assignedAt,
|
|
2940
|
+
claimed,
|
|
2941
|
+
...(recovered !== undefined && { recovered }),
|
|
2942
|
+
message: {
|
|
2943
|
+
from: message.from,
|
|
2944
|
+
content: message.content,
|
|
2945
|
+
contentType: message.contentType,
|
|
2946
|
+
...(message.metadata !== undefined && {
|
|
2947
|
+
metadata: message.metadata,
|
|
2948
|
+
}),
|
|
2949
|
+
sourceMessageId: message.sourceMessageId,
|
|
2950
|
+
},
|
|
2951
|
+
};
|
|
2952
|
+
return { ok: true, claim };
|
|
2953
|
+
}
|
|
2954
|
+
const postCasInvalidClaim = body
|
|
2955
|
+
? {
|
|
2956
|
+
...body,
|
|
2957
|
+
...(targetRef !== null && { target_ref: targetRef }),
|
|
2958
|
+
}
|
|
2959
|
+
: undefined;
|
|
2960
|
+
return {
|
|
2961
|
+
ok: false,
|
|
2962
|
+
terminal: true,
|
|
2963
|
+
status: res.status,
|
|
2964
|
+
detail: targetRef !== null && !targetMatchesClaimParams(targetRef, params)
|
|
2965
|
+
? "self-claim target mismatch"
|
|
2966
|
+
: "invalid self-claim response",
|
|
2967
|
+
...(postCasInvalidClaim !== undefined && { postCasInvalidClaim }),
|
|
2968
|
+
};
|
|
2969
|
+
}
|
|
2970
|
+
function targetMatchesClaimParams(targetRef, params) {
|
|
2971
|
+
if (targetRef === null)
|
|
2972
|
+
return false;
|
|
2973
|
+
if (targetRef.projection_endpoint_id !== params.target_endpoint_id) {
|
|
2974
|
+
return false;
|
|
2975
|
+
}
|
|
2976
|
+
if (params.expected_target_ref === undefined)
|
|
2977
|
+
return true;
|
|
2978
|
+
return sameRuntimeWorkTargetRef(targetRef, params.expected_target_ref);
|
|
2979
|
+
}
|
|
2980
|
+
function buildPostCasReleaseParams(claim, endpointNonce) {
|
|
2981
|
+
if (!claim ||
|
|
2982
|
+
typeof claim.undispatched_id !== "string" ||
|
|
2983
|
+
typeof claim.source_message_id !== "string" ||
|
|
2984
|
+
typeof claim.assignment_id !== "string" ||
|
|
2985
|
+
typeof claim.broker_projection_token !== "string" ||
|
|
2986
|
+
claim.target_ref === undefined) {
|
|
2987
|
+
return undefined;
|
|
2988
|
+
}
|
|
2989
|
+
return {
|
|
2990
|
+
undispatched_id: claim.undispatched_id,
|
|
2991
|
+
source_message_id: claim.source_message_id,
|
|
2992
|
+
assignment_id: claim.assignment_id,
|
|
2993
|
+
target_ref: claim.target_ref,
|
|
2994
|
+
endpoint_nonce: endpointNonce,
|
|
2995
|
+
broker_projection_token: claim.broker_projection_token,
|
|
2996
|
+
};
|
|
2997
|
+
}
|
|
2998
|
+
async function readErrorEnvelope(res) {
|
|
2999
|
+
try {
|
|
3000
|
+
const body = (await res.json().catch(() => null));
|
|
3001
|
+
if (!body)
|
|
3002
|
+
return {};
|
|
3003
|
+
return {
|
|
3004
|
+
...(typeof body.error === "string" && { code: body.error }),
|
|
3005
|
+
...(typeof body.message === "string" && { message: body.message }),
|
|
3006
|
+
...(isRuntimeAssignmentFailureCategory(body.assignment_failure_category) && {
|
|
3007
|
+
assignmentFailureCategory: body.assignment_failure_category,
|
|
3008
|
+
}),
|
|
3009
|
+
};
|
|
3010
|
+
}
|
|
3011
|
+
catch {
|
|
3012
|
+
return {};
|
|
3013
|
+
}
|
|
3014
|
+
}
|
|
3015
|
+
async function readErrorDetail(res) {
|
|
3016
|
+
const error = await readErrorEnvelope(res);
|
|
3017
|
+
return [error.code, error.message].filter(Boolean).join(": ") || undefined;
|
|
3018
|
+
}
|
|
3019
|
+
async function terminalHttpOutcome(res) {
|
|
3020
|
+
const error = await readErrorEnvelope(res);
|
|
3021
|
+
return {
|
|
3022
|
+
ok: false,
|
|
3023
|
+
terminal: true,
|
|
3024
|
+
status: res.status,
|
|
3025
|
+
...errorOutcomeFields(error),
|
|
3026
|
+
};
|
|
3027
|
+
}
|
|
3028
|
+
async function transientHttpOutcome(res) {
|
|
3029
|
+
const error = await readErrorEnvelope(res);
|
|
3030
|
+
return {
|
|
3031
|
+
ok: false,
|
|
3032
|
+
terminal: false,
|
|
3033
|
+
status: res.status,
|
|
3034
|
+
...errorOutcomeFields(error),
|
|
3035
|
+
};
|
|
3036
|
+
}
|
|
3037
|
+
function transientNetworkOutcome(err) {
|
|
3038
|
+
return {
|
|
3039
|
+
ok: false,
|
|
3040
|
+
terminal: false,
|
|
3041
|
+
detail: networkDetail(err),
|
|
3042
|
+
};
|
|
3043
|
+
}
|
|
3044
|
+
function networkDetail(err) {
|
|
3045
|
+
if (err instanceof RuntimeAdapterNetworkError) {
|
|
3046
|
+
return `${err.requestKind}: ${err.code}: ${err.detail}`;
|
|
3047
|
+
}
|
|
3048
|
+
return err instanceof Error ? err.message : String(err);
|
|
3049
|
+
}
|
|
3050
|
+
function errorOutcomeFields(error) {
|
|
3051
|
+
const detail = [error.code, error.message].filter(Boolean).join(": ");
|
|
3052
|
+
return {
|
|
3053
|
+
...(detail.length > 0 && { detail }),
|
|
3054
|
+
...(error.assignmentFailureCategory !== undefined && {
|
|
3055
|
+
assignmentFailureCategory: error.assignmentFailureCategory,
|
|
3056
|
+
}),
|
|
3057
|
+
};
|
|
3058
|
+
}
|
|
3059
|
+
function isRuntimeAssignmentFailureCategory(value) {
|
|
3060
|
+
return RUNTIME_ASSIGNMENT_FAILURE_CATEGORIES.includes(value);
|
|
3061
|
+
}
|
|
3062
|
+
function isRecord(value) {
|
|
3063
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3064
|
+
}
|
|
3065
|
+
function hasExactKeys(value, keys) {
|
|
3066
|
+
const actual = Object.keys(value).sort();
|
|
3067
|
+
const expected = [...keys].sort();
|
|
3068
|
+
return (actual.length === expected.length &&
|
|
3069
|
+
actual.every((key, index) => key === expected[index]));
|
|
3070
|
+
}
|
|
3071
|
+
function readOptionalStringArray(value) {
|
|
3072
|
+
if (value === undefined)
|
|
3073
|
+
return undefined;
|
|
3074
|
+
if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) {
|
|
3075
|
+
return null;
|
|
3076
|
+
}
|
|
3077
|
+
return value;
|
|
3078
|
+
}
|
|
3079
|
+
function parseRuntimeSessionNaming(value) {
|
|
3080
|
+
if (!isRecord(value) || !isNonEmptyString(value.nickname))
|
|
3081
|
+
return null;
|
|
3082
|
+
if (value.owner_edited_name !== undefined &&
|
|
3083
|
+
value.owner_edited_name !== null &&
|
|
3084
|
+
typeof value.owner_edited_name !== "string") {
|
|
3085
|
+
return null;
|
|
3086
|
+
}
|
|
3087
|
+
return {
|
|
3088
|
+
nickname: value.nickname,
|
|
3089
|
+
...(value.owner_edited_name !== undefined && {
|
|
3090
|
+
owner_edited_name: value.owner_edited_name,
|
|
3091
|
+
}),
|
|
3092
|
+
};
|
|
3093
|
+
}
|
|
3094
|
+
function isWorkspaceMetadata(value) {
|
|
3095
|
+
return (isRecord(value) &&
|
|
3096
|
+
typeof value.root === "string" &&
|
|
3097
|
+
typeof value.cwd === "string" &&
|
|
3098
|
+
(value.remote === undefined || typeof value.remote === "string") &&
|
|
3099
|
+
(value.branch === undefined || typeof value.branch === "string"));
|
|
3100
|
+
}
|
|
3101
|
+
function isEndpointFingerprint(value) {
|
|
3102
|
+
if (!isRecord(value))
|
|
3103
|
+
return false;
|
|
3104
|
+
if (value.workspace_hash_per_peer === undefined)
|
|
3105
|
+
return true;
|
|
3106
|
+
if (!isRecord(value.workspace_hash_per_peer))
|
|
3107
|
+
return false;
|
|
3108
|
+
return Object.values(value.workspace_hash_per_peer).every((item) => typeof item === "string");
|
|
3109
|
+
}
|
|
3110
|
+
function invalidWireResponse(code, message) {
|
|
3111
|
+
throw new RuntimeAdapterApiError(502, code, message);
|
|
3112
|
+
}
|