@decocms/bindings 0.1.0 → 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/README.md CHANGED
@@ -216,12 +216,12 @@ const workflowBinding = createResourceBinding("workflow");
216
216
 
217
217
  ## Collection Bindings
218
218
 
219
- Collection bindings provide standardized CRUD + Search operations for SQL table-like collections, compatible with TanStack DB query-collection. They are designed for database tables where each entity has a unique URI and audit trail fields.
219
+ Collection bindings provide standardized CRUD + Search operations for SQL table-like collections, compatible with TanStack DB query-collection. They are designed for database tables where each entity has a unique ID and human-readable title.
220
220
 
221
221
  ### Key Features
222
222
 
223
223
  - **SQL Table-like Structure**: Represents database tables with standardized operations
224
- - **URI-based Identification**: Uses collection URIs in format `rsc://{connectionid}/{collection_name}/{item_id}`
224
+ - **Simple Identification**: Uses human-readable `id` and `title` fields
225
225
  - **Audit Trail**: All entities must include `created_at`, `updated_at`, `created_by`, and `updated_by` fields
226
226
  - **TanStack DB Compatible**: Works seamlessly with TanStack DB's query-collection LoadSubsetOptions
227
227
  - **Type-Safe**: Full TypeScript support with Zod validation
@@ -230,7 +230,8 @@ Collection bindings provide standardized CRUD + Search operations for SQL table-
230
230
 
231
231
  All collection entity schemas must extend `BaseCollectionEntitySchema`, which requires:
232
232
 
233
- - `uri`: Collection URI in format `rsc://{connectionid}/{collection_name}/{item_id}`
233
+ - `id`: Unique identifier for the entity (string)
234
+ - `title`: Human-readable title for the entity (string)
234
235
  - `created_at`: Creation timestamp (datetime string)
235
236
  - `updated_at`: Last update timestamp (datetime string)
236
237
  - `created_by`: User who created the entity (optional string)
@@ -247,13 +248,13 @@ import { createBindingChecker } from "@decocms/bindings";
247
248
 
248
249
  // Define your entity schema extending the base schema
249
250
  const TodoSchema = z.object({
250
- uri: z.string(), // Collection URI: rsc://{connectionid}/todos/{item_id}
251
+ id: z.string(), // Unique identifier
252
+ title: z.string(), // Human-readable title (from base schema)
251
253
  created_at: z.string().datetime(),
252
254
  updated_at: z.string().datetime(),
253
255
  created_by: z.string().optional(),
254
256
  updated_by: z.string().optional(),
255
257
  // Your custom fields
256
- title: z.string(),
257
258
  completed: z.boolean(),
258
259
  userId: z.number(),
259
260
  });
