@glueco/shared 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,842 @@
1
+ 'use strict';
2
+
3
+ var zod = require('zod');
4
+
5
+ // src/errors.ts
6
+ var ErrorCode = /* @__PURE__ */ ((ErrorCode2) => {
7
+ ErrorCode2["ERR_RESOURCE_REQUIRED"] = "ERR_RESOURCE_REQUIRED";
8
+ ErrorCode2["ERR_UNKNOWN_RESOURCE"] = "ERR_UNKNOWN_RESOURCE";
9
+ ErrorCode2["ERR_RESOURCE_NOT_CONFIGURED"] = "ERR_RESOURCE_NOT_CONFIGURED";
10
+ ErrorCode2["ERR_UNSUPPORTED_ACTION"] = "ERR_UNSUPPORTED_ACTION";
11
+ ErrorCode2["ERR_MISSING_AUTH"] = "ERR_MISSING_AUTH";
12
+ ErrorCode2["ERR_INVALID_SIGNATURE"] = "ERR_INVALID_SIGNATURE";
13
+ ErrorCode2["ERR_EXPIRED_TIMESTAMP"] = "ERR_EXPIRED_TIMESTAMP";
14
+ ErrorCode2["ERR_INVALID_NONCE"] = "ERR_INVALID_NONCE";
15
+ ErrorCode2["ERR_APP_NOT_FOUND"] = "ERR_APP_NOT_FOUND";
16
+ ErrorCode2["ERR_APP_DISABLED"] = "ERR_APP_DISABLED";
17
+ ErrorCode2["ERR_PERMISSION_DENIED"] = "ERR_PERMISSION_DENIED";
18
+ ErrorCode2["ERR_PERMISSION_EXPIRED"] = "ERR_PERMISSION_EXPIRED";
19
+ ErrorCode2["ERR_CONSTRAINT_VIOLATION"] = "ERR_CONSTRAINT_VIOLATION";
20
+ ErrorCode2["ERR_RATE_LIMIT_EXCEEDED"] = "ERR_RATE_LIMIT_EXCEEDED";
21
+ ErrorCode2["ERR_BUDGET_EXCEEDED"] = "ERR_BUDGET_EXCEEDED";
22
+ ErrorCode2["ERR_INVALID_REQUEST"] = "ERR_INVALID_REQUEST";
23
+ ErrorCode2["ERR_INVALID_JSON"] = "ERR_INVALID_JSON";
24
+ ErrorCode2["ERR_CONTRACT_VALIDATION_FAILED"] = "ERR_CONTRACT_VALIDATION_FAILED";
25
+ ErrorCode2["ERR_INTERNAL"] = "ERR_INTERNAL";
26
+ ErrorCode2["ERR_UPSTREAM_ERROR"] = "ERR_UPSTREAM_ERROR";
27
+ ErrorCode2["ERR_INVALID_PAIRING_STRING"] = "ERR_INVALID_PAIRING_STRING";
28
+ ErrorCode2["ERR_INVALID_CONNECT_CODE"] = "ERR_INVALID_CONNECT_CODE";
29
+ ErrorCode2["ERR_SESSION_EXPIRED"] = "ERR_SESSION_EXPIRED";
30
+ ErrorCode2["ERR_UNSUPPORTED_POP_VERSION"] = "ERR_UNSUPPORTED_POP_VERSION";
31
+ ErrorCode2["ERR_POLICY_VIOLATION"] = "ERR_POLICY_VIOLATION";
32
+ ErrorCode2["ERR_MODEL_NOT_ALLOWED"] = "ERR_MODEL_NOT_ALLOWED";
33
+ ErrorCode2["ERR_MAX_TOKENS_EXCEEDED"] = "ERR_MAX_TOKENS_EXCEEDED";
34
+ ErrorCode2["ERR_TOOLS_NOT_ALLOWED"] = "ERR_TOOLS_NOT_ALLOWED";
35
+ ErrorCode2["ERR_STREAMING_NOT_ALLOWED"] = "ERR_STREAMING_NOT_ALLOWED";
36
+ return ErrorCode2;
37
+ })(ErrorCode || {});
38
+ function getErrorStatus(code) {
39
+ switch (code) {
40
+ case "ERR_RESOURCE_REQUIRED" /* ERR_RESOURCE_REQUIRED */:
41
+ case "ERR_INVALID_REQUEST" /* ERR_INVALID_REQUEST */:
42
+ case "ERR_INVALID_JSON" /* ERR_INVALID_JSON */:
43
+ case "ERR_CONSTRAINT_VIOLATION" /* ERR_CONSTRAINT_VIOLATION */:
44
+ case "ERR_INVALID_PAIRING_STRING" /* ERR_INVALID_PAIRING_STRING */:
45
+ case "ERR_INVALID_CONNECT_CODE" /* ERR_INVALID_CONNECT_CODE */:
46
+ return 400;
47
+ case "ERR_CONTRACT_VALIDATION_FAILED" /* ERR_CONTRACT_VALIDATION_FAILED */:
48
+ return 422;
49
+ case "ERR_MISSING_AUTH" /* ERR_MISSING_AUTH */:
50
+ case "ERR_INVALID_SIGNATURE" /* ERR_INVALID_SIGNATURE */:
51
+ case "ERR_EXPIRED_TIMESTAMP" /* ERR_EXPIRED_TIMESTAMP */:
52
+ case "ERR_INVALID_NONCE" /* ERR_INVALID_NONCE */:
53
+ return 401;
54
+ case "ERR_PERMISSION_DENIED" /* ERR_PERMISSION_DENIED */:
55
+ case "ERR_PERMISSION_EXPIRED" /* ERR_PERMISSION_EXPIRED */:
56
+ case "ERR_APP_DISABLED" /* ERR_APP_DISABLED */:
57
+ return 403;
58
+ case "ERR_APP_NOT_FOUND" /* ERR_APP_NOT_FOUND */:
59
+ case "ERR_UNKNOWN_RESOURCE" /* ERR_UNKNOWN_RESOURCE */:
60
+ case "ERR_UNSUPPORTED_ACTION" /* ERR_UNSUPPORTED_ACTION */:
61
+ return 404;
62
+ case "ERR_SESSION_EXPIRED" /* ERR_SESSION_EXPIRED */:
63
+ return 410;
64
+ case "ERR_UNSUPPORTED_POP_VERSION" /* ERR_UNSUPPORTED_POP_VERSION */:
65
+ return 400;
66
+ case "ERR_POLICY_VIOLATION" /* ERR_POLICY_VIOLATION */:
67
+ case "ERR_MODEL_NOT_ALLOWED" /* ERR_MODEL_NOT_ALLOWED */:
68
+ case "ERR_MAX_TOKENS_EXCEEDED" /* ERR_MAX_TOKENS_EXCEEDED */:
69
+ case "ERR_TOOLS_NOT_ALLOWED" /* ERR_TOOLS_NOT_ALLOWED */:
70
+ case "ERR_STREAMING_NOT_ALLOWED" /* ERR_STREAMING_NOT_ALLOWED */:
71
+ return 403;
72
+ case "ERR_RATE_LIMIT_EXCEEDED" /* ERR_RATE_LIMIT_EXCEEDED */:
73
+ case "ERR_BUDGET_EXCEEDED" /* ERR_BUDGET_EXCEEDED */:
74
+ return 429;
75
+ case "ERR_RESOURCE_NOT_CONFIGURED" /* ERR_RESOURCE_NOT_CONFIGURED */:
76
+ case "ERR_INTERNAL" /* ERR_INTERNAL */:
77
+ case "ERR_UPSTREAM_ERROR" /* ERR_UPSTREAM_ERROR */:
78
+ return 500;
79
+ default:
80
+ return 500;
81
+ }
82
+ }
83
+ var GatewayError = class extends Error {
84
+ constructor(code, message, options) {
85
+ super(message);
86
+ this.name = "GatewayError";
87
+ this.code = code;
88
+ this.status = getErrorStatus(code);
89
+ this.details = options?.details;
90
+ this.requestId = options?.requestId;
91
+ }
92
+ toJSON() {
93
+ return {
94
+ error: {
95
+ code: this.code,
96
+ message: this.message,
97
+ ...this.requestId && { requestId: this.requestId },
98
+ ...this.details && { details: this.details }
99
+ }
100
+ };
101
+ }
102
+ };
103
+ function resourceRequiredError(hint) {
104
+ const message = hint ? `Resource not specified. ${hint}` : "Resource not specified. Set baseURL to /r/<resourceType>/<provider>/v1 or provide x-gateway-resource header.";
105
+ return new GatewayError("ERR_RESOURCE_REQUIRED" /* ERR_RESOURCE_REQUIRED */, message, {
106
+ details: {
107
+ examples: {
108
+ groq: "/r/llm/groq/v1/chat/completions",
109
+ gemini: "/r/llm/gemini/v1/chat/completions",
110
+ header: "x-gateway-resource: llm:groq"
111
+ }
112
+ }
113
+ });
114
+ }
115
+ var GatewayErrorResponseSchema = zod.z.object({
116
+ error: zod.z.object({
117
+ code: zod.z.string(),
118
+ message: zod.z.string(),
119
+ requestId: zod.z.string().optional(),
120
+ details: zod.z.unknown().optional()
121
+ })
122
+ });
123
+ function createErrorResponse(code, message, options) {
124
+ return {
125
+ error: {
126
+ code,
127
+ message,
128
+ ...options?.requestId && { requestId: options.requestId },
129
+ ...options?.details !== void 0 && { details: options.details }
130
+ }
131
+ };
132
+ }
133
+ var DURATION_PRESETS = [
134
+ {
135
+ id: "1_hour",
136
+ label: "1 hour",
137
+ description: "Quick testing session",
138
+ durationMs: 60 * 60 * 1e3,
139
+ isTemporary: true
140
+ },
141
+ {
142
+ id: "4_hours",
143
+ label: "4 hours",
144
+ description: "Extended testing",
145
+ durationMs: 4 * 60 * 60 * 1e3,
146
+ isTemporary: true
147
+ },
148
+ {
149
+ id: "24_hours",
150
+ label: "24 hours",
151
+ description: "One day access",
152
+ durationMs: 24 * 60 * 60 * 1e3,
153
+ isTemporary: true
154
+ },
155
+ {
156
+ id: "1_week",
157
+ label: "1 week",
158
+ description: "7 days",
159
+ durationMs: 7 * 24 * 60 * 60 * 1e3
160
+ },
161
+ {
162
+ id: "1_month",
163
+ label: "1 month",
164
+ description: "30 days",
165
+ durationMs: 30 * 24 * 60 * 60 * 1e3,
166
+ isRecommended: true
167
+ },
168
+ {
169
+ id: "3_months",
170
+ label: "3 months",
171
+ description: "90 days",
172
+ durationMs: 90 * 24 * 60 * 60 * 1e3,
173
+ isRecommended: true
174
+ },
175
+ {
176
+ id: "1_year",
177
+ label: "1 year",
178
+ description: "365 days",
179
+ durationMs: 365 * 24 * 60 * 60 * 1e3
180
+ },
181
+ {
182
+ id: "forever",
183
+ label: "Forever",
184
+ description: "No expiration",
185
+ durationMs: null
186
+ },
187
+ {
188
+ id: "custom",
189
+ label: "Custom",
190
+ description: "Set specific date/time",
191
+ durationMs: null
192
+ }
193
+ ];
194
+ function getDurationPreset(id) {
195
+ return DURATION_PRESETS.find((p) => p.id === id);
196
+ }
197
+ function getExpiryFromDurationPreset(presetId, fromDate = /* @__PURE__ */ new Date()) {
198
+ const preset = getDurationPreset(presetId);
199
+ if (!preset || preset.durationMs === null) {
200
+ return null;
201
+ }
202
+ return new Date(fromDate.getTime() + preset.durationMs);
203
+ }
204
+ function getExpiryFromDuration(durationMs, fromDate = /* @__PURE__ */ new Date()) {
205
+ return new Date(fromDate.getTime() + durationMs);
206
+ }
207
+ function findClosestPreset(durationMs) {
208
+ if (durationMs === null) {
209
+ return DURATION_PRESETS.find((p) => p.id === "forever");
210
+ }
211
+ let closest = DURATION_PRESETS[0];
212
+ let closestDiff = Infinity;
213
+ for (const preset of DURATION_PRESETS) {
214
+ if (preset.durationMs === null || preset.id === "custom") continue;
215
+ const diff = Math.abs(preset.durationMs - durationMs);
216
+ if (diff < closestDiff) {
217
+ closestDiff = diff;
218
+ closest = preset;
219
+ }
220
+ }
221
+ return closest;
222
+ }
223
+ function formatDuration(durationMs) {
224
+ if (durationMs === null) return "Forever";
225
+ const hours = durationMs / (60 * 60 * 1e3);
226
+ if (hours < 24) return `${Math.round(hours)} hour${hours !== 1 ? "s" : ""}`;
227
+ const days = hours / 24;
228
+ if (days < 7) return `${Math.round(days)} day${days !== 1 ? "s" : ""}`;
229
+ const weeks = days / 7;
230
+ if (weeks < 4) return `${Math.round(weeks)} week${weeks !== 1 ? "s" : ""}`;
231
+ const months = days / 30;
232
+ if (months < 12)
233
+ return `${Math.round(months)} month${months !== 1 ? "s" : ""}`;
234
+ const years = days / 365;
235
+ return `${Math.round(years)} year${years !== 1 ? "s" : ""}`;
236
+ }
237
+ function formatExpiryRelative(expiresAt) {
238
+ if (!expiresAt) return "Never";
239
+ const now = /* @__PURE__ */ new Date();
240
+ const diff = expiresAt.getTime() - now.getTime();
241
+ if (diff <= 0) return "Expired";
242
+ return `In ${formatDuration(diff)}`;
243
+ }
244
+ var DurationPresetIdSchema = zod.z.enum([
245
+ "1_hour",
246
+ "4_hours",
247
+ "24_hours",
248
+ "1_week",
249
+ "1_month",
250
+ "3_months",
251
+ "1_year",
252
+ "forever",
253
+ "custom"
254
+ ]);
255
+ var RequestedDurationSchema = zod.z.union([
256
+ // Preset ID
257
+ zod.z.object({
258
+ type: zod.z.literal("preset"),
259
+ preset: DurationPresetIdSchema
260
+ }),
261
+ // Specific duration in milliseconds
262
+ zod.z.object({
263
+ type: zod.z.literal("duration"),
264
+ durationMs: zod.z.number().int().positive()
265
+ }),
266
+ // Specific expiry date
267
+ zod.z.object({
268
+ type: zod.z.literal("until"),
269
+ expiresAt: zod.z.string().datetime()
270
+ })
271
+ ]);
272
+ function resolveRequestedDuration(duration, fromDate = /* @__PURE__ */ new Date()) {
273
+ if (!duration) return null;
274
+ switch (duration.type) {
275
+ case "preset":
276
+ return getExpiryFromDurationPreset(duration.preset, fromDate);
277
+ case "duration":
278
+ return getExpiryFromDuration(duration.durationMs, fromDate);
279
+ case "until":
280
+ return new Date(duration.expiresAt);
281
+ }
282
+ }
283
+ function createPresetDuration(preset) {
284
+ return { type: "preset", preset };
285
+ }
286
+ function createDurationMs(durationMs) {
287
+ return { type: "duration", durationMs };
288
+ }
289
+ function createUntilDuration(expiresAt) {
290
+ return { type: "until", expiresAt: expiresAt.toISOString() };
291
+ }
292
+
293
+ // src/schemas.ts
294
+ var ChatMessageSchema = zod.z.object({
295
+ role: zod.z.enum(["system", "user", "assistant", "tool"]),
296
+ content: zod.z.union([
297
+ zod.z.string(),
298
+ zod.z.array(
299
+ zod.z.object({
300
+ type: zod.z.string(),
301
+ text: zod.z.string().optional(),
302
+ image_url: zod.z.object({
303
+ url: zod.z.string(),
304
+ detail: zod.z.string().optional()
305
+ }).optional()
306
+ })
307
+ )
308
+ ]).nullable(),
309
+ name: zod.z.string().optional(),
310
+ tool_calls: zod.z.array(
311
+ zod.z.object({
312
+ id: zod.z.string(),
313
+ type: zod.z.literal("function"),
314
+ function: zod.z.object({
315
+ name: zod.z.string(),
316
+ arguments: zod.z.string()
317
+ })
318
+ })
319
+ ).optional(),
320
+ tool_call_id: zod.z.string().optional()
321
+ });
322
+ var ChatCompletionRequestSchema = zod.z.object({
323
+ model: zod.z.string(),
324
+ messages: zod.z.array(ChatMessageSchema),
325
+ temperature: zod.z.number().min(0).max(2).optional(),
326
+ top_p: zod.z.number().min(0).max(1).optional(),
327
+ n: zod.z.number().int().min(1).max(10).optional(),
328
+ stream: zod.z.boolean().optional(),
329
+ stop: zod.z.union([zod.z.string(), zod.z.array(zod.z.string())]).optional(),
330
+ max_tokens: zod.z.number().int().positive().optional(),
331
+ max_completion_tokens: zod.z.number().int().positive().optional(),
332
+ presence_penalty: zod.z.number().min(-2).max(2).optional(),
333
+ frequency_penalty: zod.z.number().min(-2).max(2).optional(),
334
+ logit_bias: zod.z.record(zod.z.number()).optional(),
335
+ user: zod.z.string().optional(),
336
+ tools: zod.z.array(
337
+ zod.z.object({
338
+ type: zod.z.literal("function"),
339
+ function: zod.z.object({
340
+ name: zod.z.string(),
341
+ description: zod.z.string().optional(),
342
+ parameters: zod.z.record(zod.z.unknown()).optional()
343
+ })
344
+ })
345
+ ).optional(),
346
+ tool_choice: zod.z.union([
347
+ zod.z.literal("none"),
348
+ zod.z.literal("auto"),
349
+ zod.z.literal("required"),
350
+ zod.z.object({
351
+ type: zod.z.literal("function"),
352
+ function: zod.z.object({ name: zod.z.string() })
353
+ })
354
+ ]).optional(),
355
+ response_format: zod.z.object({
356
+ type: zod.z.enum(["text", "json_object"])
357
+ }).optional(),
358
+ seed: zod.z.number().int().optional()
359
+ });
360
+ var PermissionRequestSchema = zod.z.object({
361
+ resourceId: zod.z.string().regex(/^[a-z]+:[a-z0-9-]+$/, {
362
+ message: "Invalid resource ID format. Expected: <resourceType>:<provider>"
363
+ }),
364
+ actions: zod.z.array(zod.z.string()).min(1),
365
+ constraints: zod.z.record(zod.z.unknown()).optional(),
366
+ /** Optional: App's requested/preferred duration for this permission */
367
+ requestedDuration: RequestedDurationSchema.optional()
368
+ });
369
+ var AppMetadataSchema = zod.z.object({
370
+ name: zod.z.string().min(1).max(100),
371
+ description: zod.z.string().max(500).optional(),
372
+ homepage: zod.z.string().url().optional()
373
+ });
374
+ var InstallRequestSchema = zod.z.object({
375
+ connectCode: zod.z.string().min(16),
376
+ app: AppMetadataSchema,
377
+ publicKey: zod.z.string().min(40),
378
+ requestedPermissions: zod.z.array(PermissionRequestSchema).min(1),
379
+ redirectUri: zod.z.string().url()
380
+ });
381
+ var ResourceAuthSchema = zod.z.object({
382
+ pop: zod.z.object({
383
+ version: zod.z.number()
384
+ })
385
+ });
386
+ var ResourceDiscoveryEntrySchema = zod.z.object({
387
+ resourceId: zod.z.string(),
388
+ actions: zod.z.array(zod.z.string()),
389
+ auth: ResourceAuthSchema,
390
+ constraints: zod.z.object({
391
+ supports: zod.z.array(zod.z.string())
392
+ }).optional()
393
+ });
394
+ var GatewayInfoSchema = zod.z.object({
395
+ version: zod.z.string(),
396
+ name: zod.z.string().optional()
397
+ });
398
+ var ResourcesDiscoveryResponseSchema = zod.z.object({
399
+ gateway: GatewayInfoSchema,
400
+ resources: zod.z.array(ResourceDiscoveryEntrySchema)
401
+ });
402
+
403
+ // src/access-policy.ts
404
+ function getExpiryFromPreset(preset) {
405
+ const now = /* @__PURE__ */ new Date();
406
+ switch (preset) {
407
+ case "1_hour":
408
+ return new Date(now.getTime() + 60 * 60 * 1e3);
409
+ case "4_hours":
410
+ return new Date(now.getTime() + 4 * 60 * 60 * 1e3);
411
+ case "today":
412
+ const endOfDay = new Date(now);
413
+ endOfDay.setHours(23, 59, 59, 999);
414
+ return endOfDay;
415
+ case "24_hours":
416
+ return new Date(now.getTime() + 24 * 60 * 60 * 1e3);
417
+ case "this_week":
418
+ const endOfWeek = new Date(now);
419
+ const daysUntilSunday = 7 - endOfWeek.getDay();
420
+ endOfWeek.setDate(endOfWeek.getDate() + daysUntilSunday);
421
+ endOfWeek.setHours(23, 59, 59, 999);
422
+ return endOfWeek;
423
+ case "1_month":
424
+ return new Date(now.getTime() + 30 * 24 * 60 * 60 * 1e3);
425
+ case "3_months":
426
+ return new Date(now.getTime() + 90 * 24 * 60 * 60 * 1e3);
427
+ case "1_year":
428
+ return new Date(now.getTime() + 365 * 24 * 60 * 60 * 1e3);
429
+ case "never":
430
+ return null;
431
+ case "custom":
432
+ return null;
433
+ // Custom requires manual date input
434
+ default:
435
+ return null;
436
+ }
437
+ }
438
+ var EXPIRY_PRESETS = [
439
+ {
440
+ value: "1_hour",
441
+ label: "1 hour",
442
+ description: "Expires in 1 hour",
443
+ getDate: () => getExpiryFromPreset("1_hour")
444
+ },
445
+ {
446
+ value: "4_hours",
447
+ label: "4 hours",
448
+ description: "Expires in 4 hours",
449
+ getDate: () => getExpiryFromPreset("4_hours")
450
+ },
451
+ {
452
+ value: "today",
453
+ label: "End of today",
454
+ description: "Expires at midnight",
455
+ getDate: () => getExpiryFromPreset("today")
456
+ },
457
+ {
458
+ value: "24_hours",
459
+ label: "24 hours",
460
+ description: "Expires in 24 hours",
461
+ getDate: () => getExpiryFromPreset("24_hours")
462
+ },
463
+ {
464
+ value: "this_week",
465
+ label: "This week",
466
+ description: "Expires end of week",
467
+ getDate: () => getExpiryFromPreset("this_week")
468
+ },
469
+ {
470
+ value: "1_month",
471
+ label: "1 month",
472
+ description: "Expires in 30 days",
473
+ getDate: () => getExpiryFromPreset("1_month")
474
+ },
475
+ {
476
+ value: "3_months",
477
+ label: "3 months",
478
+ description: "Expires in 90 days",
479
+ getDate: () => getExpiryFromPreset("3_months")
480
+ },
481
+ {
482
+ value: "1_year",
483
+ label: "1 year",
484
+ description: "Expires in 1 year",
485
+ getDate: () => getExpiryFromPreset("1_year")
486
+ },
487
+ {
488
+ value: "never",
489
+ label: "Never",
490
+ description: "No expiration",
491
+ getDate: () => null
492
+ },
493
+ {
494
+ value: "custom",
495
+ label: "Custom",
496
+ description: "Set custom date",
497
+ getDate: () => null
498
+ }
499
+ ];
500
+ var RATE_LIMIT_PRESETS = [
501
+ {
502
+ label: "5 per minute (very restricted)",
503
+ value: { maxRequests: 5, windowSeconds: 60 }
504
+ },
505
+ { label: "10 per minute", value: { maxRequests: 10, windowSeconds: 60 } },
506
+ { label: "30 per minute", value: { maxRequests: 30, windowSeconds: 60 } },
507
+ {
508
+ label: "60 per minute (standard)",
509
+ value: { maxRequests: 60, windowSeconds: 60 }
510
+ },
511
+ { label: "100 per hour", value: { maxRequests: 100, windowSeconds: 3600 } },
512
+ { label: "500 per hour", value: { maxRequests: 500, windowSeconds: 3600 } },
513
+ { label: "1000 per day", value: { maxRequests: 1e3, windowSeconds: 86400 } }
514
+ ];
515
+ function isPermissionValidNow(policy) {
516
+ const now = /* @__PURE__ */ new Date();
517
+ if (policy.validFrom) {
518
+ const validFromDate = new Date(policy.validFrom);
519
+ if (now < validFromDate) {
520
+ return {
521
+ valid: false,
522
+ reason: `Permission not yet valid. Starts at ${validFromDate.toISOString()}`
523
+ };
524
+ }
525
+ }
526
+ if (policy.expiresAt) {
527
+ const expiresAtDate = new Date(policy.expiresAt);
528
+ if (now > expiresAtDate) {
529
+ return {
530
+ valid: false,
531
+ reason: `Permission expired at ${expiresAtDate.toISOString()}`
532
+ };
533
+ }
534
+ }
535
+ if (policy.timeWindow) {
536
+ const { startHour, endHour, timezone, allowedDays } = policy.timeWindow;
537
+ const formatter = new Intl.DateTimeFormat("en-US", {
538
+ hour: "numeric",
539
+ hour12: false,
540
+ timeZone: timezone
541
+ });
542
+ const currentHour = parseInt(formatter.format(now), 10);
543
+ const dayFormatter = new Intl.DateTimeFormat("en-US", {
544
+ weekday: "short",
545
+ timeZone: timezone
546
+ });
547
+ const dayNames = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
548
+ const currentDayName = dayFormatter.format(now);
549
+ const currentDay = dayNames.indexOf(currentDayName.slice(0, 3));
550
+ if (allowedDays && allowedDays.length > 0 && !allowedDays.includes(currentDay)) {
551
+ return {
552
+ valid: false,
553
+ reason: `Access not allowed on this day (${currentDayName})`
554
+ };
555
+ }
556
+ let inWindow;
557
+ if (startHour <= endHour) {
558
+ inWindow = currentHour >= startHour && currentHour < endHour;
559
+ } else {
560
+ inWindow = currentHour >= startHour || currentHour < endHour;
561
+ }
562
+ if (!inWindow) {
563
+ return {
564
+ valid: false,
565
+ reason: `Access only allowed between ${startHour}:00-${endHour}:00 ${timezone}`
566
+ };
567
+ }
568
+ }
569
+ return { valid: true };
570
+ }
571
+ function formatAccessPolicySummary(policy) {
572
+ const summary = [];
573
+ if (policy.expiresAt) {
574
+ const date = new Date(policy.expiresAt);
575
+ summary.push(
576
+ `Expires: ${date.toLocaleDateString()} ${date.toLocaleTimeString()}`
577
+ );
578
+ }
579
+ if (policy.validFrom) {
580
+ const date = new Date(policy.validFrom);
581
+ summary.push(
582
+ `Starts: ${date.toLocaleDateString()} ${date.toLocaleTimeString()}`
583
+ );
584
+ }
585
+ if (policy.timeWindow) {
586
+ const { startHour, endHour, timezone } = policy.timeWindow;
587
+ summary.push(`Hours: ${startHour}:00-${endHour}:00 ${timezone}`);
588
+ }
589
+ if (policy.rateLimit) {
590
+ const { maxRequests, windowSeconds } = policy.rateLimit;
591
+ if (windowSeconds === 60) summary.push(`Rate: ${maxRequests}/min`);
592
+ else if (windowSeconds === 3600) summary.push(`Rate: ${maxRequests}/hour`);
593
+ else if (windowSeconds === 86400) summary.push(`Rate: ${maxRequests}/day`);
594
+ else summary.push(`Rate: ${maxRequests}/${windowSeconds}s`);
595
+ }
596
+ if (policy.quota?.daily) {
597
+ summary.push(`Daily quota: ${policy.quota.daily}`);
598
+ }
599
+ if (policy.quota?.monthly) {
600
+ summary.push(`Monthly quota: ${policy.quota.monthly}`);
601
+ }
602
+ if (policy.tokenBudget?.daily) {
603
+ summary.push(`Daily tokens: ${policy.tokenBudget.daily.toLocaleString()}`);
604
+ }
605
+ return summary;
606
+ }
607
+ var PopHeadersV1Schema = zod.z.object({
608
+ "x-pop-v": zod.z.literal("1"),
609
+ "x-app-id": zod.z.string().min(1, "App ID is required"),
610
+ "x-ts": zod.z.string().regex(/^\d+$/, "Timestamp must be numeric"),
611
+ "x-nonce": zod.z.string().min(16, "Nonce must be at least 16 characters"),
612
+ "x-sig": zod.z.string().min(1, "Signature is required")
613
+ });
614
+ function buildCanonicalRequestV1(params) {
615
+ return [
616
+ "v1",
617
+ params.method.toUpperCase(),
618
+ params.pathWithQuery,
619
+ params.appId,
620
+ params.ts,
621
+ params.nonce,
622
+ params.bodyHash,
623
+ ""
624
+ // trailing newline
625
+ ].join("\n");
626
+ }
627
+ function getPathWithQuery(url) {
628
+ return url.pathname + url.search;
629
+ }
630
+ var POP_VERSION = "1";
631
+ var PopErrorCode = /* @__PURE__ */ ((PopErrorCode2) => {
632
+ PopErrorCode2["UNSUPPORTED_VERSION"] = "ERR_UNSUPPORTED_POP_VERSION";
633
+ return PopErrorCode2;
634
+ })(PopErrorCode || {});
635
+ var EnforcementFieldsSchema = zod.z.object({
636
+ // LLM-specific fields
637
+ model: zod.z.string().optional(),
638
+ maxOutputTokens: zod.z.number().int().positive().optional(),
639
+ usesTools: zod.z.boolean().optional(),
640
+ stream: zod.z.boolean().optional(),
641
+ // Email-specific fields
642
+ fromDomain: zod.z.string().optional(),
643
+ toDomains: zod.z.array(zod.z.string()).optional(),
644
+ recipientCount: zod.z.number().int().positive().optional(),
645
+ // Generic fields
646
+ contentType: zod.z.string().optional()
647
+ });
648
+ var ExtractedRequestSchema = EnforcementFieldsSchema;
649
+ var EnforcementMetaSchema = zod.z.object({
650
+ /** App-provided request ID for correlation */
651
+ requestId: zod.z.string().optional(),
652
+ /** Declared intent (advisory only, not enforced) */
653
+ intent: zod.z.string().optional(),
654
+ /** App-declared expected model (advisory only) */
655
+ expectedModel: zod.z.string().optional()
656
+ });
657
+ var PluginAuthSchema = zod.z.object({
658
+ pop: zod.z.object({
659
+ version: zod.z.number().int().positive()
660
+ })
661
+ });
662
+ var PluginSupportsSchema = zod.z.object({
663
+ enforcement: zod.z.array(zod.z.string())
664
+ });
665
+ var ExtractorDescriptorSchema = zod.z.object({
666
+ /** Reference to core extractor by name (e.g., "openai-compatible", "gemini") */
667
+ type: zod.z.string().optional(),
668
+ /** Custom extraction config (for future use) */
669
+ config: zod.z.record(zod.z.unknown()).optional()
670
+ });
671
+ var CredentialFieldSchema = zod.z.object({
672
+ name: zod.z.string(),
673
+ type: zod.z.enum(["string", "secret", "url", "number", "boolean"]),
674
+ label: zod.z.string(),
675
+ description: zod.z.string().optional(),
676
+ required: zod.z.boolean().default(true),
677
+ default: zod.z.unknown().optional()
678
+ });
679
+ var PluginCredentialSchemaSchema = zod.z.object({
680
+ fields: zod.z.array(CredentialFieldSchema)
681
+ });
682
+ var PluginClientContractSchema = zod.z.object({
683
+ namespace: zod.z.string().min(1),
684
+ actions: zod.z.record(
685
+ zod.z.object({
686
+ requestSchema: zod.z.any().optional(),
687
+ responseSchema: zod.z.any().optional(),
688
+ description: zod.z.string().optional()
689
+ })
690
+ ),
691
+ entrypoint: zod.z.string().optional()
692
+ });
693
+ var PluginMetadataSchema = zod.z.object({
694
+ id: zod.z.string().regex(/^[a-z]+:[a-z0-9-]+$/, {
695
+ message: "Plugin ID must be in format: <resourceType>:<provider>"
696
+ }),
697
+ resourceType: zod.z.string().min(1),
698
+ provider: zod.z.string().min(1),
699
+ version: zod.z.string().min(1),
700
+ name: zod.z.string().min(1),
701
+ actions: zod.z.array(zod.z.string()).min(1),
702
+ auth: PluginAuthSchema,
703
+ supports: PluginSupportsSchema,
704
+ extractors: zod.z.record(ExtractorDescriptorSchema).optional(),
705
+ credentialSchema: PluginCredentialSchemaSchema.optional(),
706
+ client: PluginClientContractSchema.optional()
707
+ });
708
+ function validatePluginMetadata(plugin) {
709
+ if (!plugin || typeof plugin !== "object") {
710
+ return { valid: false, error: "Plugin must be an object" };
711
+ }
712
+ const result = PluginMetadataSchema.safeParse(plugin);
713
+ if (!result.success) {
714
+ return {
715
+ valid: false,
716
+ error: `Invalid plugin metadata: ${result.error.errors.map((e) => e.message).join(", ")}`
717
+ };
718
+ }
719
+ const meta = result.data;
720
+ const expectedId = `${meta.resourceType}:${meta.provider}`;
721
+ if (meta.id !== expectedId) {
722
+ return {
723
+ valid: false,
724
+ error: `Plugin ID '${meta.id}' must match '${expectedId}'`
725
+ };
726
+ }
727
+ return { valid: true, metadata: meta };
728
+ }
729
+ function pluginToDiscoveryEntry(plugin) {
730
+ const entry = {
731
+ resourceId: plugin.id,
732
+ actions: plugin.actions,
733
+ auth: plugin.auth,
734
+ version: plugin.version,
735
+ constraints: {
736
+ supports: plugin.supports.enforcement
737
+ }
738
+ };
739
+ if (plugin.client) {
740
+ entry.client = {
741
+ namespace: plugin.client.namespace,
742
+ entrypoint: plugin.client.entrypoint ?? "./client"
743
+ };
744
+ }
745
+ return entry;
746
+ }
747
+ var DEFAULT_PLUGIN_AUTH = {
748
+ pop: { version: 1 }
749
+ };
750
+ var DEFAULT_PLUGIN_SUPPORTS = {
751
+ enforcement: []
752
+ };
753
+ function createPluginBase(options) {
754
+ return {
755
+ id: options.id,
756
+ resourceType: options.resourceType,
757
+ provider: options.provider,
758
+ version: options.version,
759
+ name: options.name,
760
+ actions: options.actions,
761
+ auth: options.auth ?? DEFAULT_PLUGIN_AUTH,
762
+ supports: options.supports ?? DEFAULT_PLUGIN_SUPPORTS,
763
+ extractors: options.extractors,
764
+ credentialSchema: options.credentialSchema,
765
+ client: options.client
766
+ };
767
+ }
768
+
769
+ // src/index.ts
770
+ function parseResourceId(resourceId) {
771
+ const parts = resourceId.split(":");
772
+ if (parts.length !== 2) {
773
+ throw new Error(
774
+ `Invalid resource ID format: ${resourceId}. Expected: <resourceType>:<provider>`
775
+ );
776
+ }
777
+ return {
778
+ resourceType: parts[0],
779
+ provider: parts[1]
780
+ };
781
+ }
782
+ function createResourceId(resourceType, provider) {
783
+ return `${resourceType}:${provider}`;
784
+ }
785
+
786
+ exports.AppMetadataSchema = AppMetadataSchema;
787
+ exports.ChatCompletionRequestSchema = ChatCompletionRequestSchema;
788
+ exports.ChatMessageSchema = ChatMessageSchema;
789
+ exports.CredentialFieldSchema = CredentialFieldSchema;
790
+ exports.DEFAULT_PLUGIN_AUTH = DEFAULT_PLUGIN_AUTH;
791
+ exports.DEFAULT_PLUGIN_SUPPORTS = DEFAULT_PLUGIN_SUPPORTS;
792
+ exports.DURATION_PRESETS = DURATION_PRESETS;
793
+ exports.DurationPresetIdSchema = DurationPresetIdSchema;
794
+ exports.EXPIRY_PRESETS = EXPIRY_PRESETS;
795
+ exports.EnforcementFieldsSchema = EnforcementFieldsSchema;
796
+ exports.EnforcementMetaSchema = EnforcementMetaSchema;
797
+ exports.ErrorCode = ErrorCode;
798
+ exports.ExtractedRequestSchema = ExtractedRequestSchema;
799
+ exports.ExtractorDescriptorSchema = ExtractorDescriptorSchema;
800
+ exports.GatewayError = GatewayError;
801
+ exports.GatewayErrorResponseSchema = GatewayErrorResponseSchema;
802
+ exports.GatewayInfoSchema = GatewayInfoSchema;
803
+ exports.InstallRequestSchema = InstallRequestSchema;
804
+ exports.POP_VERSION = POP_VERSION;
805
+ exports.PermissionRequestSchema = PermissionRequestSchema;
806
+ exports.PluginAuthSchema = PluginAuthSchema;
807
+ exports.PluginClientContractSchema = PluginClientContractSchema;
808
+ exports.PluginCredentialSchemaSchema = PluginCredentialSchemaSchema;
809
+ exports.PluginMetadataSchema = PluginMetadataSchema;
810
+ exports.PluginSupportsSchema = PluginSupportsSchema;
811
+ exports.PopErrorCode = PopErrorCode;
812
+ exports.PopHeadersV1Schema = PopHeadersV1Schema;
813
+ exports.RATE_LIMIT_PRESETS = RATE_LIMIT_PRESETS;
814
+ exports.RequestedDurationSchema = RequestedDurationSchema;
815
+ exports.ResourceAuthSchema = ResourceAuthSchema;
816
+ exports.ResourceDiscoveryEntrySchema = ResourceDiscoveryEntrySchema;
817
+ exports.ResourcesDiscoveryResponseSchema = ResourcesDiscoveryResponseSchema;
818
+ exports.buildCanonicalRequestV1 = buildCanonicalRequestV1;
819
+ exports.createDurationMs = createDurationMs;
820
+ exports.createErrorResponse = createErrorResponse;
821
+ exports.createPluginBase = createPluginBase;
822
+ exports.createPresetDuration = createPresetDuration;
823
+ exports.createResourceId = createResourceId;
824
+ exports.createUntilDuration = createUntilDuration;
825
+ exports.findClosestPreset = findClosestPreset;
826
+ exports.formatAccessPolicySummary = formatAccessPolicySummary;
827
+ exports.formatDuration = formatDuration;
828
+ exports.formatExpiryRelative = formatExpiryRelative;
829
+ exports.getDurationPreset = getDurationPreset;
830
+ exports.getErrorStatus = getErrorStatus;
831
+ exports.getExpiryFromDuration = getExpiryFromDuration;
832
+ exports.getExpiryFromDurationPreset = getExpiryFromDurationPreset;
833
+ exports.getExpiryFromPreset = getExpiryFromPreset;
834
+ exports.getPathWithQuery = getPathWithQuery;
835
+ exports.isPermissionValidNow = isPermissionValidNow;
836
+ exports.parseResourceId = parseResourceId;
837
+ exports.pluginToDiscoveryEntry = pluginToDiscoveryEntry;
838
+ exports.resolveRequestedDuration = resolveRequestedDuration;
839
+ exports.resourceRequiredError = resourceRequiredError;
840
+ exports.validatePluginMetadata = validatePluginMetadata;
841
+ //# sourceMappingURL=index.js.map
842
+ //# sourceMappingURL=index.js.map