@oomol-lab/open-flow 0.1.0-beta.74 → 0.1.0-beta.75

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 (60) hide show
  1. package/README.md +1 -0
  2. package/dist/browser/{ContentIcon-CUQHjQIw.js → ContentIcon-FCpKiwjp.js} +2 -1
  3. package/dist/browser/{FlowCanvas-Dtf8_cQp.js → FlowCanvas-dzRBnlUT.js} +1 -1
  4. package/dist/browser/{createResourceDialog-DI9pL-Yr.js → createResourceDialog-BeYDdUl-.js} +2 -2
  5. package/dist/browser/{dialog-BYJGvItc.js → dialog-ZtpsFbl4.js} +284 -13
  6. package/dist/browser/{exportSvg-BOoy7-Db.js → exportSvg-DqXw1yf-.js} +2 -2
  7. package/dist/browser/{flowChanges-RL-H4CjD.js → flowChanges-b_5UobFj.js} +285 -44
  8. package/dist/browser/{flowWorkspace-BkDsETj0.js → flowWorkspace-ItojZbO4.js} +414 -53
  9. package/dist/browser/preview.css +181 -176
  10. package/dist/browser/preview.js +955 -182
  11. package/dist/browser/workbench.css +181 -176
  12. package/dist/browser/workbench.js +12 -6
  13. package/dist/browser/{workbenchSelect-BcWPWGzw.js → workbenchSelect-M441Lz0d.js} +1 -1
  14. package/dist/command/index.js +36 -36
  15. package/dist/common/authoring.d.ts +4 -4
  16. package/dist/common/authoringExamples.d.ts +3 -2
  17. package/dist/common/authoringSchema.d.ts +67 -4
  18. package/dist/common/control-api-conformance.js +58 -7
  19. package/dist/common/control-api.js +58 -7
  20. package/dist/common/control-requests.d.ts +3 -3
  21. package/dist/common/control-requests.js +234 -62
  22. package/dist/common/engine-contract.d.ts +1 -0
  23. package/dist/common/flow-authoring.js +56 -7
  24. package/dist/common/flow-change.d.ts +1 -1
  25. package/dist/common/flow-change.js +56 -7
  26. package/dist/common/flow-encoding.js +55 -7
  27. package/dist/common/flow-graph.d.ts +2 -1
  28. package/dist/common/flow-retry.d.ts +1 -1
  29. package/dist/common/flow-semantics.js +810 -577
  30. package/dist/common/http-request.d.ts +156 -0
  31. package/dist/common/http-request.js +5254 -0
  32. package/dist/common/mcp.d.ts +3 -3
  33. package/dist/common/mcp.js +58 -7
  34. package/dist/common/run-events.js +11 -9
  35. package/dist/common/runtime-contract.js +3 -0
  36. package/dist/common/scheduler.d.ts +1 -1
  37. package/dist/common/scheduler.js +70 -21
  38. package/dist/common/webhook-response.d.ts +0 -2
  39. package/dist/common/webhook-trigger.js +12 -11
  40. package/package.json +5 -1
  41. package/skills/open-flow/references/flow-authoring.md +1 -0
  42. package/skills/open-flow/references/flow-n8n-conversion.md +19 -15
  43. package/src/control/common/api.ts +1 -0
  44. package/src/control/common/authoring.ts +14 -3
  45. package/src/control/common/authoringExamples.ts +8 -0
  46. package/src/control/common/authoringSchema.ts +30 -24
  47. package/src/execution/common/engineContract.ts +4 -0
  48. package/src/execution/common/events.ts +3 -0
  49. package/src/execution/common/node-execution.ts +4 -2
  50. package/src/execution/common/scheduler.ts +1 -0
  51. package/src/flow/common/change.ts +3 -2
  52. package/src/flow/common/changeSchema.ts +12 -3
  53. package/src/flow/common/graph.ts +10 -4
  54. package/src/flow/common/retry.ts +1 -1
  55. package/src/flow/common/semantics.ts +60 -3
  56. package/src/flow/common/webhookResponse.ts +3 -7
  57. package/src/{openapi → http}/common/authBindings.ts +3 -3
  58. package/src/http/common/headers.ts +5 -0
  59. package/src/http/common/httpRequest.ts +251 -0
  60. package/src/trigger/common/webhook.ts +4 -3