@@ -294,26 +295,26 @@ The `createCollectionBindings` function generates tool bindings based on the `re
294
295
  - Output: `items[]`, `totalCount?`, `hasMore?`
295
296
 
296
297
  2. **GET** - `DECO_COLLECTION_{NAME}_GET`
297
- - Get a single entity by URI
298
- - Input: `uri`
298
+ - Get a single entity by ID
299
+ - Input: `id` (string)
299
300
  - Output: `item | null`
300
301
 
301
302
  **Optional operations (excluded if `readOnly: true`):**
302
303
 
303
304
  3. **INSERT** - `DECO_COLLECTION_{NAME}_INSERT`
304
305
  - Create a new entity
305
- - Input: `data` (without uri, which is auto-generated)
306
- - Output: `item` (with generated URI)
306
+ - Input: `data` (id may be auto-generated by the server)
307
+ - Output: `item` (with generated id)
307
308
 
308
309
  4. **UPDATE** - `DECO_COLLECTION_{NAME}_UPDATE`
309
310
  - Update an existing entity
310
- - Input: `uri`, `data` (partial)
311
+ - Input: `id` (string), `data` (partial)
311
312
  - Output: `item`
312
313
 
313
314
  5. **DELETE** - `DECO_COLLECTION_{NAME}_DELETE`
314
315
  - Delete an entity
315
- - Input: `uri`
316
- - Output: `success`, `uri`
316
+ - Input: `id` (string)
317
+ - Output: `success` (boolean), `id` (string)
317
318
 
318
319
  ### Read-Only Collections
319
320
 
@@ -332,7 +333,7 @@ This is useful for collections that are managed externally or should not be modi
332
333
 
333
334
  ## Models Bindings
334
335
 
335
- Models bindings provide a well-known interface for AI model providers. They use collection bindings under the hood for LIST and GET operations, and add a streaming endpoint tool.
336
+ Models bindings provide a well-known interface for AI model providers. They use collection bindings under the hood for LIST and GET operations, with streaming endpoint information included directly in each model entity.
336
337
 
337
338
  ### Using Models Bindings
338
339
 
@@ -347,7 +348,6 @@ const modelsChecker = createBindingChecker(MODELS_BINDING);
347
348
  const availableTools = [
348
349
  { name: "DECO_COLLECTION_MODELS_LIST" },
349
350
  { name: "DECO_COLLECTION_MODELS_GET" },
350
- { name: "GET_STREAM_ENDPOINT" },
351
351
  ];
352
352
 
353
353
  const isImplemented = await modelsChecker.isImplementedBy(availableTools);
@@ -359,21 +359,16 @@ console.log(isImplemented); // true if all required tools are present
359
359
  The `MODELS_BINDING` includes:
360
360
 
361
361
  1. **DECO_COLLECTION_MODELS_LIST** (required)
362
- - List available AI models with their capabilities
362
+ - List available AI models with their capabilities and streaming endpoints
363
363
  - Uses collection binding LIST operation
364
364
  - Input: `where?`, `orderBy?`, `limit?`, `offset?`
365
- - Output: `items[]` (array of model entities)
365
+ - Output: `items[]` (array of model entities with endpoint info)
366
366
 
367
367
  2. **DECO_COLLECTION_MODELS_GET** (required)
368
- - Get a single model by URI
368
+ - Get a single model by ID
369
369
  - Uses collection binding GET operation
370
- - Input: `uri`
371
- - Output: `item | null`
372
-
373
- 3. **GET_STREAM_ENDPOINT** (required)
374
- - Get the streaming endpoint URL for chat completions
375
- - Input: `{}` (empty object, passthrough)
376
- - Output: `{ url?: string }` (optional URL)
370
+ - Input: `id` (string)
371
+ - Output: `item | null` (model entity with endpoint info)
377
372
 
378
373
  ### Model Entity Schema
379
374
 
@@ -381,21 +376,29 @@ Models follow the collection entity schema with additional model-specific fields
381
376
 
382
377
  ```typescript
383
378
  {
384
- uri: string; // Collection URI
385
- created_at: string; // Creation timestamp
386
- updated_at: string; // Last update timestamp
387
- created_by?: string; // User who created
388
- updated_by?: string; // User who last updated
389
- id: string; // Model ID
390
- model: string; // Model identifier
391
- name: string; // Display name
392
- logo: string | null; // Logo URL
393
- capabilities: string[]; // Array of capabilities
394
- contextWindow: number | null; // Context window size
395
- inputCost: number | null; // Input cost per token
396
- outputCost: number | null; // Output cost per token
397
- outputLimit: number | null; // Output limit
398
- description: string | null; // Model description
379
+ id: string; // Unique identifier (from base schema)
380
+ title: string; // Display name (from base schema)
381
+ created_at: string; // Creation timestamp
382
+ updated_at: string; // Last update timestamp
383
+ created_by?: string; // User who created
384
+ updated_by?: string; // User who last updated
385
+ logo: string | null; // Logo URL
386
+ description: string | null; // Model description
387
+ capabilities: string[]; // Array of capabilities
388
+ limits: { // Model limits
389
+ contextWindow: number; // Maximum context window size
390
+ maxOutputTokens: number; // Maximum output tokens
391
+ } | null;
392
+ costs: { // Model costs
393
+ input: number; // Cost per input token
394
+ output: number; // Cost per output token
395
+ } | null;
396
+ endpoint: { // Streaming endpoint information
397
+ url: string; // Endpoint URL
398
+ method: string; // HTTP method (default: "POST")
399
+ contentType: string; // Content type (default: "application/json")
400
+ stream: boolean; // Supports streaming (default: true)
401
+ } | null;
399
402
  }
400
403
  ```
401
404
 
@@ -405,7 +408,6 @@ Here's how you would implement the models binding in an MCP server:
405
408
 
406
409
  ```typescript
