skedyul 1.1.9 → 1.2.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.
Files changed (2) hide show
  1. package/dist/esm/index.mjs +4255 -0
  2. package/package.json +2 -1
@@ -0,0 +1,4255 @@
1
+ import { createRequire } from 'module'; const require = createRequire(import.meta.url);
2
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
3
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
4
+ }) : x)(function(x) {
5
+ if (typeof require !== "undefined") return require.apply(this, arguments);
6
+ throw Error('Dynamic require of "' + x + '" is not supported');
7
+ });
8
+
9
+ // src/index.ts
10
+ import { z as z6 } from "zod/v4";
11
+
12
+ // src/types/invocation.ts
13
+ function createToolCallContext(params) {
14
+ return {
15
+ invocationId: params.invocationId,
16
+ invocationType: "tool_call",
17
+ toolCallId: params.toolCallId,
18
+ toolHandle: params.toolHandle,
19
+ appInstallationId: params.appInstallationId,
20
+ workflowId: params.workflowId,
21
+ workflowVersionId: params.workflowVersionId,
22
+ workflowRunId: params.workflowRunId
23
+ };
24
+ }
25
+ function createServerHookContext(params) {
26
+ return {
27
+ invocationId: params.invocationId,
28
+ invocationType: "server_hook",
29
+ serverHookHandle: params.serverHookHandle,
30
+ appInstallationId: params.appInstallationId
31
+ };
32
+ }
33
+ function createWebhookContext(params) {
34
+ return {
35
+ invocationId: params.invocationId,
36
+ invocationType: "webhook",
37
+ appInstallationId: params.appInstallationId
38
+ };
39
+ }
40
+ function createWorkflowStepContext(params) {
41
+ return {
42
+ invocationId: params.invocationId,
43
+ invocationType: "workflow_step",
44
+ workflowId: params.workflowId,
45
+ workflowVersionId: params.workflowVersionId,
46
+ workflowRunId: params.workflowRunId,
47
+ workflowStepId: params.workflowStepId
48
+ };
49
+ }
50
+
51
+ // src/types/tool-context.ts
52
+ function isProvisionContext(ctx) {
53
+ return ctx.trigger === "provision";
54
+ }
55
+ function isRuntimeContext(ctx) {
56
+ return ctx.trigger !== "provision";
57
+ }
58
+
59
+ // src/types/tool.ts
60
+ import { z } from "zod/v4";
61
+ var ToolResponseMetaSchema = z.object({
62
+ /** Whether the tool execution succeeded */
63
+ success: z.boolean(),
64
+ /** Human-readable message describing the result or error */
65
+ message: z.string(),
66
+ /** Name of the tool that was executed */
67
+ toolName: z.string()
68
+ });
69
+
70
+ // src/types/webhook.ts
71
+ function isRuntimeWebhookContext(ctx) {
72
+ return "appInstallationId" in ctx && ctx.appInstallationId !== void 0;
73
+ }
74
+
75
+ // src/schemas.ts
76
+ import { z as z2 } from "zod/v4";
77
+ var EnvVisibilitySchema = z2.enum(["visible", "encrypted"]);
78
+ var EnvVariableDefinitionSchema = z2.object({
79
+ label: z2.string(),
80
+ required: z2.boolean().optional(),
81
+ visibility: EnvVisibilitySchema.optional(),
82
+ default: z2.string().optional(),
83
+ description: z2.string().optional(),
84
+ placeholder: z2.string().optional()
85
+ });
86
+ var EnvSchemaSchema = z2.record(z2.string(), EnvVariableDefinitionSchema);
87
+ var ComputeLayerTypeSchema = z2.enum(["serverless", "dedicated"]);
88
+ var FieldOwnerSchema = z2.enum(["APP", "SHARED"]);
89
+ var PrimitiveSchema = z2.union([z2.string(), z2.number(), z2.boolean()]);
90
+ var PrimitiveOrArraySchema = z2.union([PrimitiveSchema, z2.array(PrimitiveSchema)]);
91
+ var FilterOperatorSchema = z2.enum([
92
+ "eq",
93
+ "neq",
94
+ "gt",
95
+ "gte",
96
+ "lt",
97
+ "lte",
98
+ "in",
99
+ "contains",
100
+ "notContains",
101
+ "not_contains",
102
+ "startsWith",
103
+ "starts_with",
104
+ "endsWith",
105
+ "ends_with",
106
+ "isEmpty",
107
+ "isNotEmpty"
108
+ ]);
109
+ var FilterConditionSchema = z2.record(
110
+ FilterOperatorSchema,
111
+ PrimitiveOrArraySchema
112
+ );
113
+ var StructuredFilterSchema = z2.record(
114
+ z2.string(),
115
+ FilterConditionSchema
116
+ );
117
+ var ModelDependencySchema = z2.object({
118
+ model: z2.string(),
119
+ fields: z2.array(z2.string()).optional(),
120
+ where: StructuredFilterSchema.optional()
121
+ });
122
+ var ChannelDependencySchema = z2.object({
123
+ channel: z2.string()
124
+ });
125
+ var WorkflowDependencySchema = z2.object({
126
+ workflow: z2.string()
127
+ });
128
+ var ResourceDependencySchema = z2.union([
129
+ ModelDependencySchema,
130
+ ChannelDependencySchema,
131
+ WorkflowDependencySchema
132
+ ]);
133
+ var FieldDataTypeSchema = z2.enum([
134
+ "LONG_STRING",
135
+ "STRING",
136
+ "NUMBER",
137
+ "BOOLEAN",
138
+ "DATE",
139
+ "DATE_TIME",
140
+ "TIME",
141
+ "FILE",
142
+ "IMAGE",
143
+ "RELATION",
144
+ "OBJECT"
145
+ ]);
146
+ var FieldOptionSchema = z2.object({
147
+ label: z2.string(),
148
+ value: z2.string(),
149
+ color: z2.string().optional()
150
+ });
151
+ var InlineFieldDefinitionSchema = z2.object({
152
+ limitChoices: z2.number().optional(),
153
+ options: z2.array(FieldOptionSchema).optional(),
154
+ minLength: z2.number().optional(),
155
+ maxLength: z2.number().optional(),
156
+ min: z2.number().optional(),
157
+ max: z2.number().optional(),
158
+ relatedModel: z2.string().optional(),
159
+ pattern: z2.string().optional()
160
+ });
161
+ var AppFieldVisibilitySchema = z2.object({
162
+ data: z2.boolean().optional(),
163
+ list: z2.boolean().optional(),
164
+ filters: z2.boolean().optional()
165
+ });
166
+ var ModelFieldDefinitionSchema = z2.object({
167
+ handle: z2.string(),
168
+ label: z2.string(),
169
+ type: FieldDataTypeSchema.optional(),
170
+ definition: z2.union([InlineFieldDefinitionSchema, z2.string()]).optional(),
171
+ required: z2.boolean().optional(),
172
+ unique: z2.boolean().optional(),
173
+ system: z2.boolean().optional(),
174
+ isList: z2.boolean().optional(),
175
+ defaultValue: z2.object({ value: z2.unknown() }).optional(),
176
+ description: z2.string().optional(),
177
+ visibility: AppFieldVisibilitySchema.optional(),
178
+ owner: FieldOwnerSchema.optional()
179
+ });
180
+ var ModelDefinitionSchema = z2.object({
181
+ handle: z2.string(),
182
+ name: z2.string(),
183
+ namePlural: z2.string().optional(),
184
+ labelTemplate: z2.string().optional(),
185
+ description: z2.string().optional(),
186
+ fields: z2.array(ModelFieldDefinitionSchema),
187
+ requires: z2.array(ResourceDependencySchema).optional(),
188
+ addDefaultPages: z2.boolean().optional(),
189
+ addNavigation: z2.boolean().optional()
190
+ });
191
+ var RelationshipCardinalitySchema = z2.enum([
192
+ "ONE_TO_ONE",
193
+ "ONE_TO_MANY"
194
+ ]);
195
+ var OnDeleteBehaviorSchema = z2.enum(["NONE", "CASCADE", "RESTRICT"]);
196
+ var RelationshipLinkSchema = z2.object({
197
+ model: z2.string(),
198
+ field: z2.string(),
199
+ label: z2.string()
200
+ });
201
+ var RelationshipDefinitionSchema = z2.object({
202
+ source: RelationshipLinkSchema,
203
+ target: RelationshipLinkSchema,
204
+ cardinality: RelationshipCardinalitySchema,
205
+ onDelete: OnDeleteBehaviorSchema.default("NONE")
206
+ });
207
+ var ChannelCapabilityTypeSchema = z2.enum([
208
+ "messaging",
209
+ // Text-based: SMS, WhatsApp, Messenger, DMs
210
+ "voice",
211
+ // Audio calls: Phone, WhatsApp Voice, etc.
212
+ "video"
213
+ // Video calls (future)
214
+ ]);
215
+ var ChannelCapabilitySchema = z2.object({
216
+ label: z2.string(),
217
+ // Display name: "SMS", "WhatsApp Messages"
218
+ icon: z2.string().optional(),
219
+ // Lucide icon name
220
+ receive: z2.string().optional(),
221
+ // Inbound webhook handler
222
+ send: z2.string().optional()
223
+ // Outbound tool handle
224
+ });
225
+ var ChannelFieldDefinitionSchema = z2.object({
226
+ handle: z2.string(),
227
+ label: z2.string(),
228
+ /** Field definition reference or inline definition */
229
+ definition: z2.union([InlineFieldDefinitionSchema, z2.string()]).optional(),
230
+ /** Marks this field as the identifier field for the channel */
231
+ identifier: z2.boolean().optional(),
232
+ /** Whether this field is required */
233
+ required: z2.boolean().optional(),
234
+ /** Default value when creating a new field */
235
+ defaultValue: z2.object({ value: z2.unknown() }).passthrough().optional(),
236
+ /** Visibility settings for the field */
237
+ visibility: z2.object({
238
+ data: z2.boolean().optional(),
239
+ list: z2.boolean().optional(),
240
+ filters: z2.boolean().optional()
241
+ }).passthrough().optional(),
242
+ /** Permission settings for the field */
243
+ permissions: z2.object({
244
+ read: z2.boolean().optional(),
245
+ write: z2.boolean().optional()
246
+ }).passthrough().optional()
247
+ }).passthrough();
248
+ var ChannelDefinitionSchema = z2.object({
249
+ handle: z2.string(),
250
+ label: z2.string(),
251
+ icon: z2.string().optional(),
252
+ /** Array of field definitions for this channel. One field must have identifier: true. */
253
+ fields: z2.array(ChannelFieldDefinitionSchema),
254
+ // Capabilities keyed by standard type (messaging, voice, video) - all optional
255
+ capabilities: z2.object({
256
+ messaging: ChannelCapabilitySchema.optional(),
257
+ voice: ChannelCapabilitySchema.optional(),
258
+ video: ChannelCapabilitySchema.optional()
259
+ })
260
+ });
261
+ var WorkflowActionInputSchema = z2.object({
262
+ key: z2.string(),
263
+ label: z2.string(),
264
+ fieldRef: z2.object({
265
+ fieldHandle: z2.string(),
266
+ entityHandle: z2.string()
267
+ }).optional(),
268
+ template: z2.string().optional()
269
+ });
270
+ var WorkflowActionSchema = z2.object({
271
+ label: z2.string(),
272
+ handle: z2.string(),
273
+ batch: z2.boolean().optional(),
274
+ entityHandle: z2.string().optional(),
275
+ inputs: z2.array(WorkflowActionInputSchema).optional()
276
+ });
277
+ var WorkflowDefinitionSchema = z2.object({
278
+ path: z2.string(),
279
+ label: z2.string().optional(),
280
+ handle: z2.string().optional(),
281
+ requires: z2.array(ResourceDependencySchema).optional(),
282
+ actions: z2.array(WorkflowActionSchema)
283
+ });
284
+ var PageTypeSchema = z2.enum(["INSTANCE", "LIST"]);
285
+ var PageBlockTypeSchema = z2.enum(["form", "spreadsheet", "kanban", "calendar", "link", "list", "card"]);
286
+ var PageFieldTypeSchema = z2.enum(["STRING", "FILE", "NUMBER", "DATE", "BOOLEAN", "SELECT", "FORM", "RELATIONSHIP"]);
287
+ var PageFieldSourceSchema = z2.object({
288
+ model: z2.string(),
289
+ field: z2.string()
290
+ });
291
+ var PageFormHeaderSchema = z2.object({
292
+ title: z2.string(),
293
+ description: z2.string().optional()
294
+ });
295
+ var PageActionDefinitionSchema = z2.object({
296
+ handle: z2.string(),
297
+ label: z2.string(),
298
+ handler: z2.string(),
299
+ icon: z2.string().optional(),
300
+ variant: z2.enum(["primary", "secondary", "destructive"]).optional(),
301
+ isDisabled: z2.union([z2.boolean(), z2.string()]).optional(),
302
+ isHidden: z2.union([z2.boolean(), z2.string()]).optional()
303
+ });
304
+ var FormV2StylePropsSchema = z2.object({
305
+ id: z2.string(),
306
+ row: z2.number(),
307
+ col: z2.number(),
308
+ className: z2.string().optional(),
309
+ hidden: z2.boolean().optional()
310
+ });
311
+ var FieldSettingButtonPropsSchema = z2.object({
312
+ label: z2.string(),
313
+ variant: z2.enum(["default", "destructive", "outline", "secondary", "ghost", "link"]).optional(),
314
+ size: z2.enum(["default", "sm", "lg", "icon"]).optional(),
315
+ isLoading: z2.boolean().optional(),
316
+ /** Can be boolean or Liquid template string that resolves to boolean */
317
+ isDisabled: z2.union([z2.boolean(), z2.string()]).optional(),
318
+ leftIcon: z2.string().optional()
319
+ });
320
+ var RelationshipExtensionSchema = z2.object({
321
+ model: z2.string()
322
+ });
323
+ var FormLayoutColumnDefinitionSchema = z2.object({
324
+ field: z2.string(),
325
+ colSpan: z2.number(),
326
+ dataType: z2.string().optional(),
327
+ subQuery: z2.unknown().optional()
328
+ });
329
+ var FormLayoutRowDefinitionSchema = z2.object({
330
+ columns: z2.array(FormLayoutColumnDefinitionSchema)
331
+ });
332
+ var FormLayoutConfigDefinitionSchema = z2.object({
333
+ type: z2.literal("form"),
334
+ rows: z2.array(FormLayoutRowDefinitionSchema)
335
+ });
336
+ var InputComponentDefinitionSchema = FormV2StylePropsSchema.extend({
337
+ component: z2.literal("Input"),
338
+ props: z2.object({
339
+ label: z2.string().optional(),
340
+ placeholder: z2.string().optional(),
341
+ helpText: z2.string().optional(),
342
+ type: z2.enum(["text", "number", "email", "password", "tel", "url", "hidden"]).optional(),
343
+ required: z2.boolean().optional(),
344
+ disabled: z2.boolean().optional(),
345
+ value: z2.union([z2.string(), z2.number()]).optional()
346
+ })
347
+ });
348
+ var TextareaComponentDefinitionSchema = FormV2StylePropsSchema.extend({
349
+ component: z2.literal("Textarea"),
350
+ props: z2.object({
351
+ label: z2.string().optional(),
352
+ placeholder: z2.string().optional(),
353
+ helpText: z2.string().optional(),
354
+ required: z2.boolean().optional(),
355
+ disabled: z2.boolean().optional(),
356
+ value: z2.string().optional()
357
+ })
358
+ });
359
+ var SelectComponentDefinitionSchema = FormV2StylePropsSchema.extend({
360
+ component: z2.literal("Select"),
361
+ props: z2.object({
362
+ label: z2.string().optional(),
363
+ placeholder: z2.string().optional(),
364
+ helpText: z2.string().optional(),
365
+ /** Static items array (will be populated by iterable if using dynamic items) */
366
+ items: z2.union([z2.array(z2.object({ value: z2.string(), label: z2.string() })), z2.string()]).optional(),
367
+ value: z2.string().optional(),
368
+ isDisabled: z2.boolean().optional(),
369
+ required: z2.boolean().optional()
370
+ }),
371
+ relationship: RelationshipExtensionSchema.optional(),
372
+ /** For dynamic items using iterable pattern (e.g., 'system.models') */
373
+ iterable: z2.string().optional(),
374
+ /** Template for each item in the iterable */
375
+ itemTemplate: z2.object({
376
+ value: z2.string(),
377
+ label: z2.string()
378
+ }).optional()
379
+ });
380
+ var ComboboxComponentDefinitionSchema = FormV2StylePropsSchema.extend({
381
+ component: z2.literal("Combobox"),
382
+ props: z2.object({
383
+ label: z2.string().optional(),
384
+ placeholder: z2.string().optional(),
385
+ helpText: z2.string().optional(),
386
+ items: z2.array(z2.object({ value: z2.string(), label: z2.string() })).optional(),
387
+ value: z2.string().optional()
388
+ }),
389
+ relationship: RelationshipExtensionSchema.optional()
390
+ });
391
+ var CheckboxComponentDefinitionSchema = FormV2StylePropsSchema.extend({
392
+ component: z2.literal("Checkbox"),
393
+ props: z2.object({
394
+ label: z2.string().optional(),
395
+ helpText: z2.string().optional(),
396
+ checked: z2.boolean().optional(),
397
+ disabled: z2.boolean().optional()
398
+ })
399
+ });
400
+ var DatePickerComponentDefinitionSchema = FormV2StylePropsSchema.extend({
401
+ component: z2.literal("DatePicker"),
402
+ props: z2.object({
403
+ label: z2.string().optional(),
404
+ helpText: z2.string().optional(),
405
+ value: z2.union([z2.string(), z2.date()]).optional(),
406
+ disabled: z2.boolean().optional()
407
+ })
408
+ });
409
+ var TimePickerComponentDefinitionSchema = FormV2StylePropsSchema.extend({
410
+ component: z2.literal("TimePicker"),
411
+ props: z2.object({
412
+ label: z2.string().optional(),
413
+ helpText: z2.string().optional(),
414
+ value: z2.string().optional(),
415
+ disabled: z2.boolean().optional()
416
+ })
417
+ });
418
+ var ImageSettingComponentDefinitionSchema = FormV2StylePropsSchema.extend({
419
+ component: z2.literal("ImageSetting"),
420
+ props: z2.object({
421
+ label: z2.string().optional(),
422
+ description: z2.string().optional(),
423
+ helpText: z2.string().optional(),
424
+ accept: z2.string().optional()
425
+ })
426
+ });
427
+ var FileSettingComponentDefinitionSchema = FormV2StylePropsSchema.extend({
428
+ component: z2.literal("FileSetting"),
429
+ props: z2.object({
430
+ label: z2.string().optional(),
431
+ description: z2.string().optional(),
432
+ helpText: z2.string().optional(),
433
+ accept: z2.string().optional(),
434
+ required: z2.boolean().optional(),
435
+ button: z2.object({
436
+ label: z2.string().optional(),
437
+ variant: z2.enum(["default", "outline", "ghost", "link"]).optional(),
438
+ size: z2.enum(["sm", "md", "lg"]).optional()
439
+ }).optional()
440
+ })
441
+ });
442
+ var ListItemTemplateSchema = z2.object({
443
+ component: z2.string(),
444
+ span: z2.number().optional(),
445
+ mdSpan: z2.number().optional(),
446
+ lgSpan: z2.number().optional(),
447
+ props: z2.record(z2.string(), z2.unknown())
448
+ });
449
+ var ListComponentDefinitionSchema = FormV2StylePropsSchema.extend({
450
+ component: z2.literal("List"),
451
+ props: z2.object({
452
+ title: z2.string().optional(),
453
+ items: z2.array(z2.object({
454
+ id: z2.string(),
455
+ label: z2.string(),
456
+ description: z2.string().optional()
457
+ })).optional(),
458
+ emptyMessage: z2.string().optional()
459
+ }),
460
+ model: z2.string().optional(),
461
+ labelField: z2.string().optional(),
462
+ descriptionField: z2.string().optional(),
463
+ icon: z2.string().optional(),
464
+ /** Context variable name to iterate over (e.g., 'phone_numbers') */
465
+ iterable: z2.string().optional(),
466
+ /** Template for each item - use {{ item.xyz }} for field values */
467
+ itemTemplate: ListItemTemplateSchema.optional()
468
+ });
469
+ var EmptyFormComponentDefinitionSchema = FormV2StylePropsSchema.extend({
470
+ component: z2.literal("EmptyForm"),
471
+ props: z2.object({
472
+ title: z2.string().optional(),
473
+ description: z2.string().optional(),
474
+ icon: z2.string().optional()
475
+ })
476
+ });
477
+ var AlertComponentDefinitionSchema = FormV2StylePropsSchema.extend({
478
+ component: z2.literal("Alert"),
479
+ props: z2.object({
480
+ title: z2.string(),
481
+ description: z2.string(),
482
+ icon: z2.string().optional(),
483
+ variant: z2.enum(["default", "destructive"]).optional()
484
+ })
485
+ });
486
+ var ModalFormDefinitionSchema = z2.object({
487
+ header: PageFormHeaderSchema,
488
+ handler: z2.string(),
489
+ /** Named dialog template to use instead of inline fields */
490
+ template: z2.string().optional(),
491
+ /** Template-specific params to pass to the dialog */
492
+ templateParams: z2.record(z2.string(), z2.unknown()).optional(),
493
+ /** Inline field definitions (used when template is not specified) */
494
+ fields: z2.lazy(() => z2.array(FormV2ComponentDefinitionSchema)).optional(),
495
+ layout: FormLayoutConfigDefinitionSchema.optional(),
496
+ actions: z2.array(PageActionDefinitionSchema).optional()
497
+ });
498
+ var FieldSettingComponentDefinitionSchema = FormV2StylePropsSchema.extend({
499
+ component: z2.literal("FieldSetting"),
500
+ props: z2.object({
501
+ label: z2.string(),
502
+ description: z2.string().optional(),
503
+ helpText: z2.string().optional(),
504
+ mode: z2.enum(["field", "setting"]).optional(),
505
+ /** Status indicator - can be literal or Liquid template */
506
+ status: z2.string().optional(),
507
+ /** Text to display alongside status badge - can be Liquid template */
508
+ statusText: z2.string().optional(),
509
+ button: FieldSettingButtonPropsSchema
510
+ }),
511
+ modalForm: ModalFormDefinitionSchema.optional()
512
+ });
513
+ var FormV2ComponentDefinitionSchema = z2.discriminatedUnion("component", [
514
+ InputComponentDefinitionSchema,
515
+ TextareaComponentDefinitionSchema,
516
+ SelectComponentDefinitionSchema,
517
+ ComboboxComponentDefinitionSchema,
518
+ CheckboxComponentDefinitionSchema,
519
+ DatePickerComponentDefinitionSchema,
520
+ TimePickerComponentDefinitionSchema,
521
+ FieldSettingComponentDefinitionSchema,
522
+ ImageSettingComponentDefinitionSchema,
523
+ FileSettingComponentDefinitionSchema,
524
+ ListComponentDefinitionSchema,
525
+ EmptyFormComponentDefinitionSchema,
526
+ AlertComponentDefinitionSchema
527
+ ]);
528
+ var FormV2PropsDefinitionSchema = z2.object({
529
+ formVersion: z2.literal("v2"),
530
+ id: z2.string().optional(),
531
+ fields: z2.array(FormV2ComponentDefinitionSchema),
532
+ layout: FormLayoutConfigDefinitionSchema,
533
+ /** Optional actions that trigger MCP tool calls */
534
+ actions: z2.array(PageActionDefinitionSchema).optional()
535
+ });
536
+ var CardBlockHeaderSchema = z2.object({
537
+ title: z2.string(),
538
+ description: z2.string().optional(),
539
+ descriptionHref: z2.string().optional()
540
+ });
541
+ var CardBlockDefinitionSchema = z2.object({
542
+ type: z2.literal("card"),
543
+ restructurable: z2.boolean().optional(),
544
+ header: CardBlockHeaderSchema.optional(),
545
+ form: FormV2PropsDefinitionSchema,
546
+ actions: z2.array(PageActionDefinitionSchema).optional(),
547
+ secondaryActions: z2.array(PageActionDefinitionSchema).optional(),
548
+ primaryActions: z2.array(PageActionDefinitionSchema).optional()
549
+ });
550
+ var PageFieldDefinitionBaseSchema = z2.object({
551
+ handle: z2.string(),
552
+ type: PageFieldTypeSchema,
553
+ label: z2.string(),
554
+ description: z2.string().optional(),
555
+ required: z2.boolean().optional(),
556
+ handler: z2.string().optional(),
557
+ source: PageFieldSourceSchema.optional(),
558
+ options: z2.array(z2.object({ value: z2.string(), label: z2.string() })).optional(),
559
+ accept: z2.string().optional(),
560
+ model: z2.string().optional()
561
+ });
562
+ var PageFieldDefinitionSchema = PageFieldDefinitionBaseSchema.extend({
563
+ header: PageFormHeaderSchema.optional(),
564
+ fields: z2.lazy(() => z2.array(PageFieldDefinitionSchema)).optional(),
565
+ actions: z2.lazy(() => z2.array(PageActionDefinitionSchema)).optional()
566
+ });
567
+ var LegacyFormBlockDefinitionSchema = z2.object({
568
+ type: z2.enum(["form", "spreadsheet", "kanban", "calendar", "link"]),
569
+ title: z2.string().optional(),
570
+ fields: z2.array(PageFieldDefinitionSchema).optional(),
571
+ readonly: z2.boolean().optional()
572
+ });
573
+ var ListBlockDefinitionSchema = z2.object({
574
+ type: z2.literal("list"),
575
+ title: z2.string().optional(),
576
+ model: z2.string(),
577
+ labelField: z2.string().optional(),
578
+ descriptionField: z2.string().optional(),
579
+ icon: z2.string().optional(),
580
+ emptyMessage: z2.string().optional()
581
+ });
582
+ var ModelMapperBlockDefinitionSchema = z2.object({
583
+ type: z2.literal("model_mapper"),
584
+ /** The SHARED model handle from install config (e.g., "client", "patient") */
585
+ model: z2.string()
586
+ });
587
+ var PageBlockDefinitionSchema = z2.union([
588
+ CardBlockDefinitionSchema,
589
+ LegacyFormBlockDefinitionSchema,
590
+ ListBlockDefinitionSchema,
591
+ ModelMapperBlockDefinitionSchema
592
+ ]);
593
+ var PageContextModeSchema = z2.enum(["first", "many", "count"]);
594
+ var PageContextFiltersSchema = z2.record(
595
+ z2.string(),
596
+ z2.record(
597
+ z2.string(),
598
+ z2.union([PrimitiveSchema, z2.array(PrimitiveSchema), z2.string()])
599
+ )
600
+ );
601
+ var PageContextItemDefinitionSchema = z2.object({
602
+ /** Model handle to fetch data from */
603
+ model: z2.string(),
604
+ /** Fetch mode: 'first' returns single object, 'many' returns array, 'count' returns number */
605
+ mode: PageContextModeSchema,
606
+ /**
607
+ * Optional filters. Supports:
608
+ * - Simple key-value with Liquid templates: { id: '{{ path_params.id }}' }
609
+ * - StructuredFilter format: { status: { eq: 'APPROVED' } }
610
+ */
611
+ filters: PageContextFiltersSchema.optional(),
612
+ /** Optional limit for 'many' mode */
613
+ limit: z2.number().optional()
614
+ });
615
+ var PageContextToolItemDefinitionSchema = z2.object({
616
+ /** Tool name to invoke for fetching context data */
617
+ tool: z2.string()
618
+ });
619
+ var PageContextDefinitionSchema = z2.record(
620
+ z2.string(),
621
+ z2.union([PageContextItemDefinitionSchema, PageContextToolItemDefinitionSchema])
622
+ );
623
+ var PageInstanceFilterSchema = z2.object({
624
+ model: z2.string(),
625
+ where: z2.record(z2.string(), z2.unknown()).optional()
626
+ });
627
+ var NavigationItemSchema = z2.object({
628
+ /** Display label (supports Liquid templates) */
629
+ label: z2.string(),
630
+ /** URL href (supports Liquid templates with path_params and context) */
631
+ href: z2.string(),
632
+ /** Optional icon name */
633
+ icon: z2.string().optional()
634
+ });
635
+ var NavigationSectionSchema = z2.object({
636
+ /** Section title (supports Liquid templates) */
637
+ title: z2.string().optional(),
638
+ /** Navigation items in this section */
639
+ items: z2.array(NavigationItemSchema)
640
+ });
641
+ var NavigationSidebarSchema = z2.object({
642
+ /** Sections to display in the sidebar */
643
+ sections: z2.array(NavigationSectionSchema)
644
+ });
645
+ var BreadcrumbItemSchema = z2.object({
646
+ /** Display label (supports Liquid templates) */
647
+ label: z2.string(),
648
+ /** Optional href - if not provided, item is not clickable */
649
+ href: z2.string().optional()
650
+ });
651
+ var NavigationBreadcrumbSchema = z2.object({
652
+ /** Breadcrumb items from left to right */
653
+ items: z2.array(BreadcrumbItemSchema)
654
+ });
655
+ var NavigationConfigSchema = z2.object({
656
+ /** Sidebar navigation */
657
+ sidebar: NavigationSidebarSchema.optional(),
658
+ /** Breadcrumb navigation */
659
+ breadcrumb: NavigationBreadcrumbSchema.optional()
660
+ });
661
+ var PageDefinitionSchema = z2.object({
662
+ type: PageTypeSchema,
663
+ title: z2.string(),
664
+ /** URL path for this page (e.g., '/phone-numbers' or '/phone-numbers/[id]' for dynamic segments) */
665
+ path: z2.string(),
666
+ /** When true, this page is the default landing page for the app installation */
667
+ default: z2.boolean().optional(),
668
+ /**
669
+ * Navigation configuration:
670
+ * - true/false: show/hide in auto-generated navigation
671
+ * - string: Liquid template that evaluates to true/false
672
+ * - NavigationConfigSchema: full navigation override for this page (replaces base navigation)
673
+ */
674
+ navigation: z2.union([z2.boolean(), z2.string(), NavigationConfigSchema]).optional().default(true),
675
+ blocks: z2.array(PageBlockDefinitionSchema),
676
+ actions: z2.array(PageActionDefinitionSchema).optional(),
677
+ /** Context data to load for Liquid templates. appInstallationId filtering is automatic. */
678
+ context: PageContextDefinitionSchema.optional(),
679
+ /** @deprecated Use context instead */
680
+ filter: PageInstanceFilterSchema.optional()
681
+ });
682
+ var WebhookHttpMethodSchema = z2.enum(["GET", "POST", "PUT", "DELETE", "PATCH"]);
683
+ var WebhookTypeSchema = z2.enum(["WEBHOOK", "CALLBACK"]);
684
+ var WebhookHandlerDefinitionSchema = z2.object({
685
+ description: z2.string().optional(),
686
+ methods: z2.array(WebhookHttpMethodSchema).optional(),
687
+ /** Invocation type: WEBHOOK (fire-and-forget) or CALLBACK (waits for response). Defaults to WEBHOOK. */
688
+ type: WebhookTypeSchema.optional(),
689
+ handler: z2.unknown()
690
+ });
691
+ var WebhooksSchema = z2.record(z2.string(), WebhookHandlerDefinitionSchema);
692
+ var AgentDefinitionSchema = z2.object({
693
+ /** Unique identifier within the app (used for upserts) */
694
+ handle: z2.string().regex(/^[a-z0-9_-]+$/, "Handle must be lowercase alphanumeric with dashes/underscores"),
695
+ /** Display name */
696
+ name: z2.string().min(1),
697
+ /** Description of what the agent does */
698
+ description: z2.string(),
699
+ /** System prompt (static, no templating) */
700
+ system: z2.string(),
701
+ /** Tool names to bind (can be empty for orchestrator agents) */
702
+ tools: z2.array(z2.string()),
703
+ /** Optional LLM model override */
704
+ llmModelId: z2.string().optional(),
705
+ /** Parent agent that can call this agent ('composer' or another agent handle) */
706
+ parentAgent: z2.string().optional()
707
+ });
708
+ var InstallConfigSchema = z2.object({
709
+ env: EnvSchemaSchema.optional(),
710
+ /** SHARED model definitions (mapped to user's existing data during installation) */
711
+ models: z2.array(ModelDefinitionSchema).optional(),
712
+ /** Relationship definitions between SHARED models */
713
+ relationships: z2.array(RelationshipDefinitionSchema).optional()
714
+ });
715
+ var ProvisionConfigSchema = z2.object({
716
+ env: EnvSchemaSchema.optional(),
717
+ /** INTERNAL model definitions (app-owned, not visible to users) */
718
+ models: z2.array(ModelDefinitionSchema).optional(),
719
+ /** Relationship definitions between INTERNAL models */
720
+ relationships: z2.array(RelationshipDefinitionSchema).optional(),
721
+ channels: z2.array(ChannelDefinitionSchema).optional(),
722
+ workflows: z2.array(WorkflowDefinitionSchema).optional(),
723
+ /** Base navigation configuration for all pages (can be overridden per page) */
724
+ navigation: NavigationConfigSchema.optional(),
725
+ pages: z2.array(PageDefinitionSchema).optional()
726
+ });
727
+ var SkedyulConfigSchema = z2.object({
728
+ name: z2.string(),
729
+ version: z2.string().optional(),
730
+ description: z2.string().optional(),
731
+ computeLayer: ComputeLayerTypeSchema.optional(),
732
+ tools: z2.unknown().optional(),
733
+ webhooks: z2.unknown().optional(),
734
+ provision: z2.union([ProvisionConfigSchema, z2.unknown()]).optional(),
735
+ agents: z2.array(AgentDefinitionSchema).optional()
736
+ });
737
+ function safeParseConfig(data) {
738
+ const result = SkedyulConfigSchema.safeParse(data);
739
+ return result.success ? result.data : null;
740
+ }
741
+ var MessageSendChannelSchema = z2.object({
742
+ id: z2.string(),
743
+ handle: z2.string(),
744
+ identifierValue: z2.string()
745
+ });
746
+ var MessageSendSubscriptionSchema = z2.object({
747
+ id: z2.string(),
748
+ identifierValue: z2.string()
749
+ });
750
+ var MessageSendContactSchema = z2.object({
751
+ id: z2.string(),
752
+ name: z2.string().optional()
753
+ });
754
+ var MessageSendMessageSchema = z2.object({
755
+ id: z2.string(),
756
+ content: z2.string(),
757
+ contentRaw: z2.string().optional(),
758
+ title: z2.string().optional()
759
+ });
760
+ var MessageSendInputSchema = z2.object({
761
+ channel: MessageSendChannelSchema,
762
+ subscription: MessageSendSubscriptionSchema,
763
+ contact: MessageSendContactSchema,
764
+ message: MessageSendMessageSchema
765
+ });
766
+ var MessageSendOutputSchema = z2.object({
767
+ status: z2.enum(["sent", "queued", "failed"]),
768
+ remoteId: z2.string().optional()
769
+ });
770
+ function isModelDependency(dep) {
771
+ return "model" in dep;
772
+ }
773
+ function isChannelDependency(dep) {
774
+ return "channel" in dep;
775
+ }
776
+ function isWorkflowDependency(dep) {
777
+ return "workflow" in dep;
778
+ }
779
+
780
+ // src/server/index.ts
781
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
782
+ import * as z5 from "zod";
783
+
784
+ // src/core/service.ts
785
+ var CoreApiService = class {
786
+ register(service) {
787
+ this.service = service;
788
+ }
789
+ getService() {
790
+ return this.service;
791
+ }
792
+ setWebhookHandler(handler) {
793
+ this.webhookHandler = handler;
794
+ }
795
+ async dispatchWebhook(request) {
796
+ if (!this.webhookHandler) {
797
+ return { status: 404 };
798
+ }
799
+ return this.webhookHandler(request);
800
+ }
801
+ async callCreateChannel(channel) {
802
+ return this.service?.createCommunicationChannel(channel);
803
+ }
804
+ async callUpdateChannel(channel) {
805
+ return this.service?.updateCommunicationChannel(channel);
806
+ }
807
+ async callDeleteChannel(id) {
808
+ return this.service?.deleteCommunicationChannel(id);
809
+ }
810
+ async callGetChannel(id) {
811
+ return this.service?.getCommunicationChannel(id);
812
+ }
813
+ async callListChannels() {
814
+ return this.service?.getCommunicationChannels();
815
+ }
816
+ async callGetWorkplace(id) {
817
+ return this.service?.getWorkplace(id);
818
+ }
819
+ async callListWorkplaces() {
820
+ return this.service?.listWorkplaces();
821
+ }
822
+ async callSendMessage(args) {
823
+ return this.service?.sendMessage(args);
824
+ }
825
+ };
826
+ var coreApiService = new CoreApiService();
827
+
828
+ // src/server/utils/schema.ts
829
+ import * as z3 from "zod";
830
+ function normalizeBilling(billing) {
831
+ if (!billing || typeof billing.credits !== "number") {
832
+ return { credits: 0 };
833
+ }
834
+ return billing;
835
+ }
836
+ function toJsonSchema(schema) {
837
+ if (!schema) return void 0;
838
+ try {
839
+ return z3.toJSONSchema(schema, {
840
+ unrepresentable: "any"
841
+ // Handle z.date(), z.bigint() etc gracefully
842
+ });
843
+ } catch (err) {
844
+ console.error("[toJsonSchema] Failed to convert schema:", err);
845
+ return void 0;
846
+ }
847
+ }
848
+ function isToolSchemaWithJson(schema) {
849
+ return Boolean(
850
+ schema && typeof schema === "object" && "zod" in schema && schema.zod instanceof z3.ZodType
851
+ );
852
+ }
853
+ function getZodSchema(schema) {
854
+ if (!schema) return void 0;
855
+ if (schema instanceof z3.ZodType) {
856
+ return schema;
857
+ }
858
+ if (isToolSchemaWithJson(schema)) {
859
+ return schema.zod;
860
+ }
861
+ return void 0;
862
+ }
863
+ function getJsonSchemaFromToolSchema(schema) {
864
+ if (!schema) return void 0;
865
+ if (isToolSchemaWithJson(schema) && schema.jsonSchema) {
866
+ return schema.jsonSchema;
867
+ }
868
+ const zodSchema = getZodSchema(schema);
869
+ return toJsonSchema(zodSchema);
870
+ }
871
+
872
+ // src/server/utils/env.ts
873
+ function parseJsonRecord(value) {
874
+ if (!value) {
875
+ return {};
876
+ }
877
+ try {
878
+ return JSON.parse(value);
879
+ } catch {
880
+ return {};
881
+ }
882
+ }
883
+ function parseNumberEnv(value) {
884
+ if (!value) {
885
+ return null;
886
+ }
887
+ const parsed = Number.parseInt(value, 10);
888
+ return Number.isNaN(parsed) ? null : parsed;
889
+ }
890
+ function mergeRuntimeEnv() {
891
+ const bakedEnv = parseJsonRecord(process.env.MCP_ENV_JSON);
892
+ const runtimeEnv = parseJsonRecord(process.env.MCP_ENV);
893
+ const merged = { ...bakedEnv, ...runtimeEnv };
894
+ Object.assign(process.env, merged);
895
+ }
896
+
897
+ // src/server/utils/http.ts
898
+ function readRawRequestBody(req) {
899
+ return new Promise((resolve2, reject) => {
900
+ let body = "";
901
+ req.on("data", (chunk) => {
902
+ body += chunk.toString();
903
+ });
904
+ req.on("end", () => {
905
+ resolve2(body);
906
+ });
907
+ req.on("error", reject);
908
+ });
909
+ }
910
+ async function parseJSONBody(req) {
911
+ const rawBody = await readRawRequestBody(req);
912
+ try {
913
+ return rawBody ? JSON.parse(rawBody) : {};
914
+ } catch (err) {
915
+ throw err;
916
+ }
917
+ }
918
+ function sendJSON(res, statusCode, data) {
919
+ res.writeHead(statusCode, { "Content-Type": "application/json" });
920
+ res.end(JSON.stringify(data));
921
+ }
922
+ function getDefaultHeaders(options) {
923
+ return {
924
+ "Content-Type": "application/json",
925
+ "Access-Control-Allow-Origin": options?.allowOrigin ?? "*",
926
+ "Access-Control-Allow-Methods": options?.allowMethods ?? "GET, POST, OPTIONS",
927
+ "Access-Control-Allow-Headers": options?.allowHeaders ?? "Content-Type"
928
+ };
929
+ }
930
+ function createResponse(statusCode, body, headers) {
931
+ return {
932
+ statusCode,
933
+ headers,
934
+ body: JSON.stringify(body)
935
+ };
936
+ }
937
+ function getListeningPort(config) {
938
+ const envPort = Number.parseInt(process.env.PORT ?? "", 10);
939
+ if (!Number.isNaN(envPort)) {
940
+ return envPort;
941
+ }
942
+ return config.defaultPort ?? 3e3;
943
+ }
944
+
945
+ // src/core/client.ts
946
+ import { AsyncLocalStorage } from "async_hooks";
947
+ import { z as z4 } from "zod/v4";
948
+ var requestConfigStorage = new AsyncLocalStorage();
949
+ var globalConfig = {
950
+ baseUrl: process.env.SKEDYUL_API_URL ?? process.env.SKEDYUL_NODE_URL ?? "",
951
+ apiToken: process.env.SKEDYUL_API_TOKEN ?? ""
952
+ };
953
+ function runWithConfig(config, fn) {
954
+ return requestConfigStorage.run(config, fn);
955
+ }
956
+ function getEffectiveConfig() {
957
+ const requestConfig = requestConfigStorage.getStore();
958
+ if (requestConfig?.baseUrl && requestConfig?.apiToken) {
959
+ return requestConfig;
960
+ }
961
+ return globalConfig;
962
+ }
963
+ function configure(options) {
964
+ globalConfig = {
965
+ ...globalConfig,
966
+ ...options
967
+ };
968
+ }
969
+ function getConfig() {
970
+ return getEffectiveConfig();
971
+ }
972
+ async function callCore(method, params) {
973
+ const effectiveConfig = getEffectiveConfig();
974
+ const { baseUrl, apiToken } = effectiveConfig;
975
+ if (!baseUrl) {
976
+ throw new Error(
977
+ "Skedyul client not configured: missing baseUrl. Set SKEDYUL_API_URL environment variable or call configure()."
978
+ );
979
+ }
980
+ if (!apiToken) {
981
+ throw new Error(
982
+ "Skedyul client not configured: missing apiToken. Set SKEDYUL_API_TOKEN environment variable or call configure()."
983
+ );
984
+ }
985
+ const headers = {
986
+ "Content-Type": "application/json",
987
+ Authorization: `Bearer ${apiToken}`
988
+ };
989
+ const fetchUrl = `${baseUrl}/api/core`;
990
+ const response = await fetch(fetchUrl, {
991
+ method: "POST",
992
+ headers,
993
+ body: JSON.stringify({ method, params })
994
+ });
995
+ const contentType = response.headers.get("content-type") ?? "";
996
+ if (!contentType.includes("application/json")) {
997
+ const text = await response.text();
998
+ console.error(`[skedyul-node] Core API error: ${response.status} - ${text.slice(0, 200)}`);
999
+ throw new Error(`Core API returned non-JSON response (${response.status}): ${text.slice(0, 100)}`);
1000
+ }
1001
+ const payload = await response.json();
1002
+ if (!payload.success) {
1003
+ const message = payload.errors?.map((e) => e.field ? `${e.field}: ${e.message}` : e.message).join("; ") || "Unknown error";
1004
+ throw new Error(message);
1005
+ }
1006
+ if (!response.ok) {
1007
+ throw new Error(`Core API error (${response.status})`);
1008
+ }
1009
+ return {
1010
+ data: payload.data,
1011
+ errors: payload.errors ?? [],
1012
+ pagination: payload.pagination
1013
+ };
1014
+ }
1015
+ var workplace = {
1016
+ async list(args) {
1017
+ const { data } = await callCore("workplace.list", {
1018
+ ...args?.filter ? { filter: args.filter } : {},
1019
+ ...args?.limit ? { limit: args.limit } : {}
1020
+ });
1021
+ return data;
1022
+ },
1023
+ async get(id) {
1024
+ const { data } = await callCore("workplace.get", { id });
1025
+ return data;
1026
+ }
1027
+ };
1028
+ var communicationChannel = {
1029
+ /**
1030
+ * Create a communication channel for an app installation.
1031
+ *
1032
+ * Creates a channel with the given handle from provision.config.ts.
1033
+ * Optionally links a SHARED model to the user's model in a single operation.
1034
+ *
1035
+ * **Requires sk_wkp_ token** - channels are scoped to app installations.
1036
+ *
1037
+ * @param handle - Channel handle from provision.config.ts (e.g., "phone", "email")
1038
+ * @param params - Channel creation parameters
1039
+ *
1040
+ * @example
1041
+ * ```ts
1042
+ * // Create a phone channel and link the contact model
1043
+ * const channel = await communicationChannel.create("phone", {
1044
+ * name: "Sales Line",
1045
+ * identifierValue: "+61400000000",
1046
+ * link: {
1047
+ * handle: "contact", // SHARED model from provision config
1048
+ * targetModelId: modelId, // User's selected model
1049
+ * },
1050
+ * });
1051
+ * ```
1052
+ */
1053
+ async create(handle, params) {
1054
+ const { data } = await callCore("communicationChannel.create", {
1055
+ handle,
1056
+ ...params
1057
+ });
1058
+ return data;
1059
+ },
1060
+ /**
1061
+ * List communication channels with optional filters.
1062
+ *
1063
+ * @example
1064
+ * ```ts
1065
+ * // Find channel by phone number
1066
+ * const channels = await communicationChannel.list({
1067
+ * filter: { identifierValue: '+1234567890' },
1068
+ * limit: 1,
1069
+ * });
1070
+ * ```
1071
+ */
1072
+ async list(args) {
1073
+ const { data } = await callCore("communicationChannel.list", {
1074
+ ...args?.filter ? { filter: args.filter } : {},
1075
+ ...args?.limit ? { limit: args.limit } : {}
1076
+ });
1077
+ return data;
1078
+ },
1079
+ async get(id) {
1080
+ const { data } = await callCore("communicationChannel.get", { id });
1081
+ return data;
1082
+ },
1083
+ /**
1084
+ * Receive an inbound message on a communication channel.
1085
+ *
1086
+ * This is typically called from webhook handlers to process incoming messages
1087
+ * (e.g., SMS from Twilio, emails, WhatsApp messages).
1088
+ *
1089
+ * @example
1090
+ * ```ts
1091
+ * // In a webhook handler
1092
+ * const result = await communicationChannel.receiveMessage({
1093
+ * communicationChannelId: channel.id,
1094
+ * from: '+1234567890',
1095
+ * message: 'Hello!',
1096
+ * remoteId: 'twilio-message-sid-123',
1097
+ * });
1098
+ * ```
1099
+ */
1100
+ async receiveMessage(input) {
1101
+ const { data } = await callCore("communicationChannel.receiveMessage", {
1102
+ communicationChannelId: input.communicationChannelId,
1103
+ from: input.from,
1104
+ message: input.message,
1105
+ contact: input.contact,
1106
+ ...input.remoteId ? { remoteId: input.remoteId } : {}
1107
+ });
1108
+ return data;
1109
+ },
1110
+ /**
1111
+ * Update a communication channel's properties.
1112
+ *
1113
+ * @param channelId - The ID of the channel to update
1114
+ * @param params - The properties to update (e.g., name)
1115
+ *
1116
+ * @example
1117
+ * ```ts
1118
+ * const channel = await communicationChannel.update('channel-id-123', {
1119
+ * name: 'New Channel Name',
1120
+ * })
1121
+ * ```
1122
+ */
1123
+ async update(channelId, params) {
1124
+ const { data } = await callCore("communicationChannel.update", {
1125
+ communicationChannelId: channelId,
1126
+ ...params
1127
+ });
1128
+ return data;
1129
+ },
1130
+ /**
1131
+ * Remove a communication channel and its associated resources.
1132
+ *
1133
+ * Deletes the channel and cascades:
1134
+ * - EnvVariables scoped to this channel
1135
+ * - AppFields scoped to this channel
1136
+ * - AppResourceInstances scoped to this channel
1137
+ * - CommunicationChannelSubscriptions (Prisma cascade)
1138
+ *
1139
+ * ChatMessages are preserved with subscriptionId set to null.
1140
+ *
1141
+ * @example
1142
+ * ```ts
1143
+ * const { success } = await communicationChannel.remove('channel-id-123')
1144
+ * ```
1145
+ */
1146
+ async remove(channelId) {
1147
+ const { data } = await callCore("communicationChannel.remove", {
1148
+ communicationChannelId: channelId
1149
+ });
1150
+ return data;
1151
+ }
1152
+ };
1153
+ var instance = {
1154
+ /**
1155
+ * List instances of an internal model.
1156
+ *
1157
+ * The API token determines the context:
1158
+ * - sk_wkp_ tokens: scoped to the token's app installation
1159
+ * - sk_app_ tokens: searches across ALL installations for the app
1160
+ *
1161
+ * @example
1162
+ * ```ts
1163
+ * // List with filters
1164
+ * const { data, pagination } = await instance.list('compliance_record', {
1165
+ * filter: { status: 'pending' },
1166
+ * page: 1,
1167
+ * limit: 10,
1168
+ * })
1169
+ *
1170
+ * // Cross-installation search (with sk_app_ token)
1171
+ * const { data } = await instance.list('phone_number', {
1172
+ * filter: { phone: '+1234567890' },
1173
+ * })
1174
+ * ```
1175
+ */
1176
+ async list(modelHandle, args) {
1177
+ const { data, pagination } = await callCore("instance.list", {
1178
+ modelHandle,
1179
+ ...args?.page !== void 0 ? { page: args.page } : {},
1180
+ ...args?.limit !== void 0 ? { limit: args.limit } : {},
1181
+ ...args?.filter ? { filter: args.filter } : {}
1182
+ });
1183
+ return {
1184
+ data,
1185
+ pagination: pagination ?? { page: 1, total: 0, hasMore: false, limit: args?.limit ?? 50 }
1186
+ };
1187
+ },
1188
+ /**
1189
+ * Get a single instance by ID.
1190
+ *
1191
+ * The API token determines the context (app installation is embedded in sk_wkp_ tokens).
1192
+ *
1193
+ * @example
1194
+ * ```ts
1195
+ * const record = await instance.get('phone_number', 'ins_abc123')
1196
+ * ```
1197
+ */
1198
+ async get(modelHandle, id) {
1199
+ const { data } = await callCore("instance.get", {
1200
+ modelHandle,
1201
+ id
1202
+ });
1203
+ return data;
1204
+ },
1205
+ /**
1206
+ * Create a new instance of an internal model.
1207
+ *
1208
+ * The API token determines the context (app installation is embedded in sk_wkp_ tokens).
1209
+ *
1210
+ * @example
1211
+ * ```ts
1212
+ * const newRecord = await instance.create('compliance_record', {
1213
+ * status: 'pending',
1214
+ * document_url: 'https://...',
1215
+ * })
1216
+ * ```
1217
+ */
1218
+ async create(modelHandle, data) {
1219
+ const { data: instance2 } = await callCore("instance.create", {
1220
+ modelHandle,
1221
+ data
1222
+ });
1223
+ return instance2;
1224
+ },
1225
+ /**
1226
+ * Update an existing instance.
1227
+ *
1228
+ * The API token determines the context (app installation is embedded in sk_wkp_ tokens).
1229
+ *
1230
+ * @example
1231
+ * ```ts
1232
+ * const updated = await instance.update('compliance_record', 'ins_abc123', {
1233
+ * status: 'approved',
1234
+ * bundle_sid: 'BU123456',
1235
+ * })
1236
+ * ```
1237
+ */
1238
+ async update(modelHandle, id, data) {
1239
+ const { data: instance2 } = await callCore("instance.update", {
1240
+ modelHandle,
1241
+ id,
1242
+ data
1243
+ });
1244
+ return instance2;
1245
+ },
1246
+ /**
1247
+ * Delete an existing instance.
1248
+ *
1249
+ * The API token determines the context (app installation is embedded in sk_wkp_ tokens).
1250
+ *
1251
+ * @example
1252
+ * ```ts
1253
+ * const { deleted } = await instance.delete('phone_number', 'ins_abc123')
1254
+ * ```
1255
+ */
1256
+ async delete(modelHandle, id) {
1257
+ const { data } = await callCore("instance.delete", {
1258
+ modelHandle,
1259
+ id
1260
+ });
1261
+ return data;
1262
+ },
1263
+ /**
1264
+ * Delete multiple instances of an internal model in a single batch operation.
1265
+ *
1266
+ * This is more efficient than calling delete() multiple times as it reduces
1267
+ * API overhead and executes all deletes in a single transaction.
1268
+ *
1269
+ * Supports two modes:
1270
+ * - **By IDs**: Delete specific instances by their IDs
1271
+ * - **By Filter**: Delete instances matching a StructuredFilter
1272
+ *
1273
+ * The API token determines the context (app installation is embedded in sk_wkp_ tokens).
1274
+ *
1275
+ * @param modelHandle - The model handle from provision config
1276
+ * @param options - Either { ids: string[] } or { filter: StructuredFilter }
1277
+ * @returns Object containing deleted instance IDs and any errors that occurred
1278
+ *
1279
+ * @example
1280
+ * ```ts
1281
+ * // Delete by IDs
1282
+ * const { deleted, errors } = await instance.deleteMany('panel_result', {
1283
+ * ids: ['ins_abc123', 'ins_def456'],
1284
+ * })
1285
+ *
1286
+ * // Delete by filter
1287
+ * const { deleted, errors } = await instance.deleteMany('panel_result', {
1288
+ * filter: { status: { eq: 'pending' } },
1289
+ * })
1290
+ *
1291
+ * if (errors.length > 0) {
1292
+ * console.log('Some items failed:', errors)
1293
+ * }
1294
+ * console.log('Deleted:', deleted.length, 'instances')
1295
+ * ```
1296
+ */
1297
+ async deleteMany(modelHandle, options) {
1298
+ const { data } = await callCore("instance.deleteMany", {
1299
+ modelHandle,
1300
+ ...options
1301
+ });
1302
+ return data;
1303
+ },
1304
+ /**
1305
+ * Create multiple instances of an internal model in a single batch operation.
1306
+ *
1307
+ * This is more efficient than calling create() multiple times as it reduces
1308
+ * API overhead and executes all creates in a single transaction.
1309
+ *
1310
+ * The API token determines the context (app installation is embedded in sk_wkp_ tokens).
1311
+ *
1312
+ * @param modelHandle - The model handle from provision config
1313
+ * @param items - Array of data objects to create as instances
1314
+ * @returns Object containing created instances and any errors that occurred
1315
+ *
1316
+ * @example
1317
+ * ```ts
1318
+ * const { created, errors } = await instance.createMany('panel_result', [
1319
+ * { test_name: 'Glucose', value_string: '5.2', unit: 'mmol/L' },
1320
+ * { test_name: 'Creatinine', value_string: '80', unit: 'umol/L' },
1321
+ * ])
1322
+ *
1323
+ * if (errors.length > 0) {
1324
+ * console.log('Some items failed:', errors)
1325
+ * }
1326
+ * console.log('Created:', created.length, 'instances')
1327
+ * ```
1328
+ */
1329
+ async createMany(modelHandle, items) {
1330
+ const { data } = await callCore("instance.createMany", {
1331
+ modelHandle,
1332
+ items
1333
+ });
1334
+ return data;
1335
+ },
1336
+ /**
1337
+ * Update multiple instances of an internal model in a single batch operation.
1338
+ *
1339
+ * This is more efficient than calling update() multiple times as it reduces
1340
+ * API overhead and executes all updates in a single transaction.
1341
+ *
1342
+ * The API token determines the context (app installation is embedded in sk_wkp_ tokens).
1343
+ *
1344
+ * @param modelHandle - The model handle from provision config
1345
+ * @param items - Array of objects containing id and data to update
1346
+ * @returns Object containing updated instances and any errors that occurred
1347
+ *
1348
+ * @example
1349
+ * ```ts
1350
+ * const { updated, errors } = await instance.updateMany('panel_result', [
1351
+ * { id: 'ins_abc123', data: { value_string: '5.5' } },
1352
+ * { id: 'ins_def456', data: { value_string: '85' } },
1353
+ * ])
1354
+ *
1355
+ * if (errors.length > 0) {
1356
+ * console.log('Some items failed:', errors)
1357
+ * }
1358
+ * console.log('Updated:', updated.length, 'instances')
1359
+ * ```
1360
+ */
1361
+ async updateMany(modelHandle, items) {
1362
+ const { data } = await callCore("instance.updateMany", {
1363
+ modelHandle,
1364
+ items
1365
+ });
1366
+ return data;
1367
+ },
1368
+ /**
1369
+ * Upsert multiple instances of an internal model in a single batch operation.
1370
+ *
1371
+ * Creates instances if they don't exist, updates them if they do (based on matchField).
1372
+ * This is more efficient than calling upsert() multiple times as it reduces
1373
+ * API overhead and executes all upserts in a single transaction.
1374
+ *
1375
+ * The API token determines the context (app installation is embedded in sk_wkp_ tokens).
1376
+ *
1377
+ * @param modelHandle - The model handle from provision config
1378
+ * @param items - Array of data objects to upsert as instances
1379
+ * @param matchField - The field handle to match existing instances (e.g., 'vetnostics_id')
1380
+ * @returns Object containing upserted instances with mode and any errors that occurred
1381
+ *
1382
+ * @example
1383
+ * ```ts
1384
+ * const { results, errors } = await instance.upsertMany('panel_result', [
1385
+ * { vetnostics_id: '25-54966975/622/glucose', test_name: 'Glucose', value_string: '5.2' },
1386
+ * { vetnostics_id: '25-54966975/622/creatinine', test_name: 'Creatinine', value_string: '80' },
1387
+ * ], 'vetnostics_id')
1388
+ *
1389
+ * if (errors.length > 0) {
1390
+ * console.log('Some items failed:', errors)
1391
+ * }
1392
+ * const created = results.filter(r => r.mode === 'created')
1393
+ * const updated = results.filter(r => r.mode === 'updated')
1394
+ * console.log('Created:', created.length, 'Updated:', updated.length)
1395
+ * ```
1396
+ */
1397
+ async upsertMany(modelHandle, items, matchField) {
1398
+ const { data } = await callCore("instance.upsertMany", {
1399
+ modelHandle,
1400
+ items,
1401
+ matchField
1402
+ });
1403
+ return data;
1404
+ },
1405
+ /**
1406
+ * Check if a model is configured (linked) for the current app installation.
1407
+ *
1408
+ * This is useful for best-effort sync scenarios where you want to check
1409
+ * which models are available before attempting to create instances.
1410
+ *
1411
+ * The API token determines the context (app installation is embedded in sk_wkp_ tokens).
1412
+ *
1413
+ * @param modelHandle - The model handle from provision config
1414
+ * @returns true if the model is configured and has a valid targetId, false otherwise
1415
+ *
1416
+ * @example
1417
+ * ```ts
1418
+ * // Check if models are configured before syncing
1419
+ * const isTestOrderConfigured = await instance.isConfigured('test_order')
1420
+ * const isTestReportConfigured = await instance.isConfigured('test_report')
1421
+ *
1422
+ * if (isTestOrderConfigured) {
1423
+ * await instance.create('test_order', orderData)
1424
+ * }
1425
+ * ```
1426
+ */
1427
+ async isConfigured(modelHandle) {
1428
+ const { data } = await callCore("instance.isConfigured", {
1429
+ modelHandle
1430
+ });
1431
+ return data.configured;
1432
+ },
1433
+ /**
1434
+ * Check which models from a list are configured for the current app installation.
1435
+ *
1436
+ * This is more efficient than calling isConfigured() multiple times as it
1437
+ * makes a single API call to check all models at once.
1438
+ *
1439
+ * The API token determines the context (app installation is embedded in sk_wkp_ tokens).
1440
+ *
1441
+ * @param modelHandles - Array of model handles from provision config
1442
+ * @returns Map of model handle to configuration status
1443
+ *
1444
+ * @example
1445
+ * ```ts
1446
+ * // Check multiple models at once
1447
+ * const configStatus = await instance.getConfiguredModels([
1448
+ * 'test_order',
1449
+ * 'test_report',
1450
+ * 'panel_result',
1451
+ * 'culture_result',
1452
+ * ])
1453
+ *
1454
+ * if (configStatus.get('test_order')) {
1455
+ * // test_order is configured
1456
+ * }
1457
+ * ```
1458
+ */
1459
+ async getConfiguredModels(modelHandles) {
1460
+ const { data } = await callCore("instance.getConfiguredModels", {
1461
+ modelHandles
1462
+ });
1463
+ return new Map(Object.entries(data));
1464
+ }
1465
+ };
1466
+ var token = {
1467
+ /**
1468
+ * Exchange an sk_app_ token for an installation-scoped sk_wkp_ JWT.
1469
+ *
1470
+ * **Requires sk_app_ token** - only works with app-level tokens.
1471
+ * Used after identifying the target installation (e.g., via instance.search).
1472
+ *
1473
+ * The returned JWT is short-lived (1 hour) and scoped to the specific installation.
1474
+ *
1475
+ * @example
1476
+ * ```ts
1477
+ * // After finding the installation via instance.search
1478
+ * const { token: scopedToken } = await token.exchange(appInstallationId)
1479
+ *
1480
+ * // Use the scoped token for subsequent operations
1481
+ * runWithConfig({ apiToken: scopedToken, baseUrl: config.baseUrl }, async () => {
1482
+ * const channels = await communicationChannel.list({ filter: { identifierValue: phoneNumber } })
1483
+ * // ...
1484
+ * })
1485
+ * ```
1486
+ */
1487
+ async exchange(appInstallationId) {
1488
+ const { data } = await callCore("token.exchange", {
1489
+ appInstallationId
1490
+ });
1491
+ return data;
1492
+ }
1493
+ };
1494
+ var file = {
1495
+ /**
1496
+ * Get file metadata by ID.
1497
+ *
1498
+ * Returns file information including name, mimeType, and size.
1499
+ * Files are validated to ensure they belong to the requesting app installation.
1500
+ *
1501
+ * @example
1502
+ * ```ts
1503
+ * // Get file info
1504
+ * const fileInfo = await file.get('fl_abc123')
1505
+ * console.log(fileInfo.name) // 'document.pdf'
1506
+ * console.log(fileInfo.mimeType) // 'application/pdf'
1507
+ * console.log(fileInfo.size) // 12345
1508
+ * ```
1509
+ */
1510
+ async get(fileId) {
1511
+ const { data } = await callCore("file.get", {
1512
+ fileId
1513
+ });
1514
+ return data;
1515
+ },
1516
+ /**
1517
+ * Get a temporary download URL for an app-scoped file.
1518
+ *
1519
+ * Files are validated to ensure they belong to the requesting app installation.
1520
+ * The returned URL expires in 1 hour.
1521
+ *
1522
+ * @example
1523
+ * ```ts
1524
+ * // Get a download URL for a file
1525
+ * const { url, expiresAt } = await file.getUrl('fl_abc123')
1526
+ *
1527
+ * // Use the URL to download or pass to external services
1528
+ * const response = await fetch(url)
1529
+ * ```
1530
+ */
1531
+ async getUrl(fileId) {
1532
+ const { data } = await callCore("file.getUrl", {
1533
+ fileId
1534
+ });
1535
+ return data;
1536
+ },
1537
+ /**
1538
+ * Upload file content and create a File record.
1539
+ *
1540
+ * Files are scoped to the app installation and stored privately.
1541
+ * Use file.getUrl() to generate a temporary download URL when needed.
1542
+ *
1543
+ * @example
1544
+ * ```ts
1545
+ * // Upload a file from a Buffer
1546
+ * const buffer = await downloadFromExternalUrl(url)
1547
+ * const { id } = await file.upload({
1548
+ * content: buffer,
1549
+ * name: 'document.pdf',
1550
+ * mimeType: 'application/pdf',
1551
+ * })
1552
+ *
1553
+ * // Upload with a path prefix for organization
1554
+ * const { id } = await file.upload({
1555
+ * content: imageBuffer,
1556
+ * name: 'photo.jpg',
1557
+ * mimeType: 'image/jpeg',
1558
+ * path: 'attachments',
1559
+ * })
1560
+ * ```
1561
+ */
1562
+ async upload(params) {
1563
+ const content = typeof params.content === "string" ? params.content : params.content.toString("base64");
1564
+ const { data } = await callCore("file.upload", {
1565
+ content,
1566
+ name: params.name,
1567
+ mimeType: params.mimeType,
1568
+ ...params.path ? { path: params.path } : {}
1569
+ });
1570
+ return data;
1571
+ }
1572
+ };
1573
+ var webhook = {
1574
+ /**
1575
+ * Create a webhook registration for a handler.
1576
+ *
1577
+ * Creates a unique URL that external services can call.
1578
+ * When called, the request is routed to the handler defined in webhooks config.
1579
+ *
1580
+ * **Requires sk_wkp_ token** - registrations are scoped to the app installation.
1581
+ *
1582
+ * @param name - Handler name from webhooks config
1583
+ * @param context - Optional metadata passed to the handler when webhook fires
1584
+ * @param options - Optional configuration (e.g., expiration)
1585
+ *
1586
+ * @example
1587
+ * ```ts
1588
+ * // Create a webhook for Twilio compliance callbacks
1589
+ * const { url, id } = await webhook.create('compliance_status', {
1590
+ * bundleSid: bundle.sid,
1591
+ * complianceRecordId: record.id,
1592
+ * })
1593
+ *
1594
+ * // Pass the URL to Twilio
1595
+ * await twilioClient.bundles(bundle.sid).update({
1596
+ * statusCallback: url,
1597
+ * })
1598
+ * ```
1599
+ */
1600
+ async create(name, context, options) {
1601
+ const { data } = await callCore("webhook.create", {
1602
+ name,
1603
+ ...context ? { context } : {},
1604
+ ...options?.expiresIn ? { expiresIn: options.expiresIn } : {}
1605
+ });
1606
+ return data;
1607
+ },
1608
+ /**
1609
+ * Delete a webhook registration by ID.
1610
+ *
1611
+ * @param id - Registration ID (whkr_xxx format)
1612
+ * @returns Whether the registration was deleted (false if not found)
1613
+ *
1614
+ * @example
1615
+ * ```ts
1616
+ * const { deleted } = await webhook.delete('whkr_abc123')
1617
+ * ```
1618
+ */
1619
+ async delete(id) {
1620
+ const { data } = await callCore("webhook.delete", {
1621
+ id
1622
+ });
1623
+ return data;
1624
+ },
1625
+ /**
1626
+ * Delete webhook registrations by handler name.
1627
+ *
1628
+ * Useful for cleaning up all webhooks of a certain type,
1629
+ * or filtering by context values.
1630
+ *
1631
+ * @param name - Handler name from webhooks config
1632
+ * @param options - Optional filter by context values
1633
+ * @returns Number of registrations deleted
1634
+ *
1635
+ * @example
1636
+ * ```ts
1637
+ * // Delete all receive_sms webhooks for this installation
1638
+ * const { count } = await webhook.deleteByName('receive_sms')
1639
+ *
1640
+ * // Delete only webhooks for a specific channel
1641
+ * const { count } = await webhook.deleteByName('receive_sms', {
1642
+ * filter: { communicationChannelId: channel.id },
1643
+ * })
1644
+ * ```
1645
+ */
1646
+ async deleteByName(name, options) {
1647
+ const { data } = await callCore("webhook.deleteByName", {
1648
+ name,
1649
+ ...options?.filter ? { filter: options.filter } : {}
1650
+ });
1651
+ return data;
1652
+ },
1653
+ /**
1654
+ * List webhook registrations for this installation.
1655
+ *
1656
+ * @param options - Optional filter by handler name
1657
+ * @returns Array of webhook registrations
1658
+ *
1659
+ * @example
1660
+ * ```ts
1661
+ * // List all webhooks
1662
+ * const { webhooks } = await webhook.list()
1663
+ *
1664
+ * // List only receive_sms webhooks
1665
+ * const { webhooks } = await webhook.list({ name: 'receive_sms' })
1666
+ * ```
1667
+ */
1668
+ async list(options) {
1669
+ const { data } = await callCore("webhook.list", {
1670
+ ...options?.name ? { name: options.name } : {}
1671
+ });
1672
+ return { webhooks: data };
1673
+ }
1674
+ };
1675
+ var resource = {
1676
+ /**
1677
+ * Link a SHARED app resource (model) to a user's resource.
1678
+ *
1679
+ * Creates an AppResourceInstance hierarchy for MODEL types.
1680
+ * This establishes the connection between an app's SHARED model
1681
+ * (e.g., 'contact') and a user's actual workplace model (e.g., 'Clients').
1682
+ *
1683
+ * The API token determines the context (app installation is embedded in sk_wkp_ tokens).
1684
+ *
1685
+ * @param params - Link parameters
1686
+ *
1687
+ * @example
1688
+ * ```ts
1689
+ * // Link the SHARED 'contact' model to user's 'Clients' model
1690
+ * const { instanceId } = await resource.link({
1691
+ * handle: 'contact', // SHARED model handle from provision config
1692
+ * targetModelId: modelId, // User's selected model ID
1693
+ * channelId: channel.id, // Optional: scope to communication channel
1694
+ * })
1695
+ * ```
1696
+ */
1697
+ async link(params) {
1698
+ const { data } = await callCore("resource.link", {
1699
+ ...params
1700
+ });
1701
+ return data;
1702
+ }
1703
+ };
1704
+ function zodSchemaToJsonSchema(schema) {
1705
+ return z4.toJSONSchema(schema);
1706
+ }
1707
+ var ai = {
1708
+ /**
1709
+ * Generate a structured object using AI.
1710
+ *
1711
+ * The AI will generate an object that conforms to the provided Zod schema.
1712
+ * Supports both simple text prompts and multimodal messages with files/images.
1713
+ *
1714
+ * @example
1715
+ * ```ts
1716
+ * // Simple text prompt
1717
+ * const result = await ai.generateObject({
1718
+ * system: 'Extract patient information from the text.',
1719
+ * prompt: 'Patient: Max, Species: Canine, DOB: 2020-01-15',
1720
+ * schema: z.object({
1721
+ * patientName: z.string(),
1722
+ * species: z.string(),
1723
+ * dateOfBirth: z.string().nullable(),
1724
+ * }),
1725
+ * })
1726
+ *
1727
+ * // With files array (simple multimodal)
1728
+ * const result = await ai.generateObject({
1729
+ * model: 'openai/gpt-4o',
1730
+ * system: 'Parse the lab report and extract test results.',
1731
+ * prompt: 'Extract all test results from this report.',
1732
+ * files: ['fl_abc123'],
1733
+ * schema: TestResultsSchema,
1734
+ * })
1735
+ *
1736
+ * // With messages (advanced multimodal)
1737
+ * const result = await ai.generateObject({
1738
+ * model: 'openai/gpt-4o',
1739
+ * system: 'Parse the lab report and extract test results.',
1740
+ * schema: TestResultsSchema,
1741
+ * messages: [
1742
+ * {
1743
+ * role: 'user',
1744
+ * content: [
1745
+ * { type: 'text', text: 'Extract all test results from this report.' },
1746
+ * { type: 'file', fileId: 'fl_abc123' },
1747
+ * ],
1748
+ * },
1749
+ * ],
1750
+ * })
1751
+ * ```
1752
+ */
1753
+ async generateObject(options) {
1754
+ if (!options.prompt && !options.messages) {
1755
+ throw new Error("Either prompt or messages must be provided");
1756
+ }
1757
+ const jsonSchema = zodSchemaToJsonSchema(options.schema);
1758
+ const { data } = await callCore("ai.generateObject", {
1759
+ ...options.model ? { model: options.model } : {},
1760
+ system: options.system,
1761
+ ...options.prompt ? { prompt: options.prompt } : {},
1762
+ schema: jsonSchema,
1763
+ ...options.files && options.files.length > 0 ? { files: options.files } : {},
1764
+ ...options.messages ? { messages: options.messages } : {},
1765
+ ...options.maxTokens !== void 0 ? { maxTokens: options.maxTokens } : {},
1766
+ ...options.temperature !== void 0 ? { temperature: options.temperature } : {}
1767
+ });
1768
+ return data;
1769
+ }
1770
+ };
1771
+ var report = {
1772
+ /**
1773
+ * Generate a report from a template.
1774
+ *
1775
+ * By default, returns a URL to view the report. Set `mode: 'html'` to get
1776
+ * the rendered HTML content directly.
1777
+ *
1778
+ * **Requires sk_wkp_ token** - reports are scoped to workplaces.
1779
+ *
1780
+ * @example
1781
+ * ```ts
1782
+ * // Get report URL (default)
1783
+ * const { url } = await report.generate({
1784
+ * templateHandle: 'lab-results',
1785
+ * arguments: { patient_id: 'ins_abc123' },
1786
+ * })
1787
+ * console.log(url) // https://app.skedyul.com/crux/reports/lab-results/generate?patient_id=ins_abc123
1788
+ *
1789
+ * // Get HTML content
1790
+ * const { html } = await report.generate({
1791
+ * templateHandle: 'lab-results',
1792
+ * arguments: { patient_id: 'ins_abc123' },
1793
+ * mode: 'html',
1794
+ * })
1795
+ * // Use html for email, PDF generation, etc.
1796
+ * ```
1797
+ */
1798
+ async generate(params) {
1799
+ const { data } = await callCore("report.generate", {
1800
+ templateHandle: params.templateHandle,
1801
+ ...params.arguments ? { arguments: params.arguments } : {},
1802
+ ...params.mode ? { mode: params.mode } : {}
1803
+ });
1804
+ return data;
1805
+ },
1806
+ /**
1807
+ * Define (create or update) a report from a YAML template.
1808
+ *
1809
+ * If a report with the same handle already exists, it will be updated
1810
+ * and the version number incremented.
1811
+ *
1812
+ * **Requires sk_wkp_ token** - reports are scoped to workplaces.
1813
+ *
1814
+ * @example
1815
+ * ```ts
1816
+ * const { definitionId, handle, version } = await report.define({
1817
+ * yaml: `
1818
+ * handle: patient-summary
1819
+ * name: Patient Summary
1820
+ * sections:
1821
+ * - type: header
1822
+ * title: "{{ patient.name }}"
1823
+ * `,
1824
+ * })
1825
+ * console.log(`Created report ${handle} v${version}`)
1826
+ * ```
1827
+ */
1828
+ async define(params) {
1829
+ const { data } = await callCore("report.define", {
1830
+ yaml: params.yaml
1831
+ });
1832
+ return data;
1833
+ },
1834
+ /**
1835
+ * List report definitions in the workplace.
1836
+ *
1837
+ * **Requires sk_wkp_ token** - reports are scoped to workplaces.
1838
+ *
1839
+ * @example
1840
+ * ```ts
1841
+ * const { data, pagination } = await report.list({ page: 1, limit: 10 })
1842
+ * for (const def of data) {
1843
+ * console.log(`${def.handle}: ${def.name} (v${def.version})`)
1844
+ * }
1845
+ * ```
1846
+ */
1847
+ async list(params) {
1848
+ const { data, pagination } = await callCore("report.list", {
1849
+ ...params?.page !== void 0 ? { page: params.page } : {},
1850
+ ...params?.limit !== void 0 ? { limit: params.limit } : {}
1851
+ });
1852
+ return {
1853
+ data,
1854
+ pagination: pagination ?? { page: 1, total: 0, hasMore: false, limit: params?.limit ?? 50 }
1855
+ };
1856
+ },
1857
+ /**
1858
+ * Get a report definition by handle.
1859
+ *
1860
+ * **Requires sk_wkp_ token** - reports are scoped to workplaces.
1861
+ *
1862
+ * @example
1863
+ * ```ts
1864
+ * const definition = await report.get('lab-results')
1865
+ * if (definition) {
1866
+ * console.log(definition.templateYaml)
1867
+ * }
1868
+ * ```
1869
+ */
1870
+ async get(handle) {
1871
+ const { data } = await callCore("report.get", {
1872
+ handle
1873
+ });
1874
+ return data;
1875
+ }
1876
+ };
1877
+
1878
+ // src/errors.ts
1879
+ var InstallError = class extends Error {
1880
+ // Optional: which field caused the error
1881
+ constructor(message, code, field) {
1882
+ super(message);
1883
+ this.name = "InstallError";
1884
+ this.code = code;
1885
+ this.field = field;
1886
+ }
1887
+ };
1888
+ var MissingRequiredFieldError = class extends InstallError {
1889
+ constructor(fieldName, message) {
1890
+ super(
1891
+ message ?? `${fieldName} is required`,
1892
+ "MISSING_REQUIRED_FIELD",
1893
+ fieldName
1894
+ );
1895
+ this.name = "MissingRequiredFieldError";
1896
+ }
1897
+ };
1898
+ var AuthenticationError = class extends InstallError {
1899
+ constructor(message) {
1900
+ super(message ?? "Authentication failed", "AUTHENTICATION_FAILED");
1901
+ this.name = "AuthenticationError";
1902
+ }
1903
+ };
1904
+ var InvalidConfigurationError = class extends InstallError {
1905
+ constructor(fieldName, message) {
1906
+ super(
1907
+ message ?? "Invalid configuration",
1908
+ "INVALID_CONFIGURATION",
1909
+ fieldName
1910
+ );
1911
+ this.name = "InvalidConfigurationError";
1912
+ }
1913
+ };
1914
+ var ConnectionError = class extends InstallError {
1915
+ constructor(message) {
1916
+ super(
1917
+ message ?? "Connection failed",
1918
+ "CONNECTION_FAILED"
1919
+ );
1920
+ this.name = "ConnectionError";
1921
+ }
1922
+ };
1923
+ var AppAuthInvalidError = class extends Error {
1924
+ constructor(message) {
1925
+ super(message);
1926
+ this.code = "APP_AUTH_INVALID";
1927
+ this.name = "AppAuthInvalidError";
1928
+ }
1929
+ };
1930
+
1931
+ // src/server/context-logger.ts
1932
+ import { AsyncLocalStorage as AsyncLocalStorage2 } from "async_hooks";
1933
+ var logContextStorage = new AsyncLocalStorage2();
1934
+ function runWithLogContext(context, fn) {
1935
+ return logContextStorage.run(context, fn);
1936
+ }
1937
+ function getLogContext() {
1938
+ return logContextStorage.getStore();
1939
+ }
1940
+ function safeStringify(value) {
1941
+ if (value === void 0) return "undefined";
1942
+ if (value === null) return "null";
1943
+ if (typeof value === "string") return value;
1944
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
1945
+ if (value instanceof Error) {
1946
+ return `${value.name}: ${value.message}${value.stack ? `
1947
+ ${value.stack}` : ""}`;
1948
+ }
1949
+ try {
1950
+ return JSON.stringify(value);
1951
+ } catch {
1952
+ return String(value);
1953
+ }
1954
+ }
1955
+ function formatLogWithContext(args) {
1956
+ const context = getLogContext();
1957
+ if (!context?.invocation) {
1958
+ return args;
1959
+ }
1960
+ const contextPrefix = {
1961
+ invocationType: context.invocation.invocationType,
1962
+ ...context.invocation.toolHandle && { toolHandle: context.invocation.toolHandle },
1963
+ ...context.invocation.serverHookHandle && { serverHookHandle: context.invocation.serverHookHandle },
1964
+ ...context.invocation.appInstallationId && { appInstallationId: context.invocation.appInstallationId },
1965
+ ...context.invocation.toolCallId && { toolCallId: context.invocation.toolCallId },
1966
+ ...context.invocation.workflowId && { workflowId: context.invocation.workflowId },
1967
+ ...context.invocation.workflowRunId && { workflowRunId: context.invocation.workflowRunId }
1968
+ };
1969
+ const prefix = `[${JSON.stringify(contextPrefix)}]`;
1970
+ const messageParts = args.map((arg) => {
1971
+ if (typeof arg === "string") return arg;
1972
+ return safeStringify(arg);
1973
+ });
1974
+ return [`${prefix} ${messageParts.join(" ")}`];
1975
+ }
1976
+ var originalConsole = {
1977
+ log: console.log.bind(console),
1978
+ info: console.info.bind(console),
1979
+ warn: console.warn.bind(console),
1980
+ error: console.error.bind(console),
1981
+ debug: console.debug.bind(console)
1982
+ };
1983
+ function installContextLogger() {
1984
+ console.log = (...args) => {
1985
+ originalConsole.log(...formatLogWithContext(args));
1986
+ };
1987
+ console.info = (...args) => {
1988
+ originalConsole.info(...formatLogWithContext(args));
1989
+ };
1990
+ console.warn = (...args) => {
1991
+ originalConsole.warn(...formatLogWithContext(args));
1992
+ };
1993
+ console.error = (...args) => {
1994
+ originalConsole.error(...formatLogWithContext(args));
1995
+ };
1996
+ console.debug = (...args) => {
1997
+ originalConsole.debug(...formatLogWithContext(args));
1998
+ };
1999
+ }
2000
+
2001
+ // src/server/logger.ts
2002
+ function createContextLogger() {
2003
+ const log = ((msg, ...args) => {
2004
+ console.log(msg, ...args);
2005
+ });
2006
+ log.info = (msg, ...args) => {
2007
+ console.info(msg, ...args);
2008
+ };
2009
+ log.warn = (msg, ...args) => {
2010
+ console.warn(msg, ...args);
2011
+ };
2012
+ log.error = (msg, ...args) => {
2013
+ console.error(msg, ...args);
2014
+ };
2015
+ log.debug = (msg, ...args) => {
2016
+ console.debug(msg, ...args);
2017
+ };
2018
+ return log;
2019
+ }
2020
+
2021
+ // src/server/tool-handler.ts
2022
+ function buildToolMetadata(registry) {
2023
+ return Object.values(registry).map((tool) => {
2024
+ const timeout = typeof tool.timeout === "number" && tool.timeout > 0 ? tool.timeout : 1e4;
2025
+ const retries = typeof tool.retries === "number" && tool.retries >= 1 ? tool.retries : 1;
2026
+ return {
2027
+ name: tool.name,
2028
+ displayName: tool.label || tool.name,
2029
+ description: tool.description,
2030
+ inputSchema: getJsonSchemaFromToolSchema(tool.inputSchema),
2031
+ outputSchema: getJsonSchemaFromToolSchema(tool.outputSchema),
2032
+ timeout,
2033
+ retries
2034
+ };
2035
+ });
2036
+ }
2037
+ function createRequestState(maxRequests, ttlExtendSeconds, runtimeLabel, toolNames) {
2038
+ let requestCount = 0;
2039
+ let lastRequestTime = Date.now();
2040
+ return {
2041
+ incrementRequestCount() {
2042
+ requestCount += 1;
2043
+ lastRequestTime = Date.now();
2044
+ },
2045
+ shouldShutdown() {
2046
+ return maxRequests !== null && requestCount >= maxRequests;
2047
+ },
2048
+ getHealthStatus() {
2049
+ return {
2050
+ status: "running",
2051
+ requests: requestCount,
2052
+ maxRequests,
2053
+ requestsRemaining: maxRequests !== null ? Math.max(0, maxRequests - requestCount) : null,
2054
+ lastRequestTime,
2055
+ ttlExtendSeconds,
2056
+ runtime: runtimeLabel,
2057
+ tools: [...toolNames]
2058
+ };
2059
+ }
2060
+ };
2061
+ }
2062
+ function createCallToolHandler(registry, state, onMaxRequests) {
2063
+ return async function callTool(nameRaw, argsRaw) {
2064
+ const toolName = String(nameRaw);
2065
+ const tool = registry[toolName];
2066
+ if (!tool) {
2067
+ throw new Error(`Tool "${toolName}" not found in registry`);
2068
+ }
2069
+ if (!tool.handler || typeof tool.handler !== "function") {
2070
+ throw new Error(`Tool "${toolName}" handler is not a function`);
2071
+ }
2072
+ const fn = tool.handler;
2073
+ const args = argsRaw ?? {};
2074
+ const estimateMode = args.estimate === true;
2075
+ if (!estimateMode) {
2076
+ state.incrementRequestCount();
2077
+ if (state.shouldShutdown()) {
2078
+ onMaxRequests?.();
2079
+ }
2080
+ }
2081
+ const requestEnv = args.env ?? {};
2082
+ const originalEnv = { ...process.env };
2083
+ Object.assign(process.env, requestEnv);
2084
+ const invocation = args.invocation;
2085
+ try {
2086
+ const inputs = args.inputs ?? {};
2087
+ const rawContext = args.context ?? {};
2088
+ const app = rawContext.app;
2089
+ const trigger = rawContext.trigger || "agent";
2090
+ let executionContext;
2091
+ const log = createContextLogger();
2092
+ if (trigger === "provision") {
2093
+ executionContext = {
2094
+ trigger: "provision",
2095
+ app,
2096
+ env: process.env,
2097
+ mode: estimateMode ? "estimate" : "execute",
2098
+ invocation,
2099
+ log
2100
+ };
2101
+ } else {
2102
+ const workplace2 = rawContext.workplace;
2103
+ const request = rawContext.request;
2104
+ const appInstallationId = rawContext.appInstallationId;
2105
+ const envVars = process.env;
2106
+ const modeValue = estimateMode ? "estimate" : "execute";
2107
+ if (trigger === "field_change") {
2108
+ const field = rawContext.field;
2109
+ executionContext = { trigger: "field_change", app, appInstallationId, workplace: workplace2, request, env: envVars, mode: modeValue, field, invocation, log };
2110
+ } else if (trigger === "page_action") {
2111
+ const page = rawContext.page;
2112
+ executionContext = { trigger: "page_action", app, appInstallationId, workplace: workplace2, request, env: envVars, mode: modeValue, page, invocation, log };
2113
+ } else if (trigger === "form_submit") {
2114
+ const form = rawContext.form;
2115
+ executionContext = { trigger: "form_submit", app, appInstallationId, workplace: workplace2, request, env: envVars, mode: modeValue, form, invocation, log };
2116
+ } else if (trigger === "workflow") {
2117
+ executionContext = { trigger: "workflow", app, appInstallationId, workplace: workplace2, request, env: envVars, mode: modeValue, invocation, log };
2118
+ } else if (trigger === "page_context") {
2119
+ executionContext = { trigger: "agent", app, appInstallationId, workplace: workplace2, request, env: envVars, mode: modeValue, invocation, log };
2120
+ } else {
2121
+ executionContext = { trigger: "agent", app, appInstallationId, workplace: workplace2, request, env: envVars, mode: modeValue, invocation, log };
2122
+ }
2123
+ }
2124
+ const requestConfig = {
2125
+ baseUrl: requestEnv.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
2126
+ apiToken: requestEnv.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
2127
+ };
2128
+ const functionResult = await runWithLogContext({ invocation }, async () => {
2129
+ return await runWithConfig(requestConfig, async () => {
2130
+ return await fn(inputs, executionContext);
2131
+ });
2132
+ });
2133
+ const billing = normalizeBilling(functionResult.billing);
2134
+ return {
2135
+ output: functionResult.output,
2136
+ billing,
2137
+ meta: functionResult.meta ?? {
2138
+ success: true,
2139
+ message: "OK",
2140
+ toolName
2141
+ },
2142
+ effect: functionResult.effect
2143
+ };
2144
+ } catch (error) {
2145
+ if (error instanceof AppAuthInvalidError) {
2146
+ return {
2147
+ output: null,
2148
+ billing: { credits: 0 },
2149
+ meta: {
2150
+ success: false,
2151
+ message: error.message,
2152
+ toolName
2153
+ },
2154
+ error: {
2155
+ code: error.code,
2156
+ message: error.message
2157
+ }
2158
+ // Note: redirect URL will be added by workflow after detecting APP_AUTH_INVALID
2159
+ };
2160
+ }
2161
+ const errorMessage = error instanceof Error ? error.message : String(error ?? "");
2162
+ return {
2163
+ output: null,
2164
+ billing: { credits: 0 },
2165
+ meta: {
2166
+ success: false,
2167
+ message: errorMessage,
2168
+ toolName
2169
+ },
2170
+ error: {
2171
+ code: "TOOL_EXECUTION_ERROR",
2172
+ message: errorMessage
2173
+ }
2174
+ };
2175
+ } finally {
2176
+ process.env = originalEnv;
2177
+ }
2178
+ };
2179
+ }
2180
+
2181
+ // src/server/dedicated.ts
2182
+ import http from "http";
2183
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
2184
+
2185
+ // src/server/core-api-handler.ts
2186
+ async function handleCoreMethod(method, params) {
2187
+ const service = coreApiService.getService();
2188
+ if (!service) {
2189
+ return {
2190
+ status: 404,
2191
+ payload: { error: "Core API service not configured" }
2192
+ };
2193
+ }
2194
+ if (method === "createCommunicationChannel") {
2195
+ if (!params?.channel) {
2196
+ return { status: 400, payload: { error: "channel is required" } };
2197
+ }
2198
+ const channel = params.channel;
2199
+ const result = await coreApiService.callCreateChannel(channel);
2200
+ if (!result) {
2201
+ return {
2202
+ status: 500,
2203
+ payload: { error: "Core API service did not respond" }
2204
+ };
2205
+ }
2206
+ return { status: 200, payload: result };
2207
+ }
2208
+ if (method === "updateCommunicationChannel") {
2209
+ if (!params?.channel) {
2210
+ return { status: 400, payload: { error: "channel is required" } };
2211
+ }
2212
+ const channel = params.channel;
2213
+ const result = await coreApiService.callUpdateChannel(channel);
2214
+ if (!result) {
2215
+ return {
2216
+ status: 500,
2217
+ payload: { error: "Core API service did not respond" }
2218
+ };
2219
+ }
2220
+ return { status: 200, payload: result };
2221
+ }
2222
+ if (method === "deleteCommunicationChannel") {
2223
+ if (!params?.id || typeof params.id !== "string") {
2224
+ return { status: 400, payload: { error: "id is required" } };
2225
+ }
2226
+ const result = await coreApiService.callDeleteChannel(params.id);
2227
+ if (!result) {
2228
+ return {
2229
+ status: 500,
2230
+ payload: { error: "Core API service did not respond" }
2231
+ };
2232
+ }
2233
+ return { status: 200, payload: result };
2234
+ }
2235
+ if (method === "getCommunicationChannel") {
2236
+ if (!params?.id || typeof params.id !== "string") {
2237
+ return { status: 400, payload: { error: "id is required" } };
2238
+ }
2239
+ const result = await coreApiService.callGetChannel(params.id);
2240
+ if (!result) {
2241
+ return {
2242
+ status: 404,
2243
+ payload: { error: "Channel not found" }
2244
+ };
2245
+ }
2246
+ return { status: 200, payload: result };
2247
+ }
2248
+ if (method === "getCommunicationChannels") {
2249
+ const result = await coreApiService.callListChannels();
2250
+ if (!result) {
2251
+ return {
2252
+ status: 500,
2253
+ payload: { error: "Core API service did not respond" }
2254
+ };
2255
+ }
2256
+ return { status: 200, payload: result };
2257
+ }
2258
+ if (method === "communicationChannel.list") {
2259
+ const result = await coreApiService.callListChannels();
2260
+ if (!result) {
2261
+ return {
2262
+ status: 500,
2263
+ payload: { error: "Core API service did not respond" }
2264
+ };
2265
+ }
2266
+ return { status: 200, payload: result };
2267
+ }
2268
+ if (method === "communicationChannel.get") {
2269
+ if (!params?.id || typeof params.id !== "string") {
2270
+ return { status: 400, payload: { error: "id is required" } };
2271
+ }
2272
+ const result = await coreApiService.callGetChannel(params.id);
2273
+ if (!result) {
2274
+ return {
2275
+ status: 404,
2276
+ payload: { error: "Channel not found" }
2277
+ };
2278
+ }
2279
+ return { status: 200, payload: result };
2280
+ }
2281
+ if (method === "workplace.list") {
2282
+ const result = await coreApiService.callListWorkplaces();
2283
+ if (!result) {
2284
+ return {
2285
+ status: 500,
2286
+ payload: { error: "Core API service did not respond" }
2287
+ };
2288
+ }
2289
+ return { status: 200, payload: result };
2290
+ }
2291
+ if (method === "workplace.get") {
2292
+ if (!params?.id || typeof params.id !== "string") {
2293
+ return { status: 400, payload: { error: "id is required" } };
2294
+ }
2295
+ const result = await coreApiService.callGetWorkplace(params.id);
2296
+ if (!result) {
2297
+ return {
2298
+ status: 404,
2299
+ payload: { error: "Workplace not found" }
2300
+ };
2301
+ }
2302
+ return { status: 200, payload: result };
2303
+ }
2304
+ if (method === "sendMessage") {
2305
+ if (!params?.message || !params?.communicationChannel) {
2306
+ return { status: 400, payload: { error: "message and communicationChannel are required" } };
2307
+ }
2308
+ const msg = params.message;
2309
+ const channel = params.communicationChannel;
2310
+ const result = await coreApiService.callSendMessage({
2311
+ message: msg,
2312
+ communicationChannel: channel
2313
+ });
2314
+ if (!result) {
2315
+ return {
2316
+ status: 500,
2317
+ payload: { error: "Core API service did not respond" }
2318
+ };
2319
+ }
2320
+ return { status: 200, payload: result };
2321
+ }
2322
+ return {
2323
+ status: 400,
2324
+ payload: { error: "Unknown core method" }
2325
+ };
2326
+ }
2327
+
2328
+ // src/server/handler-helpers.ts
2329
+ function parseHandlerEnvelope(parsedBody) {
2330
+ if (typeof parsedBody !== "object" || parsedBody === null || Array.isArray(parsedBody) || !("env" in parsedBody) || !("request" in parsedBody)) {
2331
+ return null;
2332
+ }
2333
+ const envelope = parsedBody;
2334
+ if (typeof envelope.env !== "object" || envelope.env === null || Array.isArray(envelope.env)) {
2335
+ return null;
2336
+ }
2337
+ if (typeof envelope.request !== "object" || envelope.request === null || Array.isArray(envelope.request)) {
2338
+ return null;
2339
+ }
2340
+ return {
2341
+ env: envelope.env,
2342
+ request: envelope.request,
2343
+ context: envelope.context
2344
+ };
2345
+ }
2346
+ function buildRequestFromRaw(raw) {
2347
+ let parsedBody = raw.body;
2348
+ const contentType = raw.headers["content-type"] ?? "";
2349
+ if (contentType.includes("application/json")) {
2350
+ try {
2351
+ parsedBody = raw.body ? JSON.parse(raw.body) : {};
2352
+ } catch {
2353
+ parsedBody = raw.body;
2354
+ }
2355
+ }
2356
+ return {
2357
+ method: raw.method,
2358
+ url: raw.url,
2359
+ path: raw.path,
2360
+ headers: raw.headers,
2361
+ query: raw.query,
2362
+ body: parsedBody,
2363
+ rawBody: raw.body ? Buffer.from(raw.body, "utf-8") : void 0
2364
+ };
2365
+ }
2366
+ function buildRequestScopedConfig(env) {
2367
+ return {
2368
+ baseUrl: env.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
2369
+ apiToken: env.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
2370
+ };
2371
+ }
2372
+
2373
+ // src/server/startup-logger.ts
2374
+ function padEnd(str, length) {
2375
+ if (str.length >= length) {
2376
+ return str.slice(0, length);
2377
+ }
2378
+ return str + " ".repeat(length - str.length);
2379
+ }
2380
+ function printStartupLog(config, tools, webhookRegistry, port) {
2381
+ if (process.env.NODE_ENV === "test") {
2382
+ return;
2383
+ }
2384
+ const webhookCount = webhookRegistry ? Object.keys(webhookRegistry).length : 0;
2385
+ const webhookNames = webhookRegistry ? Object.keys(webhookRegistry) : [];
2386
+ const maxRequests = config.maxRequests ?? parseNumberEnv(process.env.MCP_MAX_REQUESTS) ?? null;
2387
+ const ttlExtendSeconds = config.ttlExtendSeconds ?? parseNumberEnv(process.env.MCP_TTL_EXTEND) ?? 3600;
2388
+ const executableId = process.env.SKEDYUL_EXECUTABLE_ID || "local";
2389
+ const divider = "\u2550".repeat(70);
2390
+ const thinDivider = "\u2500".repeat(70);
2391
+ console.log("");
2392
+ console.log(`\u2554${divider}\u2557`);
2393
+ console.log(`\u2551 \u{1F680} Skedyul MCP Server Starting \u2551`);
2394
+ console.log(`\u2560${divider}\u2563`);
2395
+ console.log(`\u2551 \u2551`);
2396
+ console.log(`\u2551 \u{1F4E6} Server: ${padEnd(config.metadata.name, 49)}\u2551`);
2397
+ console.log(`\u2551 \u{1F3F7}\uFE0F Version: ${padEnd(config.metadata.version, 49)}\u2551`);
2398
+ console.log(`\u2551 \u26A1 Compute: ${padEnd(config.computeLayer, 49)}\u2551`);
2399
+ if (port) {
2400
+ console.log(`\u2551 \u{1F310} Port: ${padEnd(String(port), 49)}\u2551`);
2401
+ }
2402
+ console.log(`\u2551 \u{1F511} Executable: ${padEnd(executableId, 49)}\u2551`);
2403
+ console.log(`\u2551 \u2551`);
2404
+ console.log(`\u255F${thinDivider}\u2562`);
2405
+ console.log(`\u2551 \u2551`);
2406
+ console.log(`\u2551 \u{1F527} Tools (${tools.length}): \u2551`);
2407
+ const maxToolsToShow = 10;
2408
+ const toolsToShow = tools.slice(0, maxToolsToShow);
2409
+ for (const tool of toolsToShow) {
2410
+ console.log(`\u2551 \u2022 ${padEnd(tool.name, 61)}\u2551`);
2411
+ }
2412
+ if (tools.length > maxToolsToShow) {
2413
+ console.log(`\u2551 ... and ${tools.length - maxToolsToShow} more \u2551`);
2414
+ }
2415
+ if (webhookCount > 0) {
2416
+ console.log(`\u2551 \u2551`);
2417
+ console.log(`\u2551 \u{1FA9D} Webhooks (${webhookCount}): \u2551`);
2418
+ const maxWebhooksToShow = 5;
2419
+ const webhooksToShow = webhookNames.slice(0, maxWebhooksToShow);
2420
+ for (const name of webhooksToShow) {
2421
+ console.log(`\u2551 \u2022 /webhooks/${padEnd(name, 51)}\u2551`);
2422
+ }
2423
+ if (webhookCount > maxWebhooksToShow) {
2424
+ console.log(`\u2551 ... and ${webhookCount - maxWebhooksToShow} more \u2551`);
2425
+ }
2426
+ }
2427
+ console.log(`\u2551 \u2551`);
2428
+ console.log(`\u255F${thinDivider}\u2562`);
2429
+ console.log(`\u2551 \u2551`);
2430
+ console.log(`\u2551 \u2699\uFE0F Configuration: \u2551`);
2431
+ console.log(`\u2551 Max Requests: ${padEnd(maxRequests !== null ? String(maxRequests) : "unlimited", 46)}\u2551`);
2432
+ console.log(`\u2551 TTL Extend: ${padEnd(`${ttlExtendSeconds}s`, 46)}\u2551`);
2433
+ console.log(`\u2551 \u2551`);
2434
+ console.log(`\u255F${thinDivider}\u2562`);
2435
+ console.log(`\u2551 \u2705 Ready at ${padEnd((/* @__PURE__ */ new Date()).toISOString(), 55)}\u2551`);
2436
+ console.log(`\u255A${divider}\u255D`);
2437
+ console.log("");
2438
+ }
2439
+
2440
+ // src/server/dedicated.ts
2441
+ function createDedicatedServerInstance(config, tools, callTool, state, mcpServer, webhookRegistry) {
2442
+ const port = getListeningPort(config);
2443
+ const httpServer = http.createServer(
2444
+ async (req, res) => {
2445
+ function sendCoreResult(result) {
2446
+ sendJSON(res, result.status, result.payload);
2447
+ }
2448
+ try {
2449
+ const url = new URL(
2450
+ req.url || "/",
2451
+ `http://${req.headers.host || "localhost"}`
2452
+ );
2453
+ const pathname = url.pathname;
2454
+ if (pathname === "/health" && req.method === "GET") {
2455
+ sendJSON(res, 200, state.getHealthStatus());
2456
+ return;
2457
+ }
2458
+ if (pathname.startsWith("/webhooks/") && webhookRegistry) {
2459
+ const handle = pathname.slice("/webhooks/".length);
2460
+ const webhookDef = webhookRegistry[handle];
2461
+ if (!webhookDef) {
2462
+ sendJSON(res, 404, { error: `Webhook handler '${handle}' not found` });
2463
+ return;
2464
+ }
2465
+ const allowedMethods = webhookDef.methods ?? ["POST"];
2466
+ if (!allowedMethods.includes(req.method)) {
2467
+ sendJSON(res, 405, { error: `Method ${req.method} not allowed` });
2468
+ return;
2469
+ }
2470
+ let rawBody;
2471
+ try {
2472
+ rawBody = await readRawRequestBody(req);
2473
+ } catch {
2474
+ sendJSON(res, 400, { error: "Failed to read request body" });
2475
+ return;
2476
+ }
2477
+ let parsedBody;
2478
+ const contentType = req.headers["content-type"] ?? "";
2479
+ if (contentType.includes("application/json")) {
2480
+ try {
2481
+ parsedBody = rawBody ? JSON.parse(rawBody) : {};
2482
+ } catch {
2483
+ parsedBody = rawBody;
2484
+ }
2485
+ } else {
2486
+ parsedBody = rawBody;
2487
+ }
2488
+ const envelope = parseHandlerEnvelope(parsedBody);
2489
+ let webhookRequest;
2490
+ let webhookContext;
2491
+ let requestEnv = {};
2492
+ let invocation;
2493
+ if (envelope && "context" in envelope && envelope.context) {
2494
+ const context = envelope.context;
2495
+ requestEnv = envelope.env;
2496
+ invocation = parsedBody.invocation;
2497
+ webhookRequest = buildRequestFromRaw(envelope.request);
2498
+ const envVars = { ...process.env, ...envelope.env };
2499
+ const app = context.app;
2500
+ if (context.appInstallationId && context.workplace) {
2501
+ webhookContext = {
2502
+ env: envVars,
2503
+ app,
2504
+ appInstallationId: context.appInstallationId,
2505
+ workplace: context.workplace,
2506
+ registration: context.registration ?? {},
2507
+ invocation,
2508
+ log: createContextLogger()
2509
+ };
2510
+ } else {
2511
+ webhookContext = {
2512
+ env: envVars,
2513
+ app,
2514
+ invocation,
2515
+ log: createContextLogger()
2516
+ };
2517
+ }
2518
+ } else {
2519
+ const appId = req.headers["x-skedyul-app-id"];
2520
+ const appVersionId = req.headers["x-skedyul-app-version-id"];
2521
+ if (!appId || !appVersionId) {
2522
+ throw new Error("Missing app info in webhook request (x-skedyul-app-id and x-skedyul-app-version-id headers required)");
2523
+ }
2524
+ webhookRequest = {
2525
+ method: req.method ?? "POST",
2526
+ url: url.toString(),
2527
+ path: pathname,
2528
+ headers: req.headers,
2529
+ query: Object.fromEntries(url.searchParams.entries()),
2530
+ body: parsedBody,
2531
+ rawBody: rawBody ? Buffer.from(rawBody, "utf-8") : void 0
2532
+ };
2533
+ webhookContext = {
2534
+ env: process.env,
2535
+ app: { id: appId, versionId: appVersionId },
2536
+ log: createContextLogger()
2537
+ };
2538
+ }
2539
+ const originalEnv = { ...process.env };
2540
+ Object.assign(process.env, requestEnv);
2541
+ const requestConfig = buildRequestScopedConfig(requestEnv);
2542
+ let webhookResponse;
2543
+ try {
2544
+ webhookResponse = await runWithLogContext({ invocation }, async () => {
2545
+ return await runWithConfig(requestConfig, async () => {
2546
+ return await webhookDef.handler(webhookRequest, webhookContext);
2547
+ });
2548
+ });
2549
+ } catch (err) {
2550
+ console.error(`Webhook handler '${handle}' error:`, err);
2551
+ sendJSON(res, 500, { error: "Webhook handler error" });
2552
+ return;
2553
+ } finally {
2554
+ process.env = originalEnv;
2555
+ }
2556
+ const status = webhookResponse.status ?? 200;
2557
+ const responseHeaders = {
2558
+ ...webhookResponse.headers
2559
+ };
2560
+ if (!responseHeaders["Content-Type"] && !responseHeaders["content-type"]) {
2561
+ responseHeaders["Content-Type"] = "application/json";
2562
+ }
2563
+ res.writeHead(status, responseHeaders);
2564
+ if (webhookResponse.body !== void 0) {
2565
+ if (typeof webhookResponse.body === "string") {
2566
+ res.end(webhookResponse.body);
2567
+ } else {
2568
+ res.end(JSON.stringify(webhookResponse.body));
2569
+ }
2570
+ } else {
2571
+ res.end();
2572
+ }
2573
+ return;
2574
+ }
2575
+ if (pathname === "/estimate" && req.method === "POST") {
2576
+ let estimateBody;
2577
+ try {
2578
+ estimateBody = await parseJSONBody(req);
2579
+ } catch {
2580
+ sendJSON(res, 400, {
2581
+ error: {
2582
+ code: -32700,
2583
+ message: "Parse error"
2584
+ }
2585
+ });
2586
+ return;
2587
+ }
2588
+ try {
2589
+ const estimateResponse = await callTool(estimateBody.name, {
2590
+ inputs: estimateBody.inputs,
2591
+ estimate: true
2592
+ });
2593
+ sendJSON(res, 200, {
2594
+ billing: estimateResponse.billing ?? { credits: 0 }
2595
+ });
2596
+ } catch (err) {
2597
+ sendJSON(res, 500, {
2598
+ error: {
2599
+ code: -32603,
2600
+ message: err instanceof Error ? err.message : String(err ?? "")
2601
+ }
2602
+ });
2603
+ }
2604
+ return;
2605
+ }
2606
+ if (pathname === "/oauth_callback" && req.method === "POST") {
2607
+ if (!config.hooks?.oauth_callback) {
2608
+ sendJSON(res, 404, { error: "OAuth callback handler not configured" });
2609
+ return;
2610
+ }
2611
+ let parsedBody;
2612
+ try {
2613
+ parsedBody = await parseJSONBody(req);
2614
+ } catch (err) {
2615
+ console.error("[OAuth Callback] Failed to parse JSON body:", err);
2616
+ sendJSON(res, 400, {
2617
+ error: { code: -32700, message: "Parse error" }
2618
+ });
2619
+ return;
2620
+ }
2621
+ const envelope = parseHandlerEnvelope(parsedBody);
2622
+ if (!envelope) {
2623
+ console.error("[OAuth Callback] Failed to parse envelope. Body:", JSON.stringify(parsedBody, null, 2));
2624
+ sendJSON(res, 400, {
2625
+ error: { code: -32602, message: "Missing envelope format: expected { env, request }" }
2626
+ });
2627
+ return;
2628
+ }
2629
+ const invocation = parsedBody.invocation;
2630
+ const oauthRequest = buildRequestFromRaw(envelope.request);
2631
+ const oauthCallbackRequestConfig = buildRequestScopedConfig(envelope.env);
2632
+ const oauthCallbackContext = {
2633
+ request: oauthRequest,
2634
+ invocation,
2635
+ log: createContextLogger()
2636
+ };
2637
+ try {
2638
+ const oauthCallbackHook = config.hooks.oauth_callback;
2639
+ const oauthCallbackHandler = typeof oauthCallbackHook === "function" ? oauthCallbackHook : oauthCallbackHook.handler;
2640
+ const result = await runWithLogContext({ invocation }, async () => {
2641
+ return await runWithConfig(oauthCallbackRequestConfig, async () => {
2642
+ return await oauthCallbackHandler(oauthCallbackContext);
2643
+ });
2644
+ });
2645
+ sendJSON(res, 200, {
2646
+ appInstallationId: result.appInstallationId,
2647
+ env: result.env ?? {}
2648
+ });
2649
+ } catch (err) {
2650
+ const errorMessage = err instanceof Error ? err.message : String(err ?? "Unknown error");
2651
+ sendJSON(res, 500, {
2652
+ error: {
2653
+ code: -32603,
2654
+ message: errorMessage
2655
+ }
2656
+ });
2657
+ }
2658
+ return;
2659
+ }
2660
+ if (pathname === "/install" && req.method === "POST") {
2661
+ if (!config.hooks?.install) {
2662
+ sendJSON(res, 404, { error: "Install handler not configured" });
2663
+ return;
2664
+ }
2665
+ let installBody;
2666
+ try {
2667
+ installBody = await parseJSONBody(req);
2668
+ } catch {
2669
+ sendJSON(res, 400, {
2670
+ error: { code: -32700, message: "Parse error" }
2671
+ });
2672
+ return;
2673
+ }
2674
+ if (!installBody.context?.appInstallationId || !installBody.context?.workplace) {
2675
+ sendJSON(res, 400, {
2676
+ error: { code: -32602, message: "Missing context (appInstallationId and workplace required)" }
2677
+ });
2678
+ return;
2679
+ }
2680
+ const installContext = {
2681
+ env: installBody.env ?? {},
2682
+ workplace: installBody.context.workplace,
2683
+ appInstallationId: installBody.context.appInstallationId,
2684
+ app: installBody.context.app,
2685
+ invocation: installBody.invocation,
2686
+ log: createContextLogger()
2687
+ };
2688
+ const installRequestConfig = {
2689
+ baseUrl: installBody.env?.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
2690
+ apiToken: installBody.env?.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
2691
+ };
2692
+ try {
2693
+ const installHook = config.hooks.install;
2694
+ const installHandler = typeof installHook === "function" ? installHook : installHook.handler;
2695
+ const result = await runWithLogContext({ invocation: installBody.invocation }, async () => {
2696
+ return await runWithConfig(installRequestConfig, async () => {
2697
+ return await installHandler(installContext);
2698
+ });
2699
+ });
2700
+ sendJSON(res, 200, {
2701
+ env: result.env ?? {},
2702
+ redirect: result.redirect
2703
+ });
2704
+ } catch (err) {
2705
+ if (err instanceof InstallError) {
2706
+ sendJSON(res, 400, {
2707
+ error: {
2708
+ code: err.code,
2709
+ message: err.message,
2710
+ field: err.field
2711
+ }
2712
+ });
2713
+ } else {
2714
+ sendJSON(res, 500, {
2715
+ error: {
2716
+ code: -32603,
2717
+ message: err instanceof Error ? err.message : String(err ?? "")
2718
+ }
2719
+ });
2720
+ }
2721
+ }
2722
+ return;
2723
+ }
2724
+ if (pathname === "/uninstall" && req.method === "POST") {
2725
+ if (!config.hooks?.uninstall) {
2726
+ sendJSON(res, 404, { error: "Uninstall handler not configured" });
2727
+ return;
2728
+ }
2729
+ let uninstallBody;
2730
+ try {
2731
+ uninstallBody = await parseJSONBody(req);
2732
+ } catch {
2733
+ sendJSON(res, 400, {
2734
+ error: { code: -32700, message: "Parse error" }
2735
+ });
2736
+ return;
2737
+ }
2738
+ if (!uninstallBody.context?.appInstallationId || !uninstallBody.context?.workplace || !uninstallBody.context?.app) {
2739
+ sendJSON(res, 400, {
2740
+ error: {
2741
+ code: -32602,
2742
+ message: "Missing context (appInstallationId, workplace and app required)"
2743
+ }
2744
+ });
2745
+ return;
2746
+ }
2747
+ const uninstallContext = {
2748
+ env: uninstallBody.env ?? {},
2749
+ workplace: uninstallBody.context.workplace,
2750
+ appInstallationId: uninstallBody.context.appInstallationId,
2751
+ app: uninstallBody.context.app,
2752
+ invocation: uninstallBody.invocation,
2753
+ log: createContextLogger()
2754
+ };
2755
+ const uninstallRequestConfig = {
2756
+ baseUrl: uninstallBody.env?.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
2757
+ apiToken: uninstallBody.env?.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
2758
+ };
2759
+ try {
2760
+ const uninstallHook = config.hooks.uninstall;
2761
+ const uninstallHandlerFn = typeof uninstallHook === "function" ? uninstallHook : uninstallHook.handler;
2762
+ const result = await runWithLogContext({ invocation: uninstallBody.invocation }, async () => {
2763
+ return await runWithConfig(uninstallRequestConfig, async () => {
2764
+ return await uninstallHandlerFn(uninstallContext);
2765
+ });
2766
+ });
2767
+ sendJSON(res, 200, {
2768
+ cleanedWebhookIds: result.cleanedWebhookIds ?? []
2769
+ });
2770
+ } catch (err) {
2771
+ sendJSON(res, 500, {
2772
+ error: {
2773
+ code: -32603,
2774
+ message: err instanceof Error ? err.message : String(err ?? "")
2775
+ }
2776
+ });
2777
+ }
2778
+ return;
2779
+ }
2780
+ if (pathname === "/provision" && req.method === "POST") {
2781
+ if (!config.hooks?.provision) {
2782
+ sendJSON(res, 404, { error: "Provision handler not configured" });
2783
+ return;
2784
+ }
2785
+ let provisionBody;
2786
+ try {
2787
+ provisionBody = await parseJSONBody(req);
2788
+ } catch {
2789
+ sendJSON(res, 400, {
2790
+ error: { code: -32700, message: "Parse error" }
2791
+ });
2792
+ return;
2793
+ }
2794
+ if (!provisionBody.context?.app) {
2795
+ sendJSON(res, 400, {
2796
+ error: { code: -32602, message: "Missing context (app required)" }
2797
+ });
2798
+ return;
2799
+ }
2800
+ const mergedEnv = {};
2801
+ for (const [key, value] of Object.entries(process.env)) {
2802
+ if (value !== void 0) {
2803
+ mergedEnv[key] = value;
2804
+ }
2805
+ }
2806
+ Object.assign(mergedEnv, provisionBody.env ?? {});
2807
+ const provisionContext = {
2808
+ env: mergedEnv,
2809
+ app: provisionBody.context.app,
2810
+ invocation: provisionBody.invocation,
2811
+ log: createContextLogger()
2812
+ };
2813
+ const provisionRequestConfig = {
2814
+ baseUrl: mergedEnv.SKEDYUL_API_URL ?? "",
2815
+ apiToken: mergedEnv.SKEDYUL_API_TOKEN ?? ""
2816
+ };
2817
+ try {
2818
+ const provisionHook = config.hooks.provision;
2819
+ const provisionHandler = typeof provisionHook === "function" ? provisionHook : provisionHook.handler;
2820
+ const result = await runWithLogContext({ invocation: provisionBody.invocation }, async () => {
2821
+ return await runWithConfig(provisionRequestConfig, async () => {
2822
+ return await provisionHandler(provisionContext);
2823
+ });
2824
+ });
2825
+ sendJSON(res, 200, result);
2826
+ } catch (err) {
2827
+ sendJSON(res, 500, {
2828
+ error: {
2829
+ code: -32603,
2830
+ message: err instanceof Error ? err.message : String(err ?? "")
2831
+ }
2832
+ });
2833
+ }
2834
+ return;
2835
+ }
2836
+ if (pathname === "/core" && req.method === "POST") {
2837
+ let coreBody;
2838
+ try {
2839
+ coreBody = await parseJSONBody(req);
2840
+ } catch {
2841
+ sendJSON(res, 400, {
2842
+ error: {
2843
+ code: -32700,
2844
+ message: "Parse error"
2845
+ }
2846
+ });
2847
+ return;
2848
+ }
2849
+ if (!coreBody?.method) {
2850
+ sendJSON(res, 400, {
2851
+ error: {
2852
+ code: -32602,
2853
+ message: "Missing method"
2854
+ }
2855
+ });
2856
+ return;
2857
+ }
2858
+ const method = coreBody.method;
2859
+ const result = await handleCoreMethod(method, coreBody.params);
2860
+ sendCoreResult(result);
2861
+ return;
2862
+ }
2863
+ if (pathname === "/core/webhook" && req.method === "POST") {
2864
+ let rawWebhookBody;
2865
+ try {
2866
+ rawWebhookBody = await readRawRequestBody(req);
2867
+ } catch {
2868
+ sendJSON(res, 400, { status: "parse-error" });
2869
+ return;
2870
+ }
2871
+ let webhookBody;
2872
+ try {
2873
+ webhookBody = rawWebhookBody ? JSON.parse(rawWebhookBody) : {};
2874
+ } catch {
2875
+ sendJSON(res, 400, { status: "parse-error" });
2876
+ return;
2877
+ }
2878
+ const normalizedHeaders = Object.fromEntries(
2879
+ Object.entries(req.headers).map(([key, value]) => [
2880
+ key,
2881
+ typeof value === "string" ? value : value?.[0] ?? ""
2882
+ ])
2883
+ );
2884
+ const coreWebhookRequest = {
2885
+ method: req.method ?? "POST",
2886
+ headers: normalizedHeaders,
2887
+ body: webhookBody,
2888
+ query: Object.fromEntries(url.searchParams.entries()),
2889
+ url: url.toString(),
2890
+ path: url.pathname,
2891
+ rawBody: rawWebhookBody ? Buffer.from(rawWebhookBody, "utf-8") : void 0
2892
+ };
2893
+ const webhookResponse = await coreApiService.dispatchWebhook(
2894
+ coreWebhookRequest
2895
+ );
2896
+ res.writeHead(webhookResponse.status, {
2897
+ "Content-Type": "application/json"
2898
+ });
2899
+ res.end(JSON.stringify(webhookResponse.body ?? {}));
2900
+ return;
2901
+ }
2902
+ if (pathname === "/mcp" && req.method === "POST") {
2903
+ try {
2904
+ const body = await parseJSONBody(req);
2905
+ if (body?.method === "tools/list") {
2906
+ sendJSON(res, 200, {
2907
+ jsonrpc: "2.0",
2908
+ id: body.id ?? null,
2909
+ result: { tools }
2910
+ });
2911
+ return;
2912
+ }
2913
+ if (body?.method === "webhooks/list") {
2914
+ const webhooks = webhookRegistry ? Object.values(webhookRegistry).map((w) => ({
2915
+ name: w.name,
2916
+ description: w.description,
2917
+ methods: w.methods ?? ["POST"],
2918
+ type: w.type ?? "WEBHOOK"
2919
+ })) : [];
2920
+ sendJSON(res, 200, {
2921
+ jsonrpc: "2.0",
2922
+ id: body.id ?? null,
2923
+ result: { webhooks }
2924
+ });
2925
+ return;
2926
+ }
2927
+ const transport = new StreamableHTTPServerTransport({
2928
+ sessionIdGenerator: void 0,
2929
+ enableJsonResponse: true
2930
+ });
2931
+ res.on("close", () => {
2932
+ transport.close();
2933
+ });
2934
+ await mcpServer.connect(transport);
2935
+ await transport.handleRequest(req, res, body);
2936
+ } catch (err) {
2937
+ sendJSON(res, 500, {
2938
+ jsonrpc: "2.0",
2939
+ id: null,
2940
+ error: {
2941
+ code: -32603,
2942
+ message: err instanceof Error ? err.message : String(err ?? "")
2943
+ }
2944
+ });
2945
+ }
2946
+ return;
2947
+ }
2948
+ sendJSON(res, 404, {
2949
+ jsonrpc: "2.0",
2950
+ id: null,
2951
+ error: {
2952
+ code: -32601,
2953
+ message: "Not Found"
2954
+ }
2955
+ });
2956
+ } catch (err) {
2957
+ sendJSON(res, 500, {
2958
+ jsonrpc: "2.0",
2959
+ id: null,
2960
+ error: {
2961
+ code: -32603,
2962
+ message: err instanceof Error ? err.message : String(err ?? "")
2963
+ }
2964
+ });
2965
+ }
2966
+ }
2967
+ );
2968
+ return {
2969
+ async listen(listenPort) {
2970
+ const finalPort = listenPort ?? port;
2971
+ return new Promise((resolve2, reject) => {
2972
+ httpServer.listen(finalPort, () => {
2973
+ printStartupLog(config, tools, webhookRegistry, finalPort);
2974
+ resolve2();
2975
+ });
2976
+ httpServer.once("error", reject);
2977
+ });
2978
+ },
2979
+ getHealthStatus: () => state.getHealthStatus()
2980
+ };
2981
+ }
2982
+
2983
+ // src/server/serverless.ts
2984
+ function createServerlessInstance(config, tools, callTool, state, mcpServer, registry, webhookRegistry) {
2985
+ const headers = getDefaultHeaders(config.cors);
2986
+ let hasLoggedStartup = false;
2987
+ return {
2988
+ async handler(event) {
2989
+ if (!hasLoggedStartup) {
2990
+ printStartupLog(config, tools, webhookRegistry);
2991
+ hasLoggedStartup = true;
2992
+ }
2993
+ try {
2994
+ const path2 = event.path || event.rawPath || "/";
2995
+ const method = event.httpMethod || event.requestContext?.http?.method || "POST";
2996
+ if (method === "OPTIONS") {
2997
+ return createResponse(200, { message: "OK" }, headers);
2998
+ }
2999
+ if (path2.startsWith("/webhooks/") && webhookRegistry) {
3000
+ const handle = path2.slice("/webhooks/".length);
3001
+ const webhookDef = webhookRegistry[handle];
3002
+ if (!webhookDef) {
3003
+ return createResponse(404, { error: `Webhook handler '${handle}' not found` }, headers);
3004
+ }
3005
+ const allowedMethods = webhookDef.methods ?? ["POST"];
3006
+ if (!allowedMethods.includes(method)) {
3007
+ return createResponse(405, { error: `Method ${method} not allowed` }, headers);
3008
+ }
3009
+ const rawBody = event.body ?? "";
3010
+ let parsedBody;
3011
+ const contentType = event.headers?.["content-type"] ?? event.headers?.["Content-Type"] ?? "";
3012
+ if (contentType.includes("application/json")) {
3013
+ try {
3014
+ parsedBody = rawBody ? JSON.parse(rawBody) : {};
3015
+ } catch {
3016
+ parsedBody = rawBody;
3017
+ }
3018
+ } else {
3019
+ parsedBody = rawBody;
3020
+ }
3021
+ const isEnvelope = typeof parsedBody === "object" && parsedBody !== null && "env" in parsedBody && "request" in parsedBody && "context" in parsedBody;
3022
+ let webhookRequest;
3023
+ let webhookContext;
3024
+ let requestEnv = {};
3025
+ let invocation;
3026
+ if (isEnvelope) {
3027
+ const envelope = parsedBody;
3028
+ requestEnv = envelope.env ?? {};
3029
+ invocation = envelope.invocation;
3030
+ let originalParsedBody = envelope.request.body;
3031
+ const originalContentType = envelope.request.headers["content-type"] ?? "";
3032
+ if (originalContentType.includes("application/json")) {
3033
+ try {
3034
+ originalParsedBody = envelope.request.body ? JSON.parse(envelope.request.body) : {};
3035
+ } catch {
3036
+ }
3037
+ }
3038
+ webhookRequest = {
3039
+ method: envelope.request.method,
3040
+ url: envelope.request.url,
3041
+ path: envelope.request.path,
3042
+ headers: envelope.request.headers,
3043
+ query: envelope.request.query,
3044
+ body: originalParsedBody,
3045
+ rawBody: envelope.request.body ? Buffer.from(envelope.request.body, "utf-8") : void 0
3046
+ };
3047
+ const envVars = { ...process.env, ...requestEnv };
3048
+ const app = envelope.context.app;
3049
+ if (envelope.context.appInstallationId && envelope.context.workplace) {
3050
+ webhookContext = {
3051
+ env: envVars,
3052
+ app,
3053
+ appInstallationId: envelope.context.appInstallationId,
3054
+ workplace: envelope.context.workplace,
3055
+ registration: envelope.context.registration ?? {},
3056
+ invocation,
3057
+ log: createContextLogger()
3058
+ };
3059
+ } else {
3060
+ webhookContext = {
3061
+ env: envVars,
3062
+ app,
3063
+ invocation,
3064
+ log: createContextLogger()
3065
+ };
3066
+ }
3067
+ } else {
3068
+ const appId = event.headers?.["x-skedyul-app-id"] ?? event.headers?.["X-Skedyul-App-Id"];
3069
+ const appVersionId = event.headers?.["x-skedyul-app-version-id"] ?? event.headers?.["X-Skedyul-App-Version-Id"];
3070
+ if (!appId || !appVersionId) {
3071
+ throw new Error("Missing app info in webhook request (x-skedyul-app-id and x-skedyul-app-version-id headers required)");
3072
+ }
3073
+ const forwardedProto = event.headers?.["x-forwarded-proto"] ?? event.headers?.["X-Forwarded-Proto"];
3074
+ const protocol = forwardedProto ?? "https";
3075
+ const host = event.headers?.host ?? event.headers?.Host ?? "localhost";
3076
+ const queryString = event.queryStringParameters ? "?" + new URLSearchParams(event.queryStringParameters).toString() : "";
3077
+ const webhookUrl = `${protocol}://${host}${path2}${queryString}`;
3078
+ webhookRequest = {
3079
+ method,
3080
+ url: webhookUrl,
3081
+ path: path2,
3082
+ headers: event.headers,
3083
+ query: event.queryStringParameters ?? {},
3084
+ body: parsedBody,
3085
+ rawBody: rawBody ? Buffer.from(rawBody, "utf-8") : void 0
3086
+ };
3087
+ webhookContext = {
3088
+ env: process.env,
3089
+ app: { id: appId, versionId: appVersionId },
3090
+ log: createContextLogger()
3091
+ };
3092
+ }
3093
+ const originalEnv = { ...process.env };
3094
+ Object.assign(process.env, requestEnv);
3095
+ const requestConfig = {
3096
+ baseUrl: requestEnv.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
3097
+ apiToken: requestEnv.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
3098
+ };
3099
+ let webhookResponse;
3100
+ try {
3101
+ webhookResponse = await runWithLogContext({ invocation }, async () => {
3102
+ return await runWithConfig(requestConfig, async () => {
3103
+ return await webhookDef.handler(webhookRequest, webhookContext);
3104
+ });
3105
+ });
3106
+ } catch (err) {
3107
+ console.error(`Webhook handler '${handle}' error:`, err);
3108
+ return createResponse(500, { error: "Webhook handler error" }, headers);
3109
+ } finally {
3110
+ process.env = originalEnv;
3111
+ }
3112
+ const responseHeaders = {
3113
+ ...headers,
3114
+ ...webhookResponse.headers
3115
+ };
3116
+ const status = webhookResponse.status ?? 200;
3117
+ const body = webhookResponse.body;
3118
+ return {
3119
+ statusCode: status,
3120
+ headers: responseHeaders,
3121
+ body: body !== void 0 ? typeof body === "string" ? body : JSON.stringify(body) : ""
3122
+ };
3123
+ }
3124
+ if (path2 === "/core" && method === "POST") {
3125
+ let coreBody;
3126
+ try {
3127
+ coreBody = event.body ? JSON.parse(event.body) : {};
3128
+ } catch {
3129
+ return createResponse(
3130
+ 400,
3131
+ {
3132
+ error: {
3133
+ code: -32700,
3134
+ message: "Parse error"
3135
+ }
3136
+ },
3137
+ headers
3138
+ );
3139
+ }
3140
+ if (!coreBody?.method) {
3141
+ return createResponse(
3142
+ 400,
3143
+ {
3144
+ error: {
3145
+ code: -32602,
3146
+ message: "Missing method"
3147
+ }
3148
+ },
3149
+ headers
3150
+ );
3151
+ }
3152
+ const coreMethod = coreBody.method;
3153
+ const result = await handleCoreMethod(coreMethod, coreBody.params);
3154
+ return createResponse(result.status, result.payload, headers);
3155
+ }
3156
+ if (path2 === "/core/webhook" && method === "POST") {
3157
+ const rawWebhookBody = event.body ?? "";
3158
+ let webhookBody;
3159
+ try {
3160
+ webhookBody = rawWebhookBody ? JSON.parse(rawWebhookBody) : {};
3161
+ } catch {
3162
+ return createResponse(
3163
+ 400,
3164
+ { status: "parse-error" },
3165
+ headers
3166
+ );
3167
+ }
3168
+ const forwardedProto = event.headers?.["x-forwarded-proto"] ?? event.headers?.["X-Forwarded-Proto"];
3169
+ const protocol = forwardedProto ?? "https";
3170
+ const host = event.headers?.host ?? event.headers?.Host ?? "localhost";
3171
+ const webhookUrl = `${protocol}://${host}${path2}`;
3172
+ const coreWebhookRequest = {
3173
+ method,
3174
+ headers: event.headers ?? {},
3175
+ body: webhookBody,
3176
+ query: event.queryStringParameters ?? {},
3177
+ url: webhookUrl,
3178
+ path: path2,
3179
+ rawBody: rawWebhookBody ? Buffer.from(rawWebhookBody, "utf-8") : void 0
3180
+ };
3181
+ const webhookResponse = await coreApiService.dispatchWebhook(
3182
+ coreWebhookRequest
3183
+ );
3184
+ return createResponse(
3185
+ webhookResponse.status,
3186
+ webhookResponse.body ?? {},
3187
+ headers
3188
+ );
3189
+ }
3190
+ if (path2 === "/estimate" && method === "POST") {
3191
+ let estimateBody;
3192
+ try {
3193
+ estimateBody = event.body ? JSON.parse(event.body) : {};
3194
+ } catch {
3195
+ return createResponse(
3196
+ 400,
3197
+ {
3198
+ error: {
3199
+ code: -32700,
3200
+ message: "Parse error"
3201
+ }
3202
+ },
3203
+ headers
3204
+ );
3205
+ }
3206
+ try {
3207
+ const toolName = estimateBody.name;
3208
+ const toolArgs = estimateBody.inputs ?? {};
3209
+ let toolKey = null;
3210
+ let tool = null;
3211
+ for (const [key, t] of Object.entries(registry)) {
3212
+ if (t.name === toolName || key === toolName) {
3213
+ toolKey = key;
3214
+ tool = t;
3215
+ break;
3216
+ }
3217
+ }
3218
+ if (!tool || !toolKey) {
3219
+ return createResponse(
3220
+ 400,
3221
+ {
3222
+ error: {
3223
+ code: -32602,
3224
+ message: `Tool "${toolName}" not found`
3225
+ }
3226
+ },
3227
+ headers
3228
+ );
3229
+ }
3230
+ const inputSchema = getZodSchema(tool.inputSchema);
3231
+ const validatedArgs = inputSchema ? inputSchema.parse(toolArgs) : toolArgs;
3232
+ const estimateResponse = await callTool(toolKey, {
3233
+ inputs: validatedArgs,
3234
+ estimate: true
3235
+ });
3236
+ return createResponse(
3237
+ 200,
3238
+ {
3239
+ billing: estimateResponse.billing ?? { credits: 0 }
3240
+ },
3241
+ headers
3242
+ );
3243
+ } catch (err) {
3244
+ return createResponse(
3245
+ 500,
3246
+ {
3247
+ error: {
3248
+ code: -32603,
3249
+ message: err instanceof Error ? err.message : String(err ?? "")
3250
+ }
3251
+ },
3252
+ headers
3253
+ );
3254
+ }
3255
+ }
3256
+ if (path2 === "/install" && method === "POST") {
3257
+ if (!config.hooks?.install) {
3258
+ return createResponse(404, { error: "Install handler not configured" }, headers);
3259
+ }
3260
+ let installBody;
3261
+ try {
3262
+ installBody = event.body ? JSON.parse(event.body) : {};
3263
+ } catch {
3264
+ return createResponse(
3265
+ 400,
3266
+ { error: { code: -32700, message: "Parse error" } },
3267
+ headers
3268
+ );
3269
+ }
3270
+ if (!installBody.context?.appInstallationId || !installBody.context?.workplace) {
3271
+ return createResponse(
3272
+ 400,
3273
+ { error: { code: -32602, message: "Missing context (appInstallationId and workplace required)" } },
3274
+ headers
3275
+ );
3276
+ }
3277
+ const installContext = {
3278
+ env: installBody.env ?? {},
3279
+ workplace: installBody.context.workplace,
3280
+ appInstallationId: installBody.context.appInstallationId,
3281
+ app: installBody.context.app,
3282
+ invocation: installBody.invocation,
3283
+ log: createContextLogger()
3284
+ };
3285
+ const installRequestConfig = {
3286
+ baseUrl: installBody.env?.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
3287
+ apiToken: installBody.env?.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
3288
+ };
3289
+ try {
3290
+ const installHook = config.hooks.install;
3291
+ const installHandler = typeof installHook === "function" ? installHook : installHook.handler;
3292
+ const result = await runWithLogContext({ invocation: installBody.invocation }, async () => {
3293
+ return await runWithConfig(installRequestConfig, async () => {
3294
+ return await installHandler(installContext);
3295
+ });
3296
+ });
3297
+ return createResponse(
3298
+ 200,
3299
+ { env: result.env ?? {}, redirect: result.redirect },
3300
+ headers
3301
+ );
3302
+ } catch (err) {
3303
+ if (err instanceof InstallError) {
3304
+ return createResponse(
3305
+ 400,
3306
+ {
3307
+ error: {
3308
+ code: err.code,
3309
+ message: err.message,
3310
+ field: err.field
3311
+ }
3312
+ },
3313
+ headers
3314
+ );
3315
+ }
3316
+ return createResponse(
3317
+ 500,
3318
+ {
3319
+ error: {
3320
+ code: -32603,
3321
+ message: err instanceof Error ? err.message : String(err ?? "")
3322
+ }
3323
+ },
3324
+ headers
3325
+ );
3326
+ }
3327
+ }
3328
+ if (path2 === "/uninstall" && method === "POST") {
3329
+ if (!config.hooks?.uninstall) {
3330
+ return createResponse(404, { error: "Uninstall handler not configured" }, headers);
3331
+ }
3332
+ let uninstallBody;
3333
+ try {
3334
+ uninstallBody = event.body ? JSON.parse(event.body) : {};
3335
+ } catch {
3336
+ return createResponse(
3337
+ 400,
3338
+ { error: { code: -32700, message: "Parse error" } },
3339
+ headers
3340
+ );
3341
+ }
3342
+ if (!uninstallBody.context?.appInstallationId || !uninstallBody.context?.workplace || !uninstallBody.context?.app) {
3343
+ return createResponse(
3344
+ 400,
3345
+ {
3346
+ error: {
3347
+ code: -32602,
3348
+ message: "Missing context (appInstallationId, workplace and app required)"
3349
+ }
3350
+ },
3351
+ headers
3352
+ );
3353
+ }
3354
+ const uninstallContext = {
3355
+ env: uninstallBody.env ?? {},
3356
+ workplace: uninstallBody.context.workplace,
3357
+ appInstallationId: uninstallBody.context.appInstallationId,
3358
+ app: uninstallBody.context.app,
3359
+ invocation: uninstallBody.invocation,
3360
+ log: createContextLogger()
3361
+ };
3362
+ const uninstallRequestConfig = {
3363
+ baseUrl: uninstallBody.env?.SKEDYUL_API_URL ?? process.env.SKEDYUL_API_URL ?? "",
3364
+ apiToken: uninstallBody.env?.SKEDYUL_API_TOKEN ?? process.env.SKEDYUL_API_TOKEN ?? ""
3365
+ };
3366
+ try {
3367
+ const uninstallHook = config.hooks.uninstall;
3368
+ const uninstallHandlerFn = typeof uninstallHook === "function" ? uninstallHook : uninstallHook.handler;
3369
+ const result = await runWithLogContext({ invocation: uninstallBody.invocation }, async () => {
3370
+ return await runWithConfig(uninstallRequestConfig, async () => {
3371
+ return await uninstallHandlerFn(uninstallContext);
3372
+ });
3373
+ });
3374
+ return createResponse(
3375
+ 200,
3376
+ { cleanedWebhookIds: result.cleanedWebhookIds ?? [] },
3377
+ headers
3378
+ );
3379
+ } catch (err) {
3380
+ return createResponse(
3381
+ 500,
3382
+ {
3383
+ error: {
3384
+ code: -32603,
3385
+ message: err instanceof Error ? err.message : String(err ?? "")
3386
+ }
3387
+ },
3388
+ headers
3389
+ );
3390
+ }
3391
+ }
3392
+ if (path2 === "/provision" && method === "POST") {
3393
+ console.log("[serverless] /provision endpoint called");
3394
+ if (!config.hooks?.provision) {
3395
+ console.log("[serverless] No provision handler configured");
3396
+ return createResponse(404, { error: "Provision handler not configured" }, headers);
3397
+ }
3398
+ let provisionBody;
3399
+ try {
3400
+ provisionBody = event.body ? JSON.parse(event.body) : {};
3401
+ console.log("[serverless] Provision body parsed:", {
3402
+ hasEnv: !!provisionBody.env,
3403
+ hasContext: !!provisionBody.context,
3404
+ appId: provisionBody.context?.app?.id,
3405
+ versionId: provisionBody.context?.app?.versionId
3406
+ });
3407
+ } catch {
3408
+ console.log("[serverless] Failed to parse provision body");
3409
+ return createResponse(
3410
+ 400,
3411
+ { error: { code: -32700, message: "Parse error" } },
3412
+ headers
3413
+ );
3414
+ }
3415
+ if (!provisionBody.context?.app) {
3416
+ console.log("[serverless] Missing app context in provision body");
3417
+ return createResponse(
3418
+ 400,
3419
+ { error: { code: -32602, message: "Missing context (app required)" } },
3420
+ headers
3421
+ );
3422
+ }
3423
+ const mergedEnv = {};
3424
+ for (const [key, value] of Object.entries(process.env)) {
3425
+ if (value !== void 0) {
3426
+ mergedEnv[key] = value;
3427
+ }
3428
+ }
3429
+ Object.assign(mergedEnv, provisionBody.env ?? {});
3430
+ const provisionContext = {
3431
+ env: mergedEnv,
3432
+ app: provisionBody.context.app,
3433
+ invocation: provisionBody.invocation,
3434
+ log: createContextLogger()
3435
+ };
3436
+ const provisionRequestConfig = {
3437
+ baseUrl: mergedEnv.SKEDYUL_API_URL ?? "",
3438
+ apiToken: mergedEnv.SKEDYUL_API_TOKEN ?? ""
3439
+ };
3440
+ console.log("[serverless] Calling provision handler...");
3441
+ try {
3442
+ const provisionHook = config.hooks.provision;
3443
+ const provisionHandler = typeof provisionHook === "function" ? provisionHook : provisionHook.handler;
3444
+ const result = await runWithLogContext({ invocation: provisionBody.invocation }, async () => {
3445
+ return await runWithConfig(provisionRequestConfig, async () => {
3446
+ return await provisionHandler(provisionContext);
3447
+ });
3448
+ });
3449
+ console.log("[serverless] Provision handler completed successfully");
3450
+ return createResponse(200, result, headers);
3451
+ } catch (err) {
3452
+ console.error("[serverless] Provision handler failed:", err instanceof Error ? err.message : String(err));
3453
+ return createResponse(
3454
+ 500,
3455
+ {
3456
+ error: {
3457
+ code: -32603,
3458
+ message: err instanceof Error ? err.message : String(err ?? "")
3459
+ }
3460
+ },
3461
+ headers
3462
+ );
3463
+ }
3464
+ }
3465
+ if (path2 === "/oauth_callback" && method === "POST") {
3466
+ if (!config.hooks?.oauth_callback) {
3467
+ return createResponse(
3468
+ 404,
3469
+ { error: "OAuth callback handler not configured" },
3470
+ headers
3471
+ );
3472
+ }
3473
+ let parsedBody;
3474
+ try {
3475
+ parsedBody = event.body ? JSON.parse(event.body) : {};
3476
+ } catch (err) {
3477
+ console.error("[OAuth Callback] Failed to parse JSON body:", err);
3478
+ return createResponse(
3479
+ 400,
3480
+ { error: { code: -32700, message: "Parse error" } },
3481
+ headers
3482
+ );
3483
+ }
3484
+ const envelope = parseHandlerEnvelope(parsedBody);
3485
+ if (!envelope) {
3486
+ console.error("[OAuth Callback] Failed to parse envelope. Body:", JSON.stringify(parsedBody, null, 2));
3487
+ return createResponse(
3488
+ 400,
3489
+ { error: { code: -32602, message: "Missing envelope format: expected { env, request }" } },
3490
+ headers
3491
+ );
3492
+ }
3493
+ const invocation = parsedBody.invocation;
3494
+ const oauthRequest = buildRequestFromRaw(envelope.request);
3495
+ const oauthCallbackRequestConfig = buildRequestScopedConfig(envelope.env);
3496
+ const oauthCallbackContext = {
3497
+ request: oauthRequest,
3498
+ invocation,
3499
+ log: createContextLogger()
3500
+ };
3501
+ try {
3502
+ const oauthCallbackHook = config.hooks.oauth_callback;
3503
+ const oauthCallbackHandler = typeof oauthCallbackHook === "function" ? oauthCallbackHook : oauthCallbackHook.handler;
3504
+ const result = await runWithLogContext({ invocation }, async () => {
3505
+ return await runWithConfig(oauthCallbackRequestConfig, async () => {
3506
+ return await oauthCallbackHandler(oauthCallbackContext);
3507
+ });
3508
+ });
3509
+ return createResponse(
3510
+ 200,
3511
+ {
3512
+ appInstallationId: result.appInstallationId,
3513
+ env: result.env ?? {}
3514
+ },
3515
+ headers
3516
+ );
3517
+ } catch (err) {
3518
+ const errorMessage = err instanceof Error ? err.message : String(err ?? "Unknown error");
3519
+ return createResponse(
3520
+ 500,
3521
+ {
3522
+ error: {
3523
+ code: -32603,
3524
+ message: errorMessage
3525
+ }
3526
+ },
3527
+ headers
3528
+ );
3529
+ }
3530
+ }
3531
+ if (path2 === "/health" && method === "GET") {
3532
+ return createResponse(200, state.getHealthStatus(), headers);
3533
+ }
3534
+ if (path2 === "/mcp" && method === "POST") {
3535
+ let body;
3536
+ try {
3537
+ body = event.body ? JSON.parse(event.body) : {};
3538
+ } catch {
3539
+ return createResponse(
3540
+ 400,
3541
+ {
3542
+ jsonrpc: "2.0",
3543
+ id: null,
3544
+ error: {
3545
+ code: -32700,
3546
+ message: "Parse error"
3547
+ }
3548
+ },
3549
+ headers
3550
+ );
3551
+ }
3552
+ try {
3553
+ const { jsonrpc, id, method: rpcMethod, params } = body;
3554
+ if (jsonrpc !== "2.0") {
3555
+ return createResponse(
3556
+ 400,
3557
+ {
3558
+ jsonrpc: "2.0",
3559
+ id,
3560
+ error: {
3561
+ code: -32600,
3562
+ message: "Invalid Request"
3563
+ }
3564
+ },
3565
+ headers
3566
+ );
3567
+ }
3568
+ let result;
3569
+ if (rpcMethod === "tools/list") {
3570
+ result = { tools };
3571
+ } else if (rpcMethod === "tools/call") {
3572
+ const toolName = params?.name;
3573
+ const rawArgs = params?.arguments ?? {};
3574
+ const hasSkedyulFormat = "inputs" in rawArgs || "env" in rawArgs || "context" in rawArgs || "invocation" in rawArgs;
3575
+ const toolInputs = hasSkedyulFormat ? rawArgs.inputs ?? {} : rawArgs;
3576
+ const toolContext = hasSkedyulFormat ? rawArgs.context : void 0;
3577
+ const toolEnv = hasSkedyulFormat ? rawArgs.env : void 0;
3578
+ const toolInvocation = hasSkedyulFormat ? rawArgs.invocation : void 0;
3579
+ let toolKey = null;
3580
+ let tool = null;
3581
+ for (const [key, t] of Object.entries(registry)) {
3582
+ if (t.name === toolName || key === toolName) {
3583
+ toolKey = key;
3584
+ tool = t;
3585
+ break;
3586
+ }
3587
+ }
3588
+ if (!tool || !toolKey) {
3589
+ return createResponse(
3590
+ 200,
3591
+ {
3592
+ jsonrpc: "2.0",
3593
+ id,
3594
+ error: {
3595
+ code: -32602,
3596
+ message: `Tool "${toolName}" not found`
3597
+ }
3598
+ },
3599
+ headers
3600
+ );
3601
+ }
3602
+ try {
3603
+ const inputSchema = getZodSchema(tool.inputSchema);
3604
+ const outputSchema = getZodSchema(tool.outputSchema);
3605
+ const hasOutputSchema = Boolean(outputSchema);
3606
+ const validatedInputs = inputSchema ? inputSchema.parse(toolInputs) : toolInputs;
3607
+ const toolResult = await callTool(toolKey, {
3608
+ inputs: validatedInputs,
3609
+ context: toolContext,
3610
+ env: toolEnv,
3611
+ invocation: toolInvocation
3612
+ });
3613
+ if (toolResult.error) {
3614
+ const errorOutput = { error: toolResult.error };
3615
+ result = {
3616
+ content: [{ type: "text", text: JSON.stringify(errorOutput) }],
3617
+ // Don't provide structuredContent for error responses when tool has outputSchema
3618
+ // because the error response won't match the success schema and MCP SDK validates it
3619
+ structuredContent: hasOutputSchema ? void 0 : errorOutput,
3620
+ isError: true,
3621
+ billing: toolResult.billing
3622
+ };
3623
+ } else {
3624
+ const outputData = toolResult.output;
3625
+ let structuredContent;
3626
+ if (outputData) {
3627
+ structuredContent = { ...outputData, __effect: toolResult.effect };
3628
+ } else if (toolResult.effect) {
3629
+ structuredContent = { __effect: toolResult.effect };
3630
+ } else if (hasOutputSchema) {
3631
+ structuredContent = {};
3632
+ }
3633
+ result = {
3634
+ content: [{ type: "text", text: JSON.stringify(toolResult.output) }],
3635
+ structuredContent,
3636
+ billing: toolResult.billing
3637
+ };
3638
+ }
3639
+ } catch (validationError) {
3640
+ return createResponse(
3641
+ 200,
3642
+ {
3643
+ jsonrpc: "2.0",
3644
+ id,
3645
+ error: {
3646
+ code: -32602,
3647
+ message: validationError instanceof Error ? validationError.message : "Invalid arguments"
3648
+ }
3649
+ },
3650
+ headers
3651
+ );
3652
+ }
3653
+ } else if (rpcMethod === "webhooks/list") {
3654
+ const webhooks = webhookRegistry ? Object.values(webhookRegistry).map((w) => ({
3655
+ name: w.name,
3656
+ description: w.description,
3657
+ methods: w.methods ?? ["POST"],
3658
+ type: w.type ?? "WEBHOOK"
3659
+ })) : [];
3660
+ result = { webhooks };
3661
+ } else {
3662
+ return createResponse(
3663
+ 200,
3664
+ {
3665
+ jsonrpc: "2.0",
3666
+ id,
3667
+ error: {
3668
+ code: -32601,
3669
+ message: `Method not found: ${rpcMethod}`
3670
+ }
3671
+ },
3672
+ headers
3673
+ );
3674
+ }
3675
+ return createResponse(
3676
+ 200,
3677
+ {
3678
+ jsonrpc: "2.0",
3679
+ id,
3680
+ result
3681
+ },
3682
+ headers
3683
+ );
3684
+ } catch (err) {
3685
+ return createResponse(
3686
+ 500,
3687
+ {
3688
+ jsonrpc: "2.0",
3689
+ id: body?.id ?? null,
3690
+ error: {
3691
+ code: -32603,
3692
+ message: err instanceof Error ? err.message : String(err ?? "")
3693
+ }
3694
+ },
3695
+ headers
3696
+ );
3697
+ }
3698
+ }
3699
+ return createResponse(
3700
+ 404,
3701
+ {
3702
+ jsonrpc: "2.0",
3703
+ id: null,
3704
+ error: {
3705
+ code: -32601,
3706
+ message: "Not Found"
3707
+ }
3708
+ },
3709
+ headers
3710
+ );
3711
+ } catch (err) {
3712
+ return createResponse(
3713
+ 500,
3714
+ {
3715
+ jsonrpc: "2.0",
3716
+ id: null,
3717
+ error: {
3718
+ code: -32603,
3719
+ message: err instanceof Error ? err.message : String(err ?? "")
3720
+ }
3721
+ },
3722
+ headers
3723
+ );
3724
+ }
3725
+ },
3726
+ getHealthStatus: () => state.getHealthStatus()
3727
+ };
3728
+ }
3729
+
3730
+ // src/server/index.ts
3731
+ console.log("[skedyul-node/server] Module loading - imports done");
3732
+ console.log("[skedyul-node/server] All imports complete");
3733
+ console.log("[skedyul-node/server] Installing context logger...");
3734
+ installContextLogger();
3735
+ console.log("[skedyul-node/server] Context logger installed");
3736
+ function createSkedyulServer(config, registry, webhookRegistry) {
3737
+ console.log("[createSkedyulServer] Step 1: mergeRuntimeEnv()");
3738
+ mergeRuntimeEnv();
3739
+ console.log("[createSkedyulServer] Step 2: coreApi setup");
3740
+ if (config.coreApi?.service) {
3741
+ coreApiService.register(config.coreApi.service);
3742
+ if (config.coreApi.webhookHandler) {
3743
+ coreApiService.setWebhookHandler(config.coreApi.webhookHandler);
3744
+ }
3745
+ }
3746
+ console.log("[createSkedyulServer] Step 3: buildToolMetadata()");
3747
+ const tools = buildToolMetadata(registry);
3748
+ console.log("[createSkedyulServer] Step 3 done, tools:", tools.length);
3749
+ const toolNames = Object.values(registry).map((tool) => tool.name);
3750
+ const runtimeLabel = config.computeLayer;
3751
+ const maxRequests = config.maxRequests ?? parseNumberEnv(process.env.MCP_MAX_REQUESTS) ?? null;
3752
+ const ttlExtendSeconds = config.ttlExtendSeconds ?? parseNumberEnv(process.env.MCP_TTL_EXTEND) ?? 3600;
3753
+ console.log("[createSkedyulServer] Step 4: createRequestState()");
3754
+ const state = createRequestState(
3755
+ maxRequests,
3756
+ ttlExtendSeconds,
3757
+ runtimeLabel,
3758
+ toolNames
3759
+ );
3760
+ console.log("[createSkedyulServer] Step 4 done");
3761
+ console.log("[createSkedyulServer] Step 5: new McpServer()");
3762
+ const mcpServer = new McpServer({
3763
+ name: config.metadata.name,
3764
+ version: config.metadata.version
3765
+ });
3766
+ console.log("[createSkedyulServer] Step 5 done");
3767
+ const dedicatedShutdown = () => {
3768
+ console.log("Max requests reached, shutting down...");
3769
+ setTimeout(() => process.exit(0), 1e3);
3770
+ };
3771
+ console.log("[createSkedyulServer] Step 6: createCallToolHandler()");
3772
+ const callTool = createCallToolHandler(
3773
+ registry,
3774
+ state,
3775
+ config.computeLayer === "dedicated" ? dedicatedShutdown : void 0
3776
+ );
3777
+ console.log("[createSkedyulServer] Step 6 done");
3778
+ console.log("[createSkedyulServer] Step 7: Registering tools...");
3779
+ for (const [toolKey, tool] of Object.entries(registry)) {
3780
+ console.log(`[createSkedyulServer] Registering tool: ${toolKey}`);
3781
+ const toolName = tool.name || toolKey;
3782
+ const toolDisplayName = tool.label || toolName;
3783
+ console.log(`[createSkedyulServer] Getting input schema for ${toolKey}`);
3784
+ const inputZodSchema = getZodSchema(tool.inputSchema);
3785
+ console.log(`[createSkedyulServer] Getting output schema for ${toolKey}`);
3786
+ const outputZodSchema = getZodSchema(tool.outputSchema);
3787
+ console.log(`[createSkedyulServer] Creating wrapped schema for ${toolKey}`);
3788
+ const wrappedInputSchema = z5.object({
3789
+ inputs: inputZodSchema ?? z5.record(z5.string(), z5.unknown()).optional(),
3790
+ env: z5.record(z5.string(), z5.string()).optional()
3791
+ }).passthrough();
3792
+ console.log(`[createSkedyulServer] Calling mcpServer.registerTool for ${toolKey}`);
3793
+ mcpServer.registerTool(
3794
+ toolName,
3795
+ {
3796
+ title: toolDisplayName,
3797
+ description: tool.description,
3798
+ inputSchema: wrappedInputSchema
3799
+ // Don't pass outputSchema to MCP SDK - it validates structuredContent against it
3800
+ // which fails for error responses. We handle output formatting ourselves.
3801
+ // outputSchema: outputZodSchema,
3802
+ },
3803
+ async (args) => {
3804
+ const rawArgs = args;
3805
+ const toolInputs = rawArgs.inputs ?? {};
3806
+ const toolContext = rawArgs.context;
3807
+ const toolEnv = rawArgs.env;
3808
+ const toolInvocation = rawArgs.invocation;
3809
+ let validatedInputs = toolInputs;
3810
+ if (inputZodSchema) {
3811
+ try {
3812
+ validatedInputs = inputZodSchema.parse(toolInputs);
3813
+ } catch (error) {
3814
+ console.error(
3815
+ `[registerTool] Input validation failed for tool ${toolName}:`,
3816
+ error
3817
+ );
3818
+ return {
3819
+ content: [
3820
+ {
3821
+ type: "text",
3822
+ text: JSON.stringify({
3823
+ error: `Input validation failed: ${error instanceof Error ? error.message : String(error)}`
3824
+ })
3825
+ }
3826
+ ],
3827
+ structuredContent: {
3828
+ error: `Input validation failed: ${error instanceof Error ? error.message : String(error)}`
3829
+ },
3830
+ isError: true,
3831
+ billing: { credits: 0 }
3832
+ };
3833
+ }
3834
+ }
3835
+ const result = await callTool(toolKey, {
3836
+ inputs: validatedInputs,
3837
+ context: toolContext,
3838
+ env: toolEnv,
3839
+ invocation: toolInvocation
3840
+ });
3841
+ const hasOutputSchema = Boolean(outputZodSchema);
3842
+ if (result.error) {
3843
+ const errorOutput = { error: result.error };
3844
+ return {
3845
+ content: [{ type: "text", text: JSON.stringify(errorOutput) }],
3846
+ // Don't provide structuredContent for error responses when tool has outputSchema
3847
+ // because the error response won't match the success schema and MCP SDK validates it
3848
+ structuredContent: hasOutputSchema ? void 0 : errorOutput,
3849
+ isError: true,
3850
+ billing: result.billing
3851
+ };
3852
+ }
3853
+ const outputData = result.output;
3854
+ let structuredContent;
3855
+ if (outputData) {
3856
+ structuredContent = { ...outputData, __effect: result.effect };
3857
+ } else if (result.effect) {
3858
+ structuredContent = { __effect: result.effect };
3859
+ } else if (hasOutputSchema) {
3860
+ structuredContent = {};
3861
+ }
3862
+ return {
3863
+ content: [{ type: "text", text: JSON.stringify(result.output) }],
3864
+ structuredContent,
3865
+ billing: result.billing
3866
+ };
3867
+ }
3868
+ );
3869
+ console.log(`[createSkedyulServer] Tool ${toolKey} registered successfully`);
3870
+ }
3871
+ console.log("[createSkedyulServer] Step 7 done - all tools registered");
3872
+ console.log("[createSkedyulServer] Step 8: Creating server instance");
3873
+ if (config.computeLayer === "dedicated") {
3874
+ console.log("[createSkedyulServer] Creating dedicated instance");
3875
+ return createDedicatedServerInstance(
3876
+ config,
3877
+ tools,
3878
+ callTool,
3879
+ state,
3880
+ mcpServer,
3881
+ webhookRegistry
3882
+ );
3883
+ }
3884
+ console.log("[createSkedyulServer] Creating serverless instance");
3885
+ const serverlessInstance = createServerlessInstance(config, tools, callTool, state, mcpServer, registry, webhookRegistry);
3886
+ console.log("[createSkedyulServer] Serverless instance created successfully");
3887
+ return serverlessInstance;
3888
+ }
3889
+ var server = {
3890
+ create: createSkedyulServer
3891
+ };
3892
+
3893
+ // src/dockerfile.ts
3894
+ var DEFAULT_DOCKERFILE = `# =============================================================================
3895
+ # BUILDER STAGE - Common build for all targets
3896
+ # =============================================================================
3897
+ FROM public.ecr.aws/docker/library/node:22-alpine AS builder
3898
+
3899
+ ARG COMPUTE_LAYER=serverless
3900
+ ARG BUILD_EXTERNAL=""
3901
+ WORKDIR /app
3902
+
3903
+ # Install pnpm
3904
+ RUN corepack enable && corepack prepare pnpm@latest --activate
3905
+
3906
+ # Copy package files (lockfile is optional)
3907
+ COPY package.json tsconfig.json skedyul.config.ts ./
3908
+ COPY src ./src
3909
+
3910
+ # Copy tsup.config.ts if it exists, otherwise generate based on COMPUTE_LAYER
3911
+ # BUILD_EXTERNAL is a comma-separated list of additional externals (e.g., "twilio,stripe")
3912
+ COPY tsup.config.t[s] ./
3913
+ RUN if [ ! -f tsup.config.ts ]; then \\
3914
+ BASE_EXT="skedyul,zod"; \\
3915
+ if [ "$COMPUTE_LAYER" = "serverless" ]; then \\
3916
+ BASE_EXT="skedyul,skedyul/serverless,zod"; \\
3917
+ FORMAT="esm"; \\
3918
+ else \\
3919
+ BASE_EXT="skedyul,skedyul/dedicated,zod"; \\
3920
+ FORMAT="cjs"; \\
3921
+ fi; \\
3922
+ if [ -n "$BUILD_EXTERNAL" ]; then \\
3923
+ ALL_EXT="$BASE_EXT,$BUILD_EXTERNAL"; \\
3924
+ else \\
3925
+ ALL_EXT="$BASE_EXT"; \\
3926
+ fi; \\
3927
+ EXT_ARRAY=$(echo "$ALL_EXT" | sed 's/,/","/g'); \\
3928
+ printf 'import{defineConfig}from"tsup";export default defineConfig({entry:["src/server/mcp_server.ts"],format:["%s"],target:"node22",outDir:"dist/server",clean:true,splitting:false,dts:false,external:["%s"]})' "$FORMAT" "$EXT_ARRAY" > tsup.config.ts; \\
3929
+ fi
3930
+
3931
+ # Install dependencies (including dev deps for build), compile, smoke test, then prune
3932
+ # Note: Using --no-frozen-lockfile since lockfile may not exist
3933
+ # skedyul build reads computeLayer from skedyul.config.ts
3934
+ # Smoke test runs before pruning since skedyul CLI is a dev dependency
3935
+ RUN pnpm install --no-frozen-lockfile && \\
3936
+ pnpm run build && \\
3937
+ skedyul smoke-test && \\
3938
+ pnpm prune --prod && \\
3939
+ pnpm store prune && \\
3940
+ rm -rf /tmp/* /var/cache/apk/* ~/.npm
3941
+
3942
+ # =============================================================================
3943
+ # DEDICATED STAGE - For local Docker and ECS deployments (HTTP server)
3944
+ # =============================================================================
3945
+ FROM public.ecr.aws/docker/library/node:22-alpine AS dedicated
3946
+
3947
+ WORKDIR /app
3948
+
3949
+ # Copy built artifacts
3950
+ COPY --from=builder /app/node_modules ./node_modules
3951
+ COPY --from=builder /app/dist ./dist
3952
+ COPY --from=builder /app/package.json ./package.json
3953
+
3954
+ # Allow overriding the baked-in MCP env at runtime
3955
+ ARG MCP_ENV_JSON="{}"
3956
+ ENV MCP_ENV_JSON=\${MCP_ENV_JSON}
3957
+
3958
+ # Expose the HTTP port
3959
+ EXPOSE 3000
3960
+
3961
+ # Run as HTTP server (dedicated mode auto-detected by absence of AWS_LAMBDA_FUNCTION_NAME)
3962
+ CMD ["node", "dist/server/mcp_server.js"]
3963
+
3964
+ # =============================================================================
3965
+ # SERVERLESS STAGE - For AWS Lambda deployments
3966
+ # =============================================================================
3967
+ FROM public.ecr.aws/lambda/nodejs:22 AS serverless
3968
+
3969
+ WORKDIR \${LAMBDA_TASK_ROOT}
3970
+
3971
+ COPY --from=builder /app/node_modules ./node_modules
3972
+ COPY --from=builder /app/dist ./dist
3973
+ COPY --from=builder /app/package.json ./package.json
3974
+
3975
+ # Allow overriding the baked-in MCP env at runtime
3976
+ ARG MCP_ENV_JSON="{}"
3977
+ ENV MCP_ENV_JSON=\${MCP_ENV_JSON}
3978
+
3979
+ # Lambda handler format
3980
+ CMD ["dist/server/mcp_server.handler"]
3981
+
3982
+ # =============================================================================
3983
+ # DEFAULT - Use dedicated for local development, override with --target for production
3984
+ # =============================================================================
3985
+ FROM dedicated
3986
+ `;
3987
+
3988
+ // src/config/app-config.ts
3989
+ function defineConfig(config) {
3990
+ return config;
3991
+ }
3992
+
3993
+ // src/config/define.ts
3994
+ function defineModel(model) {
3995
+ return model;
3996
+ }
3997
+ function defineChannel(channel) {
3998
+ return channel;
3999
+ }
4000
+ function definePage(page) {
4001
+ return page;
4002
+ }
4003
+ function defineWorkflow(workflow) {
4004
+ return workflow;
4005
+ }
4006
+ function defineAgent(agent) {
4007
+ return agent;
4008
+ }
4009
+ function defineEnv(env) {
4010
+ return env;
4011
+ }
4012
+ function defineNavigation(navigation) {
4013
+ return navigation;
4014
+ }
4015
+
4016
+ // src/config/loader.ts
4017
+ import * as fs from "fs";
4018
+ import * as path from "path";
4019
+ import * as os from "os";
4020
+ var CONFIG_FILE_NAMES = [
4021
+ "skedyul.config.ts",
4022
+ "skedyul.config.js",
4023
+ "skedyul.config.mjs",
4024
+ "skedyul.config.cjs"
4025
+ ];
4026
+ async function transpileTypeScript(filePath) {
4027
+ const content = fs.readFileSync(filePath, "utf-8");
4028
+ const configDir = path.dirname(path.resolve(filePath));
4029
+ let transpiled = content.replace(/import\s+type\s+\{[^}]+\}\s+from\s+['"][^'"]+['"]\s*;?\n?/g, "").replace(/import\s+\{\s*defineConfig\s*\}\s+from\s+['"]skedyul['"]\s*;?\n?/g, "").replace(/:\s*SkedyulConfig/g, "").replace(/export\s+default\s+/, "module.exports = ").replace(/defineConfig\s*\(\s*\{/, "{").replace(/\}\s*\)\s*;?\s*$/, "}");
4030
+ transpiled = transpiled.replace(
4031
+ /import\s+(\w+)\s+from\s+['"](\.[^'"]+)['"]/g,
4032
+ (match, varName, relativePath) => {
4033
+ const absolutePath = path.resolve(configDir, relativePath);
4034
+ return `const ${varName} = require('${absolutePath.replace(/\\/g, "/")}')`;
4035
+ }
4036
+ );
4037
+ transpiled = transpiled.replace(/import\s*\(\s*['"][^'"]+['"]\s*\)/g, "null");
4038
+ return transpiled;
4039
+ }
4040
+ async function loadConfig(configPath) {
4041
+ const absolutePath = path.resolve(configPath);
4042
+ if (!fs.existsSync(absolutePath)) {
4043
+ throw new Error(`Config file not found: ${absolutePath}`);
4044
+ }
4045
+ const isTypeScript = absolutePath.endsWith(".ts");
4046
+ try {
4047
+ let moduleToLoad = absolutePath;
4048
+ if (isTypeScript) {
4049
+ const transpiled = await transpileTypeScript(absolutePath);
4050
+ const tempDir = os.tmpdir();
4051
+ const tempFile = path.join(tempDir, `skedyul-config-${Date.now()}.js`);
4052
+ fs.writeFileSync(tempFile, transpiled);
4053
+ moduleToLoad = tempFile;
4054
+ try {
4055
+ const module2 = __require(moduleToLoad);
4056
+ const config2 = module2.default || module2;
4057
+ if (!config2 || typeof config2 !== "object") {
4058
+ throw new Error("Config file must export a configuration object");
4059
+ }
4060
+ if (!config2.name || typeof config2.name !== "string") {
4061
+ throw new Error('Config must have a "name" property');
4062
+ }
4063
+ return config2;
4064
+ } finally {
4065
+ try {
4066
+ fs.unlinkSync(tempFile);
4067
+ } catch {
4068
+ }
4069
+ }
4070
+ }
4071
+ const module = await import(moduleToLoad);
4072
+ const config = module.default || module;
4073
+ if (!config || typeof config !== "object") {
4074
+ throw new Error("Config file must export a configuration object");
4075
+ }
4076
+ if (!config.name || typeof config.name !== "string") {
4077
+ throw new Error('Config must have a "name" property');
4078
+ }
4079
+ return config;
4080
+ } catch (error) {
4081
+ throw new Error(
4082
+ `Failed to load config from ${configPath}: ${error instanceof Error ? error.message : String(error)}`
4083
+ );
4084
+ }
4085
+ }
4086
+ function validateConfig(config) {
4087
+ const errors = [];
4088
+ if (!config.name) {
4089
+ errors.push("Missing required field: name");
4090
+ }
4091
+ if (config.computeLayer && !["serverless", "dedicated"].includes(config.computeLayer)) {
4092
+ errors.push(`Invalid computeLayer: ${config.computeLayer}. Must be 'serverless' or 'dedicated'`);
4093
+ }
4094
+ return { valid: errors.length === 0, errors };
4095
+ }
4096
+
4097
+ // src/config/utils.ts
4098
+ function getAllEnvKeys(config) {
4099
+ const provision = config.provision && "env" in config.provision ? config.provision : void 0;
4100
+ const globalKeys = provision?.env ? Object.keys(provision.env) : [];
4101
+ return {
4102
+ global: globalKeys,
4103
+ install: []
4104
+ };
4105
+ }
4106
+ function getRequiredInstallEnvKeys(config) {
4107
+ const provision = config.provision && "env" in config.provision ? config.provision : void 0;
4108
+ if (!provision?.env) return [];
4109
+ return Object.entries(provision.env).filter(([, def]) => def.required).map(([key]) => key);
4110
+ }
4111
+
4112
+ // src/index.ts
4113
+ var src_default = { z: z6 };
4114
+ export {
4115
+ AgentDefinitionSchema,
4116
+ AlertComponentDefinitionSchema,
4117
+ AppAuthInvalidError,
4118
+ AppFieldVisibilitySchema,
4119
+ AuthenticationError,
4120
+ BreadcrumbItemSchema,
4121
+ CONFIG_FILE_NAMES,
4122
+ CardBlockDefinitionSchema,
4123
+ CardBlockHeaderSchema,
4124
+ ChannelCapabilitySchema,
4125
+ ChannelCapabilityTypeSchema,
4126
+ ChannelDefinitionSchema,
4127
+ ChannelDependencySchema,
4128
+ ChannelFieldDefinitionSchema,
4129
+ CheckboxComponentDefinitionSchema,
4130
+ ComboboxComponentDefinitionSchema,
4131
+ ComputeLayerTypeSchema,
4132
+ ConnectionError,
4133
+ DEFAULT_DOCKERFILE,
4134
+ DatePickerComponentDefinitionSchema,
4135
+ EmptyFormComponentDefinitionSchema,
4136
+ EnvSchemaSchema,
4137
+ EnvVariableDefinitionSchema,
4138
+ EnvVisibilitySchema,
4139
+ FieldDataTypeSchema,
4140
+ FieldOptionSchema,
4141
+ FieldOwnerSchema,
4142
+ FieldSettingButtonPropsSchema,
4143
+ FieldSettingComponentDefinitionSchema,
4144
+ FileSettingComponentDefinitionSchema,
4145
+ FilterConditionSchema,
4146
+ FilterOperatorSchema,
4147
+ FormLayoutColumnDefinitionSchema,
4148
+ FormLayoutConfigDefinitionSchema,
4149
+ FormLayoutRowDefinitionSchema,
4150
+ FormV2ComponentDefinitionSchema,
4151
+ FormV2PropsDefinitionSchema,
4152
+ FormV2StylePropsSchema,
4153
+ ImageSettingComponentDefinitionSchema,
4154
+ InlineFieldDefinitionSchema,
4155
+ InputComponentDefinitionSchema,
4156
+ InstallConfigSchema,
4157
+ InstallError,
4158
+ InvalidConfigurationError,
4159
+ LegacyFormBlockDefinitionSchema,
4160
+ ListBlockDefinitionSchema,
4161
+ ListComponentDefinitionSchema,
4162
+ ListItemTemplateSchema,
4163
+ MessageSendChannelSchema,
4164
+ MessageSendContactSchema,
4165
+ MessageSendInputSchema,
4166
+ MessageSendMessageSchema,
4167
+ MessageSendOutputSchema,
4168
+ MessageSendSubscriptionSchema,
4169
+ MissingRequiredFieldError,
4170
+ ModalFormDefinitionSchema,
4171
+ ModelDefinitionSchema,
4172
+ ModelDependencySchema,
4173
+ ModelFieldDefinitionSchema,
4174
+ ModelMapperBlockDefinitionSchema,
4175
+ NavigationBreadcrumbSchema,
4176
+ NavigationConfigSchema,
4177
+ NavigationItemSchema,
4178
+ NavigationSectionSchema,
4179
+ NavigationSidebarSchema,
4180
+ OnDeleteBehaviorSchema,
4181
+ PageActionDefinitionSchema,
4182
+ PageBlockDefinitionSchema,
4183
+ PageBlockTypeSchema,
4184
+ PageContextDefinitionSchema,
4185
+ PageContextFiltersSchema,
4186
+ PageContextItemDefinitionSchema,
4187
+ PageContextModeSchema,
4188
+ PageContextToolItemDefinitionSchema,
4189
+ PageDefinitionSchema,
4190
+ PageFieldDefinitionSchema,
4191
+ PageFieldSourceSchema,
4192
+ PageFieldTypeSchema,
4193
+ PageFormHeaderSchema,
4194
+ PageInstanceFilterSchema,
4195
+ PageTypeSchema,
4196
+ ProvisionConfigSchema,
4197
+ RelationshipCardinalitySchema,
4198
+ RelationshipDefinitionSchema,
4199
+ RelationshipExtensionSchema,
4200
+ RelationshipLinkSchema,
4201
+ ResourceDependencySchema,
4202
+ SelectComponentDefinitionSchema,
4203
+ SkedyulConfigSchema,
4204
+ StructuredFilterSchema,
4205
+ TextareaComponentDefinitionSchema,
4206
+ TimePickerComponentDefinitionSchema,
4207
+ ToolResponseMetaSchema,
4208
+ WebhookHandlerDefinitionSchema,
4209
+ WebhookHttpMethodSchema,
4210
+ WebhookTypeSchema,
4211
+ WebhooksSchema,
4212
+ WorkflowActionInputSchema,
4213
+ WorkflowActionSchema,
4214
+ WorkflowDefinitionSchema,
4215
+ WorkflowDependencySchema,
4216
+ ai,
4217
+ communicationChannel,
4218
+ configure,
4219
+ createContextLogger,
4220
+ createServerHookContext,
4221
+ createToolCallContext,
4222
+ createWebhookContext,
4223
+ createWorkflowStepContext,
4224
+ src_default as default,
4225
+ defineAgent,
4226
+ defineChannel,
4227
+ defineConfig,
4228
+ defineEnv,
4229
+ defineModel,
4230
+ defineNavigation,
4231
+ definePage,
4232
+ defineWorkflow,
4233
+ file,
4234
+ getAllEnvKeys,
4235
+ getConfig,
4236
+ getRequiredInstallEnvKeys,
4237
+ instance,
4238
+ isChannelDependency,
4239
+ isModelDependency,
4240
+ isProvisionContext,
4241
+ isRuntimeContext,
4242
+ isRuntimeWebhookContext,
4243
+ isWorkflowDependency,
4244
+ loadConfig,
4245
+ report,
4246
+ resource,
4247
+ runWithConfig,
4248
+ safeParseConfig,
4249
+ server,
4250
+ token,
4251
+ validateConfig,
4252
+ webhook,
4253
+ workplace,
4254
+ z6 as z
4255
+ };