@agentuity/core 1.0.34 → 1.0.36

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 (51) hide show
  1. package/dist/services/db/index.d.ts +1 -0
  2. package/dist/services/db/index.d.ts.map +1 -1
  3. package/dist/services/db/index.js +1 -0
  4. package/dist/services/db/index.js.map +1 -1
  5. package/dist/services/db/stats.d.ts +146 -0
  6. package/dist/services/db/stats.d.ts.map +1 -0
  7. package/dist/services/db/stats.js +94 -0
  8. package/dist/services/db/stats.js.map +1 -0
  9. package/dist/services/keyvalue/service.d.ts +3 -3
  10. package/dist/services/keyvalue/service.d.ts.map +1 -1
  11. package/dist/services/keyvalue/service.js +6 -6
  12. package/dist/services/keyvalue/service.js.map +1 -1
  13. package/dist/services/queue/types.d.ts.map +1 -1
  14. package/dist/services/queue/types.js +8 -2
  15. package/dist/services/queue/types.js.map +1 -1
  16. package/dist/services/queue/websocket.d.ts.map +1 -1
  17. package/dist/services/queue/websocket.js +27 -8
  18. package/dist/services/queue/websocket.js.map +1 -1
  19. package/dist/services/sandbox/types.d.ts.map +1 -1
  20. package/dist/services/sandbox/types.js.map +1 -1
  21. package/dist/services/webhook/analytics.d.ts +77 -0
  22. package/dist/services/webhook/analytics.d.ts.map +1 -0
  23. package/dist/services/webhook/analytics.js +56 -0
  24. package/dist/services/webhook/analytics.js.map +1 -0
  25. package/dist/services/webhook/destinations.d.ts +4 -0
  26. package/dist/services/webhook/destinations.d.ts.map +1 -1
  27. package/dist/services/webhook/index.d.ts +2 -1
  28. package/dist/services/webhook/index.d.ts.map +1 -1
  29. package/dist/services/webhook/index.js +5 -1
  30. package/dist/services/webhook/index.js.map +1 -1
  31. package/dist/services/webhook/service.d.ts +43 -1
  32. package/dist/services/webhook/service.d.ts.map +1 -1
  33. package/dist/services/webhook/service.js +74 -0
  34. package/dist/services/webhook/service.js.map +1 -1
  35. package/dist/services/webhook/types.d.ts +79 -0
  36. package/dist/services/webhook/types.d.ts.map +1 -1
  37. package/dist/services/webhook/types.js +62 -0
  38. package/dist/services/webhook/types.js.map +1 -1
  39. package/dist/services/webhook/webhooks.d.ts +4 -0
  40. package/dist/services/webhook/webhooks.d.ts.map +1 -1
  41. package/package.json +2 -2
  42. package/src/services/db/index.ts +15 -0
  43. package/src/services/db/stats.ts +121 -0
  44. package/src/services/keyvalue/service.ts +6 -6
  45. package/src/services/queue/types.ts +12 -2
  46. package/src/services/queue/websocket.ts +32 -8
  47. package/src/services/sandbox/types.ts +0 -1
  48. package/src/services/webhook/analytics.ts +80 -0
  49. package/src/services/webhook/index.ts +25 -0
  50. package/src/services/webhook/service.ts +124 -0
  51. package/src/services/webhook/types.ts +92 -0