407
410
  import { MODELS_BINDING } from "@decocms/bindings/well-known/models";
408
- import { parseCollectionUri } from "@decocms/bindings/well-known/collections";
409
411
  import { impl } from "@decocms/sdk/mcp/bindings/binder";
410
412
 
411
413
  const modelTools = impl(MODELS_BINDING, [
@@ -420,26 +422,19 @@ const modelTools = impl(MODELS_BINDING, [
420
422
  skip: offset,
421
423
  });
422
424
 
425
+ // Include endpoint info in each model
423
426
  return { items, hasMore: items.length === limit };
424
427
  },
425
428
  },
426
429
  {
427
- description: "Get a model by URI",
428
- handler: async ({ uri }) => {
429
- const parsed = parseCollectionUri(uri);
430
+ description: "Get a model by ID",
431
+ handler: async ({ id }) => {
430
432
  const item = await db.models.findUnique({
431
- where: { id: parsed.itemId },
433
+ where: { id },
432
434
  });
433
435
  return { item };
434
436
  },
435
437
  },
436
- {
437
- description: "Get streaming endpoint",
438
- handler: async () => {
439
- // Return your streaming endpoint URL
440
- return { url: "https://api.example.com/v1/chat/completions" };
441
- },
442
- },
443
438
  ]);
444
439
  ```
445
440
 
@@ -487,30 +482,6 @@ The `orderBy` parameter supports multi-field sorting:
487
482
  ]
488
483
  ```
489
484
 
490
- ### Collection URI Helpers
491
-
492
- The package provides utility functions for working with collection URIs:
493
-
494
- ```typescript
495
- import {
496
- validateCollectionUri,
497
- parseCollectionUri,
498
- constructCollectionUri,
499
- } from "@decocms/bindings/well-known/collections";
500
-
501
- // Validate a URI
502
- const isValid = validateCollectionUri("rsc://conn-123/users/user-456");
503
- // true
504
-
505
- // Parse a URI into components
506
- const parsed = parseCollectionUri("rsc://conn-123/users/user-456");
507
- // { connectionId: "conn-123", collectionName: "users", itemId: "user-456" }
508
-
509
- // Construct a URI from components
510
- const uri = constructCollectionUri("conn-123", "users", "user-456");
511
- // "rsc://conn-123/users/user-456"
512
- ```
513
-
514
485
  ### TanStack DB Integration
515
486
 
516
487
  Collection bindings are designed to work with TanStack DB's query-collection. The `where` and `orderBy` expressions are compatible with TanStack DB's `LoadSubsetOptions`, allowing for efficient predicate push-down to your backend.
