Skip to content

Commit 0788317

Browse files
Merge pull request #27 from VectorlyApp/fix_escaping
Fix a bug and add example routines
2 parents eeefff8 + 3301661 commit 0788317

File tree

4 files changed

+794
-182
lines changed

4 files changed

+794
-182
lines changed
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"origin_code": "BOS",
3+
"destination_code": "ATL",
4+
"depart_begin_date": "2025-11-05",
5+
"depart_end_date": "2025-11-05",
6+
"return_begin_date": "2025-11-08",
7+
"return_end_date": "2025-11-08",
8+
"passenger_adults": "1"
9+
}
Lines changed: 255 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,255 @@
1+
{
2+
"id": "routine_4f1c2d2a-7b9f-4f1e-b2db-6a7f6e2c8f41",
3+
"created_at": 1762292460,
4+
"updated_at": 1762292460,
5+
"name": "Spirit flight availability search (v3/search)",
6+
"description": "Automates Spirit token retrieval and flight availability search using observed endpoints and payloads (v3/search and prod-token).",
7+
"operations": [
8+
{
9+
"type": "navigate",
10+
"url": "https://www.spirit.com/book/flights"
11+
},
12+
{
13+
"type": "sleep",
14+
"timeout_seconds": 2.5
15+
},
16+
{
17+
"type": "fetch",
18+
"endpoint": {
19+
"url": "https://www.spirit.com/api/prod-token/api/v1/token",
20+
"description": "Issue JWT used for subsequent API calls (returns data.token).",
21+
"method": "POST",
22+
"headers": {
23+
"sec-ch-ua-platform": "macOS",
24+
"Cache-Control": "no-cache",
25+
"Referer": "https://www.spirit.com/",
26+
"Pragma": "no-cache",
27+
"sec-ch-ua": "\"Chromium\";v=\"142\", \"Google Chrome\";v=\"142\", \"Not_A Brand\";v=\"99\"",
28+
"sec-ch-ua-mobile": "?0",
29+
"Ocp-Apim-Subscription-Key": "3b6a6994753b4efc86376552e52b8432",
30+
"Accept": "application/json, text/plain, */*",
31+
"Content-Type": "application/json",
32+
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36"
33+
},
34+
"body": {
35+
"applicationName": "dotRezWeb"
36+
},
37+
"credentials": "same-origin"
38+
},
39+
"session_storage_key": "token_response"
40+
},
41+
{
42+
"type": "fetch",
43+
"endpoint": {
44+
"url": "https://www.spirit.com/api/prod-availability/api/availability/v3/search",
45+
"description": "Search flight availability by origin, destination, and dates (supports one-way or roundtrip via criteria array).",
46+
"method": "POST",
47+
"headers": {
48+
"sec-ch-ua-platform": "macOS",
49+
"Authorization": "Bearer \"{{sessionStorage:token_response.data.token}}\"",
50+
"Cache-Control": "no-cache",
51+
"Referer": "https://www.spirit.com/book/flights",
52+
"Accept-Language": "en-US",
53+
"Pragma": "no-cache",
54+
"sec-ch-ua": "\"Chromium\";v=\"142\", \"Google Chrome\";v=\"142\", \"Not_A Brand\";v=\"99\"",
55+
"sec-ch-ua-mobile": "?0",
56+
"Ocp-Apim-Subscription-Key": "3b6a6994753b4efc86376552e52b8432",
57+
"Accept": "application/json, text/plain, */*",
58+
"Content-Type": "application/json",
59+
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36"
60+
},
61+
"body": {
62+
"includeWifiAvailability": true,
63+
"criteria": [
64+
{
65+
"stations": {
66+
"originStationCodes": [
67+
"\"{{origin_code}}\""
68+
],
69+
"destinationStationCodes": [
70+
"\"{{destination_code}}\""
71+
],
72+
"searchDestinationMacs": false
73+
},
74+
"dates": {
75+
"beginDate": "\"{{depart_begin_date}}\"",
76+
"endDate": "\"{{depart_end_date}}\""
77+
},
78+
"filters": {
79+
"filter": "Default"
80+
}
81+
},
82+
{
83+
"stations": {
84+
"originStationCodes": [
85+
"\"{{destination_code}}\""
86+
],
87+
"destinationStationCodes": [
88+
"\"{{origin_code}}\""
89+
],
90+
"searchOriginMacs": false
91+
},
92+
"dates": {
93+
"beginDate": "\"{{return_begin_date}}\"",
94+
"endDate": "\"{{return_end_date}}\""
95+
},
96+
"filters": {
97+
"filter": "Default"
98+
}
99+
}
100+
],
101+
"passengers": {
102+
"types": [
103+
{
104+
"type": "ADT",
105+
"count": "{{passenger_adults}}"
106+
}
107+
]
108+
},
109+
"codes": {
110+
"currency": "USD"
111+
},
112+
"fareFilters": {
113+
"loyalty": "MonetaryOnly",
114+
"types": [],
115+
"classControl": 1
116+
},
117+
"taxesAndFees": "TaxesAndFees",
118+
"originalJourneyKeys": [],
119+
"originalBookingRecordLocator": null,
120+
"infantCount": 0,
121+
"birthDates": [],
122+
"includeBundleAvailability": true
123+
},
124+
"credentials": "same-origin"
125+
},
126+
"session_storage_key": "availability_search_response"
127+
},
128+
{
129+
"type": "return",
130+
"session_storage_key": "availability_search_response"
131+
}
132+
],
133+
"incognito": true,
134+
"parameters": [
135+
{
136+
"name": "origin_code",
137+
"type": "string",
138+
"required": true,
139+
"description": "IATA origin station code.",
140+
"default": null,
141+
"examples": [
142+
"BOS"
143+
],
144+
"min_length": 3,
145+
"max_length": 3,
146+
"min_value": null,
147+
"max_value": null,
148+
"pattern": "^[A-Z]{3}$",
149+
"enum_values": null,
150+
"format": null
151+
},
152+
{
153+
"name": "destination_code",
154+
"type": "string",
155+
"required": true,
156+
"description": "IATA destination station code.",
157+
"default": null,
158+
"examples": [
159+
"ATL"
160+
],
161+
"min_length": 3,
162+
"max_length": 3,
163+
"min_value": null,
164+
"max_value": null,
165+
"pattern": "^[A-Z]{3}$",
166+
"enum_values": null,
167+
"format": null
168+
},
169+
{
170+
"name": "depart_begin_date",
171+
"type": "date",
172+
"required": true,
173+
"description": "Outbound begin date (YYYY-MM-DD).",
174+
"default": null,
175+
"examples": [
176+
"2025-11-05"
177+
],
178+
"min_length": null,
179+
"max_length": null,
180+
"min_value": null,
181+
"max_value": null,
182+
"pattern": null,
183+
"enum_values": null,
184+
"format": "YYYY-MM-DD"
185+
},
186+
{
187+
"name": "depart_end_date",
188+
"type": "date",
189+
"required": true,
190+
"description": "Outbound end date (YYYY-MM-DD).",
191+
"default": null,
192+
"examples": [
193+
"2025-11-05"
194+
],
195+
"min_length": null,
196+
"max_length": null,
197+
"min_value": null,
198+
"max_value": null,
199+
"pattern": null,
200+
"enum_values": null,
201+
"format": "YYYY-MM-DD"
202+
},
203+
{
204+
"name": "return_begin_date",
205+
"type": "date",
206+
"required": true,
207+
"description": "Return begin date (YYYY-MM-DD). For one-way, set same as depart and the server will ignore the second leg.",
208+
"default": null,
209+
"examples": [
210+
"2025-11-08"
211+
],
212+
"min_length": null,
213+
"max_length": null,
214+
"min_value": null,
215+
"max_value": null,
216+
"pattern": null,
217+
"enum_values": null,
218+
"format": "YYYY-MM-DD"
219+
},
220+
{
221+
"name": "return_end_date",
222+
"type": "date",
223+
"required": true,
224+
"description": "Return end date (YYYY-MM-DD). For one-way, set same as depart and the server will ignore the second leg.",
225+
"default": null,
226+
"examples": [
227+
"2025-11-08"
228+
],
229+
"min_length": null,
230+
"max_length": null,
231+
"min_value": null,
232+
"max_value": null,
233+
"pattern": null,
234+
"enum_values": null,
235+
"format": "YYYY-MM-DD"
236+
},
237+
{
238+
"name": "passenger_adults",
239+
"type": "integer",
240+
"required": true,
241+
"description": "Number of adult passengers (ADT).",
242+
"default": 1,
243+
"examples": [
244+
1
245+
],
246+
"min_length": null,
247+
"max_length": null,
248+
"min_value": 1,
249+
"max_value": 9,
250+
"pattern": null,
251+
"enum_values": null,
252+
"format": null
253+
}
254+
]
255+
}

