@danceiny/gotry 0.0.1-rc.15 → 0.0.1-rc.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (238) hide show
  1. package/README.md +51 -13
  2. package/README.zh-CN.md +26 -12
  3. package/bin/gotry-booking-copilot.js +53 -0
  4. package/bin/gotry-bootstrap.js +357 -8
  5. package/bin/gotry-inner.js +442 -60
  6. package/bin/gotry-runtime-resolution.d.ts +27 -0
  7. package/bin/gotry-runtime-resolution.js +50 -0
  8. package/bin/gotry.js +1 -1
  9. package/cordis.gotry-patch.yml +15 -1
  10. package/data/airline-airports.json +39 -0
  11. package/dist/capabilities/agent-reach-deep.js +1 -1
  12. package/dist/capabilities/agent-reach.js +1 -1
  13. package/dist/capabilities/anything.js +1 -1
  14. package/dist/capabilities/artifacts.js +1 -1
  15. package/dist/capabilities/effect.js +293 -0
  16. package/dist/capabilities/fact-log.js +41 -0
  17. package/dist/capabilities/flyai.js +20 -6
  18. package/dist/capabilities/hbcli.js +43 -1
  19. package/dist/capabilities/incident-log.js +1 -1
  20. package/dist/capabilities/model-override.js +18 -0
  21. package/dist/capabilities/opensky.js +1 -1
  22. package/dist/capabilities/resilience.js +90 -0
  23. package/dist/capabilities/session/action-cache.js +1 -1
  24. package/dist/capabilities/session/adapters/ctrip-flight.js +1 -1
  25. package/dist/capabilities/session/adapters/meituan-local.js +1 -1
  26. package/dist/capabilities/session/benchmark.js +2 -1
  27. package/dist/capabilities/session/extension-bridge.js +358 -0
  28. package/dist/capabilities/session/extension-channel.js +93 -0
  29. package/dist/capabilities/session/extension-distribution.js +234 -0
  30. package/dist/capabilities/session/extract.js +1 -1
  31. package/dist/capabilities/session/golden-score.js +92 -0
  32. package/dist/capabilities/session/health-watch.js +216 -0
  33. package/dist/capabilities/session/read-guard.js +1 -1
  34. package/dist/capabilities/session/static-flight-golden.js +137 -0
  35. package/dist/capabilities/session/transport.js +1 -1
  36. package/dist/capabilities/session/wizard.js +147 -0
  37. package/dist/capabilities/session-consent.js +1 -1
  38. package/dist/capabilities/session-login.js +83 -1
  39. package/dist/capabilities/session-search.js +111 -2
  40. package/dist/capabilities/weather.js +168 -46
  41. package/dist/data/session-golden-20.json +25 -0
  42. package/dist/data/sf-golden-manifest.json +102 -0
  43. package/dist/data/sf-static-routes.json +91 -0
  44. package/dist/scripts/action-cache-tests.js +1 -1
  45. package/dist/scripts/agent-planning-budget-e2e.js +227 -0
  46. package/dist/scripts/agent-planning-budget-tests.js +173 -0
  47. package/dist/scripts/agent-reach-deep-tests.js +1 -1
  48. package/dist/scripts/agent-reach-tests.js +1 -1
  49. package/dist/scripts/agent-reach-wrapper-tests.js +1 -1
  50. package/dist/scripts/anything-tests.js +1 -1
  51. package/dist/scripts/async-collect.js +1 -1
  52. package/dist/scripts/benchmark-environment-bridge-e2e.js +981 -0
  53. package/dist/scripts/benchmark-environment-bridge-tests.js +2544 -0
  54. package/dist/scripts/booking-copilot-availability-ledger-binding-tests.js +335 -0
  55. package/dist/scripts/booking-copilot-availability-policy-v2-tests.js +1355 -0
  56. package/dist/scripts/booking-copilot-bin-proof-tests.js +89 -0
  57. package/dist/scripts/booking-copilot-crossrepo-fixture-server.js +113 -0
  58. package/dist/scripts/booking-copilot-dsh-core-proof-tests.js +356 -0
  59. package/dist/scripts/booking-copilot-dsh-planner-proof-tests.js +557 -0
  60. package/dist/scripts/booking-copilot-dsh-plugin-proof-tests.js +140 -0
  61. package/dist/scripts/booking-copilot-event-sequence-concurrency-proof-tests.js +202 -0
  62. package/dist/scripts/booking-copilot-gap-code-contract-proof-tests.js +102 -0
  63. package/dist/scripts/booking-copilot-operation-ledger-concurrency-proof-tests.js +374 -0
  64. package/dist/scripts/booking-copilot-receipt-ledger-concurrency-proof-tests.js +447 -0
  65. package/dist/scripts/booking-copilot-runtime-proof-tests.js +205 -0
  66. package/dist/scripts/booking-copilot-server-proof-tests.js +317 -0
  67. package/dist/scripts/booking-copilot-startup-proof-tests.js +283 -0
  68. package/dist/scripts/booking-copilot-v2-runtime-proof-tests.js +3935 -0
  69. package/dist/scripts/booking-saga-tests.js +1 -1
  70. package/dist/scripts/booking-surface-contract-proof-tests.js +393 -0
  71. package/dist/scripts/booking-surface-v2-contract-proof-tests.js +1174 -0
  72. package/dist/scripts/bootstrap-tests.js +59 -3
  73. package/dist/scripts/build-changelog.js +279 -0
  74. package/dist/scripts/changelog-tests.js +165 -0
  75. package/dist/scripts/companion-tests.js +1 -1
  76. package/dist/scripts/diff-test.js +1 -1
  77. package/dist/scripts/dsh-runtime-closure-tests.js +225 -0
  78. package/dist/scripts/dsh-runtime-closure.js +150 -0
  79. package/dist/scripts/effect-tests.js +474 -0
  80. package/dist/scripts/engine-run.js +1 -1
  81. package/dist/scripts/engine-tests.js +1 -1
  82. package/dist/scripts/evaluation-cadence-tests.js +347 -0
  83. package/dist/scripts/evaluation-contract-tests.js +574 -0
  84. package/dist/scripts/extension-distribution-cli.js +35 -0
  85. package/dist/scripts/extension-distribution-tests.js +341 -0
  86. package/dist/scripts/extension-tests.js +573 -0
  87. package/dist/scripts/fact-gate-tests.js +318 -0
  88. package/dist/scripts/flyai-tests.js +1 -1
  89. package/dist/scripts/hbcli-e2e-tests.js +180 -0
  90. package/dist/scripts/hbcli-tests.js +1 -1
  91. package/dist/scripts/health-watch-cli.js +51 -0
  92. package/dist/scripts/i18n-tests.js +1 -1
  93. package/dist/scripts/incident-tests.js +1 -1
  94. package/dist/scripts/journey-tests.js +1 -1
  95. package/dist/scripts/ledger-tests.js +1 -1
  96. package/dist/scripts/ledger-workflow-crash.js +1 -1
  97. package/dist/scripts/memory-capture-tests.js +1 -1
  98. package/dist/scripts/memory-decay-tests.js +1 -1
  99. package/dist/scripts/memory-metrics.js +1 -1
  100. package/dist/scripts/memory-value-report.js +1 -1
  101. package/dist/scripts/model-override-e2e.js +176 -0
  102. package/dist/scripts/nightly-evidence-tests.js +67 -4
  103. package/dist/scripts/nightly-evidence.js +59 -3
  104. package/dist/scripts/nudge-digest.js +1 -1
  105. package/dist/scripts/onboarding-tests.js +186 -0
  106. package/dist/scripts/opensky-check.js +1 -1
  107. package/dist/scripts/opensky-tests.js +1 -1
  108. package/dist/scripts/pnpm-dsh-closure-proof.js +20 -0
  109. package/dist/scripts/price-drift-tests.js +441 -0
  110. package/dist/scripts/price-drift-watch.js +491 -0
  111. package/dist/scripts/probe-poi-tests.js +1 -1
  112. package/dist/scripts/product-metrics.js +1 -1
  113. package/dist/scripts/publish-preverify.js +40 -4
  114. package/dist/scripts/realtime-pricing-tests.js +1 -1
  115. package/dist/scripts/replay-async.js +1 -1
  116. package/dist/scripts/replay-real.js +1 -1
  117. package/dist/scripts/replay.js +1 -1
  118. package/dist/scripts/session-attach-diagnose.js +1 -1
  119. package/dist/scripts/session-attach-poc.js +1 -1
  120. package/dist/scripts/session-benchmark.js +1 -1
  121. package/dist/scripts/session-extract-tests.js +1 -1
  122. package/dist/scripts/session-login.js +1 -1
  123. package/dist/scripts/session-tests.js +70 -20
  124. package/dist/scripts/sf-live-benchmark.js +338 -0
  125. package/dist/scripts/sf-live-cli-tests.js +21 -0
  126. package/dist/scripts/sf-soft-score-tests.js +108 -0
  127. package/dist/scripts/sf-summary.js +93 -0
  128. package/dist/scripts/skeleton-check.js +1 -1
  129. package/dist/scripts/skeleton-integration-test.js +1 -1
  130. package/dist/scripts/skills-contract-tests.js +1 -1
  131. package/dist/scripts/smoke-session-gate-tests.js +29 -0
  132. package/dist/scripts/smoke.js +154 -33
  133. package/dist/scripts/state-cli-tests.js +1 -1
  134. package/dist/scripts/state-cli.js +1 -1
  135. package/dist/scripts/static-golden-tests.js +299 -0
  136. package/dist/scripts/time-eval-tests.js +1 -1
  137. package/dist/scripts/travel-timeline-tests.js +1 -1
  138. package/dist/scripts/unified-tests.js +1 -1
  139. package/dist/scripts/weather-tests.js +694 -44
  140. package/dist/scripts/z3-race-tests.js +1 -1
  141. package/dist/src/artifact-gate.js +310 -0
  142. package/dist/src/benchmark-agent-conformance.js +370 -0
  143. package/dist/src/benchmark-environment-bridge.js +384 -0
  144. package/dist/src/benchmark-headless-child-diagnostics.js +173 -0
  145. package/dist/src/benchmark-tool-isolation.js +124 -0
  146. package/dist/src/bookable-facts.js +349 -0
  147. package/dist/src/booking-saga.js +1 -1
  148. package/dist/src/booking-surface/availability-policy-v2.js +830 -0
  149. package/dist/src/booking-surface/canonical-schema.js +113 -0
  150. package/dist/src/booking-surface/contracts-v2.js +89 -0
  151. package/dist/src/booking-surface/contracts.js +46 -0
  152. package/dist/src/booking-surface/dsh-planner.js +453 -0
  153. package/dist/src/booking-surface/dsh-plugin.js +93 -0
  154. package/dist/src/booking-surface/error-codes.js +94 -0
  155. package/dist/src/booking-surface/index.js +15 -0
  156. package/dist/src/booking-surface/profile.js +68 -0
  157. package/dist/src/booking-surface/runtime-v2.js +1771 -0
  158. package/dist/src/booking-surface/runtime.js +351 -0
  159. package/dist/src/booking-surface/server-v2.js +334 -0
  160. package/dist/src/booking-surface/server.js +302 -0
  161. package/dist/src/booking-surface/startup.js +159 -0
  162. package/dist/src/booking-surface/validation-v2.js +319 -0
  163. package/dist/src/booking-surface/validation.js +809 -0
  164. package/dist/src/bridge.js +1 -1
  165. package/dist/src/companions.js +1 -1
  166. package/dist/src/contracts.js +1 -1
  167. package/dist/src/dsh-llm.js +1 -1
  168. package/dist/src/engine.js +1 -1
  169. package/dist/src/evaluation-cadence.js +234 -0
  170. package/dist/src/evaluation-contracts.js +906 -0
  171. package/dist/src/i18n.js +1 -1
  172. package/dist/src/index.js +234 -60
  173. package/dist/src/journey.js +1 -1
  174. package/dist/src/loop.js +1 -1
  175. package/dist/src/memory-capture.js +1 -1
  176. package/dist/src/memory-decay.js +1 -1
  177. package/dist/src/memory-utility.js +1 -1
  178. package/dist/src/mock-llm.js +1 -1
  179. package/dist/src/model.js +1 -1
  180. package/dist/src/realtime-pricing.js +14 -7
  181. package/dist/src/slot-spec.js +1 -1
  182. package/dist/src/state-ledger.js +2 -1
  183. package/dist/src/time-anchor.js +1 -1
  184. package/dist/src/tool-budget.js +136 -0
  185. package/dist/src/tool-packet.js +1 -1
  186. package/dist/src/travel-slots.js +1 -1
  187. package/dist/src/travel-timeline.js +1 -1
  188. package/dist/src/unified.js +1 -1
  189. package/dist/src/wish-pool.js +1 -1
  190. package/dist/src/z3-shared.js +1 -1
  191. package/extension/README.md +50 -0
  192. package/extension/background.js +160 -0
  193. package/extension/content-bridge.js +41 -0
  194. package/extension/content-main.js +62 -0
  195. package/extension/manifest.json +32 -0
  196. package/package.json +291 -12
  197. package/schemas/booking.surface.v1.schema.json +927 -0
  198. package/schemas/booking.surface.v2.schema.json +61 -0
  199. package/ts/capabilities/fact-log.ts +54 -0
  200. package/ts/capabilities/flyai.ts +16 -3
  201. package/ts/capabilities/hbcli.ts +46 -0
  202. package/ts/capabilities/session/benchmark.ts +4 -1
  203. package/ts/capabilities/session/extension-bridge.ts +377 -0
  204. package/ts/capabilities/session/extension-channel.ts +95 -0
  205. package/ts/capabilities/session/extension-distribution.ts +264 -0
  206. package/ts/capabilities/session/golden-score.ts +139 -0
  207. package/ts/capabilities/session/health-watch.ts +257 -0
  208. package/ts/capabilities/session/static-flight-golden.ts +209 -0
  209. package/ts/capabilities/session/wizard.ts +140 -0
  210. package/ts/capabilities/session-login.ts +63 -2
  211. package/ts/capabilities/session-search.ts +105 -3
  212. package/ts/capabilities/weather.ts +141 -52
  213. package/ts/package.json +3 -3
  214. package/ts/src/artifact-gate.ts +365 -0
  215. package/ts/src/benchmark-agent-conformance.ts +448 -0
  216. package/ts/src/benchmark-environment-bridge.ts +348 -0
  217. package/ts/src/benchmark-headless-child-diagnostics.ts +184 -0
  218. package/ts/src/benchmark-tool-isolation.ts +166 -0
  219. package/ts/src/bookable-facts.ts +557 -0
  220. package/ts/src/booking-surface/availability-policy-v2.ts +523 -0
  221. package/ts/src/booking-surface/canonical-schema.js +113 -0
  222. package/ts/src/booking-surface/contracts-v2.ts +118 -0
  223. package/ts/src/booking-surface/contracts.ts +380 -0
  224. package/ts/src/booking-surface/dsh-planner.ts +452 -0
  225. package/ts/src/booking-surface/dsh-plugin.js +93 -0
  226. package/ts/src/booking-surface/error-codes.ts +101 -0
  227. package/ts/src/booking-surface/index.ts +12 -0
  228. package/ts/src/booking-surface/profile.ts +42 -0
  229. package/ts/src/booking-surface/runtime-v2.ts +1466 -0
  230. package/ts/src/booking-surface/runtime.ts +483 -0
  231. package/ts/src/booking-surface/server-v2.ts +247 -0
  232. package/ts/src/booking-surface/server.ts +324 -0
  233. package/ts/src/booking-surface/startup.ts +196 -0
  234. package/ts/src/booking-surface/validation-v2.ts +205 -0
  235. package/ts/src/booking-surface/validation.ts +453 -0
  236. package/ts/src/index.ts +161 -14
  237. package/ts/src/state-ledger.ts +1 -0
  238. package/ts/src/tool-budget.ts +165 -0