@@ -522,16 +493,15 @@ Here's how you would implement a collection binding in an MCP server:
522
493
  ```typescript
523
494
  import { z } from "zod";
524
495
  import { createCollectionBindings } from "@decocms/bindings/well-known/collections";
525
- import { parseCollectionUri, constructCollectionUri } from "@decocms/bindings/well-known/collections";
526
496
  import { impl } from "@decocms/sdk/mcp/bindings/binder";
527
497
 
528
498
  const TodoSchema = z.object({
529
- uri: z.string(),
499
+ id: z.string(),
500
+ title: z.string(), // From base schema
530
501
  created_at: z.string().datetime(),
531
502
  updated_at: z.string().datetime(),
532
503
  created_by: z.string().optional(),
533
504
  updated_by: z.string().optional(),
534
- title: z.string(),
535
505
  completed: z.boolean(),
536
506
  userId: z.number(),
537
507
  });
@@ -555,11 +525,10 @@ const todoTools = impl(TODO_COLLECTION_BINDING, [
555
525
  },
556
526
  },
557
527
  {
558
- description: "Get a todo by URI",
559
- handler: async ({ uri }) => {
560
- const parsed = parseCollectionUri(uri);
528
+ description: "Get a todo by ID",
529
+ handler: async ({ id }) => {
561
530
  const item = await db.todos.findUnique({
562
- where: { id: parsed.itemId },
531
+ where: { id },
563
532
  });
564
533
  return { item };
565
534
  },
@@ -567,21 +536,20 @@ const todoTools = impl(TODO_COLLECTION_BINDING, [
567
536
  {
568
537
  description: "Create a new todo",
569
538
  handler: async ({ data }) => {
570
- const item = await db.todos.create({ data });
571
- const uri = constructCollectionUri(
572
- connectionId,
573
- "todos",
574
- item.id
575
- );
576
- return { item: { ...item, uri } };
539
+ const item = await db.todos.create({
540
+ data: {
541
+ ...data,
542
+ id: data.id || generateId(), // Use provided ID or generate one
543
+ }
544
+ });
545
+ return { item };
577
546
  },
578
547
  },
579
548
  {
580
549
  description: "Update a todo",
581
- handler: async ({ uri, data }) => {
582
- const parsed = parseCollectionUri(uri);
550
+ handler: async ({ id, data }) => {
583
551
  const item = await db.todos.update({
584
- where: { id: parsed.itemId },
552
+ where: { id },
585
553
  data,
586
554
  });
587
555
  return { item };
@@ -589,10 +557,9 @@ const todoTools = impl(TODO_COLLECTION_BINDING, [
589
557
  },
590
558
  {
591
559
  description: "Delete a todo",
592
- handler: async ({ uri }) => {
593
- const parsed = parseCollectionUri(uri);
594
- await db.todos.delete({ where: { id: parsed.itemId } });
595
- return { success: true, uri };
560
+ handler: async ({ id }) => {
561
+ await db.todos.delete({ where: { id } });
562
+ return { success: true, id };
596
563
  },
597
564
  },
598
565
  ]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decocms/bindings",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "build": "tsup",
@@ -13,82 +13,16 @@ import type { ToolBinder } from "../core/binder";
13
13
  * - Compatible with TanStack DB query-collection
14
14
  * - Full TypeScript support with proper type constraints
15
15
  * - Support for filtering, sorting, and pagination
16
- * - Collection URI format: rsc://{connectionid}/{collection_name}/{item_id}
16
+ * - Simple id and title fields for human-readable identification
17
17
  */
18
18
 
19
- /**
20
- * Collection URI format validation
21
- * Format: rsc://{connectionid}/{collection_name}/{item_id}
22
- */
23
- export const CollectionUriSchema = z
24
- .string()
25
- .regex(
26
- /^rsc:\/\/[^/]+\/[^/]+\/.+$/,
27
- "Invalid collection URI format. Expected format: rsc://{connectionid}/{collection_name}/{item_id}",
28
- );
29
-
30
- /**
31
- * Utility function to validate a collection URI format
32
- *
33
- * @param uri - The URI to validate
34
- * @returns True if the URI is valid, false otherwise
35
- */
36
- export function validateCollectionUri(uri: string): boolean {
37
- return CollectionUriSchema.safeParse(uri).success;
38
- }
39
-
40
- /**
41
- * Utility function to parse a collection URI into its components
42
- *
43
- * @param uri - The URI to parse
44
- * @returns Object containing the parsed components or null if invalid
45
- */
46
- export function parseCollectionUri(uri: string): {
47
- connectionId: string;
48
- collectionName: string;
49
- itemId: string;
50
- } | null {
51
- const result = CollectionUriSchema.safeParse(uri);
52
- if (!result.success) {
53
- return null;
54
- }
55
-
56
- const match = result.data.match(/^rsc:\/\/([^/]+)\/([^/]+)\/(.+)$/);
57
- if (!match) {
58
- return null;
59
- }
60
-
61
- return {
62
- connectionId: decodeURIComponent(match[1]),
63
- collectionName: decodeURIComponent(match[2]),
64
- itemId: decodeURIComponent(match[3]),
65
- };
66
- }
67
-
68
- /**
69
- * Utility function to construct a collection URI from components
70
- *
71
- * @param connectionId - The connection identifier
72
- * @param collectionName - The collection name
73
- * @param itemId - The item identifier
74
- * @returns The constructed collection URI
75
- */
76
- export function constructCollectionUri(
77
- connectionId: string,
78
- collectionName: string,
79
- itemId: string,
80
- ): string {
81
- return `rsc://${encodeURIComponent(connectionId)}/${
82
- encodeURIComponent(collectionName)
83
- }/${encodeURIComponent(itemId)}`;
84
- }
85
-
86
19
  /**
87
20
  * Base schema for collection entities
88
- * All collection entities must have a uri field and audit trail fields
21
+ * All collection entities must have an id, title, and audit trail fields
89
22
  */
90
23
  export const BaseCollectionEntitySchema = z.object({
91
- uri: z.string(),
24
+ id: z.string().describe("Unique identifier for the entity"),
25
+ title: z.string().describe("Human-readable title for the entity"),
92
26
  created_at: z.string().datetime(),
93
27
  updated_at: z.string().datetime(),
94
28
  created_by: z.string().optional(),
@@ -200,10 +134,10 @@ export function createCollectionListOutputSchema<T extends z.ZodTypeAny>(
200
134
  }
201
135
 
202
136
  /**
203
- * Get by URI input schema
137
+ * Get by ID input schema
204
138
  */
205
139
  export const CollectionGetInputSchema = z.object({
206
- uri: CollectionUriSchema.describe("URI of the entity to retrieve"),
140
+ id: z.string().describe("ID of the entity to retrieve"),
207
141
  });
208
142
 
209
143
  /**
@@ -225,9 +159,9 @@ export function createCollectionGetOutputSchema<T extends z.ZodTypeAny>(
225
159
  export function createCollectionInsertInputSchema<T extends z.ZodTypeAny>(
226
160
  entitySchema: T,
227
161
  ) {
228
- // Remove uri field since it's auto-generated from connectionId/collectionName/itemId
162
+ // Remove id field since it may be auto-generated by the server
229
163
  return z.object({
230
- data: entitySchema.describe("Data for the new entity (without uri)"),
164
+ data: entitySchema.describe("Data for the new entity (id may be auto-generated)"),
231
165
  });
232
166
  }
233
167
 
@@ -238,7 +172,7 @@ export function createCollectionInsertOutputSchema<T extends z.ZodTypeAny>(
238
172
  entitySchema: T,
239
173
  ) {
240
174
  return z.object({
241
- item: entitySchema.describe("The created entity with generated URI"),
175
+ item: entitySchema.describe("The created entity with generated id"),
242
176
  });
243
177
  }
244
178
 
@@ -249,7 +183,7 @@ export function createCollectionUpdateInputSchema<T extends z.ZodTypeAny>(
249
183
  entitySchema: T,
250
184
  ) {
251
185
  return z.object({
252
- uri: CollectionUriSchema.describe("URI of the entity to update"),
186
+ id: z.string().describe("ID of the entity to update"),
253
187
  data: (entitySchema as unknown as z.AnyZodObject)
254
188
  .partial()
255
189
  .describe("Partial entity data to update"),
@@ -271,7 +205,7 @@ export function createCollectionUpdateOutputSchema<T extends z.ZodTypeAny>(
271
205
  * Delete input schema
272
206
  */
273
207
  export const CollectionDeleteInputSchema = z.object({
274
- uri: CollectionUriSchema.describe("URI of the entity to delete"),
208
+ id: z.string().describe("ID of the entity to delete"),
275
209
  });
276
210
 
277
211
  /**
@@ -279,7 +213,7 @@ export const CollectionDeleteInputSchema = z.object({
279
213
  */
280
214
  export const CollectionDeleteOutputSchema = z.object({
281
215
  success: z.boolean().describe("Whether the deletion was successful"),
282
- uri: CollectionUriSchema.describe("URI of the deleted entity"),
216
+ id: z.string().describe("ID of the deleted entity"),
283
217
  });
284
218
 
285
219
  /**
@@ -299,7 +233,7 @@ export interface CollectionBindingOptions {
299
233
  * This function generates standardized tool bindings that work with any collection/table
300
234
  * by accepting a custom entity schema and collection name. The bindings provide:
301
235
  * - DECO_COLLECTION_{NAME}_LIST - Query/search entities with filtering and sorting (required)
302
- * - DECO_COLLECTION_{NAME}_GET - Get a single entity by URI (required)
236
+ * - DECO_COLLECTION_{NAME}_GET - Get a single entity by ID (required)
303
237
  * - DECO_COLLECTION_{NAME}_INSERT - Create a new entity (optional, excluded if readOnly=true)
304
238
  * - DECO_COLLECTION_{NAME}_UPDATE - Update an existing entity (optional, excluded if readOnly=true)
305
239
  * - DECO_COLLECTION_{NAME}_DELETE - Delete an entity (optional, excluded if readOnly=true)
@@ -312,12 +246,12 @@ export interface CollectionBindingOptions {
312
246
  * @example
313
247
  * ```typescript
