@omnixhq/ucp-client 0.1.1

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/dist/index.js ADDED
@@ -0,0 +1,996 @@
1
+ import { z } from "zod";
2
+ import { randomUUID } from "node:crypto";
3
+ import { BuyerSchema, CheckoutResponseStatusSchema, ExtendedCheckoutCreateRequestSchema, ExtendedCheckoutResponseSchema, ExtendedCheckoutUpdateRequestSchema, FulfillmentMethodResponseSchema, FulfillmentResponseSchema, ItemResponseSchema, LineItemResponseSchema, MessageErrorSchema, MessageSchema, OrderSchema, PaymentHandlerResponseSchema, PaymentInstrumentSchema, PaymentResponseSchema, PostalAddressSchema, TotalResponseSchema, UcpDiscoveryProfileSchema } from "@ucp-js/sdk";
4
+
5
+ //#region src/errors.ts
6
+ /** Thrown when the gateway returns an error response with `messages[]`. */
7
+ var UCPError = class extends Error {
8
+ code;
9
+ type;
10
+ statusCode;
11
+ /** JSONPath to the field that caused the error, from the first message. */
12
+ path;
13
+ contentType;
14
+ /** All messages from the gateway response. */
15
+ messages;
16
+ constructor(code, message, type = "error", statusCode = 400, options = {}) {
17
+ super(message);
18
+ this.name = "UCPError";
19
+ this.code = code;
20
+ this.type = type;
21
+ this.statusCode = statusCode;
22
+ this.path = options.path;
23
+ this.contentType = options.contentType;
24
+ this.messages = options.messages ?? [];
25
+ }
26
+ };
27
+ /** Thrown on HTTP 409 when no `messages[]` body is present (idempotency key collision). */
28
+ var UCPIdempotencyConflictError = class extends UCPError {
29
+ constructor(message = "Idempotency key reused with different request body") {
30
+ super("IDEMPOTENCY_CONFLICT", message, "error", 409);
31
+ this.name = "UCPIdempotencyConflictError";
32
+ }
33
+ };
34
+ /** Thrown when a checkout response has `status: 'requires_escalation'` with a `continue_url`. */
35
+ var UCPEscalationError = class extends Error {
36
+ /** The URL to redirect the buyer to for merchant-hosted checkout UI. */
37
+ continue_url;
38
+ constructor(continue_url, message = "Payment requires escalation") {
39
+ super(message);
40
+ this.name = "UCPEscalationError";
41
+ this.continue_url = continue_url;
42
+ }
43
+ };
44
+ /** Thrown when an OAuth token exchange, refresh, or revocation fails. */
45
+ var UCPOAuthError = class extends Error {
46
+ statusCode;
47
+ constructor(message, statusCode) {
48
+ super(message);
49
+ this.name = "UCPOAuthError";
50
+ this.statusCode = statusCode;
51
+ }
52
+ };
53
+
54
+ //#endregion
55
+ //#region src/http.ts
56
+ var HttpClient = class HttpClient {
57
+ gatewayUrl;
58
+ agentProfileUrl;
59
+ ucpVersion;
60
+ requestSignature;
61
+ accessToken;
62
+ onValidationWarning;
63
+ constructor(config) {
64
+ this.gatewayUrl = config.gatewayUrl;
65
+ this.agentProfileUrl = config.agentProfileUrl;
66
+ this.ucpVersion = config.ucpVersion;
67
+ this.requestSignature = config.requestSignature;
68
+ this.accessToken = config.accessToken;
69
+ this.onValidationWarning = config.onValidationWarning ?? ((msg, detail) => console.warn(msg, detail));
70
+ }
71
+ withAccessToken(token) {
72
+ return new HttpClient({
73
+ gatewayUrl: this.gatewayUrl,
74
+ agentProfileUrl: this.agentProfileUrl,
75
+ ucpVersion: this.ucpVersion,
76
+ ...this.requestSignature !== void 0 ? { requestSignature: this.requestSignature } : {},
77
+ accessToken: token,
78
+ onValidationWarning: this.onValidationWarning
79
+ });
80
+ }
81
+ async request(method, path, body) {
82
+ const url = `${this.gatewayUrl}${path}`;
83
+ const requestId = randomUUID();
84
+ const headers = {
85
+ "UCP-Agent": `profile="${this.agentProfileUrl}", version="${this.ucpVersion}"`,
86
+ "request-id": requestId
87
+ };
88
+ if (body !== void 0) headers["Content-Type"] = "application/json";
89
+ if (this.requestSignature !== void 0) headers["request-signature"] = this.requestSignature;
90
+ if (this.accessToken !== void 0) headers["Authorization"] = `Bearer ${this.accessToken}`;
91
+ if (method === "POST" || method === "PUT") headers["idempotency-key"] = randomUUID();
92
+ const res = await fetch(url, {
93
+ method,
94
+ headers,
95
+ ...body !== void 0 ? { body: JSON.stringify(body) } : {}
96
+ });
97
+ const data = await res.json().catch(() => ({}));
98
+ if (!res.ok) this.throwFromResponse(data, res.status);
99
+ return data;
100
+ }
101
+ validate(data, schema) {
102
+ const result = schema.safeParse(data);
103
+ if (!result.success) {
104
+ this.onValidationWarning("[UCPClient] Response validation failed:", result.error.message);
105
+ return data;
106
+ }
107
+ return result.data;
108
+ }
109
+ throwFromResponse(data, statusCode) {
110
+ if (typeof data !== "object" || data === null) {
111
+ if (statusCode === 409) throw new UCPIdempotencyConflictError();
112
+ throw new UCPError("HTTP_ERROR", `Gateway returned ${statusCode}`, "error", statusCode);
113
+ }
114
+ const body = data;
115
+ const rawMessages = body["messages"];
116
+ if (Array.isArray(rawMessages) && rawMessages.length > 0) {
117
+ const allMessages = parseMessages(rawMessages);
118
+ const first = allMessages[0];
119
+ const code = first.code ?? "UNKNOWN";
120
+ throw new UCPError(code, first.content, first.type, statusCode, {
121
+ ...first.path !== void 0 ? { path: first.path } : {},
122
+ ...first.content_type !== void 0 ? { contentType: first.content_type } : {},
123
+ messages: allMessages
124
+ });
125
+ }
126
+ if (statusCode === 409) throw new UCPIdempotencyConflictError();
127
+ throw new UCPError("HTTP_ERROR", `Gateway returned ${statusCode}`, "error", statusCode);
128
+ }
129
+ };
130
+ function parseMessages(rawMessages) {
131
+ return rawMessages.map((m) => {
132
+ const record = m;
133
+ const rawType = String(record["type"] ?? "error");
134
+ const validTypes = [
135
+ "error",
136
+ "warning",
137
+ "info"
138
+ ];
139
+ const type = validTypes.includes(rawType) ? rawType : "error";
140
+ return {
141
+ type,
142
+ content: String(record["content"] ?? "Unknown error"),
143
+ ...record["code"] !== void 0 ? { code: String(record["code"]) } : {},
144
+ ...record["severity"] !== void 0 ? { severity: String(record["severity"]) } : {},
145
+ ...record["path"] !== void 0 ? { path: String(record["path"]) } : {},
146
+ ...record["content_type"] !== void 0 ? { content_type: String(record["content_type"]) } : {}
147
+ };
148
+ });
149
+ }
150
+
151
+ //#endregion
152
+ //#region src/schemas.ts
153
+ const CheckoutSessionSchema = ExtendedCheckoutResponseSchema.passthrough();
154
+ const UCPProfileSchema = UcpDiscoveryProfileSchema.passthrough();
155
+ const UCPProductSchema = z.object({
156
+ id: z.string(),
157
+ title: z.string(),
158
+ description: z.string().nullable(),
159
+ price_cents: z.number().int(),
160
+ currency: z.string().min(3).max(3),
161
+ in_stock: z.boolean(),
162
+ stock_quantity: z.number().int().min(0),
163
+ images: z.array(z.string().url()),
164
+ variants: z.array(z.object({
165
+ id: z.string(),
166
+ title: z.string(),
167
+ price_cents: z.number().int(),
168
+ in_stock: z.boolean(),
169
+ attributes: z.record(z.string())
170
+ }).passthrough())
171
+ }).passthrough();
172
+ const UCPOrderSchema = z.object({
173
+ id: z.string(),
174
+ status: z.enum([
175
+ "pending",
176
+ "processing",
177
+ "shipped",
178
+ "delivered",
179
+ "canceled"
180
+ ]),
181
+ total_cents: z.number().int(),
182
+ currency: z.string().min(3).max(3),
183
+ created_at_iso: z.string().datetime({ offset: true })
184
+ }).passthrough();
185
+ const CreateCheckoutRequestSchema = ExtendedCheckoutCreateRequestSchema.passthrough();
186
+ const UpdateCheckoutRequestSchema = ExtendedCheckoutUpdateRequestSchema.passthrough();
187
+ const CompleteCheckoutRequestSchema = z.object({ payment: z.object({ instruments: z.array(z.object({
188
+ id: z.string(),
189
+ handler_id: z.string(),
190
+ type: z.string(),
191
+ selected: z.boolean().optional(),
192
+ credential: z.object({
193
+ type: z.string(),
194
+ token: z.string().optional()
195
+ }).optional(),
196
+ billing_address: z.unknown().optional()
197
+ }).passthrough()) }) });
198
+
199
+ //#endregion
200
+ //#region src/capabilities/checkout.ts
201
+ const DEFAULT_METHOD_ID = "default";
202
+ const DEFAULT_GROUP_ID = "default";
203
+ /**
204
+ * Checkout session operations. Available when the server declares `dev.ucp.shopping.checkout`.
205
+ * Check `extensions` to see which optional features (fulfillment, discount, etc.) are supported.
206
+ */
207
+ var CheckoutCapability = class {
208
+ /** Which checkout extensions the server supports. */
209
+ extensions;
210
+ constructor(http, extensions) {
211
+ this.http = http;
212
+ this.extensions = extensions;
213
+ }
214
+ async create(payload) {
215
+ const data = await this.http.request("POST", "/checkout-sessions", payload);
216
+ return this.validateSession(data);
217
+ }
218
+ async get(id) {
219
+ const data = await this.http.request("GET", `/checkout-sessions/${encodeURIComponent(id)}`);
220
+ return this.validateSession(data);
221
+ }
222
+ async update(id, patch) {
223
+ const data = await this.http.request("PUT", `/checkout-sessions/${encodeURIComponent(id)}`, patch);
224
+ return this.validateSession(data);
225
+ }
226
+ async complete(id, payload) {
227
+ const data = await this.http.request("POST", `/checkout-sessions/${encodeURIComponent(id)}/complete`, payload);
228
+ return this.validateSession(data);
229
+ }
230
+ async cancel(id) {
231
+ const data = await this.http.request("POST", `/checkout-sessions/${encodeURIComponent(id)}/cancel`);
232
+ return this.validateSession(data);
233
+ }
234
+ async setFulfillment(id, type, patch) {
235
+ return this.update(id, {
236
+ ...patch,
237
+ fulfillment: { methods: [{
238
+ id: DEFAULT_METHOD_ID,
239
+ type
240
+ }] }
241
+ });
242
+ }
243
+ async selectDestination(id, destinationId, fulfillmentType = "shipping", patch) {
244
+ return this.update(id, {
245
+ ...patch,
246
+ fulfillment: { methods: [{
247
+ id: DEFAULT_METHOD_ID,
248
+ type: fulfillmentType,
249
+ selected_destination_id: destinationId
250
+ }] }
251
+ });
252
+ }
253
+ async selectFulfillmentOption(id, optionId, destinationId, fulfillmentType = "shipping", patch) {
254
+ return this.update(id, {
255
+ ...patch,
256
+ fulfillment: { methods: [{
257
+ id: DEFAULT_METHOD_ID,
258
+ type: fulfillmentType,
259
+ ...destinationId !== void 0 ? { selected_destination_id: destinationId } : {},
260
+ groups: [{
261
+ id: DEFAULT_GROUP_ID,
262
+ selected_option_id: optionId
263
+ }]
264
+ }] }
265
+ });
266
+ }
267
+ async applyDiscountCodes(id, codes, patch) {
268
+ return this.update(id, {
269
+ ...patch,
270
+ discounts: { codes: [...codes] }
271
+ });
272
+ }
273
+ validateSession(data) {
274
+ const session = this.http.validate(data, CheckoutSessionSchema);
275
+ if (session.status === "requires_escalation" && session.continue_url) throw new UCPEscalationError(session.continue_url);
276
+ return session;
277
+ }
278
+ };
279
+
280
+ //#endregion
281
+ //#region src/capabilities/order.ts
282
+ /** Order operations. Available when the server declares `dev.ucp.shopping.order`. */
283
+ var OrderCapability = class {
284
+ constructor(http) {
285
+ this.http = http;
286
+ }
287
+ /** Retrieve an order by ID. Returns the UCP spec-compliant Order object. */
288
+ async get(id) {
289
+ const data = await this.http.request("GET", `/orders/${encodeURIComponent(id)}`);
290
+ return this.http.validate(data, OrderSchema);
291
+ }
292
+ };
293
+
294
+ //#endregion
295
+ //#region src/capabilities/identity-linking.ts
296
+ const TokenResponseSchema = z.object({
297
+ access_token: z.string(),
298
+ token_type: z.string(),
299
+ expires_in: z.number().optional(),
300
+ refresh_token: z.string().optional(),
301
+ scope: z.string().optional()
302
+ }).passthrough();
303
+ /**
304
+ * OAuth 2.0 identity linking for account linking between platforms and merchants.
305
+ * Available when the server declares `dev.ucp.common.identity_linking`.
306
+ */
307
+ var IdentityLinkingCapability = class {
308
+ constructor(metadata) {
309
+ this.metadata = metadata;
310
+ }
311
+ /** Build the OAuth authorization URL to redirect the buyer to. */
312
+ getAuthorizationUrl(params) {
313
+ const url = new URL(this.metadata.authorization_endpoint);
314
+ url.searchParams.set("response_type", "code");
315
+ url.searchParams.set("client_id", params.client_id);
316
+ url.searchParams.set("redirect_uri", params.redirect_uri);
317
+ url.searchParams.set("scope", params.scope ?? "ucp:scopes:checkout_session");
318
+ if (params.state !== void 0) url.searchParams.set("state", params.state);
319
+ return url.toString();
320
+ }
321
+ async exchangeCode(params) {
322
+ const body = new URLSearchParams({
323
+ grant_type: "authorization_code",
324
+ code: params.code,
325
+ redirect_uri: params.redirect_uri
326
+ });
327
+ return this.tokenRequest(params.client_id, params.client_secret, body);
328
+ }
329
+ async refreshToken(params) {
330
+ const body = new URLSearchParams({
331
+ grant_type: "refresh_token",
332
+ refresh_token: params.refresh_token
333
+ });
334
+ return this.tokenRequest(params.client_id, params.client_secret, body);
335
+ }
336
+ async revokeToken(params) {
337
+ const body = new URLSearchParams({ token: params.token });
338
+ if (params.token_type_hint !== void 0) body.set("token_type_hint", params.token_type_hint);
339
+ const res = await fetch(this.metadata.revocation_endpoint, {
340
+ method: "POST",
341
+ headers: {
342
+ "Content-Type": "application/x-www-form-urlencoded",
343
+ Authorization: encodeBasicAuth(params.client_id, params.client_secret)
344
+ },
345
+ body: body.toString()
346
+ });
347
+ if (!res.ok) throw new UCPOAuthError(`Token revocation failed: ${res.status}`, res.status);
348
+ }
349
+ getMetadata() {
350
+ return this.metadata;
351
+ }
352
+ async tokenRequest(clientId, clientSecret, body) {
353
+ const res = await fetch(this.metadata.token_endpoint, {
354
+ method: "POST",
355
+ headers: {
356
+ "Content-Type": "application/x-www-form-urlencoded",
357
+ Authorization: encodeBasicAuth(clientId, clientSecret)
358
+ },
359
+ body: body.toString()
360
+ });
361
+ if (!res.ok) throw new UCPOAuthError(`Token exchange failed with status ${res.status}`, res.status);
362
+ const raw = await res.json();
363
+ const parsed = TokenResponseSchema.safeParse(raw);
364
+ if (!parsed.success) throw new UCPOAuthError(`Invalid token response: ${parsed.error.message}`, res.status);
365
+ return parsed.data;
366
+ }
367
+ };
368
+ function encodeBasicAuth(username, password) {
369
+ return `Basic ${Buffer.from(`${username}:${password}`).toString("base64")}`;
370
+ }
371
+
372
+ //#endregion
373
+ //#region src/capabilities/products.ts
374
+ /** Product catalog search and retrieval. Always available (gateway-specific, not part of UCP spec). */
375
+ var ProductsCapability = class {
376
+ constructor(http) {
377
+ this.http = http;
378
+ }
379
+ /** Search products by query string with optional filters. */
380
+ async search(query, filters = {}) {
381
+ const params = new URLSearchParams({ q: query });
382
+ const filterEntries = [
383
+ ["max_price_cents", filters.max_price_cents],
384
+ ["min_price_cents", filters.min_price_cents],
385
+ ["in_stock", filters.in_stock],
386
+ ["category", filters.category],
387
+ ["limit", filters.limit],
388
+ ["page", filters.page]
389
+ ];
390
+ for (const [key, value] of filterEntries) if (value != null) params.set(key, String(value));
391
+ const res = await this.http.request("GET", `/ucp/products?${params.toString()}`);
392
+ const data = res;
393
+ const products = Array.isArray(data) ? data : data.products ?? [];
394
+ return products.map((p) => this.http.validate(p, UCPProductSchema));
395
+ }
396
+ async get(id) {
397
+ const data = await this.http.request("GET", `/ucp/products/${encodeURIComponent(id)}`);
398
+ return this.http.validate(data, UCPProductSchema);
399
+ }
400
+ };
401
+
402
+ //#endregion
403
+ //#region src/types/config.ts
404
+ const DEFAULT_UCP_VERSION = "2026-01-23";
405
+ const UCP_CAPABILITIES = {
406
+ CHECKOUT: "dev.ucp.shopping.checkout",
407
+ FULFILLMENT: "dev.ucp.shopping.fulfillment",
408
+ DISCOUNT: "dev.ucp.shopping.discount",
409
+ BUYER_CONSENT: "dev.ucp.shopping.buyer_consent",
410
+ ORDER: "dev.ucp.shopping.order",
411
+ IDENTITY_LINKING: "dev.ucp.common.identity_linking",
412
+ AP2_MANDATE: "dev.ucp.shopping.ap2_mandate"
413
+ };
414
+
415
+ //#endregion
416
+ //#region src/agent-tools.ts
417
+ /**
418
+ * Returns ready-to-use tool definitions for agent registration.
419
+ * Only tools supported by the connected server are included.
420
+ *
421
+ * Each tool has:
422
+ * - `name` — unique tool identifier
423
+ * - `description` — what the tool does (for the LLM)
424
+ * - `parameters` — JSON Schema describing the expected input
425
+ * - `execute(params)` — function that calls the right capability method
426
+ *
427
+ * @example
428
+ * ```typescript
429
+ * const client = await UCPClient.connect(config);
430
+ * const tools = getAgentTools(client);
431
+ *
432
+ * // Register with Anthropic Claude API
433
+ * const response = await anthropic.messages.create({
434
+ * tools: tools.map(t => ({
435
+ * name: t.name,
436
+ * description: t.description,
437
+ * input_schema: t.parameters,
438
+ * })),
439
+ * // ...
440
+ * });
441
+ *
442
+ * // Execute tool calls
443
+ * for (const block of response.content) {
444
+ * if (block.type === 'tool_use') {
445
+ * const tool = tools.find(t => t.name === block.name);
446
+ * const result = await tool.execute(block.input);
447
+ * }
448
+ * }
449
+ * ```
450
+ */
451
+ function getAgentTools(client) {
452
+ const tools = [...productTools(client)];
453
+ if (client.checkout) {
454
+ tools.push(...checkoutTools(client));
455
+ if (client.checkout.extensions.fulfillment) tools.push(...fulfillmentTools(client));
456
+ if (client.checkout.extensions.discount) tools.push(...discountTools(client));
457
+ }
458
+ if (client.order) tools.push(...orderTools(client));
459
+ return tools;
460
+ }
461
+ function productTools(client) {
462
+ return [{
463
+ name: "search_products",
464
+ description: "Search the product catalog by query string. Returns matching products with prices, availability, and images.",
465
+ parameters: {
466
+ type: "object",
467
+ properties: {
468
+ query: {
469
+ type: "string",
470
+ description: "Search query (e.g., \"running shoes\")"
471
+ },
472
+ max_price_cents: {
473
+ type: "number",
474
+ description: "Maximum price in cents"
475
+ },
476
+ min_price_cents: {
477
+ type: "number",
478
+ description: "Minimum price in cents"
479
+ },
480
+ in_stock: {
481
+ type: "boolean",
482
+ description: "Filter to in-stock items only"
483
+ },
484
+ category: {
485
+ type: "string",
486
+ description: "Product category"
487
+ },
488
+ limit: {
489
+ type: "number",
490
+ description: "Max results to return"
491
+ }
492
+ },
493
+ required: ["query"]
494
+ },
495
+ execute: async (params) => {
496
+ const { query,...filters } = params;
497
+ return client.products.search(query, filters);
498
+ }
499
+ }, {
500
+ name: "get_product",
501
+ description: "Get detailed product information by ID, including variants, images, and stock.",
502
+ parameters: {
503
+ type: "object",
504
+ properties: { id: {
505
+ type: "string",
506
+ description: "Product ID"
507
+ } },
508
+ required: ["id"]
509
+ },
510
+ execute: async (params) => client.products.get(params["id"])
511
+ }];
512
+ }
513
+ function checkoutTools(client) {
514
+ return [
515
+ {
516
+ name: "create_checkout",
517
+ description: "Create a new checkout session with line items. Returns a session with an ID to use in subsequent calls.",
518
+ parameters: {
519
+ type: "object",
520
+ properties: {
521
+ line_items: {
522
+ type: "array",
523
+ description: "Products to purchase",
524
+ items: {
525
+ type: "object",
526
+ properties: {
527
+ item: {
528
+ type: "object",
529
+ properties: { id: {
530
+ type: "string",
531
+ description: "Product ID"
532
+ } },
533
+ required: ["id"]
534
+ },
535
+ quantity: {
536
+ type: "number",
537
+ description: "Quantity to purchase"
538
+ }
539
+ },
540
+ required: ["item", "quantity"]
541
+ }
542
+ },
543
+ currency: {
544
+ type: "string",
545
+ description: "ISO 4217 currency code (e.g., \"USD\")"
546
+ }
547
+ },
548
+ required: ["line_items"]
549
+ },
550
+ execute: async (params) => client.checkout.create(params)
551
+ },
552
+ {
553
+ name: "get_checkout",
554
+ description: "Get the current state of a checkout session, including status, totals, and available options.",
555
+ parameters: {
556
+ type: "object",
557
+ properties: { id: {
558
+ type: "string",
559
+ description: "Checkout session ID"
560
+ } },
561
+ required: ["id"]
562
+ },
563
+ execute: async (params) => client.checkout.get(params["id"])
564
+ },
565
+ {
566
+ name: "update_checkout",
567
+ description: "Update a checkout session with buyer information, shipping address, or payment details.",
568
+ parameters: {
569
+ type: "object",
570
+ properties: {
571
+ id: {
572
+ type: "string",
573
+ description: "Checkout session ID"
574
+ },
575
+ buyer: {
576
+ type: "object",
577
+ description: "Buyer contact information",
578
+ properties: {
579
+ first_name: { type: "string" },
580
+ last_name: { type: "string" },
581
+ email: { type: "string" },
582
+ phone_number: { type: "string" }
583
+ }
584
+ },
585
+ context: {
586
+ type: "object",
587
+ description: "Localization context for pricing and availability",
588
+ properties: {
589
+ address_country: {
590
+ type: "string",
591
+ description: "ISO 3166-1 alpha-2 country code"
592
+ },
593
+ address_region: {
594
+ type: "string",
595
+ description: "State or province"
596
+ },
597
+ postal_code: { type: "string" }
598
+ }
599
+ }
600
+ },
601
+ required: ["id"]
602
+ },
603
+ execute: async (params) => {
604
+ const { id,...patch } = params;
605
+ return client.checkout.update(id, patch);
606
+ }
607
+ },
608
+ {
609
+ name: "complete_checkout",
610
+ description: "Complete a checkout session with payment. Places the order. Returns the completed session with order ID.",
611
+ parameters: {
612
+ type: "object",
613
+ properties: {
614
+ id: {
615
+ type: "string",
616
+ description: "Checkout session ID"
617
+ },
618
+ payment: {
619
+ type: "object",
620
+ description: "Payment information",
621
+ properties: { instruments: {
622
+ type: "array",
623
+ items: {
624
+ type: "object",
625
+ properties: {
626
+ id: {
627
+ type: "string",
628
+ description: "Instrument ID"
629
+ },
630
+ handler_id: {
631
+ type: "string",
632
+ description: "Payment handler ID from the server profile"
633
+ },
634
+ type: {
635
+ type: "string",
636
+ description: "Payment type (e.g., \"card\", \"offline\")"
637
+ },
638
+ credential: {
639
+ type: "object",
640
+ properties: {
641
+ type: {
642
+ type: "string",
643
+ description: "Credential type (e.g., \"token\")"
644
+ },
645
+ token: {
646
+ type: "string",
647
+ description: "Payment token"
648
+ }
649
+ },
650
+ required: ["type"]
651
+ }
652
+ },
653
+ required: [
654
+ "id",
655
+ "handler_id",
656
+ "type"
657
+ ]
658
+ }
659
+ } },
660
+ required: ["instruments"]
661
+ }
662
+ },
663
+ required: ["id", "payment"]
664
+ },
665
+ execute: async (params) => {
666
+ const { id,...payload } = params;
667
+ return client.checkout.complete(String(id), payload);
668
+ }
669
+ },
670
+ {
671
+ name: "cancel_checkout",
672
+ description: "Cancel a checkout session. The session cannot be used after cancellation.",
673
+ parameters: {
674
+ type: "object",
675
+ properties: { id: {
676
+ type: "string",
677
+ description: "Checkout session ID"
678
+ } },
679
+ required: ["id"]
680
+ },
681
+ execute: async (params) => client.checkout.cancel(params["id"])
682
+ }
683
+ ];
684
+ }
685
+ function fulfillmentTools(client) {
686
+ return [
687
+ {
688
+ name: "set_fulfillment",
689
+ description: "Set the fulfillment method for a checkout (e.g., \"shipping\" or \"pickup\").",
690
+ parameters: {
691
+ type: "object",
692
+ properties: {
693
+ id: {
694
+ type: "string",
695
+ description: "Checkout session ID"
696
+ },
697
+ type: {
698
+ type: "string",
699
+ enum: ["shipping", "pickup"],
700
+ description: "Fulfillment method"
701
+ }
702
+ },
703
+ required: ["id", "type"]
704
+ },
705
+ execute: async (params) => client.checkout.setFulfillment(params["id"], params["type"])
706
+ },
707
+ {
708
+ name: "select_destination",
709
+ description: "Select a shipping destination for a checkout session.",
710
+ parameters: {
711
+ type: "object",
712
+ properties: {
713
+ id: {
714
+ type: "string",
715
+ description: "Checkout session ID"
716
+ },
717
+ destination_id: {
718
+ type: "string",
719
+ description: "Destination ID to select"
720
+ },
721
+ fulfillment_type: {
722
+ type: "string",
723
+ default: "shipping",
724
+ description: "Fulfillment type"
725
+ }
726
+ },
727
+ required: ["id", "destination_id"]
728
+ },
729
+ execute: async (params) => client.checkout.selectDestination(params["id"], params["destination_id"], params["fulfillment_type"] ?? "shipping")
730
+ },
731
+ {
732
+ name: "select_fulfillment_option",
733
+ description: "Select a fulfillment option (e.g., standard shipping, express shipping) for a checkout session.",
734
+ parameters: {
735
+ type: "object",
736
+ properties: {
737
+ id: {
738
+ type: "string",
739
+ description: "Checkout session ID"
740
+ },
741
+ option_id: {
742
+ type: "string",
743
+ description: "Fulfillment option ID to select"
744
+ },
745
+ destination_id: {
746
+ type: "string",
747
+ description: "Destination ID (if applicable)"
748
+ },
749
+ fulfillment_type: {
750
+ type: "string",
751
+ default: "shipping",
752
+ description: "Fulfillment type"
753
+ }
754
+ },
755
+ required: ["id", "option_id"]
756
+ },
757
+ execute: async (params) => client.checkout.selectFulfillmentOption(params["id"], params["option_id"], params["destination_id"], params["fulfillment_type"] ?? "shipping")
758
+ }
759
+ ];
760
+ }
761
+ function discountTools(client) {
762
+ return [{
763
+ name: "apply_discount_codes",
764
+ description: "Apply one or more discount codes to a checkout session.",
765
+ parameters: {
766
+ type: "object",
767
+ properties: {
768
+ id: {
769
+ type: "string",
770
+ description: "Checkout session ID"
771
+ },
772
+ codes: {
773
+ type: "array",
774
+ items: { type: "string" },
775
+ description: "Discount codes to apply"
776
+ }
777
+ },
778
+ required: ["id", "codes"]
779
+ },
780
+ execute: async (params) => client.checkout.applyDiscountCodes(params["id"], params["codes"])
781
+ }];
782
+ }
783
+ function orderTools(client) {
784
+ return [{
785
+ name: "get_order",
786
+ description: "Get order details by ID, including line items, fulfillment status, and tracking information.",
787
+ parameters: {
788
+ type: "object",
789
+ properties: { id: {
790
+ type: "string",
791
+ description: "Order ID"
792
+ } },
793
+ required: ["id"]
794
+ },
795
+ execute: async (params) => client.order.get(params["id"])
796
+ }];
797
+ }
798
+
799
+ //#endregion
800
+ //#region src/UCPClient.ts
801
+ /**
802
+ * Connect to a UCP server, discover its capabilities, and return a {@link ConnectedClient}.
803
+ *
804
+ * @example
805
+ * ```typescript
806
+ * const client = await connect({
807
+ * gatewayUrl: 'https://store.example.com/ucp',
808
+ * agentProfileUrl: 'https://platform.example.com/.well-known/ucp',
809
+ * });
810
+ *
811
+ * if (client.checkout) {
812
+ * const session = await client.checkout.create({ line_items: [...] });
813
+ * }
814
+ * ```
815
+ */
816
+ async function connect(config, options) {
817
+ validateConfig(config);
818
+ const http = new HttpClient({
819
+ gatewayUrl: config.gatewayUrl.replace(/\/+$/, ""),
820
+ agentProfileUrl: config.agentProfileUrl,
821
+ ucpVersion: config.ucpVersion ?? DEFAULT_UCP_VERSION,
822
+ ...config.requestSignature !== void 0 ? { requestSignature: config.requestSignature } : {},
823
+ ...options?.onValidationWarning !== void 0 ? { onValidationWarning: options.onValidationWarning } : {}
824
+ });
825
+ const rawProfile = await http.request("GET", "/.well-known/ucp");
826
+ const profile = http.validate(rawProfile, UCPProfileSchema);
827
+ const capabilityNames = extractCapabilityNames(profile);
828
+ const checkout = buildCheckoutCapability(http, capabilityNames);
829
+ const order = capabilityNames.has(UCP_CAPABILITIES.ORDER) ? new OrderCapability(http) : null;
830
+ const identityLinking = await buildIdentityLinking(config, capabilityNames);
831
+ const products = new ProductsCapability(http);
832
+ const paymentHandlers = extractPaymentHandlers(profile);
833
+ const client = {
834
+ profile,
835
+ checkout,
836
+ order,
837
+ identityLinking,
838
+ products,
839
+ paymentHandlers,
840
+ describeTools: () => buildToolDescriptors(checkout, order, identityLinking),
841
+ getAgentTools: () => getAgentTools(client)
842
+ };
843
+ return Object.freeze(client);
844
+ }
845
+ /**
846
+ * UCP client entry point. Use `UCPClient.connect()` to discover server capabilities
847
+ * and get a {@link ConnectedClient}.
848
+ *
849
+ * @example
850
+ * ```typescript
851
+ * const client = await UCPClient.connect({
852
+ * gatewayUrl: 'https://store.example.com/ucp',
853
+ * agentProfileUrl: 'https://platform.example.com/.well-known/ucp',
854
+ * });
855
+ * ```
856
+ */
857
+ var UCPClient = class {
858
+ constructor() {}
859
+ static connect = connect;
860
+ };
861
+ function validateConfig(config) {
862
+ new URL(config.gatewayUrl);
863
+ if (config.agentProfileUrl.includes("\"") || config.agentProfileUrl.includes("\n")) throw new Error("agentProfileUrl must not contain double quotes or newlines");
864
+ new URL(config.agentProfileUrl);
865
+ }
866
+ function extractCapabilityNames(profile) {
867
+ const capabilities = profile.ucp?.capabilities;
868
+ if (!Array.isArray(capabilities)) return new Set();
869
+ return new Set(capabilities.map((c) => c.name).filter((n) => n !== void 0));
870
+ }
871
+ const PaymentHandlerInstanceSchema = z.object({
872
+ id: z.string(),
873
+ version: z.string(),
874
+ spec: z.string(),
875
+ schema: z.string(),
876
+ config: z.record(z.unknown()).optional()
877
+ }).passthrough();
878
+ const PaymentHandlerMapSchema = z.record(z.array(PaymentHandlerInstanceSchema));
879
+ function extractPaymentHandlers(profile) {
880
+ const raw = profile["payment_handlers"];
881
+ if (typeof raw !== "object" || raw === null) return {};
882
+ const result = PaymentHandlerMapSchema.safeParse(raw);
883
+ if (!result.success) return {};
884
+ return result.data;
885
+ }
886
+ function buildCheckoutCapability(http, capabilityNames) {
887
+ if (!capabilityNames.has(UCP_CAPABILITIES.CHECKOUT)) return null;
888
+ const extensions = {
889
+ fulfillment: capabilityNames.has(UCP_CAPABILITIES.FULFILLMENT),
890
+ discount: capabilityNames.has(UCP_CAPABILITIES.DISCOUNT),
891
+ buyerConsent: capabilityNames.has(UCP_CAPABILITIES.BUYER_CONSENT),
892
+ ap2Mandate: capabilityNames.has(UCP_CAPABILITIES.AP2_MANDATE)
893
+ };
894
+ return new CheckoutCapability(http, extensions);
895
+ }
896
+ const OAuthServerMetadataSchema = z.object({
897
+ issuer: z.string(),
898
+ authorization_endpoint: z.string().url(),
899
+ token_endpoint: z.string().url(),
900
+ revocation_endpoint: z.string().url(),
901
+ scopes_supported: z.array(z.string()),
902
+ response_types_supported: z.array(z.string()),
903
+ grant_types_supported: z.array(z.string()),
904
+ token_endpoint_auth_methods_supported: z.array(z.string()),
905
+ service_documentation: z.string().url().optional()
906
+ }).passthrough();
907
+ async function buildIdentityLinking(config, capabilityNames) {
908
+ if (!capabilityNames.has(UCP_CAPABILITIES.IDENTITY_LINKING)) return null;
909
+ const gatewayUrl = config.gatewayUrl.replace(/\/+$/, "");
910
+ const metadataUrl = `${gatewayUrl}/.well-known/oauth-authorization-server`;
911
+ const res = await fetch(metadataUrl);
912
+ if (!res.ok) throw new Error(`Identity linking capability declared but OAuth metadata fetch failed: ${res.status} from ${metadataUrl}`);
913
+ const raw = await res.json();
914
+ const parsed = OAuthServerMetadataSchema.safeParse(raw);
915
+ if (!parsed.success) throw new Error(`Identity linking OAuth metadata validation failed: ${parsed.error.message}`);
916
+ return new IdentityLinkingCapability(parsed.data);
917
+ }
918
+ function buildToolDescriptors(checkout, order, identityLinking) {
919
+ const tools = [{
920
+ name: "search_products",
921
+ capability: "products",
922
+ description: "Search product catalog"
923
+ }, {
924
+ name: "get_product",
925
+ capability: "products",
926
+ description: "Get product by ID"
927
+ }];
928
+ if (checkout) {
929
+ tools.push({
930
+ name: "create_checkout",
931
+ capability: "checkout",
932
+ description: "Create a checkout session"
933
+ }, {
934
+ name: "get_checkout",
935
+ capability: "checkout",
936
+ description: "Get checkout session by ID"
937
+ }, {
938
+ name: "update_checkout",
939
+ capability: "checkout",
940
+ description: "Update a checkout session"
941
+ }, {
942
+ name: "complete_checkout",
943
+ capability: "checkout",
944
+ description: "Complete checkout with payment"
945
+ }, {
946
+ name: "cancel_checkout",
947
+ capability: "checkout",
948
+ description: "Cancel a checkout session"
949
+ });
950
+ if (checkout.extensions.fulfillment) tools.push({
951
+ name: "set_fulfillment",
952
+ capability: "checkout.fulfillment",
953
+ description: "Set fulfillment method (shipping/pickup)"
954
+ }, {
955
+ name: "select_destination",
956
+ capability: "checkout.fulfillment",
957
+ description: "Select shipping destination"
958
+ }, {
959
+ name: "select_fulfillment_option",
960
+ capability: "checkout.fulfillment",
961
+ description: "Select fulfillment option (e.g., express shipping)"
962
+ });
963
+ if (checkout.extensions.discount) tools.push({
964
+ name: "apply_discount_codes",
965
+ capability: "checkout.discount",
966
+ description: "Apply discount codes to checkout"
967
+ });
968
+ }
969
+ if (order) tools.push({
970
+ name: "get_order",
971
+ capability: "order",
972
+ description: "Get order by ID"
973
+ });
974
+ if (identityLinking) tools.push({
975
+ name: "get_authorization_url",
976
+ capability: "identity_linking",
977
+ description: "Get OAuth authorization URL for account linking"
978
+ }, {
979
+ name: "exchange_auth_code",
980
+ capability: "identity_linking",
981
+ description: "Exchange authorization code for access token"
982
+ }, {
983
+ name: "refresh_access_token",
984
+ capability: "identity_linking",
985
+ description: "Refresh an expired access token"
986
+ }, {
987
+ name: "revoke_token",
988
+ capability: "identity_linking",
989
+ description: "Revoke an access or refresh token"
990
+ });
991
+ return tools;
992
+ }
993
+
994
+ //#endregion
995
+ export { BuyerSchema, CheckoutCapability, CheckoutResponseStatusSchema, CheckoutSessionSchema, CompleteCheckoutRequestSchema, CreateCheckoutRequestSchema, DEFAULT_UCP_VERSION, FulfillmentMethodResponseSchema, FulfillmentResponseSchema, IdentityLinkingCapability, ItemResponseSchema, LineItemResponseSchema, MessageErrorSchema, MessageSchema, OrderCapability, PaymentHandlerResponseSchema, PaymentInstrumentSchema, PaymentResponseSchema, PostalAddressSchema, ProductsCapability, TotalResponseSchema, UCPClient, UCPError, UCPEscalationError, UCPIdempotencyConflictError, UCPOAuthError, UCPOrderSchema, UCPProductSchema, UCPProfileSchema, OrderSchema as UCPSpecOrderSchema, UCP_CAPABILITIES, UpdateCheckoutRequestSchema, connect, getAgentTools };
996
+ //# sourceMappingURL=index.js.map