deepline 0.3.306 → 0.3.308

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 (104) hide show
  1. package/README.md +2 -1
  2. package/dist/bundling-sources/sdk/src/client.ts +270 -11
  3. package/dist/bundling-sources/sdk/src/compat.ts +27 -9
  4. package/dist/bundling-sources/sdk/src/index.ts +35 -0
  5. package/dist/bundling-sources/sdk/src/release.ts +1 -1
  6. package/dist/bundling-sources/shared_libs/play-runtime/batch-runtime.ts +4 -0
  7. package/dist/bundling-sources/shared_libs/play-runtime/batching-types.ts +3 -0
  8. package/dist/bundling-sources/shared_libs/play-runtime/context.ts +46 -16
  9. package/dist/bundling-sources/shared_libs/play-runtime/fullenrich-batching.ts +24 -1
  10. package/dist/bundling-sources/shared_libs/play-runtime/run-event-query.ts +872 -0
  11. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-shared.ts +10 -5
  12. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres.ts +4 -1
  13. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-event-search.ts +720 -19
  14. package/dist/cli/auth-LWNR7TQB.js +31 -0
  15. package/dist/cli/{auth-WTC7LCQ6.mjs → auth-ZWX4BF5K.mjs} +4 -4
  16. package/dist/cli/{billing-VQM65LVP.mjs → billing-CLV5QRFQ.mjs} +4 -4
  17. package/dist/cli/{billing-3DYTHO32.js → billing-H24FGAKE.js} +70 -70
  18. package/dist/cli/{chunk-WV64SNDV.mjs → chunk-3GIUCJAT.mjs} +1 -1
  19. package/dist/cli/{chunk-5V56XCQN.mjs → chunk-ABMGEW3C.mjs} +1 -1
  20. package/dist/cli/{chunk-UT3VLSLM.mjs → chunk-ASWZTTKT.mjs} +13 -5
  21. package/dist/cli/{chunk-FJ3DANI4.mjs → chunk-BPD7TR27.mjs} +1 -1
  22. package/dist/cli/{chunk-7XORS6WQ.mjs → chunk-CXU7ME5X.mjs} +2 -2
  23. package/dist/cli/{chunk-RODIZCGU.js → chunk-GBFIAI4L.js} +68 -60
  24. package/dist/cli/{chunk-OZIFVQRY.js → chunk-GONK2N7U.js} +1 -1
  25. package/dist/cli/{chunk-LLGF6V6I.mjs → chunk-HGI6RGHB.mjs} +653 -5
  26. package/dist/cli/{chunk-FZNO6B4W.js → chunk-IFIGQWHC.js} +6 -6
  27. package/dist/cli/{chunk-6CRUYVU7.mjs → chunk-JUAVGRD7.mjs} +4 -4
  28. package/dist/cli/{chunk-SZQPJW2L.js → chunk-O7NNH76S.js} +6 -6
  29. package/dist/cli/{chunk-2IKIG333.js → chunk-QJBJZVG6.js} +15 -4
  30. package/dist/cli/{chunk-M6HCRRV2.js → chunk-QTCFSRGC.js} +4 -4
  31. package/dist/cli/{chunk-W7ELTWDM.js → chunk-SRZ6XDX3.js} +26 -26
  32. package/dist/cli/{chunk-4PSSDZQB.mjs → chunk-T3P4NRZU.mjs} +1 -1
  33. package/dist/cli/{chunk-PHNAFCSB.js → chunk-TH3YBIJT.js} +852 -204
  34. package/dist/cli/{chunk-U37SW6IK.js → chunk-UGTYNMRX.js} +48 -48
  35. package/dist/cli/{chunk-2KR56MPF.js → chunk-WEZVPZPE.js} +2 -2
  36. package/dist/cli/{chunk-UABLQKTZ.mjs → chunk-WXLOW623.mjs} +11 -0
  37. package/dist/cli/{chunk-QDC4XTO3.mjs → chunk-XFYVL7EM.mjs} +2 -2
  38. package/dist/cli/{chunk-5HS5EEFE.mjs → chunk-XSDL3NHZ.mjs} +3 -3
  39. package/dist/cli/{chunk-MMNZ6GBT.js → chunk-YEJMVAD3.js} +6 -6
  40. package/dist/cli/{chunk-WWPI4GJI.mjs → chunk-YSZSERRZ.mjs} +1 -1
  41. package/dist/cli/{chunk-OF7QP2XX.js → chunk-ZHHF24O5.js} +15 -15
  42. package/dist/cli/client-3J76AMVE.js +20 -0
  43. package/dist/cli/{client-DCEK34D5.mjs → client-A5ILG5GT.mjs} +3 -3
  44. package/dist/cli/{csv-LXBF5UIV.mjs → csv-NZ7KAYEF.mjs} +3 -3
  45. package/dist/cli/{csv-H42JDJ6G.js → csv-VSSAAXFX.js} +12 -12
  46. package/dist/cli/{db-DP5GU4MU.js → db-ICLEBSNO.js} +20 -20
  47. package/dist/cli/{db-3O5CFLNP.mjs → db-YSAJUBLD.mjs} +4 -4
  48. package/dist/cli/{executions-QC2UZKIP.mjs → executions-MUJRAT5R.mjs} +4 -4
  49. package/dist/cli/{executions-5FFTPHEX.js → executions-XCAJB5TF.js} +6 -6
  50. package/dist/cli/{feedback-JCGE6JFW.mjs → feedback-6C7Y2SCJ.mjs} +3 -3
  51. package/dist/cli/{feedback-XAU3FI5S.js → feedback-DBZMBHZG.js} +6 -6
  52. package/dist/cli/git-2NSE4WIK.js +23 -0
  53. package/dist/cli/{git-S3Y3E2NL.mjs → git-EAH67ITA.mjs} +4 -4
  54. package/dist/cli/index.js +79 -77
  55. package/dist/cli/index.mjs +33 -31
  56. package/dist/cli/{legacy-noops-TXOUX4FX.js → legacy-noops-EJTOLTVO.js} +7 -7
  57. package/dist/cli/{legacy-noops-FQF2IRVS.mjs → legacy-noops-O5INQP6Q.mjs} +4 -4
  58. package/dist/cli/{monitors-4BQCO6B7.js → monitors-5OYO2DPJ.js} +85 -85
  59. package/dist/cli/{monitors-F3PELPSH.mjs → monitors-SBBBMUFY.mjs} +4 -4
  60. package/dist/cli/{org-VQM6OOLK.js → org-SJRPJG42.js} +60 -60
  61. package/dist/cli/{org-ER5J3ATG.mjs → org-Z7LTOL3H.mjs} +4 -4
  62. package/dist/cli/{play-B5H2RN4N.mjs → play-2JTIEOZM.mjs} +472 -55
  63. package/dist/cli/{play-WKZPAOW3.js → play-VQJ6ABZ2.js} +961 -544
  64. package/dist/cli/{providers-5TEA2FJG.js → providers-43MR5XOV.js} +7 -7
  65. package/dist/cli/{providers-6ODEAYC2.mjs → providers-MX4CV2EH.mjs} +4 -4
  66. package/dist/cli/{quickstart-GJYCNMFC.js → quickstart-A6W3HEE2.js} +5 -5
  67. package/dist/cli/{quickstart-BCYT7BCY.mjs → quickstart-Y4YLKRZ3.mjs} +3 -3
  68. package/dist/cli/{search-KKJ535OX.js → search-AMUFSAFP.js} +6 -6
  69. package/dist/cli/{search-RY547KOT.mjs → search-FRKDLJQG.mjs} +5 -5
  70. package/dist/cli/{secrets-U42ZEYIU.mjs → secrets-E4EX7O3J.mjs} +4 -4
  71. package/dist/cli/{secrets-RFGDXSEW.js → secrets-XITHKZW2.js} +13 -13
  72. package/dist/cli/{sessions-ZRTWHMIH.js → sessions-6HJFDIR2.js} +5 -5
  73. package/dist/cli/{sessions-YWEFB62D.mjs → sessions-6YXAUJII.mjs} +4 -4
  74. package/dist/cli/{settings-XUWFXIL7.js → settings-J5NRLCV2.js} +74 -74
  75. package/dist/cli/{settings-AS4U6NVP.mjs → settings-LBJSZIET.mjs} +7 -7
  76. package/dist/cli/{setup-2QPWP2FI.js → setup-2ES4CTO5.js} +41 -41
  77. package/dist/cli/{setup-2GUIV2EW.mjs → setup-75FENNVH.mjs} +6 -6
  78. package/dist/cli/skills-VLTAVA4K.js +28 -0
  79. package/dist/cli/{skills-5QS7ERVF.mjs → skills-XGZDUXW6.mjs} +4 -4
  80. package/dist/cli/{tools-PVWSGCZD.mjs → tools-2LEKXOGY.mjs} +58 -42
  81. package/dist/cli/{tools-J47LIO2H.js → tools-ZOQOGDKI.js} +111 -95
  82. package/dist/cli/update-5CZOVYQT.js +28 -0
  83. package/dist/cli/{update-KS4TRSZI.mjs → update-SNK3IN3I.mjs} +6 -6
  84. package/dist/cli/{workflow-QCLR7GVC.mjs → workflow-G7ZM3MAC.mjs} +5 -5
  85. package/dist/cli/{workflow-HQRUUTTL.js → workflow-W3AYTB3J.js} +15 -15
  86. package/dist/{client-Dj8MAkDc.d.mts → client-DpnkGBn-.d.mts} +324 -11
  87. package/dist/{client-DsrFGXJf.d.ts → client-ypdBvf1o.d.ts} +324 -11
  88. package/dist/index.d.mts +2 -2
  89. package/dist/index.d.ts +2 -2
  90. package/dist/index.js +8 -6
  91. package/dist/index.mjs +5 -3
  92. package/dist/install-integrity.json +73 -72
  93. package/dist/plays/run-request.d.mts +1 -1
  94. package/dist/plays/run-request.d.ts +1 -1
  95. package/dist/release.d.mts +1 -1
  96. package/dist/release.d.ts +1 -1
  97. package/dist/release.js +2 -2
  98. package/dist/release.mjs +1 -1
  99. package/package.json +1 -1
  100. package/dist/cli/auth-ZT3UF33R.js +0 -31
  101. package/dist/cli/client-UQXVU2T6.js +0 -20
  102. package/dist/cli/git-CNO2UFZE.js +0 -23
  103. package/dist/cli/skills-4E5KYW2Y.js +0 -28
  104. package/dist/cli/update-GZKGOMKD.js +0 -28