src/routine_discovery/agent.py

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -650,14 +650,13 @@ def productionize_routine(self, routine: Routine) -> ProductionRoutine:
650650
f"CRITICAL: PLACEHOLDERS ARE REPLACED AT RUNTIME AND MUST RESULT IN VALID JSON! "
651651
f"EXPLANATION: Placeholders like {{{{key}}}} are replaced at runtime with actual values. The format you choose determines the resulting JSON type. "
652652
f"For STRING values: Use \\\"{{{{key}}}}\\\" format (escaped quote + placeholder + escaped quote). "
653-
f"This means in the JSON file you write: \\\"\\\"{{{{user_name}}}}\\\"\\\". At runtime, the \\\"{{{{user_name}}}}\\\" part gets replaced, "
654-
f"so \\\"\\\"{{{{user_name}}}}\\\"\\\" becomes \\\"\\\"John\\\"\\\" which becomes \\\"John\\\" (valid JSON string). "
655-
f"For NUMERIC/NULL values: Use \\\"{{{{key}}}}\\\" format (regular quote + placeholder + quote). "
656-
f"This means in the JSON file you write: \\\"{{{{item_id}}}}\\\". At runtime, the {{{{item_id}}}} part gets replaced with the number, "
657-
f"and the surrounding quotes are removed, so \\\"{{{{item_id}}}}\\\" with value 42 becomes just 42 (valid JSON number, not string). "
658-
f"Example: \\\"{{{{total_price}}}}\\\" with value 29.99 → becomes 29.99 (quotes removed, valid JSON number). "
659-
f"Example: \\\"{{{{optional_data}}}}\\\" with null → becomes null (quotes removed, valid JSON null). "
660-
"""Placeholders will be resolved using this: param_pattern = r'(?:"|\\\\")\\{\\{([^}"]*)\\}\\}(?:"|\\\\")'"""
653+
f"This means in the JSON file you write: \"\\\"{{{{user_name}}}}\\\"\". At runtime, the \\\"{{{{user_name}}}}\\\" part gets replaced, "
654+
f"so \"\\\"{{{{user_name}}}}\\\"\" becomes \"John\" (valid JSON string). "
655+
f"For NUMERIC/NULL values: Use \"{{{{key}}}}\" format (regular quote + placeholder + quote). "
656+
f"This means in the JSON file you write: \"{{{{item_id}}}}\". At runtime, the {{{{item_id}}}} part gets replaced with the number, "
657+
f"and the surrounding quotes are removed, so \"{{{{item_id}}}}\" with value 42 becomes just 42 (valid JSON number, not string). "
658+
f"Example: \"{{{{total_price}}}}\" with value 29.99 → becomes 29.99 (quotes removed, valid JSON number). "
659+
f"Example: \"{{{{optional_data}}}}\" with null → becomes null (quotes removed, valid JSON null). "
661660
f"The resulting JSON MUST be valid and parseable after all placeholder replacements are done."
662661
)
663662
self._add_to_message_history("user", message)

0 commit comments

Comments
 (0)