@nitrostack/cli 1.0.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/README.md +131 -0
- package/dist/commands/build.d.ts +6 -0
- package/dist/commands/build.d.ts.map +1 -0
- package/dist/commands/build.js +185 -0
- package/dist/commands/dev.d.ts +7 -0
- package/dist/commands/dev.d.ts.map +1 -0
- package/dist/commands/dev.js +365 -0
- package/dist/commands/generate-types.d.ts +8 -0
- package/dist/commands/generate-types.d.ts.map +1 -0
- package/dist/commands/generate-types.js +219 -0
- package/dist/commands/generate.d.ts +12 -0
- package/dist/commands/generate.d.ts.map +1 -0
- package/dist/commands/generate.js +375 -0
- package/dist/commands/init.d.ts +7 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +324 -0
- package/dist/commands/install.d.ts +10 -0
- package/dist/commands/install.d.ts.map +1 -0
- package/dist/commands/install.js +80 -0
- package/dist/commands/start.d.ts +6 -0
- package/dist/commands/start.d.ts.map +1 -0
- package/dist/commands/start.js +70 -0
- package/dist/commands/upgrade.d.ts +10 -0
- package/dist/commands/upgrade.d.ts.map +1 -0
- package/dist/commands/upgrade.js +214 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +94 -0
- package/dist/mcp-dev-wrapper.d.ts +15 -0
- package/dist/mcp-dev-wrapper.d.ts.map +1 -0
- package/dist/mcp-dev-wrapper.js +187 -0
- package/dist/ui/branding.d.ts +31 -0
- package/dist/ui/branding.d.ts.map +1 -0
- package/dist/ui/branding.js +136 -0
- package/package.json +69 -0
- package/templates/typescript-oauth/.env.example +27 -0
- package/templates/typescript-oauth/OAUTH_SETUP.md +592 -0
- package/templates/typescript-oauth/README.md +263 -0
- package/templates/typescript-oauth/package.json +29 -0
- package/templates/typescript-oauth/src/app.module.ts +92 -0
- package/templates/typescript-oauth/src/guards/oauth.guard.ts +126 -0
- package/templates/typescript-oauth/src/health/system.health.ts +55 -0
- package/templates/typescript-oauth/src/index.ts +63 -0
- package/templates/typescript-oauth/src/modules/flights/booking.tools.ts +323 -0
- package/templates/typescript-oauth/src/modules/flights/flights.module.ts +14 -0
- package/templates/typescript-oauth/src/modules/flights/flights.prompts.ts +228 -0
- package/templates/typescript-oauth/src/modules/flights/flights.resources.ts +215 -0
- package/templates/typescript-oauth/src/modules/flights/flights.tools.ts +457 -0
- package/templates/typescript-oauth/src/services/duffel.service.ts +285 -0
- package/templates/typescript-oauth/src/widgets/app/airport-search/page.tsx +270 -0
- package/templates/typescript-oauth/src/widgets/app/flight-details/page.tsx +261 -0
- package/templates/typescript-oauth/src/widgets/app/flight-search-results/page.tsx +378 -0
- package/templates/typescript-oauth/src/widgets/app/globals.css +167 -0
- package/templates/typescript-oauth/src/widgets/app/layout.tsx +18 -0
- package/templates/typescript-oauth/src/widgets/app/order-cancellation/page.tsx +207 -0
- package/templates/typescript-oauth/src/widgets/app/order-summary/page.tsx +245 -0
- package/templates/typescript-oauth/src/widgets/app/payment-confirmation/page.tsx +152 -0
- package/templates/typescript-oauth/src/widgets/app/seat-selection/page.tsx +486 -0
- package/templates/typescript-oauth/src/widgets/next-env.d.ts +5 -0
- package/templates/typescript-oauth/src/widgets/next.config.js +45 -0
- package/templates/typescript-oauth/src/widgets/package-lock.json +4493 -0
- package/templates/typescript-oauth/src/widgets/package.json +24 -0
- package/templates/typescript-oauth/src/widgets/tsconfig.json +28 -0
- package/templates/typescript-oauth/src/widgets/widget-manifest.json +395 -0
- package/templates/typescript-oauth/tsconfig.json +23 -0
- package/templates/typescript-pizzaz/README.md +252 -0
- package/templates/typescript-pizzaz/package.json +34 -0
- package/templates/typescript-pizzaz/src/app.module.ts +28 -0
- package/templates/typescript-pizzaz/src/index.ts +30 -0
- package/templates/typescript-pizzaz/src/modules/pizzaz/pizzaz.data.ts +106 -0
- package/templates/typescript-pizzaz/src/modules/pizzaz/pizzaz.module.ts +11 -0
- package/templates/typescript-pizzaz/src/modules/pizzaz/pizzaz.service.ts +60 -0
- package/templates/typescript-pizzaz/src/modules/pizzaz/pizzaz.tools.ts +197 -0
- package/templates/typescript-pizzaz/src/widgets/app/layout.tsx +18 -0
- package/templates/typescript-pizzaz/src/widgets/app/pizza-list/page.tsx +272 -0
- package/templates/typescript-pizzaz/src/widgets/app/pizza-map/page.tsx +216 -0
- package/templates/typescript-pizzaz/src/widgets/app/pizza-shop/page.tsx +374 -0
- package/templates/typescript-pizzaz/src/widgets/components/CompactShopCard.tsx +144 -0
- package/templates/typescript-pizzaz/src/widgets/components/PizzaCard.tsx +191 -0
- package/templates/typescript-pizzaz/src/widgets/next.config.js +45 -0
- package/templates/typescript-pizzaz/src/widgets/package.json +30 -0
- package/templates/typescript-pizzaz/src/widgets/tsconfig.json +28 -0
- package/templates/typescript-pizzaz/src/widgets/widget-manifest.json +253 -0
- package/templates/typescript-pizzaz/tsconfig.json +30 -0
- package/templates/typescript-starter/README.md +320 -0
- package/templates/typescript-starter/package.json +25 -0
- package/templates/typescript-starter/src/app.module.ts +34 -0
- package/templates/typescript-starter/src/health/system.health.ts +55 -0
- package/templates/typescript-starter/src/index.ts +29 -0
- package/templates/typescript-starter/src/modules/calculator/calculator.module.ts +12 -0
- package/templates/typescript-starter/src/modules/calculator/calculator.prompts.ts +73 -0
- package/templates/typescript-starter/src/modules/calculator/calculator.resources.ts +59 -0
- package/templates/typescript-starter/src/modules/calculator/calculator.tools.ts +166 -0
- package/templates/typescript-starter/src/widgets/app/calculator-result/page.tsx +180 -0
- package/templates/typescript-starter/src/widgets/app/layout.tsx +18 -0
- package/templates/typescript-starter/src/widgets/next.config.js +45 -0
- package/templates/typescript-starter/src/widgets/package.json +24 -0
- package/templates/typescript-starter/src/widgets/tsconfig.json +28 -0
- package/templates/typescript-starter/src/widgets/widget-manifest.json +48 -0
- package/templates/typescript-starter/tsconfig.json +23 -0
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import { ResourceDecorator as Resource, ExecutionContext, z, Injectable } from 'nitrostack';
|
|
2
|
+
import { DuffelService } from '../../services/duffel.service.js';
|
|
3
|
+
|
|
4
|
+
@Injectable()
|
|
5
|
+
export class FlightResources {
|
|
6
|
+
constructor(private duffelService: DuffelService) { }
|
|
7
|
+
|
|
8
|
+
@Resource({
|
|
9
|
+
uri: 'flight://search-history',
|
|
10
|
+
name: 'Flight Search History',
|
|
11
|
+
description: 'Access to recent flight searches and their results',
|
|
12
|
+
mimeType: 'application/json'
|
|
13
|
+
})
|
|
14
|
+
async getSearchHistory(ctx: ExecutionContext) {
|
|
15
|
+
// In a real implementation, this would fetch from a database
|
|
16
|
+
// For now, return a template structure
|
|
17
|
+
return {
|
|
18
|
+
searches: [],
|
|
19
|
+
message: 'Search history will be stored here after performing searches'
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
@Resource({
|
|
24
|
+
uri: 'flight://popular-routes',
|
|
25
|
+
name: 'Popular Flight Routes',
|
|
26
|
+
description: 'Information about popular flight routes and typical pricing',
|
|
27
|
+
mimeType: 'application/json'
|
|
28
|
+
})
|
|
29
|
+
async getPopularRoutes(ctx: ExecutionContext) {
|
|
30
|
+
return {
|
|
31
|
+
routes: [
|
|
32
|
+
{
|
|
33
|
+
route: 'JFK → LAX',
|
|
34
|
+
description: 'New York to Los Angeles',
|
|
35
|
+
averageDuration: '6h 30m',
|
|
36
|
+
typicalPrice: '$200-400',
|
|
37
|
+
airlines: ['American Airlines', 'Delta', 'JetBlue', 'United']
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
route: 'LHR → JFK',
|
|
41
|
+
description: 'London to New York',
|
|
42
|
+
averageDuration: '8h 00m',
|
|
43
|
+
typicalPrice: '$400-800',
|
|
44
|
+
airlines: ['British Airways', 'American Airlines', 'Virgin Atlantic']
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
route: 'SFO → NRT',
|
|
48
|
+
description: 'San Francisco to Tokyo',
|
|
49
|
+
averageDuration: '11h 30m',
|
|
50
|
+
typicalPrice: '$600-1200',
|
|
51
|
+
airlines: ['United', 'ANA', 'Japan Airlines']
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
route: 'DXB → LHR',
|
|
55
|
+
description: 'Dubai to London',
|
|
56
|
+
averageDuration: '7h 30m',
|
|
57
|
+
typicalPrice: '$400-900',
|
|
58
|
+
airlines: ['Emirates', 'British Airways']
|
|
59
|
+
}
|
|
60
|
+
],
|
|
61
|
+
note: 'Prices are approximate and vary by season, booking time, and availability'
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
@Resource({
|
|
66
|
+
uri: 'flight://booking-guide',
|
|
67
|
+
name: 'Flight Booking Guide',
|
|
68
|
+
description: 'Comprehensive guide on how to search and book flights',
|
|
69
|
+
mimeType: 'text/markdown'
|
|
70
|
+
})
|
|
71
|
+
async getBookingGuide(ctx: ExecutionContext) {
|
|
72
|
+
return `# Flight Booking Guide
|
|
73
|
+
|
|
74
|
+
## How to Search for Flights
|
|
75
|
+
|
|
76
|
+
### 1. Prepare Your Information
|
|
77
|
+
- **Origin & Destination**: Use 3-letter IATA airport codes (e.g., JFK, LAX)
|
|
78
|
+
- Use \`search_airports\` tool if you don't know the code
|
|
79
|
+
- **Dates**: Provide dates in YYYY-MM-DD format
|
|
80
|
+
- **Passengers**: Specify number of adults, children, and infants
|
|
81
|
+
- **Cabin Class**: Choose from economy, premium_economy, business, or first
|
|
82
|
+
|
|
83
|
+
### 2. Use the Search Tool
|
|
84
|
+
\`\`\`
|
|
85
|
+
search_flights({
|
|
86
|
+
origin: "JFK",
|
|
87
|
+
destination: "LAX",
|
|
88
|
+
departureDate: "2024-03-15",
|
|
89
|
+
returnDate: "2024-03-22",
|
|
90
|
+
adults: 2,
|
|
91
|
+
cabinClass: "economy"
|
|
92
|
+
})
|
|
93
|
+
\`\`\`
|
|
94
|
+
|
|
95
|
+
### 3. Review Results
|
|
96
|
+
- Compare prices, airlines, and flight times
|
|
97
|
+
- Check number of stops and total duration
|
|
98
|
+
- Review baggage allowance and fare conditions
|
|
99
|
+
|
|
100
|
+
### 4. Get Detailed Information
|
|
101
|
+
Use \`get_flight_details\` with an offer ID to see:
|
|
102
|
+
- Complete itinerary with all segments
|
|
103
|
+
- Baggage allowance per passenger
|
|
104
|
+
- Refund and change policies
|
|
105
|
+
- Payment requirements
|
|
106
|
+
|
|
107
|
+
## Tips for Best Results
|
|
108
|
+
|
|
109
|
+
### Flexible Dates
|
|
110
|
+
- Search multiple date combinations
|
|
111
|
+
- Weekday flights are often cheaper than weekends
|
|
112
|
+
- Avoid major holidays and peak seasons
|
|
113
|
+
|
|
114
|
+
### Direct vs. Connecting Flights
|
|
115
|
+
- Use \`maxConnections: 0\` for direct flights only
|
|
116
|
+
- Connecting flights are usually cheaper but take longer
|
|
117
|
+
- Consider layover duration and airport
|
|
118
|
+
|
|
119
|
+
### Cabin Class Selection
|
|
120
|
+
- **Economy**: Most affordable, basic amenities
|
|
121
|
+
- **Premium Economy**: More legroom and better service
|
|
122
|
+
- **Business**: Lie-flat seats, premium dining, lounge access
|
|
123
|
+
- **First**: Ultimate luxury, private suites, concierge service
|
|
124
|
+
|
|
125
|
+
### Booking Timing
|
|
126
|
+
- Book 2-3 months in advance for domestic flights
|
|
127
|
+
- Book 3-6 months in advance for international flights
|
|
128
|
+
- Last-minute deals exist but are risky
|
|
129
|
+
|
|
130
|
+
## Understanding Fare Conditions
|
|
131
|
+
|
|
132
|
+
### Refundable vs. Non-Refundable
|
|
133
|
+
- **Refundable**: Can cancel and get money back (usually more expensive)
|
|
134
|
+
- **Non-Refundable**: No refund if cancelled (cheaper)
|
|
135
|
+
|
|
136
|
+
### Change Policies
|
|
137
|
+
- Some fares allow changes with a fee
|
|
138
|
+
- Others don't allow any changes
|
|
139
|
+
- Check \`conditions\` in flight details
|
|
140
|
+
|
|
141
|
+
### Baggage Allowance
|
|
142
|
+
- **Carry-on**: Usually 1 bag + 1 personal item
|
|
143
|
+
- **Checked**: Varies by airline and fare class
|
|
144
|
+
- International flights typically include checked bags
|
|
145
|
+
|
|
146
|
+
## Booking Process
|
|
147
|
+
|
|
148
|
+
### Hold Orders
|
|
149
|
+
- All bookings are automatically held (no payment required at booking time)
|
|
150
|
+
- Price is guaranteed until the hold expires
|
|
151
|
+
- You'll receive an expiration time when booking
|
|
152
|
+
|
|
153
|
+
### Passenger Information Required
|
|
154
|
+
- Full legal name (as on passport/ID)
|
|
155
|
+
- Date of birth
|
|
156
|
+
- Gender
|
|
157
|
+
- Passport details for international flights
|
|
158
|
+
|
|
159
|
+
## After Booking
|
|
160
|
+
|
|
161
|
+
### Confirmation
|
|
162
|
+
- You'll receive a booking reference
|
|
163
|
+
- Save confirmation email
|
|
164
|
+
- Check-in opens 24 hours before departure
|
|
165
|
+
|
|
166
|
+
### Manage Your Booking
|
|
167
|
+
- Add seat selections
|
|
168
|
+
- Purchase extra baggage
|
|
169
|
+
- Add special meals or assistance
|
|
170
|
+
- Update passenger information
|
|
171
|
+
|
|
172
|
+
## Need Help?
|
|
173
|
+
|
|
174
|
+
Use the \`flight_search_assistant\` prompt for personalized assistance with:
|
|
175
|
+
- Finding the best flights for your needs
|
|
176
|
+
- Understanding complex itineraries
|
|
177
|
+
- Comparing different options
|
|
178
|
+
- Making booking decisions
|
|
179
|
+
`;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
@Resource({
|
|
183
|
+
uri: 'flight://airline-codes',
|
|
184
|
+
name: 'Airline Codes Reference',
|
|
185
|
+
description: 'Common airline IATA codes and names',
|
|
186
|
+
mimeType: 'application/json'
|
|
187
|
+
})
|
|
188
|
+
async getAirlineCodes(ctx: ExecutionContext) {
|
|
189
|
+
try {
|
|
190
|
+
const airlines = await this.duffelService.getAirlines();
|
|
191
|
+
return {
|
|
192
|
+
airlines: airlines.slice(0, 50).map((airline: any) => ({
|
|
193
|
+
iataCode: airline.iata_code,
|
|
194
|
+
name: airline.name
|
|
195
|
+
})),
|
|
196
|
+
note: 'Showing top 50 airlines. More available through Duffel API.'
|
|
197
|
+
};
|
|
198
|
+
} catch (error) {
|
|
199
|
+
// Return common airlines if API call fails
|
|
200
|
+
return {
|
|
201
|
+
airlines: [
|
|
202
|
+
{ iataCode: 'AA', name: 'American Airlines' },
|
|
203
|
+
{ iataCode: 'DL', name: 'Delta Air Lines' },
|
|
204
|
+
{ iataCode: 'UA', name: 'United Airlines' },
|
|
205
|
+
{ iataCode: 'BA', name: 'British Airways' },
|
|
206
|
+
{ iataCode: 'LH', name: 'Lufthansa' },
|
|
207
|
+
{ iataCode: 'AF', name: 'Air France' },
|
|
208
|
+
{ iataCode: 'EK', name: 'Emirates' },
|
|
209
|
+
{ iataCode: 'QR', name: 'Qatar Airways' }
|
|
210
|
+
],
|
|
211
|
+
note: 'Common airlines. Use search_airports tool for complete list.'
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
@@ -0,0 +1,457 @@
|
|
|
1
|
+
import { ToolDecorator as Tool, Widget, ExecutionContext, z, UseGuards, Injectable } from 'nitrostack';
|
|
2
|
+
import { OAuthGuard } from '../../guards/oauth.guard.js';
|
|
3
|
+
import { DuffelService } from '../../services/duffel.service.js';
|
|
4
|
+
import { format } from 'date-fns';
|
|
5
|
+
|
|
6
|
+
@Injectable()
|
|
7
|
+
export class FlightTools {
|
|
8
|
+
constructor(private duffelService: DuffelService) { }
|
|
9
|
+
|
|
10
|
+
@Tool({
|
|
11
|
+
name: 'search_flights',
|
|
12
|
+
description: 'Search for flight offers based on origin, destination, dates, and preferences. Returns available flight options with pricing and details.',
|
|
13
|
+
inputSchema: z.object({
|
|
14
|
+
origin: z.string().length(3).describe('Origin airport IATA code (e.g., "JFK", "LHR")'),
|
|
15
|
+
destination: z.string().length(3).describe('Destination airport IATA code (e.g., "LAX", "CDG")'),
|
|
16
|
+
departureDate: z.string().describe('Departure date in YYYY-MM-DD format'),
|
|
17
|
+
returnDate: z.string().optional().describe('Return date in YYYY-MM-DD format for round trip'),
|
|
18
|
+
adults: z.number().min(1).max(9).default(1).describe('Number of adult passengers (18+)'),
|
|
19
|
+
children: z.number().min(0).max(9).default(0).describe('Number of child passengers (2-17)'),
|
|
20
|
+
infants: z.number().min(0).max(9).default(0).describe('Number of infant passengers (under 2)'),
|
|
21
|
+
cabinClass: z.enum(['economy', 'premium_economy', 'business', 'first']).default('economy').describe('Preferred cabin class'),
|
|
22
|
+
maxConnections: z.number().min(0).max(3).optional().describe('Maximum number of connections (0 for direct flights only)'),
|
|
23
|
+
departureTimeFrom: z.string().optional().describe('Earliest departure time in HH:MM format'),
|
|
24
|
+
departureTimeTo: z.string().optional().describe('Latest departure time in HH:MM format')
|
|
25
|
+
}),
|
|
26
|
+
examples: {
|
|
27
|
+
request: {
|
|
28
|
+
origin: 'JFK',
|
|
29
|
+
destination: 'LAX',
|
|
30
|
+
departureDate: '2024-03-15',
|
|
31
|
+
returnDate: '2024-03-22',
|
|
32
|
+
adults: 2,
|
|
33
|
+
cabinClass: 'economy'
|
|
34
|
+
},
|
|
35
|
+
response: {
|
|
36
|
+
requestId: 'orq_123456',
|
|
37
|
+
searchParams: {
|
|
38
|
+
origin: 'JFK',
|
|
39
|
+
destination: 'LAX',
|
|
40
|
+
departureDate: '2024-03-15',
|
|
41
|
+
returnDate: '2024-03-22',
|
|
42
|
+
passengers: {
|
|
43
|
+
adults: 2,
|
|
44
|
+
children: 0,
|
|
45
|
+
infants: 0
|
|
46
|
+
},
|
|
47
|
+
cabinClass: 'economy'
|
|
48
|
+
},
|
|
49
|
+
totalOffers: 15,
|
|
50
|
+
offers: [
|
|
51
|
+
{
|
|
52
|
+
id: 'off_123456',
|
|
53
|
+
totalAmount: '450.00',
|
|
54
|
+
totalCurrency: 'USD',
|
|
55
|
+
expiresAt: '2024-03-01T12:00:00Z',
|
|
56
|
+
outbound: {
|
|
57
|
+
origin: 'JFK',
|
|
58
|
+
destination: 'LAX',
|
|
59
|
+
departureTime: '2024-03-15T08:00:00Z',
|
|
60
|
+
arrivalTime: '2024-03-15T14:30:00Z',
|
|
61
|
+
duration: 'PT6H30M',
|
|
62
|
+
stops: 0,
|
|
63
|
+
airline: 'American Airlines',
|
|
64
|
+
flightNumber: 'AA123',
|
|
65
|
+
segments: []
|
|
66
|
+
},
|
|
67
|
+
return: {
|
|
68
|
+
origin: 'LAX',
|
|
69
|
+
destination: 'JFK',
|
|
70
|
+
departureTime: '2024-03-22T16:00:00Z',
|
|
71
|
+
arrivalTime: '2024-03-23T00:30:00Z',
|
|
72
|
+
duration: 'PT5H30M',
|
|
73
|
+
stops: 0,
|
|
74
|
+
airline: 'American Airlines',
|
|
75
|
+
flightNumber: 'AA456',
|
|
76
|
+
segments: []
|
|
77
|
+
},
|
|
78
|
+
fareType: 'Domestic',
|
|
79
|
+
refundable: false,
|
|
80
|
+
changeable: true
|
|
81
|
+
}
|
|
82
|
+
],
|
|
83
|
+
message: 'Found 15 flight options. Showing top 10 results.'
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
})
|
|
87
|
+
@UseGuards(OAuthGuard)
|
|
88
|
+
@Widget('flight-search-results')
|
|
89
|
+
async searchFlights(input: any, ctx: ExecutionContext) {
|
|
90
|
+
ctx.logger.info('Searching for flights', {
|
|
91
|
+
user: ctx.auth?.subject,
|
|
92
|
+
origin: input.origin,
|
|
93
|
+
destination: input.destination,
|
|
94
|
+
departureDate: input.departureDate
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// Ensure we have passenger counts with defaults
|
|
98
|
+
const adults = input.adults || 1;
|
|
99
|
+
const children = input.children || 0;
|
|
100
|
+
const infants = input.infants || 0;
|
|
101
|
+
|
|
102
|
+
// Build passengers array
|
|
103
|
+
const passengers: any[] = [];
|
|
104
|
+
for (let i = 0; i < adults; i++) {
|
|
105
|
+
passengers.push({ type: 'adult' });
|
|
106
|
+
}
|
|
107
|
+
for (let i = 0; i < children; i++) {
|
|
108
|
+
passengers.push({ type: 'child', age: 12 }); // Default age for children
|
|
109
|
+
}
|
|
110
|
+
for (let i = 0; i < infants; i++) {
|
|
111
|
+
passengers.push({ type: 'infant_without_seat' });
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Build time filters if provided
|
|
115
|
+
const departureTime = input.departureTimeFrom && input.departureTimeTo
|
|
116
|
+
? { from: input.departureTimeFrom, to: input.departureTimeTo }
|
|
117
|
+
: undefined;
|
|
118
|
+
|
|
119
|
+
ctx.logger.info('Calling Duffel service with passengers:', {
|
|
120
|
+
passengersCount: passengers.length,
|
|
121
|
+
passengers: passengers,
|
|
122
|
+
adults: adults,
|
|
123
|
+
children: children,
|
|
124
|
+
infants: infants
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
const result = await this.duffelService.searchFlights({
|
|
128
|
+
origin: input.origin.toUpperCase(),
|
|
129
|
+
destination: input.destination.toUpperCase(),
|
|
130
|
+
departureDate: input.departureDate,
|
|
131
|
+
returnDate: input.returnDate,
|
|
132
|
+
passengers,
|
|
133
|
+
cabinClass: input.cabinClass,
|
|
134
|
+
maxConnections: input.maxConnections,
|
|
135
|
+
departureTime
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// Transform offers for better presentation
|
|
139
|
+
const offers = result.offers.map((offer: any) => {
|
|
140
|
+
const outboundSlice = offer.slices[0];
|
|
141
|
+
const returnSlice = offer.slices[1];
|
|
142
|
+
|
|
143
|
+
return {
|
|
144
|
+
id: offer.id,
|
|
145
|
+
totalAmount: offer.total_amount,
|
|
146
|
+
totalCurrency: offer.total_currency,
|
|
147
|
+
expiresAt: offer.expires_at,
|
|
148
|
+
|
|
149
|
+
// Outbound flight details
|
|
150
|
+
outbound: {
|
|
151
|
+
origin: outboundSlice.origin.iata_code,
|
|
152
|
+
destination: outboundSlice.destination.iata_code,
|
|
153
|
+
departureTime: outboundSlice.segments[0].departing_at,
|
|
154
|
+
arrivalTime: outboundSlice.segments[outboundSlice.segments.length - 1].arriving_at,
|
|
155
|
+
duration: outboundSlice.duration,
|
|
156
|
+
stops: outboundSlice.segments.length - 1,
|
|
157
|
+
airline: outboundSlice.segments[0].marketing_carrier.name,
|
|
158
|
+
flightNumber: outboundSlice.segments[0].marketing_carrier_flight_number,
|
|
159
|
+
segments: outboundSlice.segments.map((seg: any) => ({
|
|
160
|
+
origin: seg.origin.iata_code,
|
|
161
|
+
destination: seg.destination.iata_code,
|
|
162
|
+
departingAt: seg.departing_at,
|
|
163
|
+
arrivingAt: seg.arriving_at,
|
|
164
|
+
airline: seg.marketing_carrier.name,
|
|
165
|
+
flightNumber: seg.marketing_carrier_flight_number,
|
|
166
|
+
aircraft: seg.aircraft?.name
|
|
167
|
+
}))
|
|
168
|
+
},
|
|
169
|
+
|
|
170
|
+
// Return flight details (if round trip)
|
|
171
|
+
...(returnSlice && {
|
|
172
|
+
return: {
|
|
173
|
+
origin: returnSlice.origin.iata_code,
|
|
174
|
+
destination: returnSlice.destination.iata_code,
|
|
175
|
+
departureTime: returnSlice.segments[0].departing_at,
|
|
176
|
+
arrivalTime: returnSlice.segments[returnSlice.segments.length - 1].arriving_at,
|
|
177
|
+
duration: returnSlice.duration,
|
|
178
|
+
stops: returnSlice.segments.length - 1,
|
|
179
|
+
airline: returnSlice.segments[0].marketing_carrier.name,
|
|
180
|
+
flightNumber: returnSlice.segments[0].marketing_carrier_flight_number,
|
|
181
|
+
segments: returnSlice.segments.map((seg: any) => ({
|
|
182
|
+
origin: seg.origin.iata_code,
|
|
183
|
+
destination: seg.destination.iata_code,
|
|
184
|
+
departingAt: seg.departing_at,
|
|
185
|
+
arrivingAt: seg.arriving_at,
|
|
186
|
+
airline: seg.marketing_carrier.name,
|
|
187
|
+
flightNumber: seg.marketing_carrier_flight_number,
|
|
188
|
+
aircraft: seg.aircraft?.name
|
|
189
|
+
}))
|
|
190
|
+
}
|
|
191
|
+
}),
|
|
192
|
+
|
|
193
|
+
// Fare details
|
|
194
|
+
fareType: offer.passenger_identity_documents_required ? 'International' : 'Domestic',
|
|
195
|
+
refundable: offer.conditions?.refund_before_departure?.allowed || false,
|
|
196
|
+
changeable: offer.conditions?.change_before_departure?.allowed || false
|
|
197
|
+
};
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
ctx.logger.info('Flight search completed', {
|
|
201
|
+
user: ctx.auth?.subject,
|
|
202
|
+
offersFound: offers.length
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
return {
|
|
206
|
+
requestId: result.id,
|
|
207
|
+
searchParams: {
|
|
208
|
+
origin: input.origin.toUpperCase(),
|
|
209
|
+
destination: input.destination.toUpperCase(),
|
|
210
|
+
departureDate: input.departureDate,
|
|
211
|
+
returnDate: input.returnDate,
|
|
212
|
+
passengers: {
|
|
213
|
+
adults: adults,
|
|
214
|
+
children: children,
|
|
215
|
+
infants: infants
|
|
216
|
+
},
|
|
217
|
+
cabinClass: input.cabinClass
|
|
218
|
+
},
|
|
219
|
+
totalOffers: offers.length,
|
|
220
|
+
offers: offers.slice(0, 10), // Return top 10 offers
|
|
221
|
+
message: `Found ${offers.length} flight options. Showing top 10 results.`
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
@Tool({
|
|
226
|
+
name: 'get_flight_details',
|
|
227
|
+
description: 'Get detailed information about a specific flight offer including baggage allowance, fare conditions, and seat availability.',
|
|
228
|
+
inputSchema: z.object({
|
|
229
|
+
offerId: z.string().describe('The offer ID from search results')
|
|
230
|
+
}),
|
|
231
|
+
examples: {
|
|
232
|
+
request: {
|
|
233
|
+
offerId: 'off_123456'
|
|
234
|
+
},
|
|
235
|
+
response: {
|
|
236
|
+
id: 'off_123456',
|
|
237
|
+
totalAmount: '450.00',
|
|
238
|
+
totalCurrency: 'USD',
|
|
239
|
+
expiresAt: '2024-03-01T12:00:00Z',
|
|
240
|
+
slices: [
|
|
241
|
+
{
|
|
242
|
+
origin: {
|
|
243
|
+
code: 'JFK',
|
|
244
|
+
name: 'John F. Kennedy International Airport',
|
|
245
|
+
city: 'New York'
|
|
246
|
+
},
|
|
247
|
+
destination: {
|
|
248
|
+
code: 'LAX',
|
|
249
|
+
name: 'Los Angeles International Airport',
|
|
250
|
+
city: 'Los Angeles'
|
|
251
|
+
},
|
|
252
|
+
duration: 'PT6H30M',
|
|
253
|
+
segments: [
|
|
254
|
+
{
|
|
255
|
+
id: 'seg_123',
|
|
256
|
+
origin: 'JFK',
|
|
257
|
+
destination: 'LAX',
|
|
258
|
+
departingAt: '2024-03-15T08:00:00Z',
|
|
259
|
+
arrivingAt: '2024-03-15T14:30:00Z',
|
|
260
|
+
duration: 'PT6H30M',
|
|
261
|
+
airline: {
|
|
262
|
+
name: 'American Airlines',
|
|
263
|
+
code: 'AA',
|
|
264
|
+
flightNumber: '123'
|
|
265
|
+
},
|
|
266
|
+
aircraft: 'Boeing 777-300ER'
|
|
267
|
+
}
|
|
268
|
+
]
|
|
269
|
+
}
|
|
270
|
+
],
|
|
271
|
+
passengers: [
|
|
272
|
+
{
|
|
273
|
+
id: 'pas_123',
|
|
274
|
+
type: 'adult',
|
|
275
|
+
fareType: 'economy',
|
|
276
|
+
baggageAllowance: [
|
|
277
|
+
{
|
|
278
|
+
type: 'checked',
|
|
279
|
+
quantity: 1
|
|
280
|
+
},
|
|
281
|
+
{
|
|
282
|
+
type: 'carry_on',
|
|
283
|
+
quantity: 1
|
|
284
|
+
}
|
|
285
|
+
]
|
|
286
|
+
}
|
|
287
|
+
],
|
|
288
|
+
conditions: {
|
|
289
|
+
refundBeforeDeparture: {
|
|
290
|
+
allowed: false
|
|
291
|
+
},
|
|
292
|
+
changeBeforeDeparture: {
|
|
293
|
+
allowed: true,
|
|
294
|
+
penaltyAmount: '75.00',
|
|
295
|
+
penaltyCurrency: 'USD'
|
|
296
|
+
}
|
|
297
|
+
},
|
|
298
|
+
paymentRequirements: {
|
|
299
|
+
requiresInstantPayment: true,
|
|
300
|
+
priceGuaranteeExpiresAt: '2024-03-01T12:00:00Z'
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
})
|
|
305
|
+
@UseGuards(OAuthGuard)
|
|
306
|
+
@Widget('flight-details')
|
|
307
|
+
async getFlightDetails(input: any, ctx: ExecutionContext) {
|
|
308
|
+
ctx.logger.info('Getting flight details', {
|
|
309
|
+
user: ctx.auth?.subject,
|
|
310
|
+
offerId: input.offerId
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
const offer = await this.duffelService.getOffer(input.offerId);
|
|
314
|
+
|
|
315
|
+
return {
|
|
316
|
+
id: offer.id,
|
|
317
|
+
totalAmount: offer.total_amount,
|
|
318
|
+
totalCurrency: offer.total_currency,
|
|
319
|
+
expiresAt: offer.expires_at,
|
|
320
|
+
|
|
321
|
+
slices: offer.slices.map((slice: any) => ({
|
|
322
|
+
origin: {
|
|
323
|
+
code: slice.origin.iata_code,
|
|
324
|
+
name: slice.origin.name,
|
|
325
|
+
city: slice.origin.city_name
|
|
326
|
+
},
|
|
327
|
+
destination: {
|
|
328
|
+
code: slice.destination.iata_code,
|
|
329
|
+
name: slice.destination.name,
|
|
330
|
+
city: slice.destination.city_name
|
|
331
|
+
},
|
|
332
|
+
duration: slice.duration,
|
|
333
|
+
segments: slice.segments.map((seg: any) => ({
|
|
334
|
+
id: seg.id,
|
|
335
|
+
origin: seg.origin.iata_code,
|
|
336
|
+
destination: seg.destination.iata_code,
|
|
337
|
+
departingAt: seg.departing_at,
|
|
338
|
+
arrivingAt: seg.arriving_at,
|
|
339
|
+
duration: seg.duration,
|
|
340
|
+
airline: {
|
|
341
|
+
name: seg.marketing_carrier.name,
|
|
342
|
+
code: seg.marketing_carrier.iata_code,
|
|
343
|
+
flightNumber: seg.marketing_carrier_flight_number
|
|
344
|
+
},
|
|
345
|
+
aircraft: seg.aircraft?.name,
|
|
346
|
+
operatingCarrier: seg.operating_carrier?.name,
|
|
347
|
+
distance: seg.distance
|
|
348
|
+
}))
|
|
349
|
+
})),
|
|
350
|
+
|
|
351
|
+
passengers: offer.passengers.map((pax: any) => ({
|
|
352
|
+
id: pax.id,
|
|
353
|
+
type: pax.type,
|
|
354
|
+
fareType: pax.fare_type,
|
|
355
|
+
baggageAllowance: pax.baggages?.map((bag: any) => ({
|
|
356
|
+
type: bag.type,
|
|
357
|
+
quantity: bag.quantity
|
|
358
|
+
}))
|
|
359
|
+
})),
|
|
360
|
+
|
|
361
|
+
conditions: {
|
|
362
|
+
refundBeforeDeparture: {
|
|
363
|
+
allowed: offer.conditions?.refund_before_departure?.allowed || false,
|
|
364
|
+
penaltyAmount: offer.conditions?.refund_before_departure?.penalty_amount,
|
|
365
|
+
penaltyCurrency: offer.conditions?.refund_before_departure?.penalty_currency
|
|
366
|
+
},
|
|
367
|
+
changeBeforeDeparture: {
|
|
368
|
+
allowed: offer.conditions?.change_before_departure?.allowed || false,
|
|
369
|
+
penaltyAmount: offer.conditions?.change_before_departure?.penalty_amount,
|
|
370
|
+
penaltyCurrency: offer.conditions?.change_before_departure?.penalty_currency
|
|
371
|
+
}
|
|
372
|
+
},
|
|
373
|
+
|
|
374
|
+
paymentRequirements: {
|
|
375
|
+
requiresInstantPayment: offer.payment_requirements?.requires_instant_payment,
|
|
376
|
+
priceGuaranteeExpiresAt: offer.payment_requirements?.price_guarantee_expires_at,
|
|
377
|
+
paymentRequiredBy: offer.payment_requirements?.payment_required_by
|
|
378
|
+
}
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
@Tool({
|
|
383
|
+
name: 'search_airports',
|
|
384
|
+
description: 'Search for airports by city name or airport code. Useful for finding IATA codes.',
|
|
385
|
+
inputSchema: z.object({
|
|
386
|
+
query: z.string().min(2).describe('City name or airport code to search for')
|
|
387
|
+
}),
|
|
388
|
+
examples: {
|
|
389
|
+
request: {
|
|
390
|
+
query: 'London'
|
|
391
|
+
},
|
|
392
|
+
response: {
|
|
393
|
+
query: 'London',
|
|
394
|
+
results: [
|
|
395
|
+
{
|
|
396
|
+
id: 'arp_lhr_gb',
|
|
397
|
+
name: 'Heathrow Airport',
|
|
398
|
+
iataCode: 'LHR',
|
|
399
|
+
icaoCode: 'EGLL',
|
|
400
|
+
cityName: 'London',
|
|
401
|
+
type: 'airport',
|
|
402
|
+
latitude: 51.4700,
|
|
403
|
+
longitude: -0.4543,
|
|
404
|
+
timeZone: 'Europe/London'
|
|
405
|
+
},
|
|
406
|
+
{
|
|
407
|
+
id: 'arp_lgw_gb',
|
|
408
|
+
name: 'Gatwick Airport',
|
|
409
|
+
iataCode: 'LGW',
|
|
410
|
+
icaoCode: 'EGKK',
|
|
411
|
+
cityName: 'London',
|
|
412
|
+
type: 'airport',
|
|
413
|
+
latitude: 51.1537,
|
|
414
|
+
longitude: -0.1821,
|
|
415
|
+
timeZone: 'Europe/London'
|
|
416
|
+
},
|
|
417
|
+
{
|
|
418
|
+
id: 'arp_stn_gb',
|
|
419
|
+
name: 'Stansted Airport',
|
|
420
|
+
iataCode: 'STN',
|
|
421
|
+
icaoCode: 'EGSS',
|
|
422
|
+
cityName: 'London',
|
|
423
|
+
type: 'airport',
|
|
424
|
+
latitude: 51.8860,
|
|
425
|
+
longitude: 0.2389,
|
|
426
|
+
timeZone: 'Europe/London'
|
|
427
|
+
}
|
|
428
|
+
]
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
})
|
|
432
|
+
@UseGuards(OAuthGuard)
|
|
433
|
+
@Widget('airport-search')
|
|
434
|
+
async searchAirports(input: any, ctx: ExecutionContext) {
|
|
435
|
+
ctx.logger.info('Searching airports', {
|
|
436
|
+
user: ctx.auth?.subject,
|
|
437
|
+
query: input.query
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
const places = await this.duffelService.searchAirports(input.query);
|
|
441
|
+
|
|
442
|
+
return {
|
|
443
|
+
query: input.query,
|
|
444
|
+
results: places.slice(0, 10).map((place: any) => ({
|
|
445
|
+
id: place.id,
|
|
446
|
+
name: place.name,
|
|
447
|
+
iataCode: place.iata_code,
|
|
448
|
+
icaoCode: place.icao_code,
|
|
449
|
+
cityName: place.city_name,
|
|
450
|
+
type: place.type,
|
|
451
|
+
latitude: place.latitude,
|
|
452
|
+
longitude: place.longitude,
|
|
453
|
+
timeZone: place.time_zone
|
|
454
|
+
}))
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
}
|