@@ -0,0 +1,121 @@
1
+ import { z } from 'zod';
2
+ import { type APIClient, APIResponseSchema } from '../api.ts';
3
+ import { DbInvalidArgumentError, DbResponseError } from './util.ts';
4
+
5
+ // Request schema
6
+ export const DbLogStatsRequestSchema = z.object({
7
+ database: z.string().describe('the database name'),
8
+ orgId: z.string().describe('the organization ID'),
9
+ region: z.string().describe('the region'),
10
+ startDate: z.string().describe('start date filter (ISO 8601)'),
11
+ endDate: z.string().describe('end date filter (ISO 8601)'),
12
+ });
13
+
14
+ // Summary stats
15
+ export const DbLogStatsSummarySchema = z.object({
16
+ totalQueries: z.number().describe('total number of queries'),
17
+ errorCount: z.number().describe('number of queries with errors'),
18
+ avgDuration: z.number().describe('average query duration in ms'),
19
+ p50Duration: z.number().describe('50th percentile duration in ms'),
20
+ p95Duration: z.number().describe('95th percentile duration in ms'),
21
+ p99Duration: z.number().describe('99th percentile duration in ms'),
22
+ maxDuration: z.number().describe('maximum query duration in ms'),
23
+ totalRows: z.number().describe('total rows affected/returned'),
24
+ });
25
+
26
+ // Time series point
27
+ export const DbLogStatsTimeSeriesPointSchema = z.object({
28
+ timestamp: z.string().describe('bucket timestamp'),
29
+ queryCount: z.number().describe('queries in this bucket'),
30
+ errorCount: z.number().describe('errors in this bucket'),
31
+ avgDuration: z.number().describe('average duration in this bucket'),
32
+ p50Duration: z.number().describe('p50 duration in this bucket'),
33
+ p95Duration: z.number().describe('p95 duration in this bucket'),
34
+ p99Duration: z.number().describe('p99 duration in this bucket'),
35
+ });
36
+
37
+ // Query pattern
38
+ export const DbLogStatsQueryPatternSchema = z.object({
39
+ pattern: z.string().describe('SQL query text'),
40
+ command: z.string().describe('SQL command type'),
41
+ calls: z.number().describe('number of executions'),
42
+ avgDuration: z.number().describe('average duration in ms'),
43
+ p95Duration: z.number().describe('95th percentile duration in ms'),
44
+ maxDuration: z.number().describe('maximum duration in ms'),
45
+ totalDuration: z.number().describe('total cumulative duration in ms'),
46
+ avgRows: z.number().describe('average rows affected'),
47
+ errors: z.number().describe('number of errors'),
48
+ });
49
+
50
+ // Command breakdown
51
+ export const DbLogStatsCommandBreakdownSchema = z.object({
52
+ command: z.string().describe('SQL command type'),
53
+ queryCount: z.number().describe('number of queries'),
54
+ avgDuration: z.number().describe('average duration in ms'),
55
+ p95Duration: z.number().describe('95th percentile duration in ms'),
56
+ totalDuration: z.number().describe('total cumulative duration in ms'),
57
+ errorCount: z.number().describe('number of errors'),
58
+ });
59
+
60
+ // Combined response
61
+ export const DbLogStatsResponseSchema = z.object({
62
+ summary: DbLogStatsSummarySchema,
63
+ timeSeries: z.array(DbLogStatsTimeSeriesPointSchema),
64
+ queryPatterns: z.array(DbLogStatsQueryPatternSchema),
65
+ commandBreakdown: z.array(DbLogStatsCommandBreakdownSchema),
66
+ });
67
+
68
+ export const DbLogStatsAPIResponseSchema = APIResponseSchema(DbLogStatsResponseSchema);
69
+
70
+ // Type exports
71
+ export type DbLogStatsSummary = z.infer<typeof DbLogStatsSummarySchema>;
72
+ export type DbLogStatsTimeSeriesPoint = z.infer<typeof DbLogStatsTimeSeriesPointSchema>;
73
+ export type DbLogStatsQueryPattern = z.infer<typeof DbLogStatsQueryPatternSchema>;
74
+ export type DbLogStatsCommandBreakdown = z.infer<typeof DbLogStatsCommandBreakdownSchema>;
75
+ export type DbLogStatsResponse = z.infer<typeof DbLogStatsResponseSchema>;
76
+
77
+ type DbLogStatsRequest = z.infer<typeof DbLogStatsRequestSchema>;
78
+ type DbLogStatsAPIResponse = z.infer<typeof DbLogStatsAPIResponseSchema>;
79
+
80
+ /**
81
+ * Get query performance stats for a database
82
+ */
83
+ export async function dbLogStats(
84
+ client: APIClient,
85
+ request: DbLogStatsRequest
86
+ ): Promise<DbLogStatsResponse> {
87
+ if (!request) {
88
+ throw new DbInvalidArgumentError({
89
+ message: 'request is required',
90
+ orgId: undefined,
91
+ region: undefined,
92
+ });
93
+ }
94
+
95
+ const { database, orgId, region, startDate, endDate } = request;
96
+
97
+ if (!orgId || !region || !database || !startDate || !endDate) {
98
+ throw new DbInvalidArgumentError({
99
+ message: 'orgId, region, database, startDate, and endDate are required',
100
+ orgId,
101
+ region,
102
+ });
103
+ }
104
+
105
+ const params = new URLSearchParams();
106
+ params.append('startDate', startDate);
107
+ params.append('endDate', endDate);
108
+
109
+ const url = `/resource/${encodeURIComponent(orgId)}/${encodeURIComponent(region)}/${encodeURIComponent(database)}/logs/stats?${params.toString()}`;
110
+
111
+ const resp = await client.get<DbLogStatsAPIResponse>(url, DbLogStatsAPIResponseSchema);
112
+
113
+ if (resp.success) {
114
+ return resp.data;
115
+ }
116
+
117
+ throw new DbResponseError({
118
+ database,
119
+ message: resp.message || 'Failed to fetch database performance stats',
120
+ });
121
+ }
@@ -9,9 +9,9 @@ import { z } from 'zod';
9
9
  export const KV_MIN_TTL_SECONDS = 60;