@@ -0,0 +1,872 @@
1
+ import { RUN_LOG_LEVELS, type RunLogLevel } from './run-event-catalog';
2
+
3
+ /** Public criteria shared by the API, SDK and CLI; no database/runtime imports. */
4
+ export type RunEventScalar = string | number | boolean | null;
5
+ export type RunEventField =
6
+ | keyof typeof RUN_EVENT_QUERY_FIELD_CATALOG
7
+ | `context.${string}`;
8
+ export type RunEventFieldOperators<T extends RunEventScalar = RunEventScalar> =
9
+ {
10
+ eq?: T;
11
+ ne?: T;
12
+ gt?: Exclude<T, boolean | null>;
13
+ gte?: Exclude<T, boolean | null>;
14
+ lt?: Exclude<T, boolean | null>;
15
+ lte?: Exclude<T, boolean | null>;
16
+ exists?: boolean;
17
+ in?: readonly T[];
18
+ };
19
+ type Filter<T extends RunEventScalar> = T | RunEventFieldOperators<T>;
20
+ export type RunEventWhere = {
21
+ eventId?: Filter<string>;
22
+ orgId?: Filter<string>;
23
+ runtimeScope?: Filter<string>;
24
+ runId?: Filter<string>;
25
+ playName?: Filter<string>;
26
+ occurredAt?: Filter<string>;
27
+ kind?: Filter<string>;
28
+ source?: Filter<string | null>;
29
+ status?: Filter<string | null>;
30
+ level?: Filter<RunLogLevel | null>;
31
+ stepId?: Filter<string | null>;
32
+ stepKind?: Filter<string | null>;
33
+ datasetId?: Filter<string | null>;
34
+ phase?: Filter<string | null>;
35
+ provider?: Filter<string | null>;
36
+ operation?: Filter<string | null>;
37
+ producerAttempt?: Filter<number | null>;
38
+ context?: Readonly<Record<string, Filter<RunEventScalar>>>;
39
+ and?: readonly RunEventWhere[];
40
+ or?: readonly RunEventWhere[];
41
+ } & { [K in `context.${string}`]?: Filter<RunEventScalar> };
42
+ export type RunEventQueryCriteria = {
43
+ since?: string | number;
44
+ until?: string | number;
45
+ where?: RunEventWhere | string;
46
+ runId?: string;
47
+ stepId?: string;
48
+ minLevel?: RunLogLevel;
49
+ text?: string;
50
+ playName?: string;
51
+ kind?: string;
52
+ };
53
+ export type NormalizedRunEventQuery = Omit<
54
+ RunEventQueryCriteria,
55
+ 'since' | 'until' | 'where'
56
+ > & {
57
+ since: string;
58
+ until: string;
59
+ where?: RunEventWhere;
60
+ };
61
+ export type RunEventQueryOptions = {
62
+ limit?: number;
63
+ cursor?: string;
64
+ includePayloads?: boolean;
65
+ };
66
+ export type RunEventMeasure =
67
+ | { op: 'count'; as?: string }
68
+ | { op: 'sum'; field: RunEventField; as?: string };
69
+ export type RunEventSummaryOptions = {
70
+ groupBy: readonly RunEventField[];
71
+ measures?: readonly RunEventMeasure[];
72
+ limit?: number;
73
+ cursor?: string;
74
+ };
75
+ type RequestBase = RunEventQueryCriteria & {
76
+ resource: 'logs' | 'events';
77
+ runId?: string;
78
+ };
79
+ export type RunEventOperationRequest =
80
+ | (RequestBase & RunEventQueryOptions & { operation: 'list' })
81
+ | (RequestBase & { operation: 'count' })
82
+ | (RequestBase & RunEventSummaryOptions & { operation: 'summarize' })
83
+ | { operation: 'fields'; resource: 'logs' | 'events'; runId?: string };
84
+ export type RunEventQueryEvent = {
85
+ eventId: string;
86
+ occurredAt: string;
87
+ ingestedAt: string;
88
+ runId: string;
89
+ playName: string;
90
+ kind: string;
91
+ level: RunLogLevel | null;
92
+ message: string | null;
93
+ source?: string;
94
+ stepId?: string;
95
+ status?: string;
96
+ stepKind?: string;
97
+ datasetId?: string;
98
+ phase?: string;
99
+ provider?: string;
100
+ operation?: string;
101
+ producerAttempt?: number;
102
+ context?: Record<string, RunEventScalar>;
103
+ eventPayload?: Record<string, unknown>;
104
+ payload?: unknown;
105
+ };
106
+ export type RunEventQueryPage = {
107
+ returnedCount: number;
108
+ hasMore: boolean;
109
+ nextCursor: string | null;
110
+ };
111
+ export type RunEventQueryRetention = {
112
+ retainedSince: string;
113
+ retentionDays: 7;
114
+ };
115
+ type ResultBase = {
116
+ query: NormalizedRunEventQuery;
117
+ retention: RunEventQueryRetention;
118
+ };
119
+ export type RunEventLogsResult = ResultBase & {
120
+ logs: RunEventQueryEvent[];
121
+ page: RunEventQueryPage;
122
+ };
123
+ export type RunEventEventsResult = ResultBase & {
124
+ events: RunEventQueryEvent[];
125
+ page: RunEventQueryPage;
126
+ };
127
+ export type RunEventListResult = RunEventLogsResult | RunEventEventsResult;
128
+ export type RunEventCountResult = ResultBase & {
129
+ matchedCount: number;
130
+ exact: true;
131
+ };
132
+ export type RunEventGroupValue = RunEventScalar | { missing: true };
133
+ export type RunEventSummaryResult = ResultBase & {
134
+ groupBy: readonly RunEventField[];
135
+ measures: readonly RunEventMeasure[];
136
+ groups: Array<{
137
+ keys: Record<string, RunEventGroupValue>;
138
+ measures: Record<string, number>;
139
+ }>;
140
+ page: RunEventQueryPage;
141
+ };
142
+ export type RunEventFieldDescriptor = {
143
+ field: RunEventField;
144
+ type: 'string' | 'number' | 'timestamp' | 'level';
145
+ nullable: boolean;
146
+ operators: readonly string[];
147
+ groupable: boolean;
148
+ summable: boolean;
149
+ };
150
+ export type RunEventFieldsResult = {
151
+ fields: readonly RunEventFieldDescriptor[];
152
+ context: {
153
+ field: 'context.<key>';
154
+ type: 'scalar';
155
+ operators: readonly string[];
156
+ groupable: true;
157
+ summable: true;
158
+ missing: 'distinct_from_null';
159
+ };
160
+ };
161
+ export type RunEventOperationResult =
162
+ | RunEventListResult
163
+ | RunEventCountResult
164
+ | RunEventSummaryResult
165
+ | RunEventFieldsResult;
166
+
167
+ const stringField = { type: 'string', nullable: false } as const;
168
+ const optionalString = { type: 'string', nullable: true } as const;
169
+ /** Supported fields are declared, never inferred from rows or raw payloads. */
170
+ export const RUN_EVENT_QUERY_FIELD_CATALOG = {
171
+ eventId: stringField,
172
+ orgId: stringField,
173
+ runtimeScope: stringField,
174
+ runId: stringField,
175
+ playName: stringField,
176
+ kind: stringField,
177
+ source: optionalString,
178
+ occurredAt: { type: 'timestamp', nullable: false },
179
+ status: optionalString,
180
+ level: { type: 'level', nullable: true },
181
+ stepId: optionalString,
182
+ stepKind: optionalString,
183
+ datasetId: optionalString,
184
+ phase: optionalString,
185
+ provider: optionalString,
186
+ operation: optionalString,
187
+ producerAttempt: { type: 'number', nullable: true },
188
+ } as const;
189
+ export const RUN_EVENT_QUERY_RETENTION_MS = 7 * 86400000;
190
+ export const RUN_EVENT_QUERY_MAX_LIMIT = 200;
191
+ export const RUN_EVENT_QUERY_MAX_PAYLOAD_LIMIT = 10;
192
+ // A frozen query may expand JSON-escaped text and identities by six bytes per
193
+ // character. Base64 adds another third, followed by a retained group key.
194
+ // The encoder also admits the complete continuation against the request bound.
195
+ export const RUN_EVENT_QUERY_MAX_CURSOR_BYTES = 48 * 1024;
196
+ export const RUN_EVENT_QUERY_MAX_REQUEST_BYTES = 64 * 1024;
197
+ const MAX_BYTES = 8192;
198
+ const MAX_DEPTH = 8;
199
+ const MAX_PREDICATES = 64;
200
+ const OPERATORS = [
201
+ 'eq',
202
+ 'ne',
203
+ 'gt',
204
+ 'gte',
205
+ 'lt',
206
+ 'lte',
207
+ 'exists',
208
+ 'in',
209
+ ] as const;
210
+ type Operator = (typeof OPERATORS)[number];
211
+ /** SQL adapters consume this admitted tree, never the caller's object. */
212
+ export type RunEventPredicate =
213
+ | { and: readonly RunEventPredicate[] }
214
+ | { or: readonly RunEventPredicate[] }
215
+ | {
216
+ field: RunEventField;
217
+ op: Exclude<Operator, 'exists' | 'in'>;
218
+ value: RunEventScalar;
219
+ }
220
+ | { field: RunEventField; op: 'exists'; value: boolean }
221
+ | { field: RunEventField; op: 'in'; value: readonly RunEventScalar[] };
222
+
223
+ export class RunEventQueryInputError extends TypeError {
224
+ constructor(
225
+ message: string,
226
+ readonly code = 'invalid_run_event_query',
227
+ ) {
228
+ super(message);
229
+ this.name = 'RunEventQueryInputError';
230
+ }
231
+ }
232
+ export class RunEventQueryDataError extends Error {
233
+ constructor(
234
+ message: string,
235
+ readonly code = 'invalid_run_event_data',
236
+ ) {
237
+ super(message);
238
+ this.name = 'RunEventQueryDataError';
239
+ }
240
+ }
241
+ function fail(message: string): never {
242
+ throw new RunEventQueryInputError(message);
243
+ }
244
+ function object(value: unknown): value is Record<string, unknown> {
245
+ return (
246
+ value !== null &&
247
+ typeof value === 'object' &&
248
+ !Array.isArray(value) &&
249
+ (Object.getPrototypeOf(value) === Object.prototype ||
250
+ Object.getPrototypeOf(value) === null)
251
+ );
252
+ }
253
+ function bounded(value: unknown, maxBytes = MAX_BYTES): void {
254
+ let encoded: string | undefined;
255
+ try {
256
+ encoded = JSON.stringify(value);
257
+ } catch {
258
+ fail('Run event query must be bounded JSON without cycles.');
259
+ }
260
+ if (!encoded || new TextEncoder().encode(encoded).length > maxBytes)
261
+ fail(`Run event query exceeds ${maxBytes} bytes.`);
262
+ }
263
+ export function validateRunEventField(value: unknown): RunEventField {
264
+ if (typeof value !== 'string') fail('Run event field must be a string.');
265
+ const field = value.startsWith('$.') ? value.slice(2) : value;
266
+ if (
267
+ Object.prototype.hasOwnProperty.call(RUN_EVENT_QUERY_FIELD_CATALOG, field)
268
+ )
269
+ return field as RunEventField;
270
+ if (
271
+ /^context\.[A-Za-z_][A-Za-z0-9_]{0,63}$/.test(field) &&
272
+ !['__proto__', 'prototype', 'constructor'].includes(field.slice(8))
273
+ )
274
+ return field as RunEventField;
275
+ const alternatives: Record<string, string> = {
276
+ step: 'stepId',
277
+ severity: 'level',
278
+ timestamp: 'occurredAt',
279
+ play: 'playName',
280
+ run: 'runId',
281
+ attempt: 'producerAttempt',
282
+ };
283
+ fail(
284
+ `Unsupported run event field: ${field}. Use ${alternatives[field] ?? 'a catalog field or context.<key>'}; call fields() for supported fields.`,
285
+ );
286
+ }
287
+ function scalar(
288
+ field: RunEventField,
289
+ value: unknown,
290
+ op: Operator,
291
+ ): RunEventScalar {
292
+ if (
293
+ value !== null &&
294
+ typeof value !== 'string' &&
295
+ typeof value !== 'number' &&
296
+ typeof value !== 'boolean'
297
+ )
298
+ fail(`Run event ${field} requires a JSON scalar.`);
299
+ if (typeof value === 'number' && !Number.isFinite(value))
300
+ fail(`Run event ${field} requires a finite number.`);
301
+ if (
302
+ typeof value === 'string' &&
303
+ new TextEncoder().encode(value).length > 1024
304
+ )
305
+ fail(`Run event ${field} value exceeds 1024 bytes.`);
306
+ const descriptor =
307
+ RUN_EVENT_QUERY_FIELD_CATALOG[
308
+ field as keyof typeof RUN_EVENT_QUERY_FIELD_CATALOG
309
+ ];
310
+ if (descriptor) {
311
+ if (value === null) {
312
+ if (!descriptor.nullable) fail(`Run event ${field} cannot be null.`);
313
+ } else if (descriptor.type === 'number') {
314
+ if (
315
+ typeof value !== 'number' ||
316
+ !Number.isSafeInteger(value) ||
317
+ value < 1
318
+ )
319
+ fail(`Run event ${field} requires a positive integer.`);
320
+ } else if (typeof value !== 'string')
321
+ fail(`Run event ${field} requires a string.`);
322
+ else if (
323
+ descriptor.type === 'level' &&
324
+ !RUN_LOG_LEVELS.includes(value as RunLogLevel)
325
+ )
326
+ fail('Run event level requires debug, info, warn or error.');
327
+ else if (descriptor.type === 'timestamp') {
328
+ value = normalizeRunEventQueryTimestamp(value);
329
+ }
330
+ }
331
+ if (
332
+ ['gt', 'gte', 'lt', 'lte'].includes(op) &&
333
+ (value === null || typeof value === 'boolean')
334
+ )
335
+ fail(`Run event ${op} requires a string or number.`);
336
+ return value as RunEventScalar;
337
+ }
338
+ function comparison(
339
+ field: RunEventField,
340
+ op: string,
341
+ value: unknown,
342
+ ): RunEventPredicate {
343
+ if (!OPERATORS.includes(op as Operator))
344
+ fail(`Unsupported run event operator: ${op}. Use ${OPERATORS.join(', ')}.`);
345
+ if (op === 'exists') {
346
+ if (typeof value !== 'boolean')
347
+ fail('Run event exists requires a boolean.');
348
+ return { field, op, value };
349
+ }
350
+ if (op === 'in') {
351
+ if (!Array.isArray(value) || value.length < 1 || value.length > 32)
352
+ fail('Run event in requires between 1 and 32 scalars.');
353
+ return { field, op, value: value.map((item) => scalar(field, item, 'eq')) };
354
+ }
355
+ return {
356
+ field,
357
+ op: op as Exclude<Operator, 'exists' | 'in'>,
358
+ value: scalar(field, value, op as Operator),
359
+ };
360
+ }
361
+ function admitObject(
362
+ input: unknown,
363
+ depth = 0,
364
+ budget = { count: 0 },
365
+ ): RunEventPredicate {
366
+ if (depth > MAX_DEPTH) fail('Run event where exceeds depth 8.');
367
+ if (!object(input) || !Object.keys(input).length)
368
+ fail('Run event where requires a nonempty object.');
369
+ const terms: RunEventPredicate[] = [];
370
+ const fieldTerms = (raw: string, filter: unknown) => {
371
+ const field = validateRunEventField(raw);
372
+ const operators = object(filter)
373
+ ? Object.entries(filter)
374
+ : [['eq', filter] as const];
375
+ if (!operators.length)
376
+ fail(`Run event ${field} has an empty operator object.`);
377
+ for (const [op, value] of operators) {
378
+ if (++budget.count > MAX_PREDICATES)
379
+ fail('Run event where exceeds 64 predicates.');
380
+ terms.push(comparison(field, op, value));
381
+ }
382
+ };
383
+ for (const [key, filter] of Object.entries(input)) {
384
+ if (key === 'and' || key === 'or') {
385
+ if (
386
+ !Array.isArray(filter) ||
387
+ filter.length < 1 ||
388
+ filter.length > MAX_PREDICATES
389
+ )
390
+ fail(`Run event ${key} requires a nonempty bounded array.`);
391
+ const children = filter.map((item) =>
392
+ admitObject(item, depth + 1, budget),
393
+ );
394
+ terms.push(key === 'and' ? { and: children } : { or: children });
395
+ } else if (key === 'context') {
396
+ if (!object(filter) || !Object.keys(filter).length)
397
+ fail('Run event context requires flat scalar fields.');
398
+ for (const [contextKey, value] of Object.entries(filter))
399
+ fieldTerms(`context.${contextKey}`, value);
400
+ } else fieldTerms(key, filter);
401
+ }
402
+ return terms.length === 1 ? terms[0]! : { and: terms };
403
+ }
404
+ function compact(predicate: RunEventPredicate): RunEventWhere {
405
+ if ('and' in predicate) {
406
+ const merged: Record<string, unknown> = {};
407
+ for (const term of predicate.and) {
408
+ const child = compact(term);
409
+ for (const [key, value] of Object.entries(child)) {
410
+ if (key === 'and') return { and: predicate.and.map(compact) };
411
+ if (key === 'context') {
412
+ const context = (merged.context ?? {}) as Record<string, unknown>;
413
+ for (const [name, filter] of Object.entries(value as object)) {
414
+ if (Object.prototype.hasOwnProperty.call(context, name)) {
415
+ const combined = combineFilters(context[name], filter);
416
+ if (!combined) return { and: predicate.and.map(compact) };
417
+ context[name] = combined;
418
+ } else context[name] = filter;
419
+ }
420
+ merged.context = context;
421
+ } else if (Object.prototype.hasOwnProperty.call(merged, key)) {
422
+ const combined =
423
+ key === 'or' ? undefined : combineFilters(merged[key], value);
424
+ if (!combined) return { and: predicate.and.map(compact) };
425
+ merged[key] = combined;
426
+ } else merged[key] = value;
427
+ }
428
+ }
429
+ return merged as RunEventWhere;
430
+ }
431
+ if ('or' in predicate) return { or: predicate.or.map(compact) };
432
+ const filter =
433
+ predicate.op === 'eq'
434
+ ? predicate.value
435
+ : { [predicate.op]: predicate.value };
436
+ if (predicate.field.startsWith('context.'))
437
+ return { context: { [predicate.field.slice(8)]: filter } } as RunEventWhere;
438
+ return { [predicate.field]: filter } as RunEventWhere;
439
+ }
440
+ function combineFilters(
441
+ left: unknown,
442
+ right: unknown,
443
+ ): Record<string, unknown> | undefined {
444
+ const first = object(left) ? left : { eq: left };
445
+ const second = object(right) ? right : { eq: right };
446
+ if (
447
+ Object.keys(first).some((key) =>
448
+ Object.prototype.hasOwnProperty.call(second, key),
449
+ )
450
+ )
451
+ return undefined;
452
+ return { ...first, ...second };
453
+ }
454
+ function canonical(predicate: RunEventPredicate): RunEventPredicate {
455
+ if ('field' in predicate) return predicate;
456
+ const operator = 'and' in predicate ? 'and' : 'or';
457
+ const source = 'and' in predicate ? predicate.and : predicate.or;
458
+ const terms = source.map(canonical).flatMap((term) => {
459
+ if (operator === 'and' && 'and' in term) return term.and;
460
+ if (operator === 'or' && 'or' in term) return term.or;
461
+ return [term];
462
+ });
463
+ terms.sort((a, b) => JSON.stringify(a).localeCompare(JSON.stringify(b)));
464
+ const unique = terms.filter(
465
+ (term, i) =>
466
+ i === 0 || JSON.stringify(term) !== JSON.stringify(terms[i - 1]),
467
+ );
468
+ if (unique.length === 1) return unique[0]!;
469
+ return operator === 'and' ? { and: unique } : { or: unique };
470
+ }
471
+ /** Small grammar: fields, JSON scalars, comparisons, in/exists, &&/||, parentheses. */
472
+ function parseDsl(input: string): RunEventPredicate {
473
+ let position = 0;
474
+ let count = 0;
475
+ const skip = () => {
476
+ while (/\s/.test(input[position] ?? '') && position < input.length)
477
+ position++;
478
+ };
479
+ const take = (token: string) => {
480
+ skip();
481
+ if (input.startsWith(token, position)) {
482
+ position += token.length;
483
+ return true;
484
+ }
485
+ return false;
486
+ };
487
+ const json = (): unknown => {
488
+ skip();
489
+ const start = position;
490
+ if (input[position] === '"') {
491
+ position++;
492
+ let closed = false;
493
+ while (position < input.length) {
494
+ const char = input[position++]!;
495
+ if (char === '\\') position++;
496
+ else if (char === '"') {
497
+ closed = true;
498
+ break;
499
+ }
500
+ }
501
+ if (!closed) fail('Run event DSL has an unterminated JSON string.');
502
+ } else {
503
+ const match =
504
+ /^(?:true|false|null|-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?)/.exec(
505
+ input.slice(position),
506
+ );
507
+ if (!match)
508
+ fail('Run event DSL requires a JSON quoted string or scalar.');
509
+ position += match[0].length;
510
+ }
511
+ try {
512
+ return JSON.parse(input.slice(start, position));
513
+ } catch {
514
+ fail('Run event DSL requires valid JSON scalars.');
515
+ }
516
+ };
517
+ const term = (depth: number): RunEventPredicate => {
518
+ if (depth > MAX_DEPTH) fail('Run event where exceeds depth 8.');
519
+ if (take('(')) {
520
+ const nested = or(depth + 1);
521
+ if (!take(')')) fail('Run event DSL is missing a closing parenthesis.');
522
+ return nested;
523
+ }
524
+ skip();
525
+ const match = /^(?:\$\.)?[A-Za-z_][A-Za-z0-9_.]*/.exec(
526
+ input.slice(position),
527
+ );
528
+ if (!match) fail('Run event DSL requires a supported field.');
529
+ position += match[0].length;
530
+ const field = validateRunEventField(match[0]);
531
+ if (++count > MAX_PREDICATES)
532
+ fail('Run event where exceeds 64 predicates.');
533
+ skip();
534
+ const operation =
535
+ /^(==|!=|>=|<=|>|<|\b(?:eq|ne|gte|gt|lte|lt|in|exists)\b)/.exec(
536
+ input.slice(position),
537
+ );
538
+ if (!operation)
539
+ fail('Run event DSL requires a supported comparison operator.');
540
+ position += operation[0].length;
541
+ const symbols: Record<string, string> = {
542
+ '==': 'eq',
543
+ '!=': 'ne',
544
+ '>=': 'gte',
545
+ '<=': 'lte',
546
+ '>': 'gt',
547
+ '<': 'lt',
548
+ };
549
+ const op = symbols[operation[0]] ?? operation[0];
550
+ if (op === 'in') {
551
+ if (!take('[')) fail('Run event DSL in requires a JSON scalar array.');
552
+ const values: unknown[] = [];
553
+ if (!take(']')) {
554
+ do {
555
+ values.push(json());
556
+ if (values.length > 32)
557
+ fail('Run event in requires between 1 and 32 scalars.');
558
+ } while (take(','));
559
+ if (!take(']')) fail('Run event DSL in requires a closing bracket.');
560
+ }
561
+ return comparison(field, op, values);
562
+ }
563
+ return comparison(field, op, json());
564
+ };
565
+ const and = (depth: number): RunEventPredicate => {
566
+ const terms = [term(depth)];
567
+ while (take('&&')) terms.push(term(depth));
568
+ return terms.length === 1 ? terms[0]! : { and: terms };
569
+ };
570
+ const or = (depth: number): RunEventPredicate => {
571
+ const terms = [and(depth)];
572
+ while (take('||')) terms.push(and(depth));
573
+ return terms.length === 1 ? terms[0]! : { or: terms };
574
+ };
575
+ const result = or(0);
576
+ skip();
577
+ if (position !== input.length)
578
+ fail(`Run event DSL has unsupported input at position ${position}.`);
579
+ return result;
580
+ }
581
+ export function parseRunEventWhere(input: unknown): RunEventWhere {
582
+ bounded(input);
583
+ const where = compact(
584
+ canonical(typeof input === 'string' ? parseDsl(input) : admitObject(input)),
585
+ );
586
+ bounded(where);
587
+ return where;
588
+ }
589
+ export function getRunEventQueryPredicate(
590
+ where: RunEventWhere,
591
+ ): RunEventPredicate {
592
+ bounded(where);
593
+ return canonical(admitObject(where));
594
+ }
595
+ export function normalizeRunEventQueryTimestamp(
596
+ value: string | number,
597
+ ): string {
598
+ let extraMicros = '';
599
+ if (typeof value === 'string') {
600
+ const timestamp =
601
+ /^[+-]?\d{4,6}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.(\d{1,6}))?(?:Z|[+-]\d{2}:\d{2})$/.exec(
602
+ value,
603
+ );
604
+ if (!timestamp)
605
+ fail(
606
+ 'Run event time bounds require an ISO timestamp with a timezone and at most six fractional digits.',
607
+ );
608
+ extraMicros = (timestamp[1] ?? '').padEnd(6, '0').slice(3);
609
+ }
610
+ const date = new Date(value);
611
+ if (!Number.isFinite(date.getTime()))
612
+ fail('Run event time bound is invalid.');
613
+ const canonical = date.toISOString();
614
+ return /[1-9]/.test(extraMicros)
615
+ ? canonical.replace(/Z$/, `${extraMicros}Z`)
616
+ : canonical;
617
+ }
618
+ function timestampMicros(value: string): bigint {
619
+ const fraction = /\.(\d+)Z$/.exec(value)?.[1] ?? '';
620
+ return (
621
+ BigInt(Date.parse(value)) * BigInt(1000) +
622
+ BigInt(fraction.padEnd(6, '0').slice(3))
623
+ );
624
+ }
625
+ function time(value: unknown, nowMs: number, fallback: number): string {
626
+ if (value === undefined) value = fallback;
627
+ let timestamp: number;
628
+ if (typeof value === 'number') timestamp = value;
629
+ else if (typeof value === 'string') {
630
+ const relative = /^(\d+(?:\.\d+)?)(ms|s|m|h|d)$/.exec(value);
631
+ if (relative) {
632
+ const units: Record<string, number> = {
633
+ ms: 1,
634
+ s: 1000,
635
+ m: 60000,
636
+ h: 3600000,
637
+ d: 86400000,
638
+ };
639
+ timestamp = nowMs - Number(relative[1]) * units[relative[2]]!;
640
+ } else {
641
+ return normalizeRunEventQueryTimestamp(value);
642
+ }
643
+ } else fail('Run event time bounds require a timestamp or duration.');
644
+ return normalizeRunEventQueryTimestamp(timestamp);
645
+ }
646
+ function exact(value: unknown, name: string): string {
647
+ if (typeof value !== 'string' || !value.trim() || value.length > 256)
648
+ fail(
649
+ `Run event ${name} requires a nonempty string of at most 256 characters.`,
650
+ );
651
+ return value;
652
+ }
653
+ const CRITERIA_KEYS = [
654
+ 'since',
655
+ 'until',
656
+ 'where',
657
+ 'runId',
658
+ 'stepId',
659
+ 'minLevel',
660
+ 'text',
661
+ 'playName',
662
+ 'kind',
663
+ ];
664
+ export function normalizeRunEventQuery(
665
+ criteria: RunEventQueryCriteria,
666
+ nowMs = Date.now(),
667
+ ): NormalizedRunEventQuery {
668
+ if (!object(criteria)) fail('Run event criteria requires an object.');
669
+ bounded(criteria, RUN_EVENT_QUERY_MAX_REQUEST_BYTES);
670
+ for (const key of Object.keys(criteria)) {
671
+ if (!CRITERIA_KEYS.includes(key))
672
+ fail(`Unsupported run event criterion: ${key}.`);
673
+ }
674
+ const until = time(criteria.until, nowMs, nowMs);
675
+ const since = time(criteria.since, nowMs, Date.parse(until) - 3600000);
676
+ if (
677
+ timestampMicros(since) >= timestampMicros(until) ||
678
+ timestampMicros(until) - timestampMicros(since) >
679
+ BigInt(RUN_EVENT_QUERY_RETENTION_MS) * BigInt(1000)
680
+ )
681
+ fail('Run event query requires a window of at most seven days.');
682
+ const query: NormalizedRunEventQuery = { since, until };
683
+ if (criteria.where !== undefined)
684
+ query.where = parseRunEventWhere(criteria.where);
685
+ for (const name of ['runId', 'stepId', 'playName', 'kind'] as const) {
686
+ if (criteria[name] !== undefined) query[name] = exact(criteria[name], name);
687
+ }
688
+ if (criteria.minLevel !== undefined) {
689
+ if (!RUN_LOG_LEVELS.includes(criteria.minLevel))
690
+ fail('Run event minLevel requires debug, info, warn or error.');
691
+ query.minLevel = criteria.minLevel;
692
+ }
693
+ if (criteria.text !== undefined) {
694
+ if (
695
+ typeof criteria.text !== 'string' ||
696
+ !criteria.text.length ||
697
+ new TextEncoder().encode(criteria.text).length > 1024
698
+ )
699
+ fail('Run event text requires between 1 and 1024 bytes.');
700
+ query.text = criteria.text;
701
+ }
702
+ return query;
703
+ }
704
+ export function normalizeRunEventOperationRequest(
705
+ input: unknown,
706
+ nowMs = Date.now(),
707
+ ): RunEventOperationRequest {
708
+ bounded(input, RUN_EVENT_QUERY_MAX_REQUEST_BYTES);
709
+ if (!object(input)) fail('Run event operation requires an object.');
710
+ const operation = input.operation;
711
+ if (!['list', 'count', 'summarize', 'fields'].includes(String(operation)))
712
+ fail('Run event operation requires list, count, summarize or fields.');
713
+ if (input.resource !== 'logs' && input.resource !== 'events')
714
+ fail('Run event resource requires logs or events.');
715
+ const allowed = [
716
+ 'operation',
717
+ 'resource',
718
+ 'runId',
719
+ ...(operation === 'fields' ? [] : CRITERIA_KEYS),
720
+ ...(operation === 'list'
721
+ ? ['limit', 'cursor', 'includePayloads']
722
+ : operation === 'summarize'
723
+ ? ['groupBy', 'measures', 'limit', 'cursor']
724
+ : []),
725
+ ];
726
+ for (const key of Object.keys(input)) {
727
+ if (!allowed.includes(key))
728
+ fail(`Run event ${operation} does not support ${key}.`);
729
+ }
730
+ const base = {
731
+ operation,
732
+ resource: input.resource,
733
+ ...(input.runId === undefined
734
+ ? {}
735
+ : { runId: exact(input.runId, 'runId') }),
736
+ };
737
+ if (operation === 'fields') return base as RunEventOperationRequest;
738
+ if (input.resource === 'logs' && !base.runId)
739
+ fail('Run event logs require runId.');
740
+ if (
741
+ input.cursor !== undefined &&
742
+ (typeof input.cursor !== 'string' ||
743
+ !input.cursor.length ||
744
+ input.cursor.length > RUN_EVENT_QUERY_MAX_CURSOR_BYTES)
745
+ )
746
+ fail('Run event cursor is invalid.');
747
+ // A continuation carries its frozen query. Do not insert a fresh time window.
748
+ const rawCriteria = Object.fromEntries(
749
+ Object.entries(input).filter(
750
+ ([key]) =>
751
+ CRITERIA_KEYS.includes(key) &&
752
+ (key !== 'runId' || input.resource === 'events'),
753
+ ),
754
+ ) as RunEventQueryCriteria;
755
+ const query = normalizeRunEventQuery(
756
+ input.cursor === undefined
757
+ ? rawCriteria
758
+ : { ...rawCriteria, since: nowMs - 3600000, until: nowMs },
759
+ nowMs,
760
+ );
761
+ const criteria: RunEventQueryCriteria = { ...query };
762
+ if (input.cursor !== undefined) {
763
+ if (input.since === undefined) delete criteria.since;
764
+ else criteria.since = time(input.since, nowMs, nowMs - 3600000);
765
+ if (input.until === undefined) delete criteria.until;
766
+ else criteria.until = time(input.until, nowMs, nowMs);
767
+ }
768
+ if (operation === 'count')
769
+ return { ...base, ...criteria } as RunEventOperationRequest;
770
+ const limit = input.limit ?? (input.includePayloads === true ? 10 : 50);
771
+ if (
772
+ !Number.isSafeInteger(limit) ||
773
+ (limit as number) < 1 ||
774
+ (limit as number) > RUN_EVENT_QUERY_MAX_LIMIT
775
+ )
776
+ fail('Run event limit must be between 1 and 200.');
777
+ const page = {
778
+ limit: limit as number,
779
+ ...(input.cursor === undefined ? {} : { cursor: input.cursor as string }),
780
+ };
781
+ if (operation === 'list') {
782
+ if (
783
+ input.includePayloads !== undefined &&
784
+ typeof input.includePayloads !== 'boolean'
785
+ )
786
+ fail('Run event includePayloads requires a boolean.');
787
+ if (
788
+ input.includePayloads === true &&
789
+ (limit as number) > RUN_EVENT_QUERY_MAX_PAYLOAD_LIMIT
790
+ )
791
+ fail('Run event payload pages cannot exceed 10 rows.');
792
+ return {
793
+ ...base,
794
+ ...criteria,
795
+ ...page,
796
+ ...(input.includePayloads === undefined
797
+ ? {}
798
+ : { includePayloads: input.includePayloads }),
799
+ } as RunEventOperationRequest;
800
+ }
801
+ if (!Array.isArray(input.groupBy) || input.groupBy.length > 4)
802
+ fail('Run event groupBy requires at most four fields.');
803
+ const groupBy = input.groupBy.map(validateRunEventField);
804
+ if (new Set(groupBy).size !== groupBy.length)
805
+ fail('Run event groupBy fields must be distinct.');
806
+ const rawMeasures = input.measures ?? [{ op: 'count' }];
807
+ if (
808
+ !Array.isArray(rawMeasures) ||
809
+ rawMeasures.length < 1 ||
810
+ rawMeasures.length > 8
811
+ )
812
+ fail('Run event measures require between 1 and 8 entries.');
813
+ const names = new Set<string>();
814
+ const measures: RunEventMeasure[] = rawMeasures.map((measure) => {
815
+ if (!object(measure) || (measure.op !== 'count' && measure.op !== 'sum'))
816
+ fail('Run event measures support count and sum.');
817
+ for (const key of Object.keys(measure)) {
818
+ if (
819
+ !['op', 'as', ...(measure.op === 'sum' ? ['field'] : [])].includes(key)
820
+ )
821
+ fail(`Unsupported run event measure option: ${key}.`);
822
+ }
823
+ const field =
824
+ measure.op === 'sum' ? validateRunEventField(measure.field) : undefined;
825
+ if (
826
+ field &&
827
+ !field.startsWith('context.') &&
828
+ RUN_EVENT_QUERY_FIELD_CATALOG[
829
+ field as keyof typeof RUN_EVENT_QUERY_FIELD_CATALOG
830
+ ].type !== 'number'
831
+ )
832
+ fail(`Run event sum requires a numeric field: ${field}.`);
833
+ const name =
834
+ measure.as ?? (field ? `sum_${field.replaceAll('.', '_')}` : 'count');
835
+ if (
836
+ typeof name !== 'string' ||
837
+ !/^[A-Za-z_][A-Za-z0-9_]{0,63}$/.test(name) ||
838
+ names.has(name)
839
+ )
840
+ fail('Run event measure names must be distinct identifiers.');
841
+ names.add(name);
842
+ return field ? { op: 'sum', field, as: name } : { op: 'count', as: name };
843
+ });
844
+ return {
845
+ ...base,
846
+ ...criteria,
847
+ ...page,
848
+ groupBy,
849
+ measures,
850
+ } as RunEventOperationRequest;
851
+ }
852
+ export function getRunEventQueryFields(): RunEventFieldsResult {
853
+ return {
854
+ fields: Object.entries(RUN_EVENT_QUERY_FIELD_CATALOG).map(
855
+ ([field, descriptor]) => ({
856
+ field: field as RunEventField,
857
+ ...descriptor,
858
+ operators: OPERATORS,
859
+ groupable: true,
860
+ summable: descriptor.type === 'number',
861
+ }),
862
+ ),
863
+ context: {
864
+ field: 'context.<key>',
865
+ type: 'scalar',
866
+ operators: OPERATORS,
867
+ groupable: true,
868
+ summable: true,
869
+ missing: 'distinct_from_null',
870
+ },
871
+ };
872
+ }