@octaviaflow/flow-rules 0.13.0 → 0.13.2

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.
@@ -43,6 +43,8 @@ export interface HTTPRequestConfigType {
43
43
  method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS';
44
44
  baseUrl?: string;
45
45
  url: string;
46
+ /** An OpenAPI connection: its base URL is joined to a relative `url` at run time. */
47
+ connectionId?: string;
46
48
  queryParams?: ApiKeyValue[];
47
49
  headers?: ApiKeyValue[] | Record<string, string>;
48
50
  bodyType?: 'none' | 'json' | 'raw' | 'form-data' | 'xml';
@@ -39,6 +39,12 @@ export interface AiAgentConfigType {
39
39
  */
40
40
  input?: string;
41
41
  overrides?: AiAgentOverrides;
42
+ /**
43
+ * Per-row mode only: the most model calls the step may make (one per
44
+ * upstream row). Default 50, at most 1000 — a run with more rows stops
45
+ * before any call (DEV-428).
46
+ */
47
+ maxRows?: number;
42
48
  }
43
49
  export declare const AiAgentConfig: {
44
50
  defaults: AiAgentConfigType;
@@ -58,6 +64,12 @@ export declare const AiAgentConfig: {
58
64
  required: boolean;
59
65
  description: string;
60
66
  };
67
+ maxRows: {
68
+ type: string;
69
+ required: boolean;
70
+ min: number;
71
+ description: string;
72
+ };
61
73
  };
62
74
  validate: (config: Partial<AiAgentConfigType>) => {
63
75
  valid: boolean;
package/dist/index.cjs CHANGED
@@ -1537,7 +1537,7 @@ var queryDefinition = {
1537
1537
  key: "visualConfig.limit",
1538
1538
  label: "Limit",
1539
1539
  type: "number" /* Number */,
1540
- description: "Maximum number of rows to return",
1540
+ description: "Rows the PREVIEW shows. A run never reads it — set visualConfig.maxRows to cap a run.",
1541
1541
  defaultValue: 10,
1542
1542
  validation: { required: false, min: 1, max: 1e4 },
1543
1543
  conditional: [
@@ -1549,6 +1549,22 @@ var queryDefinition = {
1549
1549
  }
1550
1550
  ]
1551
1551
  },
1552
+ {
1553
+ key: "visualConfig.maxRows",
1554
+ label: "Max rows (run)",
1555
+ type: "number" /* Number */,
1556
+ description: "The most rows a run reads (up to 10,000). Unset: 10,000. The Limit above is preview-only.",
1557
+ validation: { required: false, min: 1, max: 1e4 },
1558
+ advanced: true,
1559
+ conditional: [
1560
+ {
1561
+ field: "mode",
1562
+ operator: "==",
1563
+ value: "visual" /* Visual */,
1564
+ action: "show"
1565
+ }
1566
+ ]
1567
+ },
1552
1568
  {
1553
1569
  key: "sqlConfig.sql",
1554
1570
  label: "SQL Query",
@@ -1595,7 +1611,7 @@ var queryDefinition = {
1595
1611
  key: "maxRows",
1596
1612
  label: "Max Rows",
1597
1613
  type: "number" /* Number */,
1598
- description: "Maximum number of rows to return",
1614
+ description: "Legacy step-level field — NOT read at run. Cap a visual Query with visualConfig.maxRows; in SQL mode write a LIMIT.",
1599
1615
  defaultValue: 1000,
1600
1616
  validation: { required: false, min: 1, max: 1e4 },
1601
1617
  advanced: true
@@ -2712,7 +2728,7 @@ var graphqlQueryDefinition = {
2712
2728
  var aiAgentDefinition = {
2713
2729
  id: "ai_agent",
2714
2730
  name: "AI Agent",
2715
- description: "Run a reusable AI agent. Pick a saved agent from /ai/agents; optionally override model, temperature, or instructions per step.",
2731
+ description: "Run a reusable AI agent. Pick a saved agent from /ai/agents; optionally override model, temperature, or instructions per step. " + "When the step before it produces rows (a Query, a Transform), the agent runs ONCE PER ROW with the prompt as the per-row template — " + "reference single fields, never the whole table; put an Aggregate first to call it once. maxRows (default 50) caps the calls.",
2716
2732
  category: "ai" /* AI */,
2717
2733
  type: "ai_agent" /* AIAgent */,
2718
2734
  tags: ["ai", "agent", "chat", "built-in"],
@@ -2732,21 +2748,28 @@ var aiAgentDefinition = {
2732
2748
  key: "input",
2733
2749
  label: "Input",
2734
2750
  type: "string" /* String */,
2735
- description: "Chat agents: a string (template-supported via {{step.field}}). ETL agents: rows array. When blank, falls back to upstream step output."
2751
+ description: "The prompt. Over a row-shaped upstream it is the per-row template: each data reference resolves to the current row. " + "A reference to the whole upstream table is refused. When blank, the upstream output is the input."
2736
2752
  },
2737
2753
  {
2738
2754
  key: "overrides",
2739
2755
  label: "Overrides",
2740
2756
  type: "json" /* JSON */,
2741
2757
  description: "Optional per-step overrides of the agent’s own settings: model, temperature, maxTokens, topP, instructions and instructionsMode."
2758
+ },
2759
+ {
2760
+ key: "maxRows",
2761
+ label: "Max rows",
2762
+ type: "number" /* Number */,
2763
+ description: "Per-row mode: the most model calls this step may make, one per upstream row (default 50, at most 1000)."
2742
2764
  }
2743
2765
  ]
2744
2766
  },
2745
2767
  outputSchema: {
2746
2768
  type: "object",
2747
2769
  properties: [
2748
- { key: "text", type: "string" /* String */, description: "Agent response text (chat agents)" },
2749
- { key: "rows", type: "json" /* JSON */, description: "Output rows (etl agents)" },
2770
+ { key: "text", type: "string" /* String */, description: "Agent response text; in per-row mode, one line per row" },
2771
+ { key: "output", type: "json" /* JSON */, description: "The parsed answer (an object when the agent replies in JSON); in per-row mode, one entry per row" },
2772
+ { key: "rows", type: "json" /* JSON */, description: "Per-row mode: one entry per upstream row" },
2750
2773
  { key: "usage", type: "json" /* JSON */, description: "Token usage + cost: tokensIn, tokensOut, latencyMs, costUsd" },
2751
2774
  { key: "model", type: "string" /* String */, description: "Model that produced the response" },
2752
2775
  { key: "agentVersion", type: "number" /* Number */, description: "Version of the agent definition that ran" }
@@ -4019,7 +4042,7 @@ var AggregateConfig = {
4019
4042
  outputAs: {
4020
4043
  type: "string",
4021
4044
  required: false,
4022
- description: "The name later steps use for this result, e.g. totals → {{totals[].TotalAmount}}. Defaults to the step name."
4045
+ description: "The name later steps use for this result, e.g. totals → {{totals[].TotalAmount}} in a Transform mapping. " + "A message or AI prompt reads it as {{ods://steps/<this step id>/output/data[0]/TotalAmount}} — the mapping form is not resolved there. " + "Defaults to the step name."
4023
4046
  }
4024
4047
  },
4025
4048
  defaults: {
@@ -4318,7 +4341,18 @@ var STORAGE_PROVIDERS = {
4318
4341
  operations: [
4319
4342
  { value: "list", label: "List files", path: { key: "folderId", label: "Folder", selectMode: "folder" }, fields: [{ key: "query", label: "Search query", type: "string", advanced: true }, { key: "pageSize", label: "Max results", type: "number", defaultValue: 50, advanced: true }] },
4320
4343
  { value: "download", label: "Download file", path: { key: "fileId", label: "File", selectMode: "both", required: true } },
4321
- { value: "upload", label: "Upload file", path: { key: "folderId", label: "Destination folder", selectMode: "both" }, content: true, fields: [{ key: "fileName", label: "File name", type: "string", required: true }, { key: "mimeType", label: "Content type", type: "string", advanced: true }] },
4344
+ {
4345
+ value: "upload",
4346
+ label: "Upload file",
4347
+ path: { key: "folderId", label: "Destination folder", selectMode: "both" },
4348
+ content: true,
4349
+ fields: [
4350
+ { key: "fileName", label: "File name", type: "string", required: true },
4351
+ { key: "folderName", label: "Folder path", type: "string", advanced: true, placeholder: "Reports/2026", hint: "Folders under My Drive, separated by /. Missing ones are created on the first run. Ignored when a destination folder is picked." },
4352
+ { key: "ifExists", label: "If the file already exists", type: "select", options: [{ label: "Replace it (Drive keeps the versions)", value: "overwrite" }, { label: "Skip it", value: "skip" }, { label: "Keep both (rename)", value: "rename" }], defaultValue: "overwrite", advanced: true },
4353
+ { key: "mimeType", label: "Content type", type: "string", advanced: true }
4354
+ ]
4355
+ },
4322
4356
  { value: "delete", label: "Delete file", path: { key: "fileId", label: "File", selectMode: "both", required: true } },
4323
4357
  { value: "move", label: "Move", path: { key: "fileId", label: "File", selectMode: "both", required: true }, dest: { key: "destFolderId", label: "Destination folder", selectMode: "both", required: true } },
4324
4358
  { value: "copy", label: "Copy", path: { key: "fileId", label: "File", selectMode: "both", required: true }, dest: { key: "destFolderId", label: "Destination folder", selectMode: "both" }, fields: [{ key: "fileName", label: "New name", type: "string" }] },
@@ -4437,7 +4471,13 @@ var AiAgentConfig = {
4437
4471
  schema: {
4438
4472
  agentId: { type: "string", required: true, description: "Public id of a saved agent (agt_…)" },
4439
4473
  input: { type: "text", required: false, description: "The prompt; may reference upstream data with {{ods://…}} tokens. Empty uses the first upstream output." },
4440
- overrides: { type: "object", required: false, description: "Per-step overrides: model, temperature, topP, maxTokens, reasoningEffort, verbosity, batchSize, instructions, instructionsMode, expectedSchema" }
4474
+ overrides: { type: "object", required: false, description: "Per-step overrides: model, temperature, topP, maxTokens, reasoningEffort, verbosity, batchSize, instructions, instructionsMode, expectedSchema" },
4475
+ maxRows: {
4476
+ type: "number",
4477
+ required: false,
4478
+ min: 1,
4479
+ description: "Per-row mode: the most model calls this step may make, one per upstream row (default 50, at most 1000). More rows stop the step before any call."
4480
+ }
4441
4481
  },
4442
4482
  validate: (config) => {
4443
4483
  const errors = [];
@@ -4446,6 +4486,9 @@ var AiAgentConfig = {
4446
4486
  if (config.overrides !== undefined && (typeof config.overrides !== "object" || config.overrides === null || Array.isArray(config.overrides))) {
4447
4487
  errors.push("overrides must be an object");
4448
4488
  }
4489
+ if (config.maxRows !== undefined && !(Number.isInteger(config.maxRows) && config.maxRows >= 1 && config.maxRows <= 1000)) {
4490
+ errors.push("maxRows must be a whole number from 1 to 1000");
4491
+ }
4449
4492
  return { valid: errors.length === 0, errors };
4450
4493
  },
4451
4494
  merge: (userConfig) => ({ ...AiAgentConfig.defaults, ...userConfig }),
@@ -5510,6 +5553,13 @@ var DelayConfig = {
5510
5553
  };
5511
5554
 
5512
5555
  // src/catalog/actions/http-request/config.ts
5556
+ function urlCheckedAtRunTime(url, connectionId) {
5557
+ const u = url.trim();
5558
+ if (u.startsWith("{{"))
5559
+ return true;
5560
+ const hasScheme = /^[a-z][a-z0-9+.-]*:/i.test(u);
5561
+ return !!connectionId && !hasScheme;
5562
+ }
5513
5563
  var HTTPRequestConfig = {
5514
5564
  defaults: {
5515
5565
  apiType: "rest",
@@ -5587,7 +5637,7 @@ var HTTPRequestConfig = {
5587
5637
  }
5588
5638
  if (!config.url) {
5589
5639
  errors.push("URL is required");
5590
- } else if (typeof config.url === "string") {
5640
+ } else if (typeof config.url === "string" && !urlCheckedAtRunTime(config.url, config.connectionId)) {
5591
5641
  try {
5592
5642
  new URL(config.url);
5593
5643
  } catch {
package/dist/index.js CHANGED
@@ -1374,7 +1374,7 @@ var queryDefinition = {
1374
1374
  key: "visualConfig.limit",
1375
1375
  label: "Limit",
1376
1376
  type: "number" /* Number */,
1377
- description: "Maximum number of rows to return",
1377
+ description: "Rows the PREVIEW shows. A run never reads it — set visualConfig.maxRows to cap a run.",
1378
1378
  defaultValue: 10,
1379
1379
  validation: { required: false, min: 1, max: 1e4 },
1380
1380
  conditional: [
@@ -1386,6 +1386,22 @@ var queryDefinition = {
1386
1386
  }
1387
1387
  ]
1388
1388
  },
1389
+ {
1390
+ key: "visualConfig.maxRows",
1391
+ label: "Max rows (run)",
1392
+ type: "number" /* Number */,
1393
+ description: "The most rows a run reads (up to 10,000). Unset: 10,000. The Limit above is preview-only.",
1394
+ validation: { required: false, min: 1, max: 1e4 },
1395
+ advanced: true,
1396
+ conditional: [
1397
+ {
1398
+ field: "mode",
1399
+ operator: "==",
1400
+ value: "visual" /* Visual */,
1401
+ action: "show"
1402
+ }
1403
+ ]
1404
+ },
1389
1405
  {
1390
1406
  key: "sqlConfig.sql",
1391
1407
  label: "SQL Query",
@@ -1432,7 +1448,7 @@ var queryDefinition = {
1432
1448
  key: "maxRows",
1433
1449
  label: "Max Rows",
1434
1450
  type: "number" /* Number */,
1435
- description: "Maximum number of rows to return",
1451
+ description: "Legacy step-level field — NOT read at run. Cap a visual Query with visualConfig.maxRows; in SQL mode write a LIMIT.",
1436
1452
  defaultValue: 1000,
1437
1453
  validation: { required: false, min: 1, max: 1e4 },
1438
1454
  advanced: true
@@ -2549,7 +2565,7 @@ var graphqlQueryDefinition = {
2549
2565
  var aiAgentDefinition = {
2550
2566
  id: "ai_agent",
2551
2567
  name: "AI Agent",
2552
- description: "Run a reusable AI agent. Pick a saved agent from /ai/agents; optionally override model, temperature, or instructions per step.",
2568
+ description: "Run a reusable AI agent. Pick a saved agent from /ai/agents; optionally override model, temperature, or instructions per step. " + "When the step before it produces rows (a Query, a Transform), the agent runs ONCE PER ROW with the prompt as the per-row template — " + "reference single fields, never the whole table; put an Aggregate first to call it once. maxRows (default 50) caps the calls.",
2553
2569
  category: "ai" /* AI */,
2554
2570
  type: "ai_agent" /* AIAgent */,
2555
2571
  tags: ["ai", "agent", "chat", "built-in"],
@@ -2569,21 +2585,28 @@ var aiAgentDefinition = {
2569
2585
  key: "input",
2570
2586
  label: "Input",
2571
2587
  type: "string" /* String */,
2572
- description: "Chat agents: a string (template-supported via {{step.field}}). ETL agents: rows array. When blank, falls back to upstream step output."
2588
+ description: "The prompt. Over a row-shaped upstream it is the per-row template: each data reference resolves to the current row. " + "A reference to the whole upstream table is refused. When blank, the upstream output is the input."
2573
2589
  },
2574
2590
  {
2575
2591
  key: "overrides",
2576
2592
  label: "Overrides",
2577
2593
  type: "json" /* JSON */,
2578
2594
  description: "Optional per-step overrides of the agent’s own settings: model, temperature, maxTokens, topP, instructions and instructionsMode."
2595
+ },
2596
+ {
2597
+ key: "maxRows",
2598
+ label: "Max rows",
2599
+ type: "number" /* Number */,
2600
+ description: "Per-row mode: the most model calls this step may make, one per upstream row (default 50, at most 1000)."
2579
2601
  }
2580
2602
  ]
2581
2603
  },
2582
2604
  outputSchema: {
2583
2605
  type: "object",
2584
2606
  properties: [
2585
- { key: "text", type: "string" /* String */, description: "Agent response text (chat agents)" },
2586
- { key: "rows", type: "json" /* JSON */, description: "Output rows (etl agents)" },
2607
+ { key: "text", type: "string" /* String */, description: "Agent response text; in per-row mode, one line per row" },
2608
+ { key: "output", type: "json" /* JSON */, description: "The parsed answer (an object when the agent replies in JSON); in per-row mode, one entry per row" },
2609
+ { key: "rows", type: "json" /* JSON */, description: "Per-row mode: one entry per upstream row" },
2587
2610
  { key: "usage", type: "json" /* JSON */, description: "Token usage + cost: tokensIn, tokensOut, latencyMs, costUsd" },
2588
2611
  { key: "model", type: "string" /* String */, description: "Model that produced the response" },
2589
2612
  { key: "agentVersion", type: "number" /* Number */, description: "Version of the agent definition that ran" }
@@ -3856,7 +3879,7 @@ var AggregateConfig = {
3856
3879
  outputAs: {
3857
3880
  type: "string",
3858
3881
  required: false,
3859
- description: "The name later steps use for this result, e.g. totals → {{totals[].TotalAmount}}. Defaults to the step name."
3882
+ description: "The name later steps use for this result, e.g. totals → {{totals[].TotalAmount}} in a Transform mapping. " + "A message or AI prompt reads it as {{ods://steps/<this step id>/output/data[0]/TotalAmount}} — the mapping form is not resolved there. " + "Defaults to the step name."
3860
3883
  }
3861
3884
  },
3862
3885
  defaults: {
@@ -4155,7 +4178,18 @@ var STORAGE_PROVIDERS = {
4155
4178
  operations: [
4156
4179
  { value: "list", label: "List files", path: { key: "folderId", label: "Folder", selectMode: "folder" }, fields: [{ key: "query", label: "Search query", type: "string", advanced: true }, { key: "pageSize", label: "Max results", type: "number", defaultValue: 50, advanced: true }] },
4157
4180
  { value: "download", label: "Download file", path: { key: "fileId", label: "File", selectMode: "both", required: true } },
4158
- { value: "upload", label: "Upload file", path: { key: "folderId", label: "Destination folder", selectMode: "both" }, content: true, fields: [{ key: "fileName", label: "File name", type: "string", required: true }, { key: "mimeType", label: "Content type", type: "string", advanced: true }] },
4181
+ {
4182
+ value: "upload",
4183
+ label: "Upload file",
4184
+ path: { key: "folderId", label: "Destination folder", selectMode: "both" },
4185
+ content: true,
4186
+ fields: [
4187
+ { key: "fileName", label: "File name", type: "string", required: true },
4188
+ { key: "folderName", label: "Folder path", type: "string", advanced: true, placeholder: "Reports/2026", hint: "Folders under My Drive, separated by /. Missing ones are created on the first run. Ignored when a destination folder is picked." },
4189
+ { key: "ifExists", label: "If the file already exists", type: "select", options: [{ label: "Replace it (Drive keeps the versions)", value: "overwrite" }, { label: "Skip it", value: "skip" }, { label: "Keep both (rename)", value: "rename" }], defaultValue: "overwrite", advanced: true },
4190
+ { key: "mimeType", label: "Content type", type: "string", advanced: true }
4191
+ ]
4192
+ },
4159
4193
  { value: "delete", label: "Delete file", path: { key: "fileId", label: "File", selectMode: "both", required: true } },
4160
4194
  { value: "move", label: "Move", path: { key: "fileId", label: "File", selectMode: "both", required: true }, dest: { key: "destFolderId", label: "Destination folder", selectMode: "both", required: true } },
4161
4195
  { value: "copy", label: "Copy", path: { key: "fileId", label: "File", selectMode: "both", required: true }, dest: { key: "destFolderId", label: "Destination folder", selectMode: "both" }, fields: [{ key: "fileName", label: "New name", type: "string" }] },
@@ -4274,7 +4308,13 @@ var AiAgentConfig = {
4274
4308
  schema: {
4275
4309
  agentId: { type: "string", required: true, description: "Public id of a saved agent (agt_…)" },
4276
4310
  input: { type: "text", required: false, description: "The prompt; may reference upstream data with {{ods://…}} tokens. Empty uses the first upstream output." },
4277
- overrides: { type: "object", required: false, description: "Per-step overrides: model, temperature, topP, maxTokens, reasoningEffort, verbosity, batchSize, instructions, instructionsMode, expectedSchema" }
4311
+ overrides: { type: "object", required: false, description: "Per-step overrides: model, temperature, topP, maxTokens, reasoningEffort, verbosity, batchSize, instructions, instructionsMode, expectedSchema" },
4312
+ maxRows: {
4313
+ type: "number",
4314
+ required: false,
4315
+ min: 1,
4316
+ description: "Per-row mode: the most model calls this step may make, one per upstream row (default 50, at most 1000). More rows stop the step before any call."
4317
+ }
4278
4318
  },
4279
4319
  validate: (config) => {
4280
4320
  const errors = [];
@@ -4283,6 +4323,9 @@ var AiAgentConfig = {
4283
4323
  if (config.overrides !== undefined && (typeof config.overrides !== "object" || config.overrides === null || Array.isArray(config.overrides))) {
4284
4324
  errors.push("overrides must be an object");
4285
4325
  }
4326
+ if (config.maxRows !== undefined && !(Number.isInteger(config.maxRows) && config.maxRows >= 1 && config.maxRows <= 1000)) {
4327
+ errors.push("maxRows must be a whole number from 1 to 1000");
4328
+ }
4286
4329
  return { valid: errors.length === 0, errors };
4287
4330
  },
4288
4331
  merge: (userConfig) => ({ ...AiAgentConfig.defaults, ...userConfig }),
@@ -5347,6 +5390,13 @@ var DelayConfig = {
5347
5390
  };
5348
5391
 
5349
5392
  // src/catalog/actions/http-request/config.ts
5393
+ function urlCheckedAtRunTime(url, connectionId) {
5394
+ const u = url.trim();
5395
+ if (u.startsWith("{{"))
5396
+ return true;
5397
+ const hasScheme = /^[a-z][a-z0-9+.-]*:/i.test(u);
5398
+ return !!connectionId && !hasScheme;
5399
+ }
5350
5400
  var HTTPRequestConfig = {
5351
5401
  defaults: {
5352
5402
  apiType: "rest",
@@ -5424,7 +5474,7 @@ var HTTPRequestConfig = {
5424
5474
  }
5425
5475
  if (!config.url) {
5426
5476
  errors.push("URL is required");
5427
- } else if (typeof config.url === "string") {
5477
+ } else if (typeof config.url === "string" && !urlCheckedAtRunTime(config.url, config.connectionId)) {
5428
5478
  try {
5429
5479
  new URL(config.url);
5430
5480
  } catch {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@octaviaflow/flow-rules",
3
- "version": "0.13.0",
3
+ "version": "0.13.2",
4
4
  "description": "The flow action catalog and the rules engine behind Flow Doctor — one definition of what a step accepts and what makes a flow valid, shared by the editor and the server",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",