10
10
 
11
11
  /**
12
- * Maximum TTL value in seconds (90 days)
12
+ * Maximum TTL value in seconds (365 days)
13
13
  */
14
- export const KV_MAX_TTL_SECONDS = 7776000;
14
+ export const KV_MAX_TTL_SECONDS = 31536000;
15
15
 
16
16
  /**
17
17
  * Default TTL value in seconds (7 days) - used when namespace is auto-created or no TTL specified
@@ -88,11 +88,11 @@ export const KeyValueStorageSetParamsSchema = z.object({
88
88
  * Time-to-live in seconds for the key. Controls when the key expires and is automatically deleted.
89
89
  * - `undefined` (not provided): Key inherits the namespace's default TTL (7 days if not configured)
90
90
  * - `null` or `0`: Key never expires
91
- * - positive number (≥60): Key expires after the specified number of seconds (max 90 days)
91
+ * - positive number (≥60): Key expires after the specified number of seconds (max 365 days)
92
92
  *
93
93
  * @remarks
94
94
  * TTL values below 60 seconds are clamped to 60 seconds by the server.
95
- * TTL values above 7,776,000 seconds (90 days) are clamped to 90 days.
95
+ * TTL values above 31,536,000 seconds (365 days) are clamped to 365 days.
96
96
  */
97
97
  ttl: z
98
98
  .number()
