@oxygen-agent/cli 1.377.3 → 1.575.19

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 (95) hide show
  1. package/README.md +1 -1
  2. package/dist/column-run-notices.d.ts +11 -0
  3. package/dist/column-run-notices.js +37 -0
  4. package/dist/command-manifest.js +13 -8
  5. package/dist/help.js +78 -16
  6. package/dist/index.js +3450 -447
  7. package/dist/skills.js +106 -1
  8. package/node_modules/@oxygen/formula/dist/coerce.d.ts +8 -0
  9. package/node_modules/@oxygen/formula/dist/coerce.js +10 -0
  10. package/node_modules/@oxygen/formula/dist/evaluate.d.ts +31 -0
  11. package/node_modules/@oxygen/formula/dist/evaluate.js +248 -0
  12. package/node_modules/@oxygen/formula/dist/expression.d.ts +64 -0
  13. package/node_modules/@oxygen/formula/dist/expression.js +428 -0
  14. package/node_modules/@oxygen/formula/dist/formula-functions.d.ts +71 -0
  15. package/node_modules/@oxygen/formula/dist/formula-functions.js +1100 -0
  16. package/node_modules/@oxygen/formula/dist/index.d.ts +17 -0
  17. package/node_modules/@oxygen/formula/dist/index.js +17 -0
  18. package/node_modules/@oxygen/formula/dist/value-normalizers.d.ts +30 -0
  19. package/node_modules/@oxygen/formula/dist/value-normalizers.js +80 -0
  20. package/node_modules/@oxygen/formula/package.json +26 -0
  21. package/node_modules/@oxygen/recipe-sdk/dist/index.d.ts +30 -0
  22. package/node_modules/@oxygen/recipe-sdk/dist/index.js +2 -2
  23. package/node_modules/@oxygen/shared/dist/billing-anchors.d.ts +60 -0
  24. package/node_modules/@oxygen/shared/dist/billing-anchors.js +135 -0
  25. package/node_modules/@oxygen/shared/dist/billing.d.ts +99 -5
  26. package/node_modules/@oxygen/shared/dist/billing.js +185 -8
  27. package/node_modules/@oxygen/shared/dist/call-outcomes.d.ts +59 -0
  28. package/node_modules/@oxygen/shared/dist/call-outcomes.js +73 -0
  29. package/node_modules/@oxygen/shared/dist/cli-result.js +1 -0
  30. package/node_modules/@oxygen/shared/dist/credit-guidance.js +3 -1
  31. package/node_modules/@oxygen/shared/dist/crm-reply-events.d.ts +35 -0
  32. package/node_modules/@oxygen/shared/dist/crm-reply-events.js +31 -0
  33. package/node_modules/@oxygen/shared/dist/dial-guardrail-overrides.d.ts +50 -0
  34. package/node_modules/@oxygen/shared/dist/dial-guardrail-overrides.js +65 -0
  35. package/node_modules/@oxygen/shared/dist/directory.d.ts +1 -1
  36. package/node_modules/@oxygen/shared/dist/directory.js +1 -0
  37. package/node_modules/@oxygen/shared/dist/hosted-ai.d.ts +15 -0
  38. package/node_modules/@oxygen/shared/dist/hosted-ai.js +19 -0
  39. package/node_modules/@oxygen/shared/dist/index.d.ts +9 -0
  40. package/node_modules/@oxygen/shared/dist/index.js +9 -0
  41. package/node_modules/@oxygen/shared/dist/linkedin-quota-denial.d.ts +31 -0
  42. package/node_modules/@oxygen/shared/dist/linkedin-quota-denial.js +56 -0
  43. package/node_modules/@oxygen/shared/dist/linkedin-sequences.d.ts +5 -4
  44. package/node_modules/@oxygen/shared/dist/linkedin-sequences.js +5 -4
  45. package/node_modules/@oxygen/shared/dist/linkedin-url.d.ts +22 -0
  46. package/node_modules/@oxygen/shared/dist/linkedin-url.js +7 -4
  47. package/node_modules/@oxygen/shared/dist/log.js +41 -2
  48. package/node_modules/@oxygen/shared/dist/microsoft-consent-url.d.ts +7 -0
  49. package/node_modules/@oxygen/shared/dist/microsoft-consent-url.js +29 -0
  50. package/node_modules/@oxygen/shared/dist/object-storage.d.ts +31 -0
  51. package/node_modules/@oxygen/shared/dist/object-storage.js +61 -0
  52. package/node_modules/@oxygen/shared/dist/plan-limits.d.ts +636 -0
  53. package/node_modules/@oxygen/shared/dist/plan-limits.js +199 -0
  54. package/node_modules/@oxygen/shared/dist/pricing-sheet.d.ts +89 -23
  55. package/node_modules/@oxygen/shared/dist/pricing-sheet.js +88 -24
  56. package/node_modules/@oxygen/shared/dist/sequence-crm-events.d.ts +291 -0
  57. package/node_modules/@oxygen/shared/dist/sequence-crm-events.js +224 -0
  58. package/node_modules/@oxygen/shared/dist/sequence-template.d.ts +42 -1
  59. package/node_modules/@oxygen/shared/dist/sequence-template.js +0 -0
  60. package/node_modules/@oxygen/shared/dist/sequences.d.ts +287 -24
  61. package/node_modules/@oxygen/shared/dist/sequences.js +940 -60
  62. package/node_modules/@oxygen/shared/dist/spend-safety.d.ts +70 -0
  63. package/node_modules/@oxygen/shared/dist/spend-safety.js +106 -0
  64. package/node_modules/@oxygen/shared/dist/tags.d.ts +90 -1
  65. package/node_modules/@oxygen/shared/dist/tags.js +122 -6
  66. package/node_modules/@oxygen/shared/dist/version.d.ts +1 -1
  67. package/node_modules/@oxygen/shared/dist/version.js +1 -1
  68. package/node_modules/@oxygen/shared/dist/workflow-trigger-metadata.d.ts +1 -1
  69. package/node_modules/@oxygen/shared/dist/workflow-trigger-metadata.js +4 -0
  70. package/node_modules/@oxygen/shared/package.json +95 -0
  71. package/node_modules/@oxygen/workflows/dist/event-dispatch.d.ts +126 -0
  72. package/node_modules/@oxygen/workflows/dist/event-dispatch.js +173 -0
  73. package/node_modules/@oxygen/workflows/dist/graph/expression.d.ts +78 -0
  74. package/node_modules/@oxygen/workflows/dist/graph/expression.js +700 -0
  75. package/node_modules/@oxygen/workflows/dist/graph/index.d.ts +20 -0
  76. package/node_modules/@oxygen/workflows/dist/graph/index.js +20 -0
  77. package/node_modules/@oxygen/workflows/dist/graph/lint.d.ts +4 -0
  78. package/node_modules/@oxygen/workflows/dist/graph/lint.js +812 -0
  79. package/node_modules/@oxygen/workflows/dist/graph/manifest-schema.d.ts +501 -0
  80. package/node_modules/@oxygen/workflows/dist/graph/manifest-schema.js +200 -0
  81. package/node_modules/@oxygen/workflows/dist/graph/params.d.ts +86 -0
  82. package/node_modules/@oxygen/workflows/dist/graph/params.js +173 -0
  83. package/node_modules/@oxygen/workflows/dist/graph/remap.d.ts +48 -0
  84. package/node_modules/@oxygen/workflows/dist/graph/remap.js +213 -0
  85. package/node_modules/@oxygen/workflows/dist/graph/topology.d.ts +46 -0
  86. package/node_modules/@oxygen/workflows/dist/graph/topology.js +280 -0
  87. package/node_modules/@oxygen/workflows/dist/graph/types.d.ts +270 -0
  88. package/node_modules/@oxygen/workflows/dist/graph/types.js +93 -0
  89. package/node_modules/@oxygen/workflows/dist/index.d.ts +113 -1
  90. package/node_modules/@oxygen/workflows/dist/index.js +179 -13
  91. package/node_modules/@oxygen/workflows/dist/tool-effects.d.ts +1 -0
  92. package/node_modules/@oxygen/workflows/dist/tool-effects.js +19 -0
  93. package/node_modules/@oxygen/workflows/dist/usage-estimate.js +135 -4
  94. package/node_modules/@oxygen/workflows/package.json +4 -0
  95. package/package.json +7 -5