@@ -0,0 +1,302 @@
1
+ import { timingSafeEqual } from 'node:crypto';
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 { validateBookingCopilotTurnV1 } from './validation.js';
6
+ import { handleBookingCopilotV2Request } from './server-v2.js';
7
+ import { normalizeBookingErrorCode, safeBookingErrorMessage } from './error-codes.js';
8
+ import { BOOKING_COPILOT_V2_ACCEPTED_TURN_KINDS, BOOKING_COPILOT_V2_INGRESS_TURN_KIND, BOOKING_COPILOT_V2_INGRESS_MODES, BOOKING_SURFACE_SCHEMA_V2_SHA256, BOOKING_SURFACE_SCHEMA_VERSION_V2 } from './contracts-v2.js';
9
+ function runtimeIdentity() {
10
+ const report = process.report?.getReport?.();
11
+ const header = report?.header;
12
+ const glibcVersion = header?.glibcVersionRuntime ?? '';
13
+ return {
14
+ nodeVersion: process.version,
15
+ nodeModulesAbi: process.versions.modules ?? '',
16
+ releaseTuple: `${process.platform}-${process.arch}-${glibcVersion ? 'glibc' : 'unknown'}`,
17
+ glibcVersion
18
+ };
19
+ }
20
+ function safeSecretEqual(actual, expected) {
21
+ const actualBytes = Buffer.from(actual);
22
+ const expectedBytes = Buffer.from(expected);
23
+ return actualBytes.length === expectedBytes.length && timingSafeEqual(actualBytes, expectedBytes);
24
+ }
25
+ function sendJson(res, status, body) {
26
+ res.writeHead(status, {
27
+ 'content-type': 'application/json; charset=utf-8'
28
+ });
29
+ res.end(JSON.stringify(body));
30
+ }
31
+ function readBody(req, maxBodyBytes) {
32
+ return new Promise((resolve, reject)=>{
33
+ const chunks = [];
34
+ let size = 0;
35
+ let exceeded = false;
36
+ req.on('data', (chunk)=>{
37
+ size += chunk.length;
38
+ if (size > maxBodyBytes) {
39
+ exceeded = true;
40
+ return;
41
+ }
42
+ chunks.push(chunk);
43
+ });
44
+ req.on('end', ()=>exceeded ? reject(new Error('payload_too_large')) : resolve(Buffer.concat(chunks).toString('utf8')));
45
+ req.on('error', reject);
46
+ });
47
+ }
48
+ function writeSse(res, event) {
49
+ res.write(`event: ${event.kind}\ndata: ${JSON.stringify(event)}\n\n`);
50
+ }
51
+ export function startBookingCopilotServer(options) {
52
+ if (!options.apiKey) return Promise.reject(new Error('booking_copilot_api_key_required'));
53
+ if (options.artifactId !== undefined && !/^[0-9a-f]{40}$/.test(options.artifactId)) {
54
+ return Promise.reject(new Error('booking_copilot_artifact_id_invalid'));
55
+ }
56
+ const hasIngressBinding = Boolean(options.v2?.ingressBinding);
57
+ const hasPrincipal = Boolean(options.v2?.principal);
58
+ if (options.v2 && hasIngressBinding !== hasPrincipal) {
59
+ return Promise.reject(new Error('booking_copilot_v2_ingress_binding_pair_required'));
60
+ }
61
+ if (options.v2 && hasIngressBinding && (typeof options.v2.ingressBinding?.bind !== 'function' || typeof options.v2.principal?.subject !== 'string' || options.v2.principal.subject.length === 0 || options.v2.principal.subject.length > 256 || typeof options.v2.principal?.scope !== 'string' || options.v2.principal.scope.length === 0 || options.v2.principal.scope.length > 256)) {
62
+ return Promise.reject(new Error('booking_copilot_v2_ingress_binding_invalid'));
63
+ }
64
+ const ingressMode = options.v2 ? options.v2.ingressMode ?? (hasIngressBinding ? 'bff-ingress-binding' : 'bff-bound-turn-only') : undefined;
65
+ if (options.v2 && !BOOKING_COPILOT_V2_INGRESS_MODES.includes(ingressMode)) {
66
+ return Promise.reject(new Error('booking_copilot_v2_ingress_mode_invalid'));
67
+ }
68
+ if (options.v2 && ingressMode === 'bff-ingress-binding' && !hasIngressBinding) {
69
+ return Promise.reject(new Error('booking_copilot_v2_ingress_binding_required'));
70
+ }
71
+ if (options.v2 && ingressMode === 'bff-bound-turn-only' && hasIngressBinding) {
72
+ return Promise.reject(new Error('booking_copilot_v2_ingress_mode_conflict'));
73
+ }
74
+ const v2 = options.v2 ? {
75
+ ...options.v2,
76
+ ingressMode
77
+ } : undefined;
78
+ const v2Only = Boolean(v2 && !options.runtime && !options.plannerFactory);
79
+ const sessions = new Map();
80
+ const maxBodyBytes = options.maxBodyBytes ?? 1_000_000;
81
+ const runningIdentity = runtimeIdentity();
82
+ const server = createServer(async (req, res)=>{
83
+ const isProbe = req.method === 'GET' && (req.url === '/healthz' || req.url === '/status');
84
+ if (!isProbe && (req.method !== 'POST' || req.url !== '/a2a/booking-copilot/turn')) {
85
+ sendJson(res, 404, {
86
+ error: {
87
+ code: 'not_found'
88
+ }
89
+ });
90
+ return;
91
+ }
92
+ const auth = String(req.headers.authorization ?? '');
93
+ if (!safeSecretEqual(auth, `Bearer ${options.apiKey}`)) {
94
+ sendJson(res, 401, {
95
+ error: {
96
+ code: 'unauthorized'
97
+ }
98
+ });
99
+ return;
100
+ }
101
+ if (isProbe) {
102
+ const activeV2Only = Boolean(v2 && !options.runtime && !options.plannerFactory);
103
+ res.setHeader(BOOKING_SURFACE_VERSION_HEADER, activeV2Only ? BOOKING_SURFACE_SCHEMA_VERSION_V2 : BOOKING_SURFACE_SCHEMA_VERSION);
104
+ res.setHeader(BOOKING_SURFACE_SCHEMA_SHA256_HEADER, activeV2Only ? BOOKING_SURFACE_SCHEMA_V2_SHA256 : BOOKING_SURFACE_SCHEMA_SHA256);
105
+ if (options.artifactId) res.setHeader('X-GoTry-Artifact-ID', options.artifactId);
106
+ res.setHeader('X-GoTry-Node-Version', runningIdentity.nodeVersion);
107
+ res.setHeader('X-GoTry-Node-Modules-ABI', runningIdentity.nodeModulesAbi);
108
+ res.setHeader('X-GoTry-Release-Tuple', runningIdentity.releaseTuple);
109
+ if (runningIdentity.glibcVersion) res.setHeader('X-GoTry-Glibc-Version', runningIdentity.glibcVersion);
110
+ const healthBody = {
111
+ schemaVersion: activeV2Only ? BOOKING_SURFACE_SCHEMA_VERSION_V2 : BOOKING_SURFACE_SCHEMA_VERSION,
112
+ schemaSha256: activeV2Only ? BOOKING_SURFACE_SCHEMA_V2_SHA256 : BOOKING_SURFACE_SCHEMA_SHA256,
113
+ status: 'ready'
114
+ };
115
+ if (activeV2Only || v2) healthBody.supportedSchemaVersions = activeV2Only ? [
116
+ BOOKING_SURFACE_SCHEMA_VERSION_V2
117
+ ] : [
118
+ BOOKING_SURFACE_SCHEMA_VERSION,
119
+ BOOKING_SURFACE_SCHEMA_VERSION_V2
120
+ ];
121
+ if (v2) {
122
+ const acceptedTurnKinds = v2.ingressMode === 'bff-ingress-binding' ? [
123
+ ...BOOKING_COPILOT_V2_ACCEPTED_TURN_KINDS,
124
+ BOOKING_COPILOT_V2_INGRESS_TURN_KIND
125
+ ] : [
126
+ ...BOOKING_COPILOT_V2_ACCEPTED_TURN_KINDS
127
+ ];
128
+ healthBody.ingressMode = v2.ingressMode;
129
+ healthBody.acceptedTurnKinds = acceptedTurnKinds;
130
+ res.setHeader('X-GoTry-Ingress-Mode', v2.ingressMode);
131
+ res.setHeader('X-GoTry-Accepted-Turn-Kinds', acceptedTurnKinds.join(','));
132
+ }
133
+ sendJson(res, 200, healthBody);
134
+ return;
135
+ }
136
+ const schemaVersion = String(req.headers[BOOKING_SURFACE_VERSION_HEADER] ?? '');
137
+ 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
+ if (schemaVersion !== BOOKING_SURFACE_SCHEMA_VERSION || schemaHash !== BOOKING_SURFACE_SCHEMA_SHA256) {
153
+ sendJson(res, 409, {
154
+ error: {
155
+ code: 'booking_surface_schema_mismatch',
156
+ expectedVersion: BOOKING_SURFACE_SCHEMA_VERSION,
157
+ expectedSchemaSha256: BOOKING_SURFACE_SCHEMA_SHA256
158
+ }
159
+ });
160
+ return;
161
+ }
162
+ if (!options.runtime || !options.plannerFactory) {
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
+ }
285
+ });
286
+ return new Promise((resolve, reject)=>{
287
+ server.once('error', reject);
288
+ server.listen(options.port ?? 0, options.host ?? '127.0.0.1', ()=>{
289
+ server.off('error', reject);
290
+ const address = server.address();
291
+ const port = typeof address === 'object' && address ? address.port : options.port ?? 0;
292
+ resolve({
293
+ server,
294
+ port,
295
+ close: ()=>new Promise((done, fail)=>server.close((error)=>error ? fail(error) : done()))
296
+ });
297
+ });
298
+ });
299
+ }
300
+
301
+
302
+ //# sourceURL=ts/src/booking-surface/server.ts
@@ -0,0 +1,159 @@
1
+ import { mkdirSync } from 'node:fs';
2
+ import { isAbsolute, resolve } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { ensureLedger } from '../state-ledger.js';
5
+ import { buildDshPlannerEnvironment, createDshEmbeddedBookingPlanner, createDshEmbeddedBookingPlannerV2 } from './dsh-planner.js';
6
+ import { BookingCopilotTaskRuntime } from './runtime.js';
7
+ import { BookingCopilotTaskRuntimeV2 } from './runtime-v2.js';
8
+ import { BOOKING_COPILOT_V2_INGRESS_MODES } from './contracts-v2.js';
9
+ import { startBookingCopilotServer } from './server.js';
10
+ const DEFAULT_DEPENDENCIES = {
11
+ ensureLedger,
12
+ runtimeFactory: (ledger)=>new BookingCopilotTaskRuntime(ledger),
13
+ createPlanner: createDshEmbeddedBookingPlanner,
14
+ runtimeFactoryV2: (ledger)=>new BookingCopilotTaskRuntimeV2(ledger),
15
+ createPlannerV2: createDshEmbeddedBookingPlannerV2,
16
+ startServer: startBookingCopilotServer
17
+ };
18
+ function parsePort(raw) {
19
+ if (raw === undefined || raw === '') return 3_082;
20
+ const value = Number(raw);
21
+ if (!Number.isSafeInteger(value) || value < 0 || value > 65_535) throw new Error('booking_copilot_invalid_port');
22
+ return value;
23
+ }
24
+ export function resolveBookingCopilotStartupConfig(env = process.env) {
25
+ const apiKey = env.GOTRY_BOOKING_COPILOT_API_KEY ?? '';
26
+ if (!apiKey) throw new Error('booking_copilot_api_key_required');
27
+ const stateRoot = env.GOTRY_BOOKING_COPILOT_STATE_ROOT ?? '';
28
+ if (!stateRoot) throw new Error('booking_copilot_state_root_required');
29
+ if (!isAbsolute(stateRoot)) throw new Error('booking_copilot_state_root_must_be_absolute');
30
+ const artifactId = env.GOTRY_BOOKING_COPILOT_ARTIFACT_ID || undefined;
31
+ if (artifactId !== undefined && !/^[0-9a-f]{40}$/.test(artifactId)) {
32
+ throw new Error('booking_copilot_artifact_id_invalid');
33
+ }
34
+ const rawIngressMode = env.GOTRY_BOOKING_COPILOT_V2_INGRESS_MODE;
35
+ const ingressMode = rawIngressMode === undefined || rawIngressMode === '' ? 'bff-bound-turn-only' : rawIngressMode;
36
+ if (!BOOKING_COPILOT_V2_INGRESS_MODES.includes(ingressMode)) {
37
+ throw new Error('booking_copilot_v2_ingress_mode_invalid');
38
+ }
39
+ return {
40
+ apiKey,
41
+ stateRoot,
42
+ host: env.GOTRY_BOOKING_COPILOT_HOST || '127.0.0.1',
43
+ port: parsePort(env.GOTRY_BOOKING_COPILOT_PORT),
44
+ artifactId,
45
+ ingressMode: ingressMode
46
+ };
47
+ }
48
+ async function closeAll(server, planner, plannerV2, ledger) {
49
+ const failures = [];
50
+ for (const close of [
51
+ server ? ()=>server.close() : undefined,
52
+ planner ? ()=>planner.close() : undefined,
53
+ plannerV2 ? ()=>plannerV2.close() : undefined,
54
+ ledger ? ()=>Promise.resolve(ledger.close()) : undefined
55
+ ]){
56
+ if (!close) continue;
57
+ try {
58
+ await close();
59
+ } catch (error) {
60
+ failures.push(error);
61
+ }
62
+ }
63
+ if (failures.length === 1) throw failures[0];
64
+ if (failures.length > 1) throw new AggregateError(failures, 'booking_copilot_shutdown_failed');
65
+ }
66
+ export async function startBookingCopilotFromEnvironment(env = process.env, dependencies = DEFAULT_DEPENDENCIES) {
67
+ const config = resolveBookingCopilotStartupConfig(env);
68
+ const activeDependencies = dependencies;
69
+ const hasIngressBinding = Boolean(activeDependencies.ingressBinding);
70
+ const hasPrincipal = Boolean(activeDependencies.principal);
71
+ if (hasIngressBinding !== hasPrincipal) throw new Error('booking_copilot_v2_ingress_binding_pair_required');
72
+ const ingressPair = hasIngressBinding && hasPrincipal;
73
+ if (ingressPair && env.GOTRY_BOOKING_COPILOT_V2_INGRESS_MODE !== undefined && config.ingressMode === 'bff-bound-turn-only') {
74
+ throw new Error('booking_copilot_v2_ingress_mode_conflict');
75
+ }
76
+ if (!ingressPair && config.ingressMode === 'bff-ingress-binding') throw new Error('booking_copilot_v2_ingress_binding_required');
77
+ if (ingressPair && (typeof activeDependencies.ingressBinding?.bind !== 'function' || typeof activeDependencies.principal?.subject !== 'string' || activeDependencies.principal.subject.length === 0 || activeDependencies.principal.subject.length > 256 || typeof activeDependencies.principal?.scope !== 'string' || activeDependencies.principal.scope.length === 0 || activeDependencies.principal.scope.length > 256)) {
78
+ throw new Error('booking_copilot_v2_ingress_binding_invalid');
79
+ }
80
+ const activeIngressMode = ingressPair ? 'bff-ingress-binding' : 'bff-bound-turn-only';
81
+ mkdirSync(config.stateRoot, {
82
+ recursive: true
83
+ });
84
+ let ledger;
85
+ let planner;
86
+ let plannerV2;
87
+ let server;
88
+ try {
89
+ ledger = dependencies.ensureLedger(config.stateRoot);
90
+ const plannerOptions = {
91
+ stateRoot: config.stateRoot,
92
+ env: buildDshPlannerEnvironment(env)
93
+ };
94
+ if (!activeDependencies.runtimeFactoryV2 || !activeDependencies.createPlannerV2) throw new Error('booking_copilot_v2_startup_dependency_missing');
95
+ const runtime = activeDependencies.runtimeFactory(ledger);
96
+ planner = await activeDependencies.createPlanner(plannerOptions);
97
+ const runtimeV2 = activeDependencies.runtimeFactoryV2(ledger);
98
+ plannerV2 = await activeDependencies.createPlannerV2(plannerOptions);
99
+ server = await activeDependencies.startServer({
100
+ apiKey: config.apiKey,
101
+ runtime,
102
+ plannerFactory: planner.plannerFactory,
103
+ v2: {
104
+ runtime: runtimeV2,
105
+ plannerFactory: plannerV2.plannerFactory,
106
+ ingressBinding: activeDependencies.ingressBinding,
107
+ principal: activeDependencies.principal,
108
+ ingressMode: activeIngressMode
109
+ },
110
+ host: config.host,
111
+ port: config.port,
112
+ artifactId: config.artifactId
113
+ });
114
+ } catch (error) {
115
+ try {
116
+ await closeAll(server, planner, plannerV2, ledger);
117
+ } catch (cleanupError) {
118
+ throw new AggregateError([
119
+ error,
120
+ cleanupError
121
+ ], 'booking_copilot_startup_and_cleanup_failed');
122
+ }
123
+ throw error;
124
+ }
125
+ let closed = false;
126
+ return {
127
+ port: server.port,
128
+ async close () {
129
+ if (closed) return;
130
+ closed = true;
131
+ await closeAll(server, planner, plannerV2, ledger);
132
+ }
133
+ };
134
+ }
135
+ async function runCli() {
136
+ const handle = await startBookingCopilotFromEnvironment();
137
+ process.stderr.write(`[gotry-booking-copilot] listening on http://127.0.0.1:${handle.port}/a2a/booking-copilot/turn\n`);
138
+ let stopping = false;
139
+ const stop = ()=>{
140
+ if (stopping) return;
141
+ stopping = true;
142
+ void handle.close().then(()=>process.exit(0), (error)=>{
143
+ process.stderr.write(`[gotry-booking-copilot] shutdown failed: ${error instanceof Error ? error.message : String(error)}\n`);
144
+ process.exit(1);
145
+ });
146
+ };
147
+ process.once('SIGINT', stop);
148
+ process.once('SIGTERM', stop);
149
+ }
150
+ const invokedPath = process.argv[1] ? resolve(process.argv[1]) : '';
151
+ if (invokedPath === fileURLToPath(import.meta.url)) {
152
+ runCli().catch((error)=>{
153
+ process.stderr.write(`[gotry-booking-copilot] startup failed: ${error instanceof Error ? error.message : String(error)}\n`);
154
+ process.exit(1);
155
+ });
156
+ }
157
+
158
+
159
+ //# sourceURL=ts/src/booking-surface/startup.ts