@danceiny/gotry 0.0.1-rc.18 → 0.0.1-rc.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +42 -16
- package/README.zh-CN.md +37 -15
- package/bin/gotry-bootstrap.js +309 -30
- package/bin/gotry-inner.js +74 -23
- package/bin/gotry-runtime-resolution.d.ts +1 -1
- package/bin/gotry-runtime-resolution.js +7 -8
- package/cordis.gotry-patch.yml +22 -8
- package/data/stations-12306-verify.json +524 -0
- package/dist/capabilities/agent-reach.js +4 -4
- package/dist/capabilities/anything.js +62 -29
- package/dist/capabilities/channel-health.js +103 -0
- package/dist/capabilities/channel-registry.js +213 -0
- package/dist/capabilities/doctor.js +307 -0
- package/dist/capabilities/effect.js +178 -5
- package/dist/capabilities/flyai.js +13 -0
- package/dist/capabilities/hbcli.js +28 -2
- package/dist/capabilities/session/adapters/ctrip-hotel.js +207 -0
- package/dist/capabilities/session/adapters/rail-12306.js +285 -0
- package/dist/capabilities/session/extension-bridge.js +14 -1
- package/dist/capabilities/session/extension-channel.js +4 -2
- package/dist/capabilities/session/extension-distribution.js +1 -1
- package/dist/capabilities/session-search.js +311 -0
- package/dist/capabilities/visa-policy.js +126 -0
- package/dist/scripts/agent-planning-turn-deadline-e2e.js +305 -0
- package/dist/scripts/agent-planning-turn-deadline-tests.js +281 -0
- package/dist/scripts/anything-tests.js +70 -21
- package/dist/scripts/benchmark-environment-bridge-e2e.js +139 -37
- package/dist/scripts/benchmark-environment-bridge-tests.js +1099 -306
- package/dist/scripts/booking-copilot-availability-ledger-binding-tests.js +26 -26
- package/dist/scripts/{booking-copilot-availability-policy-v2-tests.js → booking-copilot-availability-policy-tests.js} +115 -115
- package/dist/scripts/booking-copilot-crossrepo-fixture-server.js +41 -1
- package/dist/scripts/booking-copilot-dsh-core-proof-tests.js +75 -120
- package/dist/scripts/booking-copilot-dsh-planner-proof-tests.js +370 -61
- package/dist/scripts/booking-copilot-dsh-plugin-proof-tests.js +2 -2
- package/dist/scripts/booking-copilot-event-sequence-concurrency-proof-tests.js +7 -3
- package/dist/scripts/booking-copilot-gap-code-contract-proof-tests.js +15 -9
- package/dist/scripts/booking-copilot-operation-ledger-concurrency-proof-tests.js +19 -11
- package/dist/scripts/booking-copilot-receipt-ledger-concurrency-proof-tests.js +16 -52
- package/dist/scripts/booking-copilot-runtime-proof-tests.js +3953 -155
- package/dist/scripts/booking-copilot-server-proof-tests.js +58 -23
- package/dist/scripts/booking-copilot-startup-proof-tests.js +10 -119
- package/dist/scripts/booking-surface-contract-proof-tests.js +1154 -326
- package/dist/scripts/bootstrap-tests.js +113 -2
- package/dist/scripts/build-metrics-report.js +339 -0
- package/dist/scripts/channel-probe-tests.js +148 -0
- package/dist/scripts/channel-probe.js +252 -0
- package/dist/scripts/channel-registry-tests.js +262 -0
- package/dist/scripts/doctor-tests.js +175 -0
- package/dist/scripts/effect-tests.js +274 -1
- package/dist/scripts/extension-tests.js +129 -11
- package/dist/scripts/fact-gate-tests.js +91 -2
- package/dist/scripts/flyai-tests.js +17 -1
- package/dist/scripts/hbcli-e2e-tests.js +1 -3
- package/dist/scripts/hbcli-tests.js +38 -1
- package/dist/scripts/metrics-report-tests.js +288 -0
- package/dist/scripts/persona-surface-guard-tests.js +19 -0
- package/dist/scripts/session-tests.js +401 -1
- package/dist/scripts/smoke.js +100 -83
- package/dist/scripts/turn-handoff-collect-tests.js +216 -0
- package/dist/scripts/turn-handoff-collect.js +189 -0
- package/dist/scripts/turn-policy-tests.js +56 -0
- package/dist/scripts/typed-contract-canary.js +261 -0
- package/dist/scripts/visa-policy-tests.js +75 -0
- package/dist/scripts/wish-channel-gate-tests.js +111 -0
- package/dist/src/artifact-gate.js +91 -3
- package/dist/src/benchmark-agent-conformance.js +39 -16
- package/dist/src/benchmark-environment-bridge.js +316 -52
- package/dist/src/bookable-facts.js +62 -3
- package/dist/src/booking-surface/{availability-policy-v2.js → availability-policy.js} +89 -89
- package/dist/src/booking-surface/canonical-schema.js +8 -22
- package/dist/src/booking-surface/contracts.js +70 -19
- package/dist/src/booking-surface/dsh-planner.js +310 -177
- package/dist/src/booking-surface/dsh-plugin.js +3 -3
- package/dist/src/booking-surface/index.js +1 -5
- package/dist/src/booking-surface/profile.js +1 -1
- package/dist/src/booking-surface/runtime.js +1682 -247
- package/dist/src/booking-surface/server.js +371 -185
- package/dist/src/booking-surface/startup.js +17 -27
- package/dist/src/booking-surface/validation.js +270 -760
- package/dist/src/index.js +739 -150
- package/dist/src/turn-deadline.js +292 -0
- package/dist/src/turn-policy.js +146 -0
- package/dist/src/wish-pool.js +5 -0
- package/extension/background.js +23 -4
- package/extension/content-main.js +47 -9
- package/extension/manifest.json +27 -9
- package/package.json +7 -26
- package/schemas/booking.surface.schema.json +2593 -0
- package/ts/capabilities/agent-reach.ts +4 -4
- package/ts/capabilities/anything.ts +89 -38
- package/ts/capabilities/flyai.ts +21 -3
- package/ts/capabilities/hbcli.ts +53 -4
- package/ts/capabilities/session/action-cache.ts +1 -1
- package/ts/capabilities/session/adapters/ctrip-hotel.ts +238 -0
- package/ts/capabilities/session/adapters/rail-12306.ts +293 -0
- package/ts/capabilities/session/extension-bridge.ts +32 -2
- package/ts/capabilities/session/extension-channel.ts +9 -8
- package/ts/capabilities/session/extension-distribution.ts +3 -1
- package/ts/capabilities/session/health-watch.ts +1 -1
- package/ts/capabilities/session/read-guard.ts +1 -1
- package/ts/capabilities/session/wizard.ts +1 -1
- package/ts/capabilities/session-search.ts +323 -1
- package/ts/dsh-runtime/vendor/README.md +60 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/LICENSE +21 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/README.en.md +200 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/README.md +202 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/client/client.js +216 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/cordis.patch.yml +5 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/amap.js +371 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/nominatim.js +63 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/osrm.js +56 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/photon.js +55 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/config-file.js +87 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/config-route.js +126 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/config.js +16 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/index.js +109 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/settings-ns.js +27 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/tools/geocode.js +127 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/tools/poi.js +84 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/tools/routes.js +170 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/amap.d.ts +53 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/nominatim.d.ts +17 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/osrm.d.ts +15 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/photon.d.ts +22 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/config-file.d.ts +33 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/config-route.d.ts +20 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/config.d.ts +23 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/index.d.ts +18 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/settings-ns.d.ts +14 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/tools/geocode.d.ts +11 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/tools/poi.d.ts +9 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/tools/routes.d.ts +16 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/types.d.ts +55 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types.js +16 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/package.json +91 -0
- package/ts/package.json +9 -0
- package/ts/scripts/turn-handoff-collect.ts +178 -0
- package/ts/src/artifact-gate.ts +82 -2
- package/ts/src/benchmark-agent-conformance.ts +54 -15
- package/ts/src/benchmark-environment-bridge.ts +234 -46
- package/ts/src/bookable-facts.ts +109 -5
- package/ts/src/booking-saga.ts +1 -1
- package/ts/src/booking-surface/{availability-policy-v2.ts → availability-policy.ts} +124 -125
- package/ts/src/booking-surface/canonical-schema.js +8 -22
- package/ts/src/booking-surface/contracts.ts +278 -239
- package/ts/src/booking-surface/dsh-planner.ts +333 -194
- package/ts/src/booking-surface/dsh-plugin.js +3 -3
- package/ts/src/booking-surface/index.ts +1 -5
- package/ts/src/booking-surface/profile.ts +11 -11
- package/ts/src/booking-surface/runtime.ts +1351 -351
- package/ts/src/booking-surface/server.ts +282 -175
- package/ts/src/booking-surface/startup.ts +36 -48
- package/ts/src/booking-surface/validation.ts +194 -442
- package/ts/src/contracts.ts +1 -1
- package/ts/src/index.ts +492 -172
- package/ts/src/loop.ts +1 -1
- package/ts/src/state-ledger.ts +1 -1
- package/ts/src/turn-deadline.ts +365 -0
- package/ts/src/turn-policy.ts +154 -0
- package/ts/src/wish-pool.ts +17 -4
- package/dist/scripts/agent-planning-budget-e2e.js +0 -227
- package/dist/scripts/agent-planning-budget-tests.js +0 -173
- package/dist/scripts/booking-copilot-v2-runtime-proof-tests.js +0 -3935
- package/dist/scripts/booking-surface-v2-contract-proof-tests.js +0 -1174
- package/dist/src/booking-surface/contracts-v2.js +0 -89
- package/dist/src/booking-surface/runtime-v2.js +0 -1771
- package/dist/src/booking-surface/server-v2.js +0 -334
- package/dist/src/booking-surface/validation-v2.js +0 -319
- package/dist/src/tool-budget.js +0 -136
- package/schemas/booking.surface.v1.schema.json +0 -927
- package/schemas/booking.surface.v2.schema.json +0 -61
- package/ts/src/booking-surface/contracts-v2.ts +0 -118
- package/ts/src/booking-surface/runtime-v2.ts +0 -1466
- package/ts/src/booking-surface/server-v2.ts +0 -247
- package/ts/src/booking-surface/validation-v2.ts +0 -205
- package/ts/src/tool-budget.ts +0 -165
|
@@ -1,11 +1,39 @@
|
|
|
1
1
|
import { timingSafeEqual } from 'node:crypto';
|
|
2
2
|
import { createServer } from 'node:http';
|
|
3
|
-
import { BOOKING_SURFACE_SCHEMA_SHA256, BOOKING_SURFACE_SCHEMA_SHA256_HEADER, BOOKING_SURFACE_SCHEMA_VERSION, BOOKING_SURFACE_VERSION_HEADER } from './contracts.js';
|
|
4
|
-
import { BookingCopilotTaskRuntime } from './runtime.js';
|
|
5
|
-
import {
|
|
6
|
-
import { handleBookingCopilotV2Request } from './server-v2.js';
|
|
3
|
+
import { BOOKING_COPILOT_ACCEPTED_TURN_KINDS, BOOKING_COPILOT_INGRESS_MODES, BOOKING_READ_ACTION_KINDS, BOOKING_SURFACE_ALLOWED_ACTIONS, BOOKING_SURFACE_SCHEMA_SHA256, BOOKING_SURFACE_SCHEMA_SHA256_HEADER, BOOKING_SURFACE_SCHEMA_VERSION, BOOKING_SURFACE_VERSION_HEADER } from './contracts.js';
|
|
4
|
+
import { bookingDigest, BookingCopilotTaskRuntime } from './runtime.js';
|
|
5
|
+
import { validateBookingSurface } from './validation.js';
|
|
7
6
|
import { normalizeBookingErrorCode, safeBookingErrorMessage } from './error-codes.js';
|
|
8
|
-
|
|
7
|
+
const sessionsByComposition = new WeakMap();
|
|
8
|
+
const decisionFlightsByComposition = new WeakMap();
|
|
9
|
+
function sessionsFor(composition) {
|
|
10
|
+
let sessions = sessionsByComposition.get(composition);
|
|
11
|
+
if (!sessions) {
|
|
12
|
+
sessions = new Map();
|
|
13
|
+
sessionsByComposition.set(composition, sessions);
|
|
14
|
+
}
|
|
15
|
+
return sessions;
|
|
16
|
+
}
|
|
17
|
+
function decisionFlightsFor(composition) {
|
|
18
|
+
let flights = decisionFlightsByComposition.get(composition);
|
|
19
|
+
if (!flights) {
|
|
20
|
+
flights = new Map();
|
|
21
|
+
decisionFlightsByComposition.set(composition, flights);
|
|
22
|
+
}
|
|
23
|
+
return flights;
|
|
24
|
+
}
|
|
25
|
+
async function runDecisionSingleFlight(composition, key, work) {
|
|
26
|
+
const flights = decisionFlightsFor(composition);
|
|
27
|
+
const prior = flights.get(key);
|
|
28
|
+
if (prior) return prior;
|
|
29
|
+
const current = work();
|
|
30
|
+
flights.set(key, current);
|
|
31
|
+
try {
|
|
32
|
+
return await current;
|
|
33
|
+
} finally{
|
|
34
|
+
if (flights.get(key) === current) flights.delete(key);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
9
37
|
function runtimeIdentity() {
|
|
10
38
|
const report = process.report?.getReport?.();
|
|
11
39
|
const header = report?.header;
|
|
@@ -45,38 +73,341 @@ function readBody(req, maxBodyBytes) {
|
|
|
45
73
|
req.on('error', reject);
|
|
46
74
|
});
|
|
47
75
|
}
|
|
48
|
-
function
|
|
76
|
+
function write(res, event) {
|
|
49
77
|
res.write(`event: ${event.kind}\ndata: ${JSON.stringify(event)}\n\n`);
|
|
50
78
|
}
|
|
79
|
+
function sameCapabilities(a, b) {
|
|
80
|
+
return a.length === b.length && [
|
|
81
|
+
...a
|
|
82
|
+
].sort().every((kind, index)=>kind === [
|
|
83
|
+
...b
|
|
84
|
+
].sort()[index]);
|
|
85
|
+
}
|
|
86
|
+
function exactKeys(value, allowed) {
|
|
87
|
+
return Object.keys(value).length === allowed.length && Object.keys(value).every((key)=>allowed.includes(key));
|
|
88
|
+
}
|
|
89
|
+
const SURFACE_ACTION_MATRIX = BOOKING_SURFACE_ALLOWED_ACTIONS;
|
|
90
|
+
function validSurfaceActions(surface, allowed) {
|
|
91
|
+
if (typeof surface !== 'string' || !Object.prototype.hasOwnProperty.call(SURFACE_ACTION_MATRIX, surface)) return false;
|
|
92
|
+
if (!Array.isArray(allowed) || allowed.length < 1 || new Set(allowed).size !== allowed.length) return false;
|
|
93
|
+
const matrix = SURFACE_ACTION_MATRIX[surface];
|
|
94
|
+
return allowed.every((action)=>typeof action === 'string' && matrix.includes(action) && BOOKING_READ_ACTION_KINDS.includes(action));
|
|
95
|
+
}
|
|
96
|
+
function validBoundWorkspaceAuthority(workspace) {
|
|
97
|
+
return workspace.capabilities.surface === workspace.surface && validSurfaceActions(workspace.surface, workspace.capabilities.allowedActions);
|
|
98
|
+
}
|
|
99
|
+
function validIngressBinding(binding, ingress) {
|
|
100
|
+
if (!binding || typeof binding !== 'object' || Array.isArray(binding)) return false;
|
|
101
|
+
const candidate = binding;
|
|
102
|
+
if (!exactKeys(candidate, [
|
|
103
|
+
'taskId',
|
|
104
|
+
'turnId',
|
|
105
|
+
'contextRef',
|
|
106
|
+
'surface',
|
|
107
|
+
'allowedActions'
|
|
108
|
+
])) return false;
|
|
109
|
+
if (typeof candidate.taskId !== 'string' || typeof candidate.turnId !== 'string' || typeof candidate.contextRef !== 'string') return false;
|
|
110
|
+
if (typeof candidate.surface !== 'string' || candidate.surface !== ingress.surfaceHint || !Object.prototype.hasOwnProperty.call(SURFACE_ACTION_MATRIX, candidate.surface)) return false;
|
|
111
|
+
return validSurfaceActions(candidate.surface, candidate.allowedActions);
|
|
112
|
+
}
|
|
113
|
+
function validIngressApprovalAuthority(ingress, binding) {
|
|
114
|
+
if (!ingress.request.approval) return true;
|
|
115
|
+
return ingress.request.approval.taskId === binding.taskId && ingress.request.approval.contextRef === binding.contextRef;
|
|
116
|
+
}
|
|
117
|
+
function fallbackErrorEvent(task, code) {
|
|
118
|
+
return {
|
|
119
|
+
schemaVersion: BOOKING_SURFACE_SCHEMA_VERSION,
|
|
120
|
+
eventId: `error-${task.taskId}-${task.lastSequence + 1}`,
|
|
121
|
+
taskId: task.taskId,
|
|
122
|
+
contextRef: task.contextRef,
|
|
123
|
+
sequence: task.lastSequence + 1,
|
|
124
|
+
emittedAt: new Date().toISOString(),
|
|
125
|
+
kind: 'error',
|
|
126
|
+
error: {
|
|
127
|
+
code: normalizeBookingErrorCode(code),
|
|
128
|
+
message: safeBookingErrorMessage(normalizeBookingErrorCode(code)),
|
|
129
|
+
retryable: false
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
function writeTypedError(res, composition, task, error, requestKey, includeSubmitted = false) {
|
|
134
|
+
const code = normalizeBookingErrorCode(error);
|
|
135
|
+
console.error(`[booking-copilot] planner boundary error (${code}):`, error instanceof Error ? error.stack || error.message : error);
|
|
136
|
+
try {
|
|
137
|
+
const decision = {
|
|
138
|
+
kind: 'error',
|
|
139
|
+
error: {
|
|
140
|
+
code,
|
|
141
|
+
message: safeBookingErrorMessage(code),
|
|
142
|
+
retryable: false
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
if (requestKey) {
|
|
146
|
+
const events = composition.runtime.applyDecisionBatch(task.taskId, requestKey, [
|
|
147
|
+
decision
|
|
148
|
+
], includeSubmitted);
|
|
149
|
+
for (const event of events)write(res, event);
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
write(res, composition.runtime.emitEvent(task.taskId, decision));
|
|
153
|
+
} catch {
|
|
154
|
+
write(res, fallbackErrorEvent(task, code));
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
async function handleBookingCopilotRequest(req, res, composition, maxBodyBytes) {
|
|
158
|
+
let turn;
|
|
159
|
+
let browserRequestKey;
|
|
160
|
+
let requestBinding;
|
|
161
|
+
try {
|
|
162
|
+
const parsed = JSON.parse(await readBody(req, maxBodyBytes));
|
|
163
|
+
const valid = validateBookingSurface(parsed);
|
|
164
|
+
if (!valid.ok) {
|
|
165
|
+
sendJson(res, 400, {
|
|
166
|
+
error: {
|
|
167
|
+
code: 'invalid_booking_surface_turn',
|
|
168
|
+
details: valid.errors
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
if (parsed.kind === 'user.turn.ingress') {
|
|
174
|
+
const ingress = parsed;
|
|
175
|
+
browserRequestKey = ingress.requestKey;
|
|
176
|
+
if (composition.ingressMode !== 'bff-ingress-binding' || !composition.ingressBinding || !composition.principal) {
|
|
177
|
+
sendJson(res, 503, {
|
|
178
|
+
error: {
|
|
179
|
+
code: 'trusted_ingress_binding_required',
|
|
180
|
+
mode: composition.ingressMode ?? 'bff-bound-turn-only',
|
|
181
|
+
acceptedTurnKinds: [
|
|
182
|
+
...BOOKING_COPILOT_ACCEPTED_TURN_KINDS
|
|
183
|
+
]
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
const binding = await composition.ingressBinding.bind(ingress, composition.principal);
|
|
189
|
+
if (!validIngressBinding(binding, ingress)) {
|
|
190
|
+
sendJson(res, 502, {
|
|
191
|
+
error: {
|
|
192
|
+
code: 'invalid_ingress_binding'
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
if (!validIngressApprovalAuthority(ingress, binding)) {
|
|
198
|
+
sendJson(res, 400, {
|
|
199
|
+
error: {
|
|
200
|
+
code: 'invalid_ingress_approval_authority'
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
const boundWorkspace = {
|
|
206
|
+
...ingress.workspace,
|
|
207
|
+
contextRef: binding.contextRef,
|
|
208
|
+
surface: binding.surface,
|
|
209
|
+
capabilities: {
|
|
210
|
+
surface: binding.surface,
|
|
211
|
+
allowedActions: [
|
|
212
|
+
...binding.allowedActions
|
|
213
|
+
]
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
const boundRequest = ingress.request.approval ? {
|
|
217
|
+
text: ingress.request.text,
|
|
218
|
+
approval: {
|
|
219
|
+
...ingress.request.approval
|
|
220
|
+
}
|
|
221
|
+
} : {
|
|
222
|
+
text: ingress.request.text
|
|
223
|
+
};
|
|
224
|
+
turn = {
|
|
225
|
+
schemaVersion: BOOKING_SURFACE_SCHEMA_VERSION,
|
|
226
|
+
kind: 'user.turn',
|
|
227
|
+
taskId: binding.taskId,
|
|
228
|
+
turnId: binding.turnId,
|
|
229
|
+
workspace: boundWorkspace,
|
|
230
|
+
request: boundRequest
|
|
231
|
+
};
|
|
232
|
+
requestBinding = {
|
|
233
|
+
requestKey: ingress.requestKey,
|
|
234
|
+
principal: composition.principal,
|
|
235
|
+
...ingress.taskHandle ? {
|
|
236
|
+
taskHandle: ingress.taskHandle
|
|
237
|
+
} : {}
|
|
238
|
+
};
|
|
239
|
+
const boundValid = validateBookingSurface(turn);
|
|
240
|
+
if (!boundValid.ok) {
|
|
241
|
+
sendJson(res, 502, {
|
|
242
|
+
error: {
|
|
243
|
+
code: 'invalid_ingress_binding',
|
|
244
|
+
details: boundValid.errors
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
} else {
|
|
250
|
+
turn = parsed;
|
|
251
|
+
if (!validBoundWorkspaceAuthority(turn.workspace)) {
|
|
252
|
+
sendJson(res, 403, {
|
|
253
|
+
error: {
|
|
254
|
+
code: 'invalid_bound_turn_authority'
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
} catch (error) {
|
|
261
|
+
const code = normalizeBookingErrorCode(error);
|
|
262
|
+
sendJson(res, code === 'PAYLOAD_TOO_LARGE' ? 413 : 400, {
|
|
263
|
+
error: {
|
|
264
|
+
code
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
let task;
|
|
270
|
+
let fresh = false;
|
|
271
|
+
let replayKey;
|
|
272
|
+
let replayEvents = null;
|
|
273
|
+
let activeDecisionKey;
|
|
274
|
+
try {
|
|
275
|
+
fresh = turn.kind === 'action.receipt.continuation' ? false : !turn.taskId || composition.runtime.resumeTask(turn.taskId) === null;
|
|
276
|
+
if (turn.kind === 'action.receipt.continuation') {
|
|
277
|
+
replayKey = `receipt:${turn.receipt.actionId}:${composition.runtime.receiptDigest(turn.receipt)}`;
|
|
278
|
+
task = composition.runtime.continueWithReceipt(turn);
|
|
279
|
+
if (task.awaitingApproval) {
|
|
280
|
+
replayKey = composition.runtime.approvalPresentationRequestKey(turn.taskId);
|
|
281
|
+
}
|
|
282
|
+
replayEvents = composition.runtime.readDecisionBatch(turn.taskId, replayKey);
|
|
283
|
+
if (!replayEvents && task.phase === 'terminal') replayEvents = composition.runtime.terminalDecisionBatch(turn.taskId, replayKey);
|
|
284
|
+
} else {
|
|
285
|
+
const existing = turn.taskId ? composition.runtime.resumeTask(turn.taskId) : null;
|
|
286
|
+
if (requestBinding && turn.kind === 'user.turn') composition.runtime.assertRequestBinding(browserRequestKey, turn, requestBinding);
|
|
287
|
+
const requestReplayKey = existing && browserRequestKey ? `ingress:${existing.taskId}:${browserRequestKey}` : undefined;
|
|
288
|
+
const directReplayKey = existing && turn.kind === 'user.turn' && turn.turnId ? `turn:${existing.taskId}:${turn.turnId}` : undefined;
|
|
289
|
+
const stableReplayKey = requestReplayKey ?? directReplayKey;
|
|
290
|
+
const stableReplay = stableReplayKey ? composition.runtime.readDecisionBatch(existing.taskId, stableReplayKey) : null;
|
|
291
|
+
if (stableReplay) {
|
|
292
|
+
if (existing && turn.kind === 'user.turn' && directReplayKey) composition.runtime.assertTurnBinding(existing.taskId, turn);
|
|
293
|
+
task = existing;
|
|
294
|
+
replayKey = stableReplayKey;
|
|
295
|
+
replayEvents = stableReplay;
|
|
296
|
+
} else if (existing?.phase === 'terminal' || existing?.phase === 'error') {
|
|
297
|
+
throw new Error('task_terminal');
|
|
298
|
+
} else if (existing?.phase === 'waiting_receipt') {
|
|
299
|
+
if (turn.kind === 'user.turn' && turn.request.approval) composition.runtime.assertApprovalNotConsumed(existing.taskId, turn.request.approval);
|
|
300
|
+
const workspace = turn.workspace;
|
|
301
|
+
if (workspace.contextRef !== existing.contextRef || workspace.surface !== existing.surface || workspace.revision !== existing.revision || workspace.capabilities.surface !== existing.surface || !sameCapabilities(workspace.capabilities.allowedActions, existing.allowedActions)) throw new Error('task_conflict:workspace_mismatch');
|
|
302
|
+
if (!turn.turnId || existing.lastTurnId !== turn.turnId) throw new Error('receipt_required');
|
|
303
|
+
replayKey = browserRequestKey ? `ingress:${existing.taskId}:${browserRequestKey}` : `turn:${existing.taskId}:${existing.lastTurnId ?? existing.userTurnCount}`;
|
|
304
|
+
replayEvents = composition.runtime.readDecisionBatch(existing.taskId, replayKey);
|
|
305
|
+
if (!replayEvents) throw new Error('receipt_required');
|
|
306
|
+
task = existing;
|
|
307
|
+
} else {
|
|
308
|
+
if (existing && turn.kind === 'user.turn' && turn.request.approval) composition.runtime.assertApprovalNotConsumed(existing.taskId, turn.request.approval);
|
|
309
|
+
task = composition.runtime.startTask(turn, requestBinding);
|
|
310
|
+
if (turn.kind === 'user.turn' && turn.taskId) {
|
|
311
|
+
replayKey = browserRequestKey ? `ingress:${task.taskId}:${browserRequestKey}` : `turn:${task.taskId}:${turn.turnId ?? task.userTurnCount}`;
|
|
312
|
+
replayEvents = composition.runtime.readDecisionBatch(task.taskId, replayKey);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
} catch (error) {
|
|
317
|
+
sendJson(res, 409, {
|
|
318
|
+
error: {
|
|
319
|
+
code: normalizeBookingErrorCode(error)
|
|
320
|
+
}
|
|
321
|
+
});
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
res.writeHead(200, {
|
|
325
|
+
'content-type': 'text/event-stream; charset=utf-8',
|
|
326
|
+
'cache-control': 'no-cache, no-transform',
|
|
327
|
+
connection: 'keep-alive',
|
|
328
|
+
'x-content-type-options': 'nosniff',
|
|
329
|
+
[BOOKING_SURFACE_VERSION_HEADER]: BOOKING_SURFACE_SCHEMA_VERSION,
|
|
330
|
+
[BOOKING_SURFACE_SCHEMA_SHA256_HEADER]: BOOKING_SURFACE_SCHEMA_SHA256
|
|
331
|
+
});
|
|
332
|
+
try {
|
|
333
|
+
if (replayEvents) {
|
|
334
|
+
for (const event of replayEvents)write(res, event);
|
|
335
|
+
} else {
|
|
336
|
+
const requestKey = replayKey ?? `turn:${task.taskId}:${task.userTurnCount}`;
|
|
337
|
+
activeDecisionKey = requestKey;
|
|
338
|
+
const approval = turn.kind === 'user.turn' ? turn.request.approval : undefined;
|
|
339
|
+
const flightKey = JSON.stringify([
|
|
340
|
+
task.taskId,
|
|
341
|
+
approval ? `approval:${bookingDigest(approval)}` : requestKey
|
|
342
|
+
]);
|
|
343
|
+
const decisionEvents = await runDecisionSingleFlight(composition, flightKey, async ()=>{
|
|
344
|
+
if (turn.kind === 'action.receipt.continuation' && task.awaitingApproval) {
|
|
345
|
+
return composition.runtime.applyDecisionBatch(task.taskId, requestKey, [
|
|
346
|
+
composition.runtime.approvalQuestion(task.taskId)
|
|
347
|
+
], false);
|
|
348
|
+
}
|
|
349
|
+
const sessions = sessionsFor(composition);
|
|
350
|
+
const session = sessions.get(task.taskId) ?? composition.plannerFactory(task);
|
|
351
|
+
sessions.set(task.taskId, session);
|
|
352
|
+
const decisions = await session.next({
|
|
353
|
+
turn,
|
|
354
|
+
task: composition.runtime.resumeTask(task.taskId) ?? task
|
|
355
|
+
});
|
|
356
|
+
const plannerDecisions = decisions.length ? decisions : [
|
|
357
|
+
{
|
|
358
|
+
kind: 'error',
|
|
359
|
+
error: {
|
|
360
|
+
code: 'PLANNER_NO_DECISION',
|
|
361
|
+
message: 'Planner returned no typed decision.',
|
|
362
|
+
retryable: false
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
];
|
|
366
|
+
return composition.runtime.applyDecisionBatch(task.taskId, requestKey, plannerDecisions, fresh);
|
|
367
|
+
});
|
|
368
|
+
for (const event of decisionEvents)write(res, event);
|
|
369
|
+
}
|
|
370
|
+
} catch (error) {
|
|
371
|
+
writeTypedError(res, composition, task, error, activeDecisionKey, fresh);
|
|
372
|
+
} finally{
|
|
373
|
+
res.end();
|
|
374
|
+
}
|
|
375
|
+
}
|
|
51
376
|
export function startBookingCopilotServer(options) {
|
|
52
377
|
if (!options.apiKey) return Promise.reject(new Error('booking_copilot_api_key_required'));
|
|
53
378
|
if (options.artifactId !== undefined && !/^[0-9a-f]{40}$/.test(options.artifactId)) {
|
|
54
379
|
return Promise.reject(new Error('booking_copilot_artifact_id_invalid'));
|
|
55
380
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
381
|
+
if (!options.runtime || !options.plannerFactory) {
|
|
382
|
+
return Promise.reject(new Error('booking_copilot_runtime_and_planner_required'));
|
|
383
|
+
}
|
|
384
|
+
const hasIngressBinding = Boolean(options.ingressBinding);
|
|
385
|
+
const hasPrincipal = Boolean(options.principal);
|
|
386
|
+
if (hasIngressBinding !== hasPrincipal) {
|
|
387
|
+
return Promise.reject(new Error('booking_copilot_ingress_binding_pair_required'));
|
|
60
388
|
}
|
|
61
|
-
if (
|
|
62
|
-
return Promise.reject(new Error('
|
|
389
|
+
if (hasIngressBinding && (typeof options.ingressBinding?.bind !== 'function' || typeof options.principal?.subject !== 'string' || options.principal.subject.length === 0 || options.principal.subject.length > 256 || typeof options.principal?.scope !== 'string' || options.principal.scope.length === 0 || options.principal.scope.length > 256)) {
|
|
390
|
+
return Promise.reject(new Error('booking_copilot_ingress_binding_invalid'));
|
|
63
391
|
}
|
|
64
|
-
const ingressMode = options.
|
|
65
|
-
if (
|
|
66
|
-
return Promise.reject(new Error('
|
|
392
|
+
const ingressMode = options.ingressMode ?? (hasIngressBinding ? 'bff-ingress-binding' : 'bff-bound-turn-only');
|
|
393
|
+
if (!BOOKING_COPILOT_INGRESS_MODES.includes(ingressMode)) {
|
|
394
|
+
return Promise.reject(new Error('booking_copilot_ingress_mode_invalid'));
|
|
67
395
|
}
|
|
68
|
-
if (
|
|
69
|
-
return Promise.reject(new Error('
|
|
396
|
+
if (ingressMode === 'bff-ingress-binding' && !hasIngressBinding) {
|
|
397
|
+
return Promise.reject(new Error('booking_copilot_ingress_binding_required'));
|
|
70
398
|
}
|
|
71
|
-
if (
|
|
72
|
-
return Promise.reject(new Error('
|
|
399
|
+
if (ingressMode === 'bff-bound-turn-only' && hasIngressBinding) {
|
|
400
|
+
return Promise.reject(new Error('booking_copilot_ingress_mode_conflict'));
|
|
73
401
|
}
|
|
74
|
-
const
|
|
75
|
-
|
|
402
|
+
const composition = {
|
|
403
|
+
runtime: options.runtime,
|
|
404
|
+
plannerFactory: options.plannerFactory,
|
|
405
|
+
...hasIngressBinding ? {
|
|
406
|
+
ingressBinding: options.ingressBinding,
|
|
407
|
+
principal: options.principal
|
|
408
|
+
} : {},
|
|
76
409
|
ingressMode
|
|
77
|
-
}
|
|
78
|
-
const v2Only = Boolean(v2 && !options.runtime && !options.plannerFactory);
|
|
79
|
-
const sessions = new Map();
|
|
410
|
+
};
|
|
80
411
|
const maxBodyBytes = options.maxBodyBytes ?? 1_000_000;
|
|
81
412
|
const runningIdentity = runtimeIdentity();
|
|
82
413
|
const server = createServer(async (req, res)=>{
|
|
@@ -99,56 +430,33 @@ export function startBookingCopilotServer(options) {
|
|
|
99
430
|
return;
|
|
100
431
|
}
|
|
101
432
|
if (isProbe) {
|
|
102
|
-
|
|
103
|
-
res.setHeader(
|
|
104
|
-
res.setHeader(BOOKING_SURFACE_SCHEMA_SHA256_HEADER, activeV2Only ? BOOKING_SURFACE_SCHEMA_V2_SHA256 : BOOKING_SURFACE_SCHEMA_SHA256);
|
|
433
|
+
res.setHeader(BOOKING_SURFACE_VERSION_HEADER, BOOKING_SURFACE_SCHEMA_VERSION);
|
|
434
|
+
res.setHeader(BOOKING_SURFACE_SCHEMA_SHA256_HEADER, BOOKING_SURFACE_SCHEMA_SHA256);
|
|
105
435
|
if (options.artifactId) res.setHeader('X-GoTry-Artifact-ID', options.artifactId);
|
|
106
436
|
res.setHeader('X-GoTry-Node-Version', runningIdentity.nodeVersion);
|
|
107
437
|
res.setHeader('X-GoTry-Node-Modules-ABI', runningIdentity.nodeModulesAbi);
|
|
108
438
|
res.setHeader('X-GoTry-Release-Tuple', runningIdentity.releaseTuple);
|
|
109
439
|
if (runningIdentity.glibcVersion) res.setHeader('X-GoTry-Glibc-Version', runningIdentity.glibcVersion);
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
status: 'ready'
|
|
114
|
-
};
|
|
115
|
-
if (activeV2Only || v2) healthBody.supportedSchemaVersions = activeV2Only ? [
|
|
116
|
-
BOOKING_SURFACE_SCHEMA_VERSION_V2
|
|
440
|
+
const acceptedTurnKinds = ingressMode === 'bff-ingress-binding' ? [
|
|
441
|
+
...BOOKING_COPILOT_ACCEPTED_TURN_KINDS,
|
|
442
|
+
'user.turn.ingress'
|
|
117
443
|
] : [
|
|
118
|
-
|
|
119
|
-
BOOKING_SURFACE_SCHEMA_VERSION_V2
|
|
444
|
+
...BOOKING_COPILOT_ACCEPTED_TURN_KINDS
|
|
120
445
|
];
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
res.setHeader('X-GoTry-Ingress-Mode', v2.ingressMode);
|
|
131
|
-
res.setHeader('X-GoTry-Accepted-Turn-Kinds', acceptedTurnKinds.join(','));
|
|
132
|
-
}
|
|
446
|
+
const healthBody = {
|
|
447
|
+
schemaVersion: BOOKING_SURFACE_SCHEMA_VERSION,
|
|
448
|
+
schemaSha256: BOOKING_SURFACE_SCHEMA_SHA256,
|
|
449
|
+
status: 'ready',
|
|
450
|
+
ingressMode,
|
|
451
|
+
acceptedTurnKinds
|
|
452
|
+
};
|
|
453
|
+
res.setHeader('X-GoTry-Ingress-Mode', ingressMode);
|
|
454
|
+
res.setHeader('X-GoTry-Accepted-Turn-Kinds', acceptedTurnKinds.join(','));
|
|
133
455
|
sendJson(res, 200, healthBody);
|
|
134
456
|
return;
|
|
135
457
|
}
|
|
136
458
|
const schemaVersion = String(req.headers[BOOKING_SURFACE_VERSION_HEADER] ?? '');
|
|
137
459
|
const schemaHash = String(req.headers[BOOKING_SURFACE_SCHEMA_SHA256_HEADER] ?? '');
|
|
138
|
-
if (schemaVersion === BOOKING_SURFACE_SCHEMA_VERSION_V2) {
|
|
139
|
-
if (!v2 || schemaHash !== BOOKING_SURFACE_SCHEMA_V2_SHA256) {
|
|
140
|
-
sendJson(res, 409, {
|
|
141
|
-
error: {
|
|
142
|
-
code: 'booking_surface_schema_mismatch',
|
|
143
|
-
expectedVersion: BOOKING_SURFACE_SCHEMA_VERSION_V2,
|
|
144
|
-
expectedSchemaSha256: BOOKING_SURFACE_SCHEMA_V2_SHA256
|
|
145
|
-
}
|
|
146
|
-
});
|
|
147
|
-
return;
|
|
148
|
-
}
|
|
149
|
-
await handleBookingCopilotV2Request(req, res, v2, maxBodyBytes);
|
|
150
|
-
return;
|
|
151
|
-
}
|
|
152
460
|
if (schemaVersion !== BOOKING_SURFACE_SCHEMA_VERSION || schemaHash !== BOOKING_SURFACE_SCHEMA_SHA256) {
|
|
153
461
|
sendJson(res, 409, {
|
|
154
462
|
error: {
|
|
@@ -159,129 +467,7 @@ export function startBookingCopilotServer(options) {
|
|
|
159
467
|
});
|
|
160
468
|
return;
|
|
161
469
|
}
|
|
162
|
-
|
|
163
|
-
sendJson(res, 503, {
|
|
164
|
-
error: {
|
|
165
|
-
code: 'booking_surface_v1_not_configured'
|
|
166
|
-
}
|
|
167
|
-
});
|
|
168
|
-
return;
|
|
169
|
-
}
|
|
170
|
-
let turn;
|
|
171
|
-
try {
|
|
172
|
-
const raw = await readBody(req, maxBodyBytes);
|
|
173
|
-
const parsed = JSON.parse(raw);
|
|
174
|
-
const validation = validateBookingCopilotTurnV1(parsed);
|
|
175
|
-
if (!validation.ok) {
|
|
176
|
-
sendJson(res, 400, {
|
|
177
|
-
error: {
|
|
178
|
-
code: 'invalid_booking_surface_turn',
|
|
179
|
-
details: validation.errors
|
|
180
|
-
}
|
|
181
|
-
});
|
|
182
|
-
return;
|
|
183
|
-
}
|
|
184
|
-
turn = parsed;
|
|
185
|
-
} catch (error) {
|
|
186
|
-
const code = normalizeBookingErrorCode(error);
|
|
187
|
-
sendJson(res, code === 'PAYLOAD_TOO_LARGE' ? 413 : 400, {
|
|
188
|
-
error: {
|
|
189
|
-
code
|
|
190
|
-
}
|
|
191
|
-
});
|
|
192
|
-
return;
|
|
193
|
-
}
|
|
194
|
-
let task;
|
|
195
|
-
let isNewTask = false;
|
|
196
|
-
try {
|
|
197
|
-
const taskId = turn.taskId;
|
|
198
|
-
if (v2 && taskId && v2.runtime.resumeTask(taskId)) throw new Error('task_conflict:protocol_version');
|
|
199
|
-
if (turn.kind === 'user.turn') {
|
|
200
|
-
const suppliedTaskId = turn.taskId;
|
|
201
|
-
isNewTask = !suppliedTaskId || options.runtime.resumeTask(suppliedTaskId) === null;
|
|
202
|
-
task = options.runtime.startTask(turn);
|
|
203
|
-
} else {
|
|
204
|
-
task = options.runtime.continueWithReceipt(turn);
|
|
205
|
-
}
|
|
206
|
-
} catch (error) {
|
|
207
|
-
sendJson(res, 409, {
|
|
208
|
-
error: {
|
|
209
|
-
code: normalizeBookingErrorCode(error)
|
|
210
|
-
}
|
|
211
|
-
});
|
|
212
|
-
return;
|
|
213
|
-
}
|
|
214
|
-
res.writeHead(200, {
|
|
215
|
-
'content-type': 'text/event-stream; charset=utf-8',
|
|
216
|
-
'cache-control': 'no-cache, no-transform',
|
|
217
|
-
connection: 'keep-alive',
|
|
218
|
-
'x-content-type-options': 'nosniff',
|
|
219
|
-
[BOOKING_SURFACE_VERSION_HEADER]: BOOKING_SURFACE_SCHEMA_VERSION,
|
|
220
|
-
[BOOKING_SURFACE_SCHEMA_SHA256_HEADER]: BOOKING_SURFACE_SCHEMA_SHA256
|
|
221
|
-
});
|
|
222
|
-
try {
|
|
223
|
-
if (isNewTask) writeSse(res, options.runtime.emitEvent(task.taskId, {
|
|
224
|
-
kind: 'status',
|
|
225
|
-
status: 'submitted'
|
|
226
|
-
}));
|
|
227
|
-
writeSse(res, options.runtime.emitEvent(task.taskId, {
|
|
228
|
-
kind: 'status',
|
|
229
|
-
status: 'working'
|
|
230
|
-
}));
|
|
231
|
-
task = options.runtime.resumeTask(task.taskId) ?? task;
|
|
232
|
-
let session = sessions.get(task.taskId);
|
|
233
|
-
if (!session) {
|
|
234
|
-
session = options.plannerFactory(task);
|
|
235
|
-
sessions.set(task.taskId, session);
|
|
236
|
-
}
|
|
237
|
-
const decisions = await session.next({
|
|
238
|
-
turn,
|
|
239
|
-
task
|
|
240
|
-
});
|
|
241
|
-
if (decisions.length === 0) {
|
|
242
|
-
writeSse(res, options.runtime.emitEvent(task.taskId, {
|
|
243
|
-
kind: 'error',
|
|
244
|
-
error: {
|
|
245
|
-
code: 'PLANNER_NO_DECISION',
|
|
246
|
-
message: 'Planner returned no typed decision.',
|
|
247
|
-
retryable: false
|
|
248
|
-
}
|
|
249
|
-
}));
|
|
250
|
-
} else {
|
|
251
|
-
for (const decision of decisions){
|
|
252
|
-
const event = decision.kind === 'operation' ? options.runtime.issueOperation(task.taskId, decision.action) : options.runtime.emitEvent(task.taskId, decision);
|
|
253
|
-
writeSse(res, event);
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
} catch (error) {
|
|
257
|
-
try {
|
|
258
|
-
writeSse(res, options.runtime.emitEvent(task.taskId, {
|
|
259
|
-
kind: 'error',
|
|
260
|
-
error: {
|
|
261
|
-
code: normalizeBookingErrorCode(error),
|
|
262
|
-
message: safeBookingErrorMessage(normalizeBookingErrorCode(error)),
|
|
263
|
-
retryable: false
|
|
264
|
-
}
|
|
265
|
-
}));
|
|
266
|
-
} catch {
|
|
267
|
-
writeSse(res, {
|
|
268
|
-
schemaVersion: 'booking.surface.v1',
|
|
269
|
-
eventId: `error-${task.taskId}-${task.lastSequence + 1}`,
|
|
270
|
-
taskId: task.taskId,
|
|
271
|
-
contextRef: task.contextRef,
|
|
272
|
-
sequence: task.lastSequence + 1,
|
|
273
|
-
emittedAt: new Date().toISOString(),
|
|
274
|
-
kind: 'error',
|
|
275
|
-
error: {
|
|
276
|
-
code: normalizeBookingErrorCode(error),
|
|
277
|
-
message: safeBookingErrorMessage(normalizeBookingErrorCode(error)),
|
|
278
|
-
retryable: false
|
|
279
|
-
}
|
|
280
|
-
});
|
|
281
|
-
}
|
|
282
|
-
} finally{
|
|
283
|
-
res.end();
|
|
284
|
-
}
|
|
470
|
+
await handleBookingCopilotRequest(req, res, composition, maxBodyBytes);
|
|
285
471
|
});
|
|
286
472
|
return new Promise((resolve, reject)=>{
|
|
287
473
|
server.once('error', reject);
|