314
248
  * const UserSchema = z.object({
315
- * uri: z.string(),
249
+ * id: z.string(),
250
+ * title: z.string(),
316
251
  * created_at: z.string().datetime(),
317
252
  * updated_at: z.string().datetime(),
318
253
  * created_by: z.string().optional(),
319
254
  * updated_by: z.string().optional(),
320
- * name: z.string(),
321
255
  * email: z.string().email(),
322
256
  * });
323
257
  *
@@ -393,7 +327,6 @@ export type CollectionTools<
393
327
  > = CollectionBinding<TEntitySchema>[number]["name"];
394
328
 
395
329
  // Export types for TypeScript usage
396
- export type CollectionUri = z.infer<typeof CollectionUriSchema>;
397
330
  export type CollectionListInput = z.infer<typeof CollectionListInputSchema>;
398
331
  export type CollectionGetInput = z.infer<typeof CollectionGetInputSchema>;
399
332
  export type CollectionDeleteInput = z.infer<typeof CollectionDeleteInputSchema>;
@@ -4,8 +4,8 @@
4
4
  * Defines the interface for AI model providers.
5
5
  * Any MCP that implements this binding can provide AI models and streaming endpoints.
6
6
  *
7
- * This binding uses collection bindings for LIST and GET operations (read-only),
8
- * and adds a GET_STREAM_ENDPOINT tool for streaming functionality.
7
+ * This binding uses collection bindings for LIST and GET operations (read-only).
8
+ * Streaming endpoint information is included directly in the model entity schema.
9
9
  */