@@ -0,0 +1,428 @@
1
+ import { FORMULA_FUNCTION_REGISTRY, checkFormulaFunctionArity, formulaExpressionError, } from "./formula-functions.js";
2
+ /**
3
+ * Functions evaluated against the whole column rather than the current scope.
4
+ * They are intercepted before registry dispatch (the registry entry carries
5
+ * metadata only) because they need a corpus the scope supplies.
6
+ */
7
+ export const CROSS_ROW_FUNCTIONS = new Set(["novelty_score"]);
8
+ export function parseFormulaExpression(expression, options = {}) {
9
+ const parser = new FormulaParser(tokenize(expression, options), expression);
10
+ return parser.parse();
11
+ }
12
+ /** Validate supported functions and arity on an already-parsed tree. */
13
+ export function validateFormulaAst(ast) {
14
+ walkExpression(ast, (node) => {
15
+ if (node.type !== "call")
16
+ return;
17
+ const normalized = node.name.toLowerCase();
18
+ if (normalized === "novelty_score") {
19
+ const firstArg = node.args[0];
20
+ if (node.args.length !== 1
21
+ || !firstArg
22
+ || (firstArg.type !== "identifier"
23
+ && !(firstArg.type === "literal" && typeof firstArg.value === "string"))) {
24
+ throw formulaExpressionError("novelty_score requires exactly one column argument.", {
25
+ function: node.name,
26
+ received: node.args.length,
27
+ });
28
+ }
29
+ return;
30
+ }
31
+ const spec = FORMULA_FUNCTION_REGISTRY.get(normalized);
32
+ if (!spec) {
33
+ throw formulaExpressionError("Unsupported formula function.", {
34
+ function: node.name,
35
+ });
36
+ }
37
+ checkFormulaFunctionArity(spec, node.args.length);
38
+ });
39
+ }
40
+ /** Validate syntax, supported functions, and arity without evaluating any data. */
41
+ export function validateFormulaExpression(expression, options = {}) {
42
+ validateFormulaAst(parseFormulaExpression(expression, options));
43
+ }
44
+ export function walkExpression(node, visit) {
45
+ visit(node);
46
+ switch (node.type) {
47
+ case "unary":
48
+ walkExpression(node.argument, visit);
49
+ return;
50
+ case "binary":
51
+ walkExpression(node.left, visit);
52
+ walkExpression(node.right, visit);
53
+ return;
54
+ case "call":
55
+ for (const arg of node.args)
56
+ walkExpression(arg, visit);
57
+ return;
58
+ default:
59
+ return;
60
+ }
61
+ }
62
+ export function analyzeCrossRowDependencies(node) {
63
+ const functions = new Set();
64
+ const columnKeys = new Set();
65
+ walkExpression(node, (entry) => {
66
+ if (entry.type !== "call")
67
+ return;
68
+ const normalized = entry.name.toLowerCase();
69
+ if (!CROSS_ROW_FUNCTIONS.has(normalized))
70
+ return;
71
+ functions.add(normalized);
72
+ for (const arg of entry.args) {
73
+ if (arg.type === "identifier") {
74
+ columnKeys.add(arg.name);
75
+ }
76
+ else if (arg.type === "literal" && typeof arg.value === "string") {
77
+ columnKeys.add(arg.value);
78
+ }
79
+ }
80
+ });
81
+ return { functions: [...functions], columnKeys: [...columnKeys] };
82
+ }
83
+ export function analyzeFormulaCrossRowDependencies(expression) {
84
+ const ast = parseFormulaExpression(expression);
85
+ validateFormulaAst(ast);
86
+ return analyzeCrossRowDependencies(ast);
87
+ }
88
+ /**
89
+ * Every identifier the expression reads, plus its cross-row dependencies.
90
+ * Identifiers are bare names — the host decides what they bind to (a table
91
+ * column key today, a workflow node output tomorrow).
92
+ */
93
+ export function analyzeFormulaDependencies(expression, options = {}) {
94
+ const ast = parseFormulaExpression(expression, options);
95
+ validateFormulaAst(ast);
96
+ const identifiers = new Set();
97
+ walkExpression(ast, (entry) => {
98
+ if (entry.type === "identifier")
99
+ identifiers.add(entry.name);
100
+ });
101
+ const crossRow = analyzeCrossRowDependencies(ast);
102
+ return {
103
+ identifiers: [...identifiers],
104
+ crossRowFunctions: [...crossRow.functions],
105
+ crossRowColumnKeys: [...crossRow.columnKeys],
106
+ };
107
+ }
108
+ class FormulaParser {
109
+ tokens;
110
+ expression;
111
+ index = 0;
112
+ constructor(tokens, expression) {
113
+ this.tokens = tokens;
114
+ this.expression = expression;
115
+ }
116
+ parse() {
117
+ const node = this.parseOr();
118
+ if (this.current().type !== "eof") {
119
+ throw this.error("Unexpected token.", this.current());
120
+ }
121
+ return node;
122
+ }
123
+ parseOr() {
124
+ let node = this.parseAnd();
125
+ while (this.matchWord("or") || this.matchOperator("||")) {
126
+ const operator = this.previousValue();
127
+ node = { type: "binary", operator: operator === "||" ? "or" : operator, left: node, right: this.parseAnd() };
128
+ }
129
+ return node;
130
+ }
131
+ parseAnd() {
132
+ let node = this.parseEquality();
133
+ while (this.matchWord("and") || this.matchOperator("&&")) {
134
+ const operator = this.previousValue();
135
+ node = { type: "binary", operator: operator === "&&" ? "and" : operator, left: node, right: this.parseEquality() };
136
+ }
137
+ return node;
138
+ }
139
+ parseEquality() {
140
+ let node = this.parseComparison();
141
+ while (this.matchOperator("=")
142
+ || this.matchOperator("==")
143
+ || this.matchOperator("!=")
144
+ || this.matchOperator("<>")) {
145
+ const operator = this.previousValue();
146
+ node = { type: "binary", operator, left: node, right: this.parseComparison() };
147
+ }
148
+ return node;
149
+ }
150
+ parseComparison() {
151
+ let node = this.parseAdditive();
152
+ while (this.matchOperator("<")
153
+ || this.matchOperator("<=")
154
+ || this.matchOperator(">")
155
+ || this.matchOperator(">=")) {
156
+ const operator = this.previousValue();
157
+ node = { type: "binary", operator, left: node, right: this.parseAdditive() };
158
+ }
159
+ return node;
160
+ }
161
+ parseAdditive() {
162
+ let node = this.parseMultiplicative();
163
+ while (this.matchOperator("+") || this.matchOperator("-")) {
164
+ const operator = this.previousValue();
165
+ node = { type: "binary", operator, left: node, right: this.parseMultiplicative() };
166
+ }
167
+ return node;
168
+ }
169
+ parseMultiplicative() {
170
+ let node = this.parseUnary();
171
+ while (this.matchOperator("*")
172
+ || this.matchOperator("/")
173
+ || this.matchOperator("%")) {
174
+ const operator = this.previousValue();
175
+ node = { type: "binary", operator, left: node, right: this.parseUnary() };
176
+ }
177
+ return node;
178
+ }
179
+ parseUnary() {
180
+ if (this.matchOperator("-") || this.matchOperator("+") || this.matchOperator("!") || this.matchWord("not")) {
181
+ const operator = this.previousValue();
182
+ return {
183
+ type: "unary",
184
+ operator: operator === "!" ? "not" : operator,
185
+ argument: this.parseUnary(),
186
+ };
187
+ }
188
+ return this.parsePrimary();
189
+ }
190
+ parsePrimary() {
191
+ const token = this.current();
192
+ if (token.type === "number") {
193
+ this.advance();
194
+ return { type: "literal", value: token.value };
195
+ }
196
+ if (token.type === "string") {
197
+ this.advance();
198
+ return { type: "literal", value: token.value };
199
+ }
200
+ if (token.type === "identifier") {
201
+ this.advance();
202
+ const normalized = token.value.toLowerCase();
203
+ if (normalized === "true")
204
+ return { type: "literal", value: true };
205
+ if (normalized === "false")
206
+ return { type: "literal", value: false };
207
+ if (normalized === "null")
208
+ return { type: "literal", value: null };
209
+ if (this.match("left_paren")) {
210
+ const args = [];
211
+ if (!this.check("right_paren")) {
212
+ do {
213
+ args.push(this.parseOr());
214
+ } while (this.match("comma"));
215
+ }
216
+ this.consume("right_paren", "Expected ')' after function arguments.");
217
+ return { type: "call", name: token.value, args };
218
+ }
219
+ return { type: "identifier", name: token.value };
220
+ }
221
+ if (this.match("left_paren")) {
222
+ const node = this.parseOr();
223
+ this.consume("right_paren", "Expected ')' after expression.");
224
+ return node;
225
+ }
226
+ throw this.error("Expected expression.", token);
227
+ }
228
+ match(type) {
229
+ if (!this.check(type))
230
+ return false;
231
+ this.advance();
232
+ return true;
233
+ }
234
+ matchOperator(value) {
235
+ const token = this.current();
236
+ if (token.type !== "operator" || token.value !== value)
237
+ return false;
238
+ this.advance();
239
+ return true;
240
+ }
241
+ matchWord(value) {
242
+ const token = this.current();
243
+ if (token.type !== "identifier" || token.value.toLowerCase() !== value)
244
+ return false;
245
+ this.advance();
246
+ return true;
247
+ }
248
+ consume(type, message) {
249
+ if (this.match(type))
250
+ return;
251
+ throw this.error(message, this.current());
252
+ }
253
+ check(type) {
254
+ return this.current().type === type;
255
+ }
256
+ current() {
257
+ return this.tokens[this.index] ?? { type: "eof", position: this.expression.length };
258
+ }
259
+ advance() {
260
+ const token = this.current();
261
+ this.index += 1;
262
+ return token;
263
+ }
264
+ previousValue() {
265
+ const token = this.tokens[this.index - 1];
266
+ return token && "value" in token ? String(token.value) : "";
267
+ }
268
+ error(message, token) {
269
+ return formulaExpressionError(message, {
270
+ position: token.position,
271
+ expression: this.expression,
272
+ });
273
+ }
274
+ }
275
+ /**
276
+ * Consume `.field` and `[0]` segments after an identifier.
277
+ *
278
+ * A trailing dot is NOT consumed: `sum(a.)` should fail on the dot rather than
279
+ * producing an identifier `a.` that no scope can resolve, which would turn a typo
280
+ * into an unknown-identifier error pointing at the wrong thing.
281
+ */
282
+ function readPathSuffix(expression, from) {
283
+ let index = from;
284
+ for (;;) {
285
+ const char = expression[index];
286
+ if (char === "." && /[A-Za-z0-9_]/.test(expression[index + 1] ?? "")) {
287
+ index += 2;
288
+ while (/[A-Za-z0-9_]/.test(expression[index] ?? ""))
289
+ index += 1;
290
+ continue;
291
+ }
292
+ if (char === "[") {
293
+ const close = expression.indexOf("]", index);
294
+ if (close === -1)
295
+ return index;
296
+ index = close + 1;
297
+ continue;
298
+ }
299
+ return index;
300
+ }
301
+ }
302
+ function tokenize(expression, options = {}) {
303
+ const tokens = [];
304
+ let index = 0;
305
+ while (index < expression.length) {
306
+ const char = expression[index] ?? "";
307
+ if (/\s/.test(char)) {
308
+ index += 1;
309
+ continue;
310
+ }
311
+ if (char === "(") {
312
+ tokens.push({ type: "left_paren", position: index });
313
+ index += 1;
314
+ continue;
315
+ }
316
+ if (char === ")") {
317
+ tokens.push({ type: "right_paren", position: index });
318
+ index += 1;
319
+ continue;
320
+ }
321
+ if (char === ",") {
322
+ tokens.push({ type: "comma", position: index });
323
+ index += 1;
324
+ continue;
325
+ }
326
+ if (char === "\"" || char === "'") {
327
+ const parsed = readStringLiteral(expression, index, char);
328
+ tokens.push({ type: "string", value: parsed.value, position: index });
329
+ index = parsed.nextIndex;
330
+ continue;
331
+ }
332
+ if (/\d/.test(char) || (char === "." && /\d/.test(expression[index + 1] ?? ""))) {
333
+ const parsed = readNumberLiteral(expression, index);
334
+ tokens.push({ type: "number", value: parsed.value, position: index });
335
+ index = parsed.nextIndex;
336
+ continue;
337
+ }
338
+ if (/[A-Za-z_]/.test(char)) {
339
+ const start = index;
340
+ index += 1;
341
+ while (/[A-Za-z0-9_]/.test(expression[index] ?? ""))
342
+ index += 1;
343
+ // Opt-in dotted paths. A table formula must tokenize BYTE-IDENTICALLY to
344
+ // before, so this is off by default: `a.b` there is still a syntax error, not
345
+ // a silently-different identifier. Workflow expressions turn it on so a
346
+ // formula can spell the same `steps.<id>.output.x` the ref and template
347
+ // bindings already use — without it the three binding kinds disagreed about
348
+ // how to name the same value.
349
+ if (options.paths === true)
350
+ index = readPathSuffix(expression, index);
351
+ tokens.push({ type: "identifier", value: expression.slice(start, index), position: start });
352
+ continue;
353
+ }
354
+ const twoChar = expression.slice(index, index + 2);
355
+ if (["<=", ">=", "==", "!=", "<>", "&&", "||"].includes(twoChar)) {
356
+ tokens.push({ type: "operator", value: twoChar, position: index });
357
+ index += 2;
358
+ continue;
359
+ }
360
+ if (["+", "-", "*", "/", "%", "<", ">", "=", "!"].includes(char)) {
361
+ tokens.push({ type: "operator", value: char, position: index });
362
+ index += 1;
363
+ continue;
364
+ }
365
+ throw formulaExpressionError("Unexpected character in formula expression.", {
366
+ position: index,
367
+ character: char,
368
+ expression,
369
+ });
370
+ }
371
+ tokens.push({ type: "eof", position: expression.length });
372
+ return tokens;
373
+ }
374
+ function readStringLiteral(expression, start, quote) {
375
+ let value = "";
376
+ let index = start + 1;
377
+ while (index < expression.length) {
378
+ const char = expression[index] ?? "";
379
+ if (char === quote)
380
+ return { value, nextIndex: index + 1 };
381
+ if (char === "\\") {
382
+ const escaped = expression[index + 1];
383
+ if (escaped === undefined)
384
+ break;
385
+ value += decodeEscapedCharacter(escaped);
386
+ index += 2;
387
+ continue;
388
+ }
389
+ value += char;
390
+ index += 1;
391
+ }
392
+ throw formulaExpressionError("Unterminated string literal.", {
393
+ position: start,
394
+ expression,
395
+ });
396
+ }
397
+ function decodeEscapedCharacter(char) {
398
+ switch (char) {
399
+ case "n":
400
+ return "\n";
401
+ case "r":
402
+ return "\r";
403
+ case "t":
404
+ return "\t";
405
+ default:
406
+ return char;
407
+ }
408
+ }
409
+ function readNumberLiteral(expression, start) {
410
+ let index = start;
411
+ while (/\d/.test(expression[index] ?? ""))
412
+ index += 1;
413
+ if (expression[index] === ".") {
414
+ index += 1;
415
+ while (/\d/.test(expression[index] ?? ""))
416
+ index += 1;
417
+ }
418
+ const raw = expression.slice(start, index);
419
+ const value = Number(raw);
420
+ if (!Number.isFinite(value)) {
421
+ throw formulaExpressionError("Invalid number literal.", {
422
+ position: start,
423
+ value: raw,
424
+ expression,
425
+ });
426
+ }
427
+ return { value, nextIndex: index };
428
+ }
@@ -0,0 +1,71 @@
1
+ import { OxygenError } from "@oxygen/shared/cli-result";
2
+ /**
3
+ * Table-driven formula function registry. The tokenizer/parser/AST live in
4
+ * expression.ts and the evaluator in evaluate.ts; this module owns every
5
+ * callable function plus the value-coercion helpers the operators share.
6
+ * Adding a function is one registry entry + one test row — the discoverability surfaces
7
+ * (`/api/cli/tables/formulas/functions`, `oxygen formulas functions`,
8
+ * `oxygen_formulas_list`) all render from this metadata. Run conditions
9
+ * (`only run if`) evaluate through the same registry, so every function here
10
+ * is usable as a per-row execution gate too.
11
+ *
12
+ * Value semantics, kept uniform on purpose:
13
+ * - blank in (null/undefined/""/[]), null out for scalar transforms;
14
+ * boolean predicates return false, `count`/`sum` return 0.
15
+ * - numeric functions coerce strings ("42") and throw on non-numeric input.
16
+ * - dates are UTC-only ISO-8601 strings; unparseable input yields null so
17
+ * dirty enrichment data flows instead of failing a whole run.
18
+ */
19
+ export type FormulaFunctionCategory = "logic" | "string" | "number" | "date" | "url_email" | "array" | "json" | "null_handling" | "cross_row";
20
+ export type FormulaFunctionExample = {
21
+ expression: string;
22
+ result: unknown;
23
+ };
24
+ export type FormulaFunctionMeta = {
25
+ name: string;
26
+ category: FormulaFunctionCategory;
27
+ signature: string;
28
+ description: string;
29
+ minArgs: number;
30
+ /** null = variadic (no upper bound). */
31
+ maxArgs: number | null;
32
+ examples: FormulaFunctionExample[];
33
+ };
34
+ export type FormulaFunctionSpec = FormulaFunctionMeta & {
35
+ /** Lazy functions receive argument thunks so untaken branches never evaluate. */
36
+ lazy?: boolean;
37
+ evaluate?: (args: unknown[]) => unknown;
38
+ evaluateLazy?: (thunks: Array<() => unknown>) => unknown;
39
+ };
40
+ export declare const MAX_FORMULA_REGEX_PATTERN_LENGTH = 256;
41
+ export declare const MAX_FORMULA_REGEX_INPUT_LENGTH = 20000;
42
+ export declare function formulaExpressionError(message: string, details: Record<string, unknown>): OxygenError;
43
+ export declare function isBlankFormulaValue(value: unknown): boolean;
44
+ export declare function stringifyFormulaValue(value: unknown): string;
45
+ export declare function readFormulaNumber(value: unknown, operator: string): number;
46
+ export declare function toFormulaBoolean(value: unknown): boolean;
47
+ export declare function compareFormulaValues(left: unknown, right: unknown): number;
48
+ export declare function formulaValuesEqual(left: unknown, right: unknown): boolean;
49
+ export declare function readFormulaJsonPath(value: unknown, rawPath: string): unknown;
50
+ export declare function checkFormulaFunctionArity(spec: FormulaFunctionMeta, received: number): void;
51
+ export declare const FORMULA_DATE_UNITS: readonly ["seconds", "minutes", "hours", "days", "weeks", "months", "years"];
52
+ export declare const FORMULA_FUNCTION_REGISTRY: ReadonlyMap<string, FormulaFunctionSpec>;
53
+ export declare const FORMULA_FUNCTION_CATEGORIES: readonly FormulaFunctionCategory[];
54
+ export declare function listFormulaFunctions(category?: FormulaFunctionCategory): FormulaFunctionMeta[];
55
+ /** Operators the expression language supports, for the discovery surfaces. */
56
+ export declare const FORMULA_OPERATORS: readonly [{
57
+ readonly operator: "+ - * / %";
58
+ readonly description: "Arithmetic (numbers or numeric strings).";
59
+ }, {
60
+ readonly operator: "= == != <>";
61
+ readonly description: "Equality (numbers compare numerically).";
62
+ }, {
63
+ readonly operator: "< <= > >=";
64
+ readonly description: "Comparison (numbers numerically, text lexicographically).";
65
+ }, {
66
+ readonly operator: "and or not (&& || !)";
67
+ readonly description: "Boolean logic with short-circuit evaluation.";
68
+ }, {
69
+ readonly operator: "( )";
70
+ readonly description: "Grouping and function calls.";
71
+ }];