@@ -117,7 +117,7 @@ export const CreateNamespaceParamsSchema = z.object({
117
117
  * Default TTL for keys in this namespace (in seconds).
118
118
  * - If undefined/omitted: uses server default (7 days / 604,800 seconds)
119
119
  * - If 0: keys will not expire by default
120
- * - If 60-7,776,000: custom TTL in seconds (1 minute to 90 days)
120
+ * - If 60-31,536,000: custom TTL in seconds (1 minute to 365 days)
121
121
  *
122
122
  * Keys can override this default by specifying TTL in the set() call.
123
123
  * Active keys are automatically extended (sliding expiration) when read
@@ -444,7 +444,7 @@ export class KeyValueStorageService implements KeyValueStorage {
444
444
  * - If TTL is null or 0, the key will not expire
445
445
  * - If TTL is a positive number, the key expires after that many seconds
446
446
  * - TTL values below 60 seconds are clamped to 60 seconds by the server
447
- * - TTL values above 7,776,000 seconds (90 days) are clamped to 90 days
447
+ * - TTL values above 31,536,000 seconds (365 days) are clamped to 365 days
448
448
  * - If the namespace doesn't exist, it is auto-created with a 7-day default TTL
449
449
  */
450
450
  async set<T = unknown>(
@@ -1568,7 +1568,14 @@ export const ConsumerSchema = z
1568
1568
  .object({
1569
1569
  id: z.string().describe('Unique consumer identifier (qcns_ prefix).'),
1570
1570
  queue_id: z.string().describe('Queue this consumer is connected to.'),
1571
- client_id: z.string().nullable().optional().describe('Client-provided ID for reconnection.'),
1571
+ client_id: z
1572
+ .string()
1573
+ .max(256)
1574
+ .nullable()
1575
+ .optional()
1576
+ .describe(
1577
+ 'Client-provided identifier (max 256 characters). Can be any string for your own identification purposes.'
1578
+ ),
1572
1579
  durable: z.boolean().describe('Whether this consumer uses durable offset tracking.'),
1573
1580
  ip_address: z.string().nullable().optional().describe('IP address of the consumer.'),
1574
1581
  last_offset: z.number().nullable().optional().describe('Last processed message offset.'),
@@ -1603,8 +1610,11 @@ export const WebSocketAuthRequestSchema = z
1603
1610
  .describe('The API key for authentication (raw key, not "Bearer ...").'),
1604
1611
  client_id: z
1605
1612
  .string()
1613
+ .max(256)
1606
1614
  .optional()
1607
- .describe('Optional client ID from a previous connection for reconnection.'),
1615
+ .describe(
1616
+ 'Optional client identifier (max 256 characters). Can be any string for your own identification purposes. If omitted, the server generates one. Store and reuse on reconnect for resume semantics.'
1617
+ ),
1608
1618
  last_offset: z
1609
1619
  .number()
1610
1620
  .optional()
@@ -58,9 +58,9 @@
58
58
  */
59
59
 
60
60
  import { z } from 'zod';
61
+ import { getEnv } from '../env.ts';
61
62
  import type { Message } from './types.ts';
62
63
  import { WebSocketAuthResponseSchema, WebSocketMessageSchema } from './types.ts';
63
- import { getEnv } from '../env.ts';
64
64
  import { QueueError } from './util.ts';
65
65
  import { validateQueueName } from './validation.ts';
66
66
 
@@ -106,7 +106,13 @@ export const QueueWebSocketOptionsSchema = z.object({
106
106
  .describe('Maximum reconnect attempts before giving up'),
107
107
  reconnectDelayMs: z.number().optional().describe('Initial reconnect delay in milliseconds'),
108
108
  maxReconnectDelayMs: z.number().optional().describe('Maximum reconnect delay in milliseconds'),
109
- clientId: z.string().optional().describe('Optional prior client id used for resume semantics'),
109
+ clientId: z
110
+ .string()
111
+ .max(256)
112
+ .optional()
113
+ .describe(
114
+ 'Optional client identifier (max 256 characters). Can be any string for your own identification. Reuse on reconnect for resume semantics.'
115
+ ),
110
116
  lastOffset: z
111
117
  .number()
112
118
  .optional()
@@ -140,7 +146,13 @@ export const SubscribeToQueueOptionsSchema = z.object({
140
146
  .describe('Optional API key override; falls back to AGENTUITY_SDK_KEY'),
141
147
  baseUrl: z.string().describe('Base Catalyst URL used to construct the WebSocket endpoint'),
142
148
  signal: z.custom<AbortSignal>().optional().describe('AbortSignal used to stop the subscription'),
143
- clientId: z.string().optional().describe('Optional prior client id used for resume semantics'),
149
+ clientId: z
150
+ .string()
151
+ .max(256)
152
+ .optional()
153
+ .describe(
154
+ 'Optional client identifier (max 256 characters). Can be any string for your own identification. Reuse on reconnect for resume semantics.'
155
+ ),
144
156
  lastOffset: z
145
157
  .number()
146
158
  .optional()
@@ -227,7 +239,7 @@ export function createQueueWebSocket(options: QueueWebSocketOptions): QueueWebSo
227
239
  onClose,
228
240
  onError,
229
241
  autoReconnect = true,
230
- maxReconnectAttempts = Infinity,
242
+ maxReconnectAttempts = Number.POSITIVE_INFINITY,
231
243
  reconnectDelayMs = 1000,
232
244
  maxReconnectDelayMs = 30000,
233
245
  orgId,
@@ -349,6 +361,12 @@ export function createQueueWebSocket(options: QueueWebSocketOptions): QueueWebSo
349
361
  state = 'closed';
350
362
  ws = null;
351
363
 
364
+ // Close codes 4000–4999 are application-level terminal errors
365
+ // (auth failure, validation error, etc.) — do not reconnect.
366
+ if (event.code >= 4000 && event.code < 5000) {
367
+ intentionallyClosed = true;
368
+ }
369
+
352
370
  onClose?.(event.code, event.reason);
353
371
 
354
372
  // Reconnect on any unintentional close — whether we were fully
@@ -383,7 +401,7 @@ export function createQueueWebSocket(options: QueueWebSocketOptions): QueueWebSo
383
401
  }
384
402
 
385
403
  // Exponential backoff with jitter, capped at maxReconnectDelayMs.
386
- const baseDelay = reconnectDelayMs * Math.pow(2, reconnectAttempts);
404
+ const baseDelay = reconnectDelayMs * 2 ** reconnectAttempts;
387
405
  const jitter = 0.5 + Math.random() * 0.5;
388
406
  const delay = Math.min(Math.floor(baseDelay * jitter), maxReconnectDelayMs);
389
407
 
@@ -508,9 +526,15 @@ export async function* subscribeToQueue(
508
526
  // it to be thrown on clean shutdown / abort.
509
527
  }
510
528
  },
511
- onClose: () => {
512
- // Only finish if the connection is intentionally closed (signal aborted).
513
- // Otherwise, the callback-based API handles reconnection.
529
+ onClose: (code: number) => {
530
+ // Terminal close codes (4000–4999) mean the connection will not
531
+ // reconnect — signal the async iterator to stop so it doesn't
532
+ // hang forever. For abort-initiated closes, `finish()` is
533
+ // already called by the `onAbort` handler; calling it again is
534
+ // harmless (it's idempotent).
535
+ if (code >= 4000 && code < 5000) {
536
+ finish();
537
+ }
514
538
  },
515
539
  autoReconnect: true,
516
540
  });
@@ -130,7 +130,6 @@ const SandboxSnapshotInfoBaseSchema = z.object({
130
130
  /** Full name with org slug (@slug/name:tag) */
131
131
  fullName: z.string().optional().describe('Full name with org slug (@slug/name:tag)'),
132
132
  });
133
- type SandboxSnapshotInfoBase = z.infer<typeof SandboxSnapshotInfoBaseSchema>;
134
133
 
135
134
  /** Public snapshot information - includes org info */
136
135
  export const SandboxSnapshotInfoPublicSchema = SandboxSnapshotInfoBaseSchema.extend({
@@ -0,0 +1,80 @@
1
+ import { z } from 'zod';
2
+ import { type APIClient, APIResponseSchema } from '../api.ts';
3
+ import {
4
+ type WebhookAnalyticsOptions,
5
+ type WebhookOrgAnalytics,
6
+ WebhookOrgAnalyticsSchema,
7
+ type WebhookTimeSeriesData,
8
+ WebhookTimeSeriesDataSchema,
9
+ } from './types.ts';
10
+ import { buildWebhookHeaders, WebhookError, webhookApiPathWithQuery } from './util.ts';
11
+
12
+ export const WebhookOrgAnalyticsResponseSchema = APIResponseSchema(
13
+ z.object({ analytics: WebhookOrgAnalyticsSchema })
14
+ );
15
+
16
+ export const WebhookTimeSeriesResponseSchema = APIResponseSchema(
17
+ z.object({ timeseries: WebhookTimeSeriesDataSchema })
18
+ );
19
+
20
+ function buildAnalyticsQuery(options?: WebhookAnalyticsOptions): string | undefined {
21
+ if (!options) return undefined;
22
+ const params = new URLSearchParams();
23
+ if (options.start) params.set('start', options.start);
24
+ if (options.end) params.set('end', options.end);
25
+ if (options.granularity) params.set('granularity', options.granularity);
26
+ const query = params.toString();
27
+ return query || undefined;
28
+ }
29
+
30
+ /**
31
+ * Get org-level webhook analytics summary.
32
+ *
33
+ * Returns total received, delivered, and failed counts for all webhooks in the org
34
+ * within the specified time period.
35
+ *
36
+ * @param client - The API client
37
+ * @param options - Analytics options (start, end, granularity)
38
+ * @returns Org-level webhook analytics
39
+ */
40
+ export async function getWebhookOrgAnalytics(
41
+ client: APIClient,
42
+ options?: WebhookAnalyticsOptions
43
+ ): Promise<WebhookOrgAnalytics> {
44
+ const queryString = buildAnalyticsQuery(options);
45
+ const url = webhookApiPathWithQuery('analytics/org', queryString);
46
+ const resp = await client.get(
47
+ url,
48
+ WebhookOrgAnalyticsResponseSchema,
49
+ undefined,
50
+ buildWebhookHeaders(options?.orgId)
51
+ );
52
+ if (resp.success) return resp.data.analytics;
53
+ throw new WebhookError({ message: resp.message || 'Failed to get webhook org analytics' });
54
+ }
55
+
56
+ /**
57
+ * Get org-level webhook time series data.
58
+ *
59
+ * Returns time-bucketed received, delivered, and failed counts for all webhooks
60
+ * in the org within the specified time period.
61
+ *
62
+ * @param client - The API client
63
+ * @param options - Analytics options (start, end, granularity)
64
+ * @returns Webhook time series data
65
+ */
66
+ export async function getWebhookOrgTimeSeries(
67
+ client: APIClient,
68
+ options?: WebhookAnalyticsOptions
69
+ ): Promise<WebhookTimeSeriesData> {
70
+ const queryString = buildAnalyticsQuery(options);
71
+ const url = webhookApiPathWithQuery('analytics/org/timeseries', queryString);
72
+ const resp = await client.get(
73
+ url,
74
+ WebhookTimeSeriesResponseSchema,
75
+ undefined,
76
+ buildWebhookHeaders(options?.orgId)
77
+ );
78
+ if (resp.success) return resp.data.timeseries;
79
+ throw new WebhookError({ message: resp.message || 'Failed to get webhook org time series' });
80
+ }
@@ -68,6 +68,20 @@ export {
68
68
  WebhookDestinationTypeSchema,
69
69
  type WebhookReceipt,
70
70
  WebhookReceiptSchema,
71
+ type WebhookAnalyticsGranularity,
72
+ WebhookAnalyticsGranularitySchema,
73
+ type WebhookAnalyticsOptions,
74
+ WebhookAnalyticsOptionsSchema,
75
+ type WebhookAnalyticsSummary,
76
+ WebhookAnalyticsSummarySchema,
77
+ type WebhookOrgAnalytics,
78
+ WebhookOrgAnalyticsSchema,
79
+ type WebhookTimePeriod,
80
+ WebhookTimePeriodSchema,
81
+ type WebhookTimeSeriesData,
82
+ WebhookTimeSeriesDataSchema,
83
+ type WebhookTimeSeriesPoint,
84
+ WebhookTimeSeriesPointSchema,
71
85
  WebhookSchema,
72
86
  } from './types.ts';
73
87
 
@@ -133,3 +147,14 @@ export {
133
147
  WebhookDeliveriesListResponseSchema,
134
148
  WebhookDeliveryResponseSchema,
135
149
  } from './deliveries.ts';
150
+
151
+ // ============================================================================
152
+ // Analytics Operations
153
+ // ============================================================================
154
+
155
+ export {
156
+ getWebhookOrgAnalytics,
157
+ getWebhookOrgTimeSeries,
158
+ WebhookOrgAnalyticsResponseSchema,
159
+ WebhookTimeSeriesResponseSchema,
160
+ } from './analytics.ts';
@@ -8,6 +8,7 @@ import type {
8
8
  Webhook,
9
9
  WebhookDelivery,
10
10
  WebhookDestination,
11
+ WebhookOrgAnalytics,
11
12
  WebhookReceipt,
12
13
  } from './types.ts';
13
14
 
@@ -773,4 +774,127 @@ export class WebhookService {
773
774
 
774
775
  throw await toServiceException('POST', url, res.response);
775
776
  }
777
+
778
+ /**
779
+ * Get org-level webhook analytics summary.
780
+ *
781
+ * Returns total received, delivered, and failed counts for all webhooks
782
+ * in the org within the specified time period.
783
+ *
784
+ * @param options - Analytics options (start, end, granularity)
785
+ * @returns Org-level webhook analytics with period and summary
786
+ * @throws {@link ServiceException} if the API request fails
787
+ */
788
+ async getOrgAnalytics(options?: {
789
+ start?: string;
790
+ end?: string;
791
+ granularity?: string;
792
+ }): Promise<WebhookOrgAnalytics> {
793
+ const params = new URLSearchParams();
794
+ if (options?.start) params.set('start', options.start);
795
+ if (options?.end) params.set('end', options.end);
796
+ if (options?.granularity) params.set('granularity', options.granularity);
797
+ const query = params.toString();
798
+ const path = query ? `/webhook/analytics/org?${query}` : '/webhook/analytics/org';
799
+ const url = buildUrl(this.#baseUrl, path);
800
+ const signal = createTimeoutSignal();
801
+ const res = await this.#adapter.invoke<
802
+ WebhookResponse<{
803
+ analytics: {
804
+ period: { start: string; end: string };
805
+ summary: { total_received: number; total_delivered: number; total_failed: number };
806
+ };
807
+ }>
808
+ >(url, {
809
+ method: 'GET',
810
+ signal,
811
+ telemetry: { name: 'agentuity.webhook.analytics.org' },
812
+ });
813
+
814
+ if (res.ok) {
815
+ if (res.data.success) {
816
+ const unwrapped = this.#unwrap<{
817
+ analytics: {
818
+ period: { start: string; end: string };
819
+ summary: {
820
+ total_received: number;
821
+ total_delivered: number;
822
+ total_failed: number;
823
+ };
824
+ };
825
+ }>(res.data.data);
826
+ return unwrapped.analytics;
827
+ }
828
+ throw new WebhookResponseError({ status: res.response.status, message: res.data.message });
829
+ }
830
+
831
+ throw await toServiceException('GET', url, res.response);
832
+ }
833
+
834
+ /**
835
+ * Get org-level webhook time series data.
836
+ *
837
+ * Returns time-bucketed received, delivered, and failed counts for all webhooks
838
+ * in the org within the specified time period.
839
+ *
840
+ * @param options - Analytics options (start, end, granularity)
841
+ * @returns Webhook time series data with period and series array
842
+ * @throws {@link ServiceException} if the API request fails
843
+ */
844
+ async getOrgTimeSeries(options?: {
845
+ start?: string;
846
+ end?: string;
847
+ granularity?: string;
848
+ }): Promise<{
849
+ period: { start: string; end: string; granularity?: string };
850
+ series: Array<{ timestamp: string; received: number; delivered: number; failed: number }>;
851
+ }> {
852
+ const params = new URLSearchParams();
853
+ if (options?.start) params.set('start', options.start);
854
+ if (options?.end) params.set('end', options.end);
855
+ if (options?.granularity) params.set('granularity', options.granularity);
856
+ const query = params.toString();
857
+ const path = query
858
+ ? `/webhook/analytics/org/timeseries?${query}`
859
+ : '/webhook/analytics/org/timeseries';
860
+ const url = buildUrl(this.#baseUrl, path);
861
+ const signal = createTimeoutSignal();
862
+ const res = await this.#adapter.invoke<
863
+ WebhookResponse<{
864
+ timeseries: {
865
+ period: { start: string; end: string; granularity?: string };
866
+ series: Array<{
867
+ timestamp: string;
868
+ received: number;
869
+ delivered: number;
870
+ failed: number;
871
+ }>;
872
+ };
873
+ }>
874
+ >(url, {
875
+ method: 'GET',
876
+ signal,
877
+ telemetry: { name: 'agentuity.webhook.analytics.org.timeseries' },
878
+ });
879
+
880
+ if (res.ok) {
881
+ if (res.data.success) {
882
+ const unwrapped = this.#unwrap<{
883
+ timeseries: {
884
+ period: { start: string; end: string; granularity?: string };
885
+ series: Array<{
886
+ timestamp: string;
887
+ received: number;
888
+ delivered: number;
889
+ failed: number;
890
+ }>;
891
+ };
892
+ }>(res.data.data);
893
+ return unwrapped.timeseries;
894
+ }
895
+ throw new WebhookResponseError({ status: res.response.status, message: res.data.message });
896
+ }
897
+
898
+ throw await toServiceException('GET', url, res.response);
899
+ }
776
900
  }
@@ -40,6 +40,17 @@ export const WebhookSchema = z
40
40
  .describe(
41
41
  'Fully-qualified ingest URL for sending events to this webhook. Only present on create'
42
42
  ),
43
+ internal: z
44
+ .boolean()
45
+ .describe(
46
+ 'Whether this is a system-managed webhook (e.g., S3 bucket notifications). Internal webhooks cannot be modified or deleted by users'
47
+ ),
48
+ metadata: z
49
+ .record(z.string(), z.unknown())
50
+ .nullable()
51
+ .describe(
52
+ 'System metadata for internal webhooks (e.g., bucket_name, type). Null for user-created webhooks'
53
+ ),
43
54
  })
44
55
  .describe('Webhook endpoint configuration');
45
56
 
@@ -56,6 +67,17 @@ export const WebhookDestinationSchema = z
56
67
  config: z
57
68
  .record(z.string(), z.unknown())
58
69
  .describe('Configuration object for the destination (e.g., URL, headers)'),
70
+ internal: z
71
+ .boolean()
72
+ .describe(
73
+ 'Whether this is a system-managed destination. Internal destinations cannot be modified or deleted by users'
74
+ ),
75
+ metadata: z
76
+ .record(z.string(), z.unknown())
77
+ .nullable()
78
+ .describe(
79
+ 'System metadata for internal destinations (e.g., bucket_name, type). Null for user-created destinations'
80
+ ),
59
81
  })
60
82
  .describe('Webhook destination representing a delivery target for webhook events');
61
83
 
@@ -98,6 +120,76 @@ export const WebhookDeliverySchema = z
98
120
 
99
121
  export type WebhookDelivery = z.infer<typeof WebhookDeliverySchema>;
100
122
 
123
+ // ============================================================================
124
+ // Analytics Types
125
+ // ============================================================================
126
+
127
+ export const WebhookAnalyticsGranularitySchema = z
128
+ .enum(['minute', 'hour', 'day'])
129
+ .describe('Time bucket granularity for analytics queries');
130
+
131
+ export type WebhookAnalyticsGranularity = z.infer<typeof WebhookAnalyticsGranularitySchema>;
132
+
133
+ export const WebhookAnalyticsOptionsSchema = z
134
+ .object({
135
+ start: z.string().optional().describe('ISO 8601 start time for the analytics window'),
136
+ end: z.string().optional().describe('ISO 8601 end time for the analytics window'),
137
+ granularity: WebhookAnalyticsGranularitySchema.optional().describe('Time bucket granularity'),
138
+ orgId: z.string().optional().describe('Organization ID for CLI-authenticated requests'),
139
+ })
140
+ .describe('Options for webhook analytics queries');
141
+
142
+ export type WebhookAnalyticsOptions = z.infer<typeof WebhookAnalyticsOptionsSchema>;
143
+
144
+ export const WebhookTimePeriodSchema = z
145
+ .object({
146
+ start: z.string().describe('ISO 8601 start time'),
147
+ end: z.string().describe('ISO 8601 end time'),
148
+ granularity: WebhookAnalyticsGranularitySchema.optional(),
149
+ })
150
+ .describe('Time period for analytics data');
151
+
152
+ export type WebhookTimePeriod = z.infer<typeof WebhookTimePeriodSchema>;
153
+
154
+ export const WebhookAnalyticsSummarySchema = z
155
+ .object({
156
+ total_received: z.number().describe('Total webhook receipts in the period'),
157
+ total_delivered: z.number().describe('Total successful deliveries in the period'),
158
+ total_failed: z.number().describe('Total failed deliveries in the period'),
159
+ })
160
+ .describe('Summary analytics for webhook activity');
161
+
162
+ export type WebhookAnalyticsSummary = z.infer<typeof WebhookAnalyticsSummarySchema>;
163
+
164
+ export const WebhookOrgAnalyticsSchema = z
165
+ .object({
166
+ period: WebhookTimePeriodSchema,
167
+ summary: WebhookAnalyticsSummarySchema,
168
+ })
169
+ .describe('Org-level webhook analytics response');
170
+
171
+ export type WebhookOrgAnalytics = z.infer<typeof WebhookOrgAnalyticsSchema>;
172
+
173
+ export const WebhookTimeSeriesPointSchema = z
174
+ .object({
175
+ timestamp: z.string().describe('ISO 8601 timestamp for this data point'),
176
+ received: z.number().describe('Number of receipts in this bucket'),
177
+ delivered: z.number().describe('Number of successful deliveries in this bucket'),
178
+ failed: z.number().describe('Number of failed deliveries in this bucket'),
179
+ })
180
+ .describe('Single data point in webhook time series');
181
+
182
+ export type WebhookTimeSeriesPoint = z.infer<typeof WebhookTimeSeriesPointSchema>;
183
+
184
+ export const WebhookTimeSeriesDataSchema = z
185
+ .object({
186
+ period: WebhookTimePeriodSchema,
187
+ series: z.array(WebhookTimeSeriesPointSchema),
188
+ })
189
+ .describe('Webhook time series analytics data');
190
+
191
+ export type WebhookTimeSeriesData = z.infer<typeof WebhookTimeSeriesDataSchema>;
192
+
101
193
  // ============================================================================
102
194
  // API Options
103
195
  // ============================================================================