10
10
 
11
11
  import { z } from "zod";
@@ -18,19 +18,28 @@ import {
18
18
  /**
19
19
  * Model entity schema for AI models
20
20
  * Extends BaseCollectionEntitySchema with model-specific fields
21
+ * Base schema already includes: id, title, created_at, updated_at, created_by, updated_by
21
22
  */
22
23
  const ModelSchema = BaseCollectionEntitySchema.extend({
23
24
  // Model-specific fields
24
- id: z.string(),
25
- model: z.string(),
26
- name: z.string(),
27
25
  logo: z.string().nullable(),
28
- capabilities: z.array(z.string()),
29
- contextWindow: z.number().nullable(),
30
- inputCost: z.number().nullable(),
31
- outputCost: z.number().nullable(),
32
- outputLimit: z.number().nullable(),
33
26
  description: z.string().nullable(),
27
+ capabilities: z.array(z.string()),
28
+ limits: z.object({
29
+ contextWindow: z.number(),
30
+ maxOutputTokens: z.number(),
31
+ }).nullable(),
32
+ costs: z.object({
33
+ input: z.number(),
34
+ output: z.number(),
35
+ }).nullable(),
36
+ // Streaming endpoint information
37
+ endpoint: z.object({
38
+ url: z.string().url(),
39
+ method: z.string().default("POST"),
40
+ contentType: z.string().default("application/json"),
41
+ stream: z.boolean().default(true),
42
+ }).nullable(),
34
43
  });
35
44
 
36
45
  /**
@@ -53,17 +62,8 @@ export const MODELS_COLLECTION_BINDING = createCollectionBindings(
53
62
  *
54
63
  * Required tools:
55
64
  * - DECO_COLLECTION_MODELS_LIST: List available AI models with their capabilities
56
- * - DECO_COLLECTION_MODELS_GET: Get a single model by URI
57
- * - GET_STREAM_ENDPOINT: Get the streaming endpoint URL for chat completions
65
+ * - DECO_COLLECTION_MODELS_GET: Get a single model by ID (includes streaming endpoint info)
58
66
  */
59
67
  export const MODELS_BINDING = [
60
68
  ...MODELS_COLLECTION_BINDING,
61
- {
62
- name: "GET_STREAM_ENDPOINT" as const,
63
- inputSchema: z.object({}).passthrough(),
64
- outputSchema: z.object({
65
- url: z.string().url(),
66
- }).partial(),
67
- },
68
69
  ] as const satisfies Binder;
69
-