imprint-mcp 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +168 -0
- package/LICENSE +21 -0
- package/README.md +322 -0
- package/examples/discoverandgo/README.md +57 -0
- package/examples/discoverandgo/book_discoverandgo_museum_pass/cron.json +8 -0
- package/examples/discoverandgo/book_discoverandgo_museum_pass/index.ts +89 -0
- package/examples/discoverandgo/book_discoverandgo_museum_pass/workflow.json +39 -0
- package/examples/echo/README.md +37 -0
- package/examples/echo/echo_test/index.ts +31 -0
- package/examples/google-flights/search_google_flights/index.ts +101 -0
- package/examples/google-flights/search_google_flights/parser.test.ts +140 -0
- package/examples/google-flights/search_google_flights/parser.ts +189 -0
- package/examples/google-flights/search_google_flights/playbook.yaml +130 -0
- package/examples/google-flights/search_google_flights/workflow.json +48 -0
- package/examples/google-hotels/search_google_hotels/index.ts +194 -0
- package/examples/google-hotels/search_google_hotels/parser.test.ts +168 -0
- package/examples/google-hotels/search_google_hotels/parser.ts +330 -0
- package/examples/google-hotels/search_google_hotels/playbook.yaml +125 -0
- package/examples/google-hotels/search_google_hotels/workflow.json +111 -0
- package/examples/namecheap-domains/search_namecheap_domains/index.ts +144 -0
- package/examples/namecheap-domains/search_namecheap_domains/parser.ts +380 -0
- package/examples/namecheap-domains/search_namecheap_domains/playbook.yaml +50 -0
- package/examples/namecheap-domains/search_namecheap_domains/request-transform.ts +136 -0
- package/examples/namecheap-domains/search_namecheap_domains/workflow.json +97 -0
- package/examples/southwest/README.md +81 -0
- package/examples/southwest/search_southwest_flights/backends.json +23 -0
- package/examples/southwest/search_southwest_flights/cron.json +19 -0
- package/examples/southwest/search_southwest_flights/index.ts +110 -0
- package/examples/southwest/search_southwest_flights/playbook.yaml +46 -0
- package/examples/southwest/search_southwest_flights/workflow.json +54 -0
- package/package.json +78 -0
- package/prompts/compile-agent.md +580 -0
- package/prompts/intent-detection.md +198 -0
- package/prompts/playbook-compilation.md +279 -0
- package/prompts/request-triage.md +74 -0
- package/prompts/tool-candidate-detection.md +104 -0
- package/src/cli.ts +1287 -0
- package/src/imprint/agent.ts +468 -0
- package/src/imprint/app-api-hosts.ts +53 -0
- package/src/imprint/backend-ladder.ts +568 -0
- package/src/imprint/check.ts +136 -0
- package/src/imprint/chromium.ts +211 -0
- package/src/imprint/claude-cli-compile.ts +640 -0
- package/src/imprint/cli-credential.ts +394 -0
- package/src/imprint/codex-cli-compile.ts +712 -0
- package/src/imprint/compile-agent-types.ts +40 -0
- package/src/imprint/compile-agent.ts +404 -0
- package/src/imprint/compile-tools.ts +1389 -0
- package/src/imprint/compile.ts +720 -0
- package/src/imprint/cookie-jar.ts +246 -0
- package/src/imprint/credential-bundle.ts +195 -0
- package/src/imprint/credential-extract.ts +290 -0
- package/src/imprint/credential-store.ts +707 -0
- package/src/imprint/cron.ts +312 -0
- package/src/imprint/doctor.ts +223 -0
- package/src/imprint/emit.ts +154 -0
- package/src/imprint/etld.ts +134 -0
- package/src/imprint/freeform-redact.ts +216 -0
- package/src/imprint/inject-listener.ts +137 -0
- package/src/imprint/install.ts +795 -0
- package/src/imprint/integrations.ts +385 -0
- package/src/imprint/is-compiled.ts +2 -0
- package/src/imprint/json-path.ts +100 -0
- package/src/imprint/llm.ts +998 -0
- package/src/imprint/load-json.ts +54 -0
- package/src/imprint/log.ts +33 -0
- package/src/imprint/login.ts +166 -0
- package/src/imprint/mcp-compile-server.ts +282 -0
- package/src/imprint/mcp-maintenance.ts +1790 -0
- package/src/imprint/mcp-server.ts +350 -0
- package/src/imprint/multi-progress.ts +69 -0
- package/src/imprint/notify.ts +155 -0
- package/src/imprint/paths.ts +64 -0
- package/src/imprint/playbook-parser.ts +21 -0
- package/src/imprint/playbook-runner.ts +465 -0
- package/src/imprint/probe-backends.ts +251 -0
- package/src/imprint/progress.ts +28 -0
- package/src/imprint/record.ts +470 -0
- package/src/imprint/redact.ts +550 -0
- package/src/imprint/replay-capture.ts +387 -0
- package/src/imprint/request-context.ts +66 -0
- package/src/imprint/runtime-link.ts +73 -0
- package/src/imprint/runtime.ts +942 -0
- package/src/imprint/sensitive-keys.ts +156 -0
- package/src/imprint/session-diff.ts +409 -0
- package/src/imprint/session-merge.ts +198 -0
- package/src/imprint/session-writer.ts +149 -0
- package/src/imprint/sites.ts +27 -0
- package/src/imprint/stealth-fetch.ts +434 -0
- package/src/imprint/teach-state.ts +235 -0
- package/src/imprint/teach.ts +2120 -0
- package/src/imprint/tool-candidates.ts +423 -0
- package/src/imprint/tool-loader.ts +186 -0
- package/src/imprint/tool-selection.ts +70 -0
- package/src/imprint/tracing.ts +508 -0
- package/src/imprint/types.ts +472 -0
- package/src/imprint/version.ts +21 -0
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
toolName: search_google_flights
|
|
2
|
+
summary: Search Google Flights for round-trip flights between two airports on specific dates.
|
|
3
|
+
parameters:
|
|
4
|
+
- name: origin_airport
|
|
5
|
+
type: string
|
|
6
|
+
description: Origin airport or city code, e.g. SJC
|
|
7
|
+
- name: destination_airport
|
|
8
|
+
type: string
|
|
9
|
+
description: Destination airport or city code, e.g. SAN
|
|
10
|
+
- name: depart_date
|
|
11
|
+
type: string
|
|
12
|
+
description: Departure date in YYYY-MM-DD format
|
|
13
|
+
- name: return_date
|
|
14
|
+
type: string
|
|
15
|
+
description: Return date in YYYY-MM-DD format
|
|
16
|
+
steps:
|
|
17
|
+
- action: navigate
|
|
18
|
+
url: https://www.google.com/travel/flights
|
|
19
|
+
wait_for: networkidle
|
|
20
|
+
- action: type
|
|
21
|
+
locators:
|
|
22
|
+
- by: role
|
|
23
|
+
value: textbox
|
|
24
|
+
name: Where from?
|
|
25
|
+
- by: aria_label
|
|
26
|
+
value: Where from?
|
|
27
|
+
- by: css
|
|
28
|
+
value: input[aria-label="Where from?"]
|
|
29
|
+
value: ${origin_airport}
|
|
30
|
+
wait_for:
|
|
31
|
+
sleep_ms: 500
|
|
32
|
+
- action: click
|
|
33
|
+
locators:
|
|
34
|
+
- by: role
|
|
35
|
+
value: option
|
|
36
|
+
name: ${origin_airport}
|
|
37
|
+
- by: aria_label
|
|
38
|
+
value_pattern: ${origin_airport}
|
|
39
|
+
- by: text
|
|
40
|
+
value_pattern: ${origin_airport}
|
|
41
|
+
- by: css
|
|
42
|
+
value: ul li
|
|
43
|
+
wait_for: visible
|
|
44
|
+
- action: type
|
|
45
|
+
locators:
|
|
46
|
+
- by: role
|
|
47
|
+
value: textbox
|
|
48
|
+
name: Where to?
|
|
49
|
+
- by: aria_label
|
|
50
|
+
value: "Where to? "
|
|
51
|
+
- by: css
|
|
52
|
+
value: input[aria-label="Where to? "]
|
|
53
|
+
value: ${destination_airport}
|
|
54
|
+
wait_for:
|
|
55
|
+
sleep_ms: 500
|
|
56
|
+
- action: click
|
|
57
|
+
locators:
|
|
58
|
+
- by: role
|
|
59
|
+
value: option
|
|
60
|
+
name: ${destination_airport}
|
|
61
|
+
- by: aria_label
|
|
62
|
+
value_pattern: ${destination_airport}
|
|
63
|
+
- by: text
|
|
64
|
+
value_pattern: ${destination_airport}
|
|
65
|
+
- by: css
|
|
66
|
+
value: ul li
|
|
67
|
+
wait_for: visible
|
|
68
|
+
- action: type
|
|
69
|
+
locators:
|
|
70
|
+
- by: role
|
|
71
|
+
value: textbox
|
|
72
|
+
name: Departure
|
|
73
|
+
- by: aria_label
|
|
74
|
+
value: Departure
|
|
75
|
+
- by: css
|
|
76
|
+
value: input[aria-label="Departure"]
|
|
77
|
+
value: ${depart_date}
|
|
78
|
+
wait_for:
|
|
79
|
+
sleep_ms: 300
|
|
80
|
+
- action: type
|
|
81
|
+
locators:
|
|
82
|
+
- by: role
|
|
83
|
+
value: textbox
|
|
84
|
+
name: Return
|
|
85
|
+
- by: aria_label
|
|
86
|
+
value: Return
|
|
87
|
+
- by: css
|
|
88
|
+
value: input[aria-label="Return"]
|
|
89
|
+
value: ${return_date}
|
|
90
|
+
wait_for:
|
|
91
|
+
sleep_ms: 300
|
|
92
|
+
- action: click
|
|
93
|
+
locators:
|
|
94
|
+
- by: role
|
|
95
|
+
value: button
|
|
96
|
+
name: Search
|
|
97
|
+
- by: aria_label
|
|
98
|
+
value_pattern: Search
|
|
99
|
+
- by: css
|
|
100
|
+
value: button
|
|
101
|
+
wait_for:
|
|
102
|
+
xhr: /FlightsFrontendService/GetShoppingResults
|
|
103
|
+
- action: click
|
|
104
|
+
locators:
|
|
105
|
+
- by: aria_label
|
|
106
|
+
value: Nonstop flight.
|
|
107
|
+
- by: text
|
|
108
|
+
value: Nonstop
|
|
109
|
+
- by: css
|
|
110
|
+
value: div.OgQvJf.nKlB3b
|
|
111
|
+
wait_for:
|
|
112
|
+
xhr: /FlightsFrontendService/GetShoppingResults
|
|
113
|
+
- action: click
|
|
114
|
+
locators:
|
|
115
|
+
- by: aria_label
|
|
116
|
+
value: Nonstop flight.
|
|
117
|
+
- by: text
|
|
118
|
+
value: Nonstop
|
|
119
|
+
- by: css
|
|
120
|
+
value: div.OgQvJf.nKlB3b
|
|
121
|
+
wait_for:
|
|
122
|
+
xhr: /FlightsFrontendService/GetBookingResults
|
|
123
|
+
result:
|
|
124
|
+
source: dom
|
|
125
|
+
locators:
|
|
126
|
+
- by: css
|
|
127
|
+
value: a[href*="/travel/clk/f"]
|
|
128
|
+
extract: href
|
|
129
|
+
return_as: booking_links
|
|
130
|
+
notes: Assumes Google Flights opens in round-trip Economy (include Basic) by default and selects the first visible outbound and return flight cards, which in the captured session were nonstop Southwest options.
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"toolName": "search_google_flights",
|
|
3
|
+
"intent": {
|
|
4
|
+
"description": "Search Google Flights for round-trip flights between two airports on specific dates.",
|
|
5
|
+
"userSaid": "i searched for a sample flight the default mode is economy (include basic) now i changed to economy (exclude basic) now i searched for premium economy now i searched for business now i searched for first class now im going to start messing with all the filters available i played with all the filters now i clicked one of the departure flights, and now it's showing me a list of the return flights i selected the return flight, and now it's showing me the booking options"
|
|
6
|
+
},
|
|
7
|
+
"parameters": [
|
|
8
|
+
{
|
|
9
|
+
"name": "origin_airport",
|
|
10
|
+
"type": "string",
|
|
11
|
+
"description": "Origin airport IATA code.",
|
|
12
|
+
"default": "SJC"
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"name": "destination_airport",
|
|
16
|
+
"type": "string",
|
|
17
|
+
"description": "Destination airport IATA code.",
|
|
18
|
+
"default": "SAN"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"name": "depart_date",
|
|
22
|
+
"type": "string",
|
|
23
|
+
"description": "Outbound departure date in YYYY-MM-DD format.",
|
|
24
|
+
"default": "2026-07-10"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"name": "return_date",
|
|
28
|
+
"type": "string",
|
|
29
|
+
"description": "Return departure date in YYYY-MM-DD format.",
|
|
30
|
+
"default": "2026-07-16"
|
|
31
|
+
}
|
|
32
|
+
],
|
|
33
|
+
"requests": [
|
|
34
|
+
{
|
|
35
|
+
"method": "POST",
|
|
36
|
+
"url": "https://www.google.com/_/FlightsFrontendUi/data/travel.frontend.flights.FlightsFrontendService/GetShoppingResults?f.sid=-1290970187393889182&bl=boq_travel-frontend-flights-ui_20260506.02_p0&hl=en-US&soc-app=162&soc-platform=1&soc-device=1&_reqid=4008662&rt=c",
|
|
37
|
+
"headers": {
|
|
38
|
+
"Content-Type": "application/x-www-form-urlencoded;charset=UTF-8",
|
|
39
|
+
"Referer": "https://www.google.com/travel/flights",
|
|
40
|
+
"X-Same-Domain": "1",
|
|
41
|
+
"x-goog-ext-259736195-jspb": "[\"en-US\",\"US\",\"USD\",2,null,[420],null,null,7,[]]"
|
|
42
|
+
},
|
|
43
|
+
"body": "f.req=%5Bnull%2C%22%5B%5B%5D%2C%5Bnull%2Cnull%2C1%2Cnull%2C%5B%5D%2C1%2C%5B1%2C0%2C0%2C0%5D%2Cnull%2Cnull%2Cnull%2Cnull%2Cnull%2Cnull%2C%5B%5B%5B%5B%5B%5C%22${param.origin_airport}%5C%22%2C0%5D%5D%5D%2C%5B%5B%5B%5C%22${param.destination_airport}%5C%22%2C0%5D%5D%5D%2Cnull%2C0%2Cnull%2Cnull%2C%5C%22${param.depart_date}%5C%22%2Cnull%2Cnull%2Cnull%2Cnull%2Cnull%2C690%2Cnull%2C3%5D%2C%5B%5B%5B%5B%5C%22${param.destination_airport}%5C%22%2C0%5D%5D%5D%2C%5B%5B%5B%5C%22${param.origin_airport}%5C%22%2C0%5D%5D%5D%2Cnull%2C0%2Cnull%2Cnull%2C%5C%22${param.return_date}%5C%22%2Cnull%2Cnull%2Cnull%2Cnull%2Cnull%2C690%2Cnull%2C3%5D%5D%2Cnull%2Cnull%2Cnull%2C1%2Cnull%2Cnull%2Cnull%2Cnull%2Cnull%2Cnull%2Cnull%2Cnull%2Cnull%2Cnull%2C0%5D%2C0%2C0%2C0%2C1%5D%22%5D&"
|
|
44
|
+
}
|
|
45
|
+
],
|
|
46
|
+
"site": "google-flights",
|
|
47
|
+
"parserModule": "./parser.ts"
|
|
48
|
+
}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GENERATED by `imprint emit` — DO NOT EDIT BY HAND.
|
|
3
|
+
*
|
|
4
|
+
* Tool: search_google_hotels
|
|
5
|
+
* Site: google-hotels
|
|
6
|
+
* Intent: Search Google Hotels for lodging in a destination over a given date range and party composition, returning the list of nearby hotels with star rating, guest rating, nightly + total prices, and Google identifiers.
|
|
7
|
+
*
|
|
8
|
+
* To regenerate: imprint emit ~/.imprint/google-hotels/search_google_hotels/workflow.json --force
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { fileURLToPath } from 'node:url';
|
|
12
|
+
import { dirname, join } from 'node:path';
|
|
13
|
+
import {
|
|
14
|
+
executeWorkflow,
|
|
15
|
+
type CredentialStore,
|
|
16
|
+
} from 'imprint/runtime';
|
|
17
|
+
import type { ToolResult, Workflow } from 'imprint/types';
|
|
18
|
+
|
|
19
|
+
const WORKFLOW: Workflow = {
|
|
20
|
+
"toolName": "search_google_hotels",
|
|
21
|
+
"intent": {
|
|
22
|
+
"description": "Search Google Hotels for lodging in a destination over a given date range and party composition, returning the list of nearby hotels with star rating, guest rating, nightly + total prices, and Google identifiers.",
|
|
23
|
+
"userSaid": "i just searched for htoels in tahoe city from june 19 to june 27 for 2 people | i added some adutls and some children | i clicked one of hte hotels | i clicked hte box that shows the stay total price (not nightly prices before taxes and fees) | i played around with all the filters | i played around with the brands"
|
|
24
|
+
},
|
|
25
|
+
"parameters": [
|
|
26
|
+
{
|
|
27
|
+
"name": "query",
|
|
28
|
+
"type": "string",
|
|
29
|
+
"description": "Free-text destination query the user typed into the search box (e.g. 'tahoe city').",
|
|
30
|
+
"default": "tahoe city"
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"name": "place_mid",
|
|
34
|
+
"type": "string",
|
|
35
|
+
"description": "Google Knowledge Graph machine ID for the destination (the '/m/0gyvmkl' style identifier returned by the autocomplete request). Required to anchor the search to a specific place.",
|
|
36
|
+
"default": "/m/0gyvmkl"
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"name": "place_ftid",
|
|
40
|
+
"type": "string",
|
|
41
|
+
"description": "Google Maps feature ID for the destination, of the form '0xHEX:0xHEX'. Comes from the autocomplete response alongside the place_mid.",
|
|
42
|
+
"default": "0x809bd62ecf1fa721:0x2a98b230816c9ed1"
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"name": "place_name",
|
|
46
|
+
"type": "string",
|
|
47
|
+
"description": "Display name for the destination (e.g. 'Tahoe City').",
|
|
48
|
+
"default": "Tahoe City"
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"name": "check_in_year",
|
|
52
|
+
"type": "number",
|
|
53
|
+
"description": "Check-in year (e.g. 2026).",
|
|
54
|
+
"default": 2026
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"name": "check_in_month",
|
|
58
|
+
"type": "number",
|
|
59
|
+
"description": "Check-in month (1–12).",
|
|
60
|
+
"default": 6
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"name": "check_in_day",
|
|
64
|
+
"type": "number",
|
|
65
|
+
"description": "Check-in day of month (1–31).",
|
|
66
|
+
"default": 19
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"name": "check_out_year",
|
|
70
|
+
"type": "number",
|
|
71
|
+
"description": "Check-out year.",
|
|
72
|
+
"default": 2026
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"name": "check_out_month",
|
|
76
|
+
"type": "number",
|
|
77
|
+
"description": "Check-out month (1–12).",
|
|
78
|
+
"default": 6
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
"name": "check_out_day",
|
|
82
|
+
"type": "number",
|
|
83
|
+
"description": "Check-out day of month (1–31).",
|
|
84
|
+
"default": 27
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
"name": "nights",
|
|
88
|
+
"type": "number",
|
|
89
|
+
"description": "Length of stay in nights. Must equal check_out - check_in.",
|
|
90
|
+
"default": 8
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
"name": "child_age_1",
|
|
94
|
+
"type": "number",
|
|
95
|
+
"description": "Age of the first child (omit by leaving default; the workflow currently always sends two children to mirror the recorded request).",
|
|
96
|
+
"default": 3
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
"name": "child_age_2",
|
|
100
|
+
"type": "number",
|
|
101
|
+
"description": "Age of the second child.",
|
|
102
|
+
"default": 3
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
"name": "currency",
|
|
106
|
+
"type": "string",
|
|
107
|
+
"description": "ISO currency code for displayed prices.",
|
|
108
|
+
"default": "USD"
|
|
109
|
+
}
|
|
110
|
+
],
|
|
111
|
+
"requests": [
|
|
112
|
+
{
|
|
113
|
+
"method": "POST",
|
|
114
|
+
"url": "https://www.google.com/_/TravelFrontendUi/data/batchexecute?rpcids=AtySUc&source-path=%2Ftravel%2Fsearch&hl=en-US&soc-app=162&soc-platform=1&soc-device=1&rt=c",
|
|
115
|
+
"headers": {
|
|
116
|
+
"Content-Type": "application/x-www-form-urlencoded;charset=UTF-8",
|
|
117
|
+
"X-Same-Domain": "1",
|
|
118
|
+
"Origin": "https://www.google.com",
|
|
119
|
+
"Referer": "https://www.google.com/travel/search?q=${param.query}",
|
|
120
|
+
"Accept-Language": "en-US,en;q=0.9",
|
|
121
|
+
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/147.0.0.0 Safari/537.36",
|
|
122
|
+
"x-goog-ext-259736195-jspb": "[\"en-US\",\"US\",\"${param.currency}\",2,null,[420],null,null,7,[]]"
|
|
123
|
+
},
|
|
124
|
+
"body": "f.req=%5B%5B%5B%22AtySUc%22%2C%22%5B%5C%22${param.query}%5C%22%2C%5B1%2C%5B%5B%5B${param.child_age_1}%5D%2C%5B${param.child_age_2}%5D%5D%2C0%5D%2C%5B%5Bnull%2C%5B%5B%5C%22${param.place_mid}%5C%22%2Cnull%2Cnull%2Cnull%2Cnull%2C%5C%22${param.place_ftid}%5C%22%2C%5C%22${param.place_name}%5C%22%5D%5D%2C%5B%5D%5D%2C%5Bnull%2C%5B%5B${param.check_in_year}%2C${param.check_in_month}%2C${param.check_in_day}%5D%2C%5B${param.check_out_year}%2C${param.check_out_month}%2C${param.check_out_day}%5D%2C${param.nights}%5D%2Cnull%2Cnull%2Cnull%2C%5B1%5D%5D%5D%2Cnull%2C%5B%5Bnull%2Cnull%2Cnull%2Cnull%2Cnull%2Cnull%2C%5C%22${param.currency}%5C%22%5D%2Cnull%2C%5B%5D%5D%5D%2C%5B1%2Cnull%2Cnull%2C0%2C0%2Cnull%2C13%2Cnull%2C0%5D%5D%22%2Cnull%2C%221%22%5D%5D%5D&"
|
|
125
|
+
}
|
|
126
|
+
],
|
|
127
|
+
"site": "google-hotels",
|
|
128
|
+
"parserModule": "./parser.ts"
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
export interface SearchGoogleHotelsInput {
|
|
132
|
+
/** Free-text destination query the user typed into the search box (e.g. 'tahoe city'). */
|
|
133
|
+
query?: string;
|
|
134
|
+
/** Google Knowledge Graph machine ID for the destination (the '/m/0gyvmkl' style identifier returned by the autocomplete request). Required to anchor the search to a specific place. */
|
|
135
|
+
place_mid?: string;
|
|
136
|
+
/** Google Maps feature ID for the destination, of the form '0xHEX:0xHEX'. Comes from the autocomplete response alongside the place_mid. */
|
|
137
|
+
place_ftid?: string;
|
|
138
|
+
/** Display name for the destination (e.g. 'Tahoe City'). */
|
|
139
|
+
place_name?: string;
|
|
140
|
+
/** Check-in year (e.g. 2026). */
|
|
141
|
+
check_in_year?: number;
|
|
142
|
+
/** Check-in month (1–12). */
|
|
143
|
+
check_in_month?: number;
|
|
144
|
+
/** Check-in day of month (1–31). */
|
|
145
|
+
check_in_day?: number;
|
|
146
|
+
/** Check-out year. */
|
|
147
|
+
check_out_year?: number;
|
|
148
|
+
/** Check-out month (1–12). */
|
|
149
|
+
check_out_month?: number;
|
|
150
|
+
/** Check-out day of month (1–31). */
|
|
151
|
+
check_out_day?: number;
|
|
152
|
+
/** Length of stay in nights. Must equal check_out - check_in. */
|
|
153
|
+
nights?: number;
|
|
154
|
+
/** Age of the first child (omit by leaving default; the workflow currently always sends two children to mirror the recorded request). */
|
|
155
|
+
child_age_1?: number;
|
|
156
|
+
/** Age of the second child. */
|
|
157
|
+
child_age_2?: number;
|
|
158
|
+
/** ISO currency code for displayed prices. */
|
|
159
|
+
currency?: string;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export async function searchGoogleHotels(
|
|
163
|
+
input: SearchGoogleHotelsInput,
|
|
164
|
+
opts: { credentials?: CredentialStore; fetchImpl?: typeof fetch; initialState?: Record<string, unknown> } = {},
|
|
165
|
+
): Promise<ToolResult> {
|
|
166
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
167
|
+
const params: Record<string, string | number | boolean> = {
|
|
168
|
+
query: input.query ?? "tahoe city",
|
|
169
|
+
place_mid: input.place_mid ?? "/m/0gyvmkl",
|
|
170
|
+
place_ftid: input.place_ftid ?? "0x809bd62ecf1fa721:0x2a98b230816c9ed1",
|
|
171
|
+
place_name: input.place_name ?? "Tahoe City",
|
|
172
|
+
check_in_year: input.check_in_year ?? 2026,
|
|
173
|
+
check_in_month: input.check_in_month ?? 6,
|
|
174
|
+
check_in_day: input.check_in_day ?? 19,
|
|
175
|
+
check_out_year: input.check_out_year ?? 2026,
|
|
176
|
+
check_out_month: input.check_out_month ?? 6,
|
|
177
|
+
check_out_day: input.check_out_day ?? 27,
|
|
178
|
+
nights: input.nights ?? 8,
|
|
179
|
+
child_age_1: input.child_age_1 ?? 3,
|
|
180
|
+
child_age_2: input.child_age_2 ?? 3,
|
|
181
|
+
currency: input.currency ?? "USD",
|
|
182
|
+
|
|
183
|
+
};
|
|
184
|
+
return executeWorkflow({
|
|
185
|
+
workflow: WORKFLOW,
|
|
186
|
+
params,
|
|
187
|
+
credentials: opts.credentials,
|
|
188
|
+
fetchImpl: opts.fetchImpl,
|
|
189
|
+
initialState: opts.initialState,
|
|
190
|
+
workflowPath: join(__dirname, 'workflow.json'),
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export { WORKFLOW };
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test';
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
|
|
5
|
+
import { extract, parseEnvelope, type ExtractResult, type Hotel } from './parser.ts';
|
|
6
|
+
|
|
7
|
+
const FIXTURE = readFileSync(
|
|
8
|
+
join(
|
|
9
|
+
import.meta.dir,
|
|
10
|
+
'../../../test/fixtures/examples/google-hotels/search_google_hotels/response-287.txt',
|
|
11
|
+
),
|
|
12
|
+
'utf8',
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
describe('google-hotels parser (Tahoe City, AtySUc)', () => {
|
|
16
|
+
test('parseEnvelope returns the AtySUc payload', () => {
|
|
17
|
+
const env = parseEnvelope(FIXTURE) as { rpcid: string; payload: unknown } | null;
|
|
18
|
+
expect(env).not.toBeNull();
|
|
19
|
+
expect(env!.rpcid).toBe('AtySUc');
|
|
20
|
+
expect(Array.isArray(env!.payload)).toBe(true);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test('extract surfaces the searched destination and stay window', () => {
|
|
24
|
+
const result = extract(FIXTURE) as ExtractResult;
|
|
25
|
+
expect(result.destination).toBe('Tahoe City');
|
|
26
|
+
expect(result.checkInDate).toBe('2026-06-19');
|
|
27
|
+
expect(result.checkOutDate).toBe('2026-06-27');
|
|
28
|
+
expect(result.nights).toBe(8);
|
|
29
|
+
expect(result.currency).toBe('USD');
|
|
30
|
+
// The destination header carries a result count (107 hotels in the area).
|
|
31
|
+
expect(result.totalResults).toBeGreaterThan(0);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test('extract returns multiple Tahoe-City-area hotels', () => {
|
|
35
|
+
const result = extract(FIXTURE) as ExtractResult;
|
|
36
|
+
expect(result.hotels.length).toBeGreaterThanOrEqual(15);
|
|
37
|
+
expect(result.hotelCount).toBe(result.hotels.length);
|
|
38
|
+
for (const h of result.hotels) {
|
|
39
|
+
expect(typeof h.name).toBe('string');
|
|
40
|
+
expect(h.name.length).toBeGreaterThan(0);
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test('extract finds the named hotels seen in the recording', () => {
|
|
45
|
+
const result = extract(FIXTURE) as ExtractResult;
|
|
46
|
+
const names = result.hotels.map((h) => h.name);
|
|
47
|
+
expect(names).toContain('Northstar California Resort');
|
|
48
|
+
expect(names).toContain('Franciscan Lakeside Lodge');
|
|
49
|
+
expect(names).toContain('The Ritz-Carlton, Lake Tahoe');
|
|
50
|
+
expect(names).toContain('Granlibakken Tahoe');
|
|
51
|
+
expect(names).toContain('Basecamp Tahoe City');
|
|
52
|
+
expect(names).toContain('The Cottage Inn at Lake Tahoe');
|
|
53
|
+
expect(names).toContain('evo Hotel Tahoe City');
|
|
54
|
+
expect(names).toContain('Pepper Tree Inn');
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test('extract pulls correct details for The Ritz-Carlton, Lake Tahoe', () => {
|
|
58
|
+
const result = extract(FIXTURE) as ExtractResult;
|
|
59
|
+
const ritz = result.hotels.find((h) => h.name === 'The Ritz-Carlton, Lake Tahoe');
|
|
60
|
+
expect(ritz).toBeDefined();
|
|
61
|
+
expect(ritz!.starRating).toBe(5);
|
|
62
|
+
expect(ritz!.starDescription).toBe('5-star hotel');
|
|
63
|
+
expect(ritz!.nightlyPrice).toBe('$616');
|
|
64
|
+
expect(ritz!.nightlyPriceValue).toBeCloseTo(615.79, 1);
|
|
65
|
+
expect(ritz!.totalPrice).toBe('$4,926');
|
|
66
|
+
expect(ritz!.guestRating).toBeCloseTo(4.4, 1);
|
|
67
|
+
expect(ritz!.reviewCount).toBe(1705);
|
|
68
|
+
expect(ritz!.checkInDate).toBe('2026-06-19');
|
|
69
|
+
expect(ritz!.checkOutDate).toBe('2026-06-27');
|
|
70
|
+
expect(ritz!.nights).toBe(8);
|
|
71
|
+
expect(ritz!.hotelId).toBeTruthy();
|
|
72
|
+
expect(ritz!.mapsFeatureId).toMatch(/^0x[0-9a-f]+:0x[0-9a-f]+$/);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test('extract pulls correct details for Northstar California Resort', () => {
|
|
76
|
+
const result = extract(FIXTURE) as ExtractResult;
|
|
77
|
+
const northstar = result.hotels.find(
|
|
78
|
+
(h) => h.name === 'Northstar California Resort',
|
|
79
|
+
);
|
|
80
|
+
expect(northstar).toBeDefined();
|
|
81
|
+
expect(northstar!.starRating).toBe(3);
|
|
82
|
+
expect(northstar!.nightlyPrice).toBe('$189');
|
|
83
|
+
expect(northstar!.totalPrice).toBe('$1,514');
|
|
84
|
+
expect(northstar!.guestRating).toBeCloseTo(4.5, 1);
|
|
85
|
+
expect(northstar!.reviewCount).toBe(6251);
|
|
86
|
+
expect(northstar!.latitude).toBeCloseTo(39.2647, 3);
|
|
87
|
+
expect(northstar!.longitude).toBeCloseTo(-120.1332, 3);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test('extract pulls correct details for Everline Resort & Spa', () => {
|
|
91
|
+
const result = extract(FIXTURE) as ExtractResult;
|
|
92
|
+
const everline = result.hotels.find((h) =>
|
|
93
|
+
h.name.startsWith('Everline Resort'),
|
|
94
|
+
);
|
|
95
|
+
expect(everline).toBeDefined();
|
|
96
|
+
expect(everline!.starRating).toBe(4);
|
|
97
|
+
expect(everline!.nightlyPrice).toBe('$376');
|
|
98
|
+
expect(everline!.totalPrice).toBe('$3,007');
|
|
99
|
+
expect(everline!.reviewCount).toBe(2085);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test('every priced hotel has a positive nightly value and matching dates', () => {
|
|
103
|
+
const result = extract(FIXTURE) as ExtractResult;
|
|
104
|
+
const priced = result.hotels.filter((h) => h.nightlyPrice !== null);
|
|
105
|
+
expect(priced.length).toBeGreaterThanOrEqual(5);
|
|
106
|
+
for (const h of priced) {
|
|
107
|
+
expect(h.nightlyPrice!.startsWith('$')).toBe(true);
|
|
108
|
+
if (h.nightlyPriceValue !== null) {
|
|
109
|
+
expect(h.nightlyPriceValue).toBeGreaterThan(0);
|
|
110
|
+
}
|
|
111
|
+
expect(h.checkInDate).toBe('2026-06-19');
|
|
112
|
+
expect(h.checkOutDate).toBe('2026-06-27');
|
|
113
|
+
expect(h.nights).toBe(8);
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test('every hotel carries identifiers, coordinates and a star or rating', () => {
|
|
118
|
+
const result = extract(FIXTURE) as ExtractResult;
|
|
119
|
+
let withCoords = 0;
|
|
120
|
+
let withRating = 0;
|
|
121
|
+
let withToken = 0;
|
|
122
|
+
for (const h of result.hotels) {
|
|
123
|
+
if (h.latitude !== null && h.longitude !== null) withCoords++;
|
|
124
|
+
if (h.guestRating !== null) withRating++;
|
|
125
|
+
if (h.hotelToken !== null) withToken++;
|
|
126
|
+
}
|
|
127
|
+
// Effectively all hotels in this fixture have coordinates + a guest
|
|
128
|
+
// rating + a hotel token; require ≥80 % to leave a little slack.
|
|
129
|
+
const n = result.hotels.length;
|
|
130
|
+
expect(withCoords).toBeGreaterThanOrEqual(Math.ceil(n * 0.8));
|
|
131
|
+
expect(withRating).toBeGreaterThanOrEqual(Math.ceil(n * 0.8));
|
|
132
|
+
expect(withToken).toBeGreaterThanOrEqual(Math.ceil(n * 0.8));
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test('hotels are deduplicated by id (no name appears twice)', () => {
|
|
136
|
+
const result = extract(FIXTURE) as ExtractResult;
|
|
137
|
+
const ids: string[] = [];
|
|
138
|
+
for (const h of result.hotels) {
|
|
139
|
+
const id = h.hotelId ?? h.hotelToken ?? h.name;
|
|
140
|
+
ids.push(id);
|
|
141
|
+
}
|
|
142
|
+
expect(new Set(ids).size).toBe(ids.length);
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
// Smoke type assertion: the public Hotel shape stays consistent.
|
|
147
|
+
const _typecheck: Hotel = {
|
|
148
|
+
name: 'x',
|
|
149
|
+
latitude: 0,
|
|
150
|
+
longitude: 0,
|
|
151
|
+
starDescription: null,
|
|
152
|
+
starRating: null,
|
|
153
|
+
guestRating: null,
|
|
154
|
+
reviewCount: null,
|
|
155
|
+
nightlyPrice: null,
|
|
156
|
+
nightlyPriceValue: null,
|
|
157
|
+
totalPrice: null,
|
|
158
|
+
totalPriceValue: null,
|
|
159
|
+
description: null,
|
|
160
|
+
photoUrl: null,
|
|
161
|
+
hotelId: null,
|
|
162
|
+
hotelToken: null,
|
|
163
|
+
mapsFeatureId: null,
|
|
164
|
+
checkInDate: null,
|
|
165
|
+
checkOutDate: null,
|
|
166
|
+
nights: null,
|
|
167
|
+
};
|
|
168
|
+
void _typecheck;
|