@@ -161,7 +161,7 @@ export declare const mcpTools: {
161
161
  value: {
162
162
  revision?: string | undefined;
163
163
  query: string;
164
- type?: "agent" | "approval" | "code" | "condition" | "connector" | "cron" | "decision" | "error" | "integration" | "llm" | "openapi" | "outputs" | "poll" | "start" | "value" | "wait" | "webhook" | "webhook-response" | "workflow" | undefined;
164
+ type?: "agent" | "approval" | "code" | "condition" | "connector" | "cron" | "decision" | "error" | "http-request" | "integration" | "llm" | "openapi" | "outputs" | "poll" | "start" | "value" | "wait" | "webhook" | "webhook-response" | "workflow" | undefined;
165
165
  offset: number;
166
166
  limit: number;
167
167
  flowId: string;
@@ -193,7 +193,7 @@ export declare const mcpTools: {
193
193
  };
194
194
  validate(value: unknown): {
195
195
  value: {
196
- type?: "agent" | "approval" | "code" | "condition" | "connector" | "cron" | "decision" | "error" | "integration" | "llm" | "openapi" | "outputs" | "poll" | "start" | "value" | "wait" | "webhook" | "webhook-response" | "workflow" | undefined;
196
+ type?: "agent" | "approval" | "code" | "condition" | "connector" | "cron" | "decision" | "error" | "http-request" | "integration" | "llm" | "openapi" | "outputs" | "poll" | "start" | "value" | "wait" | "webhook" | "webhook-response" | "workflow" | undefined;
197
197
  action?: string | undefined;
198
198
  flowId?: string | undefined;
199
199
  };
@@ -321,7 +321,7 @@ export declare const mcpTools: {
321
321
  edits: ({
322
322
  op: "node.add";
323
323
  as: string;
324
- type: "agent" | "approval" | "code" | "condition" | "connector" | "cron" | "decision" | "error" | "integration" | "llm" | "openapi" | "outputs" | "poll" | "start" | "value" | "wait" | "webhook" | "webhook-response" | "workflow";
324
+ type: "agent" | "approval" | "code" | "condition" | "connector" | "cron" | "decision" | "error" | "http-request" | "integration" | "llm" | "openapi" | "outputs" | "poll" | "start" | "value" | "wait" | "webhook" | "webhook-response" | "workflow";
325
325
  name: string;
326
326
  onError?: "error" | "stop" | undefined;
327
327
  retry?: {
@@ -8693,12 +8693,12 @@ function isSchemaKeyword(value) {
8693
8693
  /** Shared by decision entry points, the editor, and durable decision storage. */
8694
8694
  var waitCommentSchema = string$1().trim().refine((value) => [...value].length <= 2e3, "Wait comment must contain at most 2,000 Unicode code points.").nullable().optional();
8695
8695
  //#endregion
8696
- //#region src/openapi/common/authBindings.ts
8696
+ //#region src/http/common/authBindings.ts
8697
8697
  /** Never allow credentials to enter a Revision as literal node inputs. */
8698
- function assertOpenApiAuthBindings(document) {
8698
+ function assertHttpAuthBindings(document) {
8699
8699
  for (const node of Object.values(document.graph.nodes)) {
8700
- if (node.kind != "task" || !("executor" in node.task) || node.task.executor.kind != "openapi") continue;
8701
- for (const [handle, mapping] of Object.entries(node.inputs)) if (handle.startsWith("auth.") && mapping.kind == "value") throw new TypeError("OpenAPI authentication accepts deployment variables and upstream outputs only.");
8700
+ if (node.kind != "task" || !("executor" in node.task) || !["openapi", "http-request"].includes(node.task.executor.kind)) continue;
8701
+ for (const [handle, mapping] of Object.entries(node.inputs)) if (handle.startsWith("auth.") && mapping.kind == "value") throw new TypeError("HTTP authentication accepts deployment variables and upstream outputs only.");
8702
8702
  }
8703
8703
  }
8704
8704
  //#endregion
@@ -8791,6 +8791,53 @@ function answerSchema(question) {
8791
8791
  required: Object.keys(properties)
8792
8792
  };
8793
8793
  }
8794
+ //#endregion
8795
+ //#region src/http/common/httpRequest.ts
8796
+ var parameterSchema = strictObject({
8797
+ mode: _enum(["fields", "object"]).default("fields"),
8798
+ names: array(string$1()).default([])
8799
+ });
8800
+ var httpRequestConfigSchema = strictObject({
8801
+ method: _enum([
8802
+ "GET",
8803
+ "POST",
8804
+ "PUT",
8805
+ "PATCH",
8806
+ "DELETE",
8807
+ "HEAD",
8808
+ "OPTIONS"
8809
+ ]).default("GET"),
8810
+ query: parameterSchema.default({
8811
+ mode: "fields",
8812
+ names: []
8813
+ }),
8814
+ headers: parameterSchema.default({
8815
+ mode: "fields",
8816
+ names: []
8817
+ }),
8818
+ bodyFormat: _enum([
8819
+ "none",
8820
+ "json",
8821
+ "text",
8822
+ "form"
8823
+ ]).default("none"),
8824
+ authentication: union([
8825
+ strictObject({ type: literal("none") }),
8826
+ strictObject({ type: _enum(["bearer", "basic"]) }),
8827
+ strictObject({
8828
+ type: literal("apiKey"),
8829
+ name: string$1(),
8830
+ in: _enum(["header", "query"])
8831
+ })
8832
+ ]).default({ type: "none" }),
8833
+ responseFormat: _enum([
8834
+ "auto",
8835
+ "json",
8836
+ "text"
8837
+ ]).default("auto"),
8838
+ statusPolicy: _enum(["success", "all"]).default("success")
8839
+ });
8840
+ var httpRequestExecutorSchema = httpRequestConfigSchema.extend({ kind: literal("http-request") });
8794
8841
  function checkJsonDepth(value, depth = 0) {
8795
8842
  if (depth > 64) throw new TypeError("Value exceeds the maximum JSON depth.");
8796
8843
  if (value != null && typeof value == "object") for (const child of Object.values(value)) checkJsonDepth(child, depth + 1);
@@ -9070,7 +9117,8 @@ var managedFields = object({
9070
9117
  kind: literal("workflow"),
9071
9118
  flowId: text,
9072
9119
  revisionId: text
9073
- })
9120
+ }),
9121
+ httpRequestExecutorSchema
9074
9122
  ])
9075
9123
  });
9076
9124
  var managed = managedFields.transform(limitDecisionTask);
@@ -9370,7 +9418,7 @@ function decodeRevisionContent(value) {
9370
9418
  checkJsonDepth(value);
9371
9419
  const content = revision.parse(value);
9372
9420
  checkModelVersion(content);
9373
- assertOpenApiAuthBindings(content.document);
9421
+ assertHttpAuthBindings(content.document);
9374
9422
  return content;
9375
9423
  }
9376
9424
  var shapes = {
@@ -9540,6 +9588,7 @@ array(union([...new Map(Object.entries(shapes).map(([kind, shape]) => [kind, sha
9540
9588
  ...shape
9541
9589
  })])).values()])).min(1);
9542
9590
  function checkModelVersion(content) {
9591
+ if (content.modelVersion < 11 && Object.values(content.document.graph.nodes).some((candidateNode) => candidateNode.kind == "task" && "executor" in candidateNode.task && candidateNode.task.executor.kind == "http-request")) throw new TypeError("HTTP Request requires Flow model version 11.");
9543
9592
  if (content.modelVersion < 11 && Object.values(content.document.graph.nodes).some((candidateNode) => candidateNode.kind == "task" && ("moduleId" in candidateNode.task ? candidateNode.task.capabilities?.some((item) => item.kind == "workflow") : candidateNode.task.executor.kind == "agent" && candidateNode.task.executor.tools.some((item) => item.kind == "workflow")))) throw new TypeError("Workflow tools require Flow model version 11.");
9544
9593
  if (content.modelVersion < 10 && Object.values(content.document.graph.nodes).some((candidateNode) => candidateNode.kind == "webhook" && (candidateNode.options?.responseBody != null || candidateNode.options?.responseStatusCodeOutput != null || candidateNode.options?.responseHeadersSource != null || candidateNode.options?.responseHeaderOutputs != null))) throw new TypeError("Dynamic Webhook responses require Flow model version 10.");
9545
9594
  if (content.modelVersion < 9 && Object.values(content.document.graph.nodes).some((candidateNode) => "retry" in candidateNode && candidateNode.retry != null)) throw new TypeError("Node retries require Flow model version 9.");
@@ -9612,6 +9661,7 @@ var type = _enum([
9612
9661
  "llm",
9613
9662
  "decision",
9614
9663
  "openapi",
9664
+ "http-request",
9615
9665
  "condition",
9616
9666
  "value",
9617
9667
  "wait",
@@ -9737,6 +9787,7 @@ var authoringConfigurations = {
9737
9787
  }),
9738
9788
  "connector": authoringDefinitionSchemas.connector.omit({ outputMode: true }).strict(),
9739
9789
  "openapi": authoringOpenApiSchema,
9790
+ "http-request": httpRequestConfigSchema,
9740
9791
  "agent": authoringDefinitionSchemas.agent.omit({
9741
9792
  prompt: true,
9742
9793
  tools: true
@@ -11855,7 +11906,7 @@ function decodeRunEvent(value) {
11855
11906
  switch (kind) {
11856
11907
  case "node.started": {
11857
11908
  const nodeKind = payload.nodeKind;
11858
- if (nodeKind !== void 0 && nodeKind !== "approval" && nodeKind !== "condition" && nodeKind !== "connector" && nodeKind !== "javascript" && nodeKind !== "decision" && nodeKind !== "openapi" && nodeKind !== "llm" && nodeKind !== "agent" && nodeKind !== "value" && nodeKind !== "outputs" && nodeKind !== "webhook-response" && nodeKind !== "workflow" && nodeKind !== "wait") return invalidResponse();
11909
+ if (nodeKind !== void 0 && nodeKind !== "approval" && nodeKind !== "condition" && nodeKind !== "connector" && nodeKind !== "javascript" && nodeKind !== "decision" && nodeKind !== "openapi" && nodeKind !== "http-request" && nodeKind !== "llm" && nodeKind !== "agent" && nodeKind !== "value" && nodeKind !== "outputs" && nodeKind !== "webhook-response" && nodeKind !== "workflow" && nodeKind !== "wait") return invalidResponse();
11859
11910
  return {
11860
11911
  ...base,
11861
11912
  kind,
@@ -1,8 +1,15 @@
1
+ //#region src/http/common/headers.ts
2
+ /** Header names are case-insensitive; never silently combine duplicate business fields. */
3
+ function httpHeaderNameIssue(name, names) {
4
+ if (!/^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/.test(name)) return "invalid";
5
+ if (names.some((candidate) => candidate.toLowerCase() === name.toLowerCase())) return "duplicate";
6
+ }
7
+ //#endregion
1
8
  //#region src/flow/common/webhookResponse.ts
2
9
  function webhookResponseAllowsBody(status) {
3
10
  return status !== 204 && status !== 205 && status !== 304;
4
11
  }
5
- var webhookHeadersPort = {
12
+ ({
6
13
  jsonSchema: {
7
14
  type: "object",
8
15
  additionalProperties: { type: [
@@ -12,13 +19,7 @@ var webhookHeadersPort = {
12
19
  ] }
13
20
  },
14
21
  nullable: true
15
- };
16
- /** Header names are case-insensitive; never silently combine duplicate business fields. */
17
- function webhookHeaderNameIssue(name, names) {
18
- if (!/^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/.test(name)) return "invalid";
19
- if (names.some((candidate) => candidate.toLowerCase() === name.toLowerCase())) return "duplicate";
20
- }
21
- webhookHeadersPort.jsonSchema;
22
+ }).jsonSchema;
22
23
  function validWebhookStatus(value) {
23
24
  return typeof value === "number" && Number.isInteger(value) && value >= 200 && value <= 599 && webhookResponseAllowsBody(value);
24
25
  }
@@ -28,7 +29,7 @@ function webhookHeaderValues(source) {
28
29
  const headers = {};
29
30
  const names = [];
30
31
  for (const [name, value] of Object.entries(source)) {
31
- if (webhookHeaderNameIssue(name, names) != null) return;
32
+ if (httpHeaderNameIssue(name, names) != null) return;
32
33
  if (typeof value !== "string" && typeof value !== "boolean" && !(typeof value === "number" && Number.isFinite(value))) return;
33
34
  const text = String(value);
34
35
  if ([...text].some((character) => character.charCodeAt(0) > 255 || /[\0\r\n]/.test(character))) return;
@@ -52,6 +53,7 @@ var nodeKinds = /* @__PURE__ */ new Set([
52
53
  "decision",
53
54
  "javascript",
54
55
  "openapi",
56
+ "http-request",
55
57
  "llm",
56
58
  "value",
57
59
  "wait",
@@ -27,6 +27,7 @@ var currentEngineContract = "open-flow-engine/v8";
27
27
  var nodejsEngineContract = "open-flow-engine/v8/nodejs-compat-v1";
28
28
  var currentContract = {
29
29
  workflowTools: true,
30
+ httpRequests: true,
30
31
  waitAutoContinue: true,
31
32
  webhookResponses: true,
32
33
  platformExports: /* @__PURE__ */ new Set(["engineContract", "identity"]),
@@ -73,11 +74,13 @@ function findEngineContract(contract) {
73
74
  if (contract == "open-flow-engine/v7" || contract == "open-flow-engine/v7/nodejs-compat-v1") return {
74
75
  ...contract.endsWith("/nodejs-compat-v1") ? nodejsContract : currentContract,
75
76
  workflowTools: false,
77
+ httpRequests: false,
76
78
  platformSource: `export const engineContract = ${JSON.stringify(contract)}\nexport function identity(value) { return value }\n`
77
79
  };
78
80
  if (contract == "open-flow-engine/v6" || contract == "open-flow-engine/v6/nodejs-compat-v1") return {
79
81
  ...contract == "open-flow-engine/v6" ? currentContract : nodejsContract,
80
82
  workflowTools: false,
83
+ httpRequests: false,
81
84
  waitAutoContinue: false,
82
85
  webhookResponses: false,
83
86
  platformSource: `export const engineContract = ${JSON.stringify(contract)}
@@ -33,7 +33,7 @@ export type SchedulerEvent = {
33
33
  readonly inputs: Readonly<Record<string, JsonValue>>;
34
34
  readonly jobId: string;
35
35
  readonly nodeId: string;
36
- readonly nodeKind: 'agent' | 'approval' | 'condition' | 'connector' | 'decision' | 'javascript' | 'openapi' | 'llm' | 'value' | 'wait' | 'outputs' | 'workflow' | 'webhook-response';
36
+ readonly nodeKind: 'agent' | 'approval' | 'condition' | 'connector' | 'decision' | 'javascript' | 'openapi' | 'http-request' | 'llm' | 'value' | 'wait' | 'outputs' | 'workflow' | 'webhook-response';
37
37
  readonly nodeTitle?: string;
38
38
  readonly runId: string;
39
39
  readonly type: 'node.started';
@@ -9472,11 +9472,65 @@ function answerSchema(question) {
9472
9472
  };
9473
9473
  }
9474
9474
  //#endregion
9475
+ //#region src/http/common/headers.ts
9476
+ /** Header names are case-insensitive; never silently combine duplicate business fields. */
9477
+ function httpHeaderNameIssue(name, names) {
9478
+ if (!/^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/.test(name)) return "invalid";
9479
+ if (names.some((candidate) => candidate.toLowerCase() === name.toLowerCase())) return "duplicate";
9480
+ }
9481
+ //#endregion
9482
+ //#region src/http/common/httpRequest.ts
9483
+ var parameterSchema = strictObject({
9484
+ mode: _enum(["fields", "object"]).default("fields"),
9485
+ names: array(string$1()).default([])
9486
+ });
9487
+ var httpRequestExecutorSchema = strictObject({
9488
+ method: _enum([
9489
+ "GET",
9490
+ "POST",
9491
+ "PUT",
9492
+ "PATCH",
9493
+ "DELETE",
9494
+ "HEAD",
9495
+ "OPTIONS"
9496
+ ]).default("GET"),
9497
+ query: parameterSchema.default({
9498
+ mode: "fields",
9499
+ names: []
9500
+ }),
9501
+ headers: parameterSchema.default({
9502
+ mode: "fields",
9503
+ names: []
9504
+ }),
9505
+ bodyFormat: _enum([
9506
+ "none",
9507
+ "json",
9508
+ "text",
9509
+ "form"
9510
+ ]).default("none"),
9511
+ authentication: union([
9512
+ strictObject({ type: literal("none") }),
9513
+ strictObject({ type: _enum(["bearer", "basic"]) }),
9514
+ strictObject({
9515
+ type: literal("apiKey"),
9516
+ name: string$1(),
9517
+ in: _enum(["header", "query"])
9518
+ })
9519
+ ]).default({ type: "none" }),
9520
+ responseFormat: _enum([
9521
+ "auto",
9522
+ "json",
9523
+ "text"
9524
+ ]).default("auto"),
9525
+ statusPolicy: _enum(["success", "all"]).default("success")
9526
+ }).extend({ kind: literal("http-request") });
9527
+ //#endregion
9475
9528
  //#region src/flow/common/retry.ts
9476
9529
  var retryNodeTypes = [
9477
9530
  "code",
9478
9531
  "connector",
9479
9532
  "openapi",
9533
+ "http-request",
9480
9534
  "llm",
9481
9535
  "decision"
9482
9536
  ];
@@ -9565,22 +9619,6 @@ var webhookResponseTimeout = {
9565
9619
  function webhookResponseAllowsBody(status) {
9566
9620
  return status !== 204 && status !== 205 && status !== 304;
9567
9621
  }
9568
- var webhookHeadersPort = {
9569
- jsonSchema: {
9570
- type: "object",
9571
- additionalProperties: { type: [
9572
- "string",
9573
- "number",
9574
- "boolean"
9575
- ] }
9576
- },
9577
- nullable: true
9578
- };
9579
- /** Header names are case-insensitive; never silently combine duplicate business fields. */
9580
- function webhookHeaderNameIssue(name, names) {
9581
- if (!/^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/.test(name)) return "invalid";
9582
- if (names.some((candidate) => candidate.toLowerCase() === name.toLowerCase())) return "duplicate";
9583
- }
9584
9622
  /** Fixed inputs belong to the node, not author-supplied definitions. */
9585
9623
  var webhookResponseInputs = [
9586
9624
  {
@@ -9597,7 +9635,17 @@ var webhookResponseInputs = [
9597
9635
  },
9598
9636
  {
9599
9637
  handle: "headers",
9600
- jsonSchema: webhookHeadersPort.jsonSchema,
9638
+ jsonSchema: {
9639
+ jsonSchema: {
9640
+ type: "object",
9641
+ additionalProperties: { type: [
9642
+ "string",
9643
+ "number",
9644
+ "boolean"
9645
+ ] }
9646
+ },
9647
+ nullable: true
9648
+ }.jsonSchema,
9601
9649
  nullable: false,
9602
9650
  value: {}
9603
9651
  }
@@ -9611,7 +9659,7 @@ function webhookHeaderValues(source) {
9611
9659
  const headers = {};
9612
9660
  const names = [];
9613
9661
  for (const [name, value] of Object.entries(source)) {
9614
- if (webhookHeaderNameIssue(name, names) != null) return;
9662
+ if (httpHeaderNameIssue(name, names) != null) return;
9615
9663
  if (typeof value !== "string" && typeof value !== "boolean" && !(typeof value === "number" && Number.isFinite(value))) return;
9616
9664
  const text = String(value);
9617
9665
  if ([...text].some((character) => character.charCodeAt(0) > 255 || /[\0\r\n]/.test(character))) return;
@@ -9822,7 +9870,8 @@ var managedFields = object({
9822
9870
  kind: literal("workflow"),
9823
9871
  flowId: text,
9824
9872
  revisionId: text
9825
- })
9873
+ }),
9874
+ httpRequestExecutorSchema
9826
9875
  ])
9827
9876
  });
9828
9877
  var managed = managedFields.transform(limitDecisionTask);
@@ -11458,7 +11507,7 @@ function executeNode(context, input) {
11458
11507
  const kind = nodeKind(node);
11459
11508
  const title = nodeTitle(node);
11460
11509
  const projectedInputs = Object.fromEntries(Object.entries(nodeInputs).filter(([handle]) => {
11461
- if (node.kind == "task" && "executor" in node.task && node.task?.executor.kind == "openapi" && handle.startsWith("auth.")) return false;
11510
+ if (node.kind == "task" && "executor" in node.task && ["openapi", "http-request"].includes(node.task.executor.kind) && handle.startsWith("auth.")) return false;
11462
11511
  const mapping = nodeInputMappings(node)[handle];
11463
11512
  return mapping?.kind != "sources" || mapping.sources.every((source) => source.kind != "binding");
11464
11513
  }));
@@ -11509,7 +11558,7 @@ function executeNode(context, input) {
11509
11558
  input: Object.fromEntries(Object.entries(nodeInputs).filter(([handle]) => {
11510
11559
  if (additional.has(handle)) return false;
11511
11560
  const task = "executor" in node.task ? node.task : void 0;
11512
- if (task?.executor.kind != "openapi") return true;
11561
+ if (task == null || !["openapi", "http-request"].includes(task.executor.kind)) return true;
11513
11562
  const mapping = node.inputs[handle];
11514
11563
  return mapping?.kind != "unset" && (mapping != null || task.inputs.some((port) => "handle" in port && port.handle == handle && port.value !== void 0));
11515
11564
  })),
@@ -27,8 +27,6 @@ export declare const webhookHeadersPort: {
27
27
  readonly nullable: true;
28
28
  };
29
29
  export declare function workflowOutputsPort(outputs: readonly Port[]): PortDefinition;
30
- /** Header names are case-insensitive; never silently combine duplicate business fields. */
31
- export declare function webhookHeaderNameIssue(name: string, names: readonly string[]): 'invalid' | 'duplicate' | undefined;
32
30
  /** Fixed inputs belong to the node, not author-supplied definitions. */
33
31
  export declare const webhookResponseInputs: readonly InputPort[];
34
32
  export interface WebhookResponseSnapshot {
@@ -91,6 +91,13 @@ function isJsonObject(value) {
91
91
  }
92
92
  }
93
93
  //#endregion
94
+ //#region src/http/common/headers.ts
95
+ /** Header names are case-insensitive; never silently combine duplicate business fields. */
96
+ function httpHeaderNameIssue(name, names) {
97
+ if (!/^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/.test(name)) return "invalid";
98
+ if (names.some((candidate) => candidate.toLowerCase() === name.toLowerCase())) return "duplicate";
99
+ }
100
+ //#endregion
94
101
  //#region src/flow/common/webhookResponse.ts
95
102
  var webhookResponseTimeout = {
96
103
  defaultMs: 3e4,
@@ -100,7 +107,7 @@ var webhookResponseTimeout = {
100
107
  function webhookResponseAllowsBody(status) {
101
108
  return status !== 204 && status !== 205 && status !== 304;
102
109
  }
103
- var webhookHeadersPort = {
110
+ ({
104
111
  jsonSchema: {
105
112
  type: "object",
106
113
  additionalProperties: { type: [
@@ -110,13 +117,7 @@ var webhookHeadersPort = {
110
117
  ] }
111
118
  },
112
119
  nullable: true
113
- };
114
- /** Header names are case-insensitive; never silently combine duplicate business fields. */
115
- function webhookHeaderNameIssue(name, names) {
116
- if (!/^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/.test(name)) return "invalid";
117
- if (names.some((candidate) => candidate.toLowerCase() === name.toLowerCase())) return "duplicate";
118
- }
119
- webhookHeadersPort.jsonSchema;
120
+ }).jsonSchema;
120
121
  function validWebhookStatus(value) {
121
122
  return typeof value === "number" && Number.isInteger(value) && value >= 200 && value <= 599 && webhookResponseAllowsBody(value);
122
123
  }
@@ -126,7 +127,7 @@ function webhookHeaderValues(source) {
126
127
  const headers = {};
127
128
  const names = [];
128
129
  for (const [name, value] of Object.entries(source)) {
129
- if (webhookHeaderNameIssue(name, names) != null) return;
130
+ if (httpHeaderNameIssue(name, names) != null) return;
130
131
  if (typeof value !== "string" && typeof value !== "boolean" && !(typeof value === "number" && Number.isFinite(value))) return;
131
132
  const text = String(value);
132
133
  if ([...text].some((character) => character.charCodeAt(0) > 255 || /[\0\r\n]/.test(character))) return;
@@ -2929,7 +2930,7 @@ function webhookResponseHeaders(result, options) {
2929
2930
  if (!isJsonObject(source)) return;
2930
2931
  const values = /* @__PURE__ */ new Map();
2931
2932
  for (const [name, value] of Object.entries(source)) {
2932
- if (webhookHeaderNameIssue(name, [...values.values()].map((entry) => entry.name)) != null) return;
2933
+ if (httpHeaderNameIssue(name, [...values.values()].map((entry) => entry.name)) != null) return;
2933
2934
  values.set(name.toLowerCase(), {
2934
2935
  name,
2935
2936
  value
@@ -2938,7 +2939,7 @@ function webhookResponseHeaders(result, options) {
2938
2939
  if (whole == null) {
2939
2940
  const names = [];
2940
2941
  for (const [name, output] of Object.entries(options.responseHeaderOutputs ?? {})) {
2941
- if (webhookHeaderNameIssue(name, names) != null) return;
2942
+ if (httpHeaderNameIssue(name, names) != null) return;
2942
2943
  names.push(name);
2943
2944
  values.set(name.toLowerCase(), {
2944
2945
  name,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oomol-lab/open-flow",
3
- "version": "0.1.0-beta.74",
3
+ "version": "0.1.0-beta.75",
4
4
  "description": "TypeScript contracts and Browser runtime for Open Flow.",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "repository": {
@@ -25,6 +25,10 @@
25
25
  "types": "./dist/common/decision.d.ts",
26
26
  "import": "./dist/common/decision.js"
27
27
  },
28
+ "./http-request": {
29
+ "types": "./dist/common/http-request.d.ts",
30
+ "import": "./dist/common/http-request.js"
31
+ },
28
32
  "./openapi": {
29
33
  "types": "./dist/common/openapi.d.ts",
30
34
  "import": "./dist/common/openapi.js"
@@ -298,6 +298,7 @@ For Connector Tasks, independent Code Actions, Agent tools and notifications, an
298
298
  | Manual, HTTP or scheduled entry | `start`, `webhook`, `cron`: workflow `inputDefs`, HTTP body fields, or schedule. |
299
299
  | Workflow failure handler | `error`: published upstream Flows' automatic Run failures; not a node-level catch or a handler for manual Run failures. |
300
300
  | Call another workflow | `workflow`: select an owned published Flow ID; inputs and outputs derive from its saved contract. Use `node.refresh` to update it. |
301
+ | Single HTTP request | `http-request`: method, parameter modes, authentication, body and response configuration; URL and request values use public inputs. |
301
302
  | Fixed HTTP operation | `openapi`: an OpenAPI source, path and method; resolved authentication choices and ports appear in node details. |
302
303
  | Reusable constants | `value`: named JSON values and optional output constraints. |
303
304
  | Durable interaction | `wait`, `approval`: prompt and named inputs; resume through the Run's Wait ID. Wait additionally supports optional `config.autoContinue` with `{kind:"after",durationMs:86400000}` or `{kind:"at",at:"2026-10-25T10:00:00+08:00"}`. |
@@ -13,17 +13,17 @@ publishing and enabling remain separate actions within the user's authorization.
13
13
 
14
14
  ## Choose a representation
15
15
 
16
- | Source behavior | Open Flow building blocks | What determines a good fit |
17
- | --------------------------------------------- | ----------------------------------------------- | ------------------------------------------------------------------------------ |
18
- | Scheduled, webhook or provider event | Cron, Webhook, Poll or Integration Trigger | Schedule/timezone, event payload, delivery and response behavior. |
19
- | Provider read or write | Connector | Exact operation, input/output shape, account and number of calls. |
20
- | Field shaping, filtering, aggregation or join | Code with named input/output fields | Explicit collection representation, ordering, keys and empty-input behavior. |
21
- | IF or Switch | Condition and named execution branches | Predicate, first/all matches, fallback route and data sent onward. |
22
- | Prompt plus model and output parser | LLM with template, inputs and `resultSchema` | Supported model, result shape and any parser repair behavior. |
23
- | Agent with tools or memory | Agent where its current contract fits | Tool permissions, state lifetime and how context is supplied. |
24
- | HTTP request | OpenAPI where an actual operation contract fits | Method, parameters, auth, response format and error behavior. |
25
- | Human response or durable pause | Wait or Approval | Who resumes it, payload, timing, notification and external response lifecycle. |
26
- | Workflow failure handling | Error Trigger where its scope fits | Which failures trigger it and what failure data it receives. |
16
+ | Source behavior | Open Flow building blocks | What determines a good fit |
17
+ | --------------------------------------------- | ------------------------------------------------------- | ------------------------------------------------------------------------------ |
18
+ | Scheduled, webhook or provider event | Cron, Webhook, Poll or Integration Trigger | Schedule/timezone, event payload, delivery and response behavior. |
19
+ | Provider read or write | Connector | Exact operation, input/output shape, account and number of calls. |
20
+ | Field shaping, filtering, aggregation or join | Code with named input/output fields | Explicit collection representation, ordering, keys and empty-input behavior. |
21
+ | IF or Switch | Condition and named execution branches | Predicate, first/all matches, fallback route and data sent onward. |
22
+ | Prompt plus model and output parser | LLM with template, inputs and `resultSchema` | Supported model, result shape and any parser repair behavior. |
23
+ | Agent with tools or memory | Agent where its current contract fits | Tool permissions, state lifetime and how context is supplied. |
24
+ | HTTP request | `http-request`, or `openapi` for a documented operation | Method, parameters, auth, response format and error behavior. |
25
+ | Human response or durable pause | Wait or Approval | Who resumes it, payload, timing, notification and external response lifecycle. |
26
+ | Workflow failure handling | Error Trigger where its scope fits | Which failures trigger it and what failure data it receives. |
27
27
 
28
28
  Use `schema TYPE` and provider discovery for the actual deployment. A node's
29
29
  name alone does not establish equivalence. Provider operations can differ even
@@ -120,10 +120,14 @@ inside URLs, headers, expressions or Code; keep those out of Drafts and reports.
120
120
  Use supported credential bindings for the selected operation. A missing account
121
121
  can leave a Draft incomplete without implying that the action itself is absent.
122
122
 
123
- For HTTP Request conversion, `schema openapi` describes source URL, operation
124
- selection and supported authentication. Check the actual operation's response,
125
- non-success status, redirect, binary and retry requirements against that contract.
126
- An invented API specification does not establish support for an unknown operation.
123
+ For HTTP Request conversion, `schema http-request` describes a single request's
124
+ method, parameter modes, body, authentication and response handling. Bind `url`,
125
+ request parameters and body through public inputs. Authentication inputs require
126
+ variables or upstream outputs. Query arrays become repeated parameters; response
127
+ arrays remain one value. The first version does not follow redirects or support
128
+ pagination, files, OAuth or direct Agent tools. Check these requirements before
129
+ calling the conversion equivalent. Use `schema openapi` when an actual operation
130
+ contract is available; do not invent an API specification to make a request.
127
131
 
128
132
  A provider event may expose an ID where the source expected a full record. A
129
133
  Connector read can supply the missing data if it preserves the desired behavior.
@@ -675,6 +675,7 @@ export function decodeRunEvent(value: unknown) {
675
675
  nodeKind !== 'javascript' &&
676
676
  nodeKind !== 'decision' &&
677
677
  nodeKind !== 'openapi' &&
678
+ nodeKind !== 'http-request' &&
678
679
  nodeKind !== 'llm' &&
679
680
  nodeKind !== 'agent' &&
680
681
  nodeKind !== 'value' &&
@@ -42,6 +42,7 @@ import {
42
42
  renameFlowOutputSources,
43
43
  } from '../../flow/common/nodeChanges.ts'
44
44
  import { retryConfigurationSchema } from '../../flow/common/retry.ts'
45
+ import { httpRequestTask, httpRequestConfigSchema, httpRequestInputs } from '../../http/common/httpRequest.ts'
45
46
  import { withWorkflowTask, workflowTask, workflowSelectionTask, workflowCapability, workflowAgentTool } from '../../workflow/common/workflow.ts'
46
47
  import { conditionConfigurationView, prepareConditionConfiguration } from './authoringCondition.ts'
47
48
  import { authoringDiagnosticField } from './authoringDiagnosticFields.ts'
@@ -145,7 +146,10 @@ export function authoringNode(content: RevisionContent, ref: string) {
145
146
  ),
146
147
  }
147
148
  else if (task.executor.kind == 'workflow') config = { flowId: task.executor.flowId }
148
- else if (task.executor.kind == 'openapi') {
149
+ else if (task.executor.kind == 'http-request') {
150
+ const { kind: _kind, ...settings } = task.executor
151
+ config = settings
152
+ } else if (task.executor.kind == 'openapi') {
149
153
  config = { ...openApiConfigView(task.executor) }
150
154
  }
151
155
  } else if (node.kind == 'task' && 'moduleId' in node.task) {
@@ -442,7 +446,8 @@ export async function compileAuthoring(base: RevisionContent, request: Authoring
442
446
  const timing = record(record(patch.config).autoContinue)
443
447
  if (Object.hasOwn(timing, 'kind') && timing.kind != record(record(view.config).autoContinue).kind) record(update.config).autoContinue = timing
444
448
  }
445
- if (type == 'openapi' && Object.hasOwn(record(patch.config), 'authentication')) record(update.config).authentication = record(patch.config).authentication
449
+ if ((type == 'openapi' || type == 'http-request') && Object.hasOwn(record(patch.config), 'authentication'))
450
+ record(update.config).authentication = record(patch.config).authentication
446
451
  for (const path of clear) clearPath(update, path)
447
452
  if (typeof update.name != 'string') fail('node.name-required', 'Node name cannot be cleared.')
448
453
  const config = authoringConfigurations[type].parse(update.config) as Record<string, unknown>
@@ -516,7 +521,10 @@ export async function compileAuthoring(base: RevisionContent, request: Authoring
516
521
  task = { ...before, name: String(update.name), executor: { ...executor, ...config } } as ManagedTaskDefinition
517
522
  } else task = await connectorTask(config, String(update.name))
518
523
  } else if (type == 'decision') task = decisionTask(config.questions as Parameters<typeof decisionTask>[0], String(update.name))
519
- else if (type == 'openapi') {
524
+ else if (type == 'http-request') {
525
+ task = httpRequestTask(httpRequestConfigSchema.parse(config), String(update.name), before)
526
+ next = { ...next, inputs: httpRequestInputs(node.inputs, before, task) } as GraphNode
527
+ } else if (type == 'openapi') {
520
528
  task = await prepareOpenApiTask(config as AuthoringOpenApiConfig, before, (url) => host.openapi(url), String(update.name), {
521
529
  authentication: explicit('authentication'),
522
530
  serverUrl: explicit('serverUrl'),
@@ -766,6 +774,9 @@ export async function compileAuthoring(base: RevisionContent, request: Authoring
766
774
  case 'connector':
767
775
  changes = createManagedTask(identity, await connectorTask(config, name))
768
776
  break
777
+ case 'http-request':
778
+ changes = createManagedTask(identity, httpRequestTask(httpRequestConfigSchema.parse(config), name))
779
+ break
769
780
  case 'openapi':
770
781
  changes = createManagedTask(
771
782
  identity,
@@ -11,6 +11,7 @@ import {
11
11
  createValue,
12
12
  createWait,
13
13
  } from '../../flow/common/nodeChanges.ts'
14
+ import { httpRequestTask } from '../../http/common/httpRequest.ts'
14
15
  import { openApiTask, selectOperation } from '../../openapi/common/openapi.ts'
15
16
 
16
17
  const descriptions = {
@@ -22,6 +23,7 @@ const descriptions = {
22
23
  'poll': 'Gmail polling by key; replace CONNECTION_ID with an active Gmail connection.',
23
24
  'integration': 'Telegram webhook integration by key; replace CONNECTION_ID with an active Telegram connection.',
24
25
  'decision': 'AI Decision evaluates named questions against target and returns a complete answer object per output. Configure target before running.',
26
+ 'http-request': 'A single HTTP request. Bind url and request inputs; outputs are body, statusCode and headers.',
25
27
  'openapi': 'A fixed JSON API operation. Replace the example document and URL before running.',
26
28
  'connector':
27
29
  'A Connector Task and its node. Replace ACTION_ID, CONNECTION_ID and port definitions using connector_get / connector show. Set connectionId to an empty string to defer account configuration; execution will require an account.',
@@ -55,6 +57,12 @@ export function authoringExample(name: string): { version: 1; operations: readon
55
57
  { name: 'needs_support', type: 'noul', instructions: 'Does the customer need support?' },
56
58
  ])
57
59
  break
60
+ case 'http-request':
61
+ operations = [
62
+ ...createManagedTask({ nodeId: 'http' }, httpRequestTask()),
63
+ { kind: 'graph.node.input.set', nodeId: 'http', handle: 'url', value: { kind: 'value', value: 'https://example.com' } },
64
+ ]
65
+ break
58
66
  case 'openapi': {
59
67
  const document = {
60
68
  openapi: '3.1.0',