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

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 (125) hide show
  1. package/README.md +2 -1
  2. package/dist/browser/{ContentIcon-FCpKiwjp.js → ContentIcon-Cm1hbVzW.js} +2 -2
  3. package/dist/browser/{FlowCanvas-dzRBnlUT.js → FlowCanvas-NzKtla_c.js} +62 -62
  4. package/dist/browser/{createResourceDialog-BeYDdUl-.js → createResourceDialog-BgdedQqu.js} +3 -3
  5. package/dist/browser/{dialog-ZtpsFbl4.js → dialog-aSElFFw2.js} +49 -24
  6. package/dist/browser/{exportSvg-DqXw1yf-.js → exportSvg-Dcz4HTo_.js} +2 -2
  7. package/dist/browser/{flowChanges-b_5UobFj.js → flowChanges-NZyS7HSp.js} +207 -396
  8. package/dist/browser/{flowWorkspace-ItojZbO4.js → flowWorkspace-Bl0SDehy.js} +90 -13
  9. package/dist/browser/preview.css +515 -649
  10. package/dist/browser/preview.js +454 -545
  11. package/dist/browser/theme.css +114 -262
  12. package/dist/browser/{tooltip-wZJRB9oP.js → tooltip-3XMK9jkr.js} +1 -1
  13. package/dist/browser/{typeScriptSession-CSRwWmZm.js → typeScriptSession-C9sBGOKZ.js} +1 -1
  14. package/dist/browser/ui.css +354 -460
  15. package/dist/browser/ui.js +1 -1
  16. package/dist/browser/workbench.css +515 -649
  17. package/dist/browser/workbench.js +7 -7
  18. package/dist/browser/{workbenchSelect-M441Lz0d.js → workbenchSelect-DBGP7Y4Z.js} +2 -2
  19. package/dist/command/index.js +33 -35
  20. package/dist/common/authoring.d.ts +3 -3
  21. package/dist/common/authoringSchema.d.ts +3 -0
  22. package/dist/common/connector-proxy.d.ts +1 -1
  23. package/dist/common/control-api-conformance.js +197 -136
  24. package/dist/common/control-api.d.ts +3 -2
  25. package/dist/common/control-api.js +192 -131
  26. package/dist/common/control-requests.js +126 -24
  27. package/dist/common/cron-trigger.d.ts +2 -1
  28. package/dist/common/decision.d.ts +4 -4
  29. package/dist/common/decision.js +5 -2
  30. package/dist/common/engine-contract.d.ts +5 -1
  31. package/dist/common/event-source-definition.d.ts +1 -1
  32. package/dist/common/event-source-view.d.ts +22 -0
  33. package/dist/common/event-source.d.ts +1 -1
  34. package/dist/common/event-source.js +24 -65
  35. package/dist/common/event-sources.d.ts +0 -21
  36. package/dist/common/flow-authoring.js +40 -18
  37. package/dist/common/flow-change-schema.d.ts +9 -1
  38. package/dist/common/flow-change.d.ts +7 -201
  39. package/dist/common/flow-change.js +46 -22
  40. package/dist/common/flow-encoding.js +40 -20
  41. package/dist/common/flow-input-value.d.ts +1 -1
  42. package/dist/common/flow-model.d.ts +121 -0
  43. package/dist/common/flow-notifications.d.ts +1 -1
  44. package/dist/common/flow-retry.d.ts +2 -4
  45. package/dist/common/flow-semantics.js +83 -86
  46. package/dist/common/flow-workflowReferences.d.ts +8 -0
  47. package/dist/common/flowInspection.d.ts +1 -1
  48. package/dist/common/http-hosts.d.ts +6 -0
  49. package/dist/common/http-request.d.ts +13 -5
  50. package/dist/common/http-request.js +23 -5
  51. package/dist/common/integration-trigger.d.ts +2 -1
  52. package/dist/common/mcp.js +195 -134
  53. package/dist/common/openapi.d.ts +4 -4
  54. package/dist/common/openapi.js +4 -1
  55. package/dist/common/poll-trigger.d.ts +2 -1
  56. package/dist/common/runtime-contract.d.ts +23 -1
  57. package/dist/common/runtime-contract.js +604 -72
  58. package/dist/common/runtime-nodejs-compat.d.ts +44 -0
  59. package/dist/common/runtime-nodejs-compat.js +5870 -0
  60. package/dist/common/scheduler-checkpoint.d.ts +1 -1
  61. package/dist/common/scheduler-node-execution.d.ts +1 -1
  62. package/dist/common/scheduler.d.ts +1 -1
  63. package/dist/common/scheduler.js +25 -4
  64. package/dist/common/trigger-catalog.d.ts +1 -7
  65. package/dist/common/trigger-config-options.d.ts +1 -1
  66. package/dist/common/trigger-config.d.ts +1 -1
  67. package/dist/common/trigger-contract.d.ts +2 -1
  68. package/dist/common/trigger-localization.d.ts +8 -2
  69. package/dist/common/trigger-model.d.ts +101 -0
  70. package/dist/common/webhook-response.d.ts +1 -1
  71. package/dist/common/webhook-trigger.d.ts +3 -1
  72. package/dist/common/workflow.d.ts +3 -12
  73. package/dist/common/workflow.js +25 -23
  74. package/dist/index.d.ts +41 -14
  75. package/package.json +5 -1
  76. package/src/base/common/json.ts +1 -1
  77. package/src/connector/common/proxy.ts +1 -1
  78. package/src/control/common/api.ts +6 -5
  79. package/src/control/common/authoringSchema.ts +9 -2
  80. package/src/control/common/authoringTools.ts +1 -1
  81. package/src/control/common/conformance.ts +1 -1
  82. package/src/control/common/connectorDecoders.ts +1 -1
  83. package/src/control/common/eventSources.ts +2 -29
  84. package/src/control/common/triggerCatalog.ts +1 -8
  85. package/src/decision/common/decision.ts +15 -7
  86. package/src/event-source/common/definition.ts +1 -1
  87. package/src/event-source/common/index.ts +1 -1
  88. package/src/event-source/common/view.ts +20 -0
  89. package/src/execution/common/checkpoint.ts +1 -1
  90. package/src/execution/common/node-execution.ts +1 -1
  91. package/src/execution/common/scheduler.ts +1 -1
  92. package/src/flow/common/change.ts +58 -192
  93. package/src/flow/common/changeSchema.ts +29 -4
  94. package/src/flow/common/connectionUsage.ts +1 -1
  95. package/src/flow/common/inputValue.ts +1 -1
  96. package/src/flow/common/model.ts +130 -0
  97. package/src/flow/common/modules.ts +1 -1
  98. package/src/flow/common/portChanges.ts +1 -1
  99. package/src/flow/common/retry.ts +2 -4
  100. package/src/flow/common/semantics.ts +11 -4
  101. package/src/flow/common/webhookResponse.ts +1 -1
  102. package/src/flow/common/workflowReferences.ts +31 -0
  103. package/src/http/common/authBindings.ts +6 -9
  104. package/src/http/common/hosts.ts +13 -0
  105. package/src/http/common/httpRequest.ts +30 -15
  106. package/src/openapi/common/openapi.ts +9 -5
  107. package/src/{execution → runtime}/common/engineContract.ts +20 -15
  108. package/src/runtime/common/nodejsCompat.ts +275 -0
  109. package/src/runtime/common/nodejsGuest.generated.json +47 -0
  110. package/src/runtime/common/runtime.ts +1138 -0
  111. package/src/trigger/common/config.ts +1 -1
  112. package/src/trigger/common/configOptions.ts +1 -1
  113. package/src/trigger/common/contract.ts +2 -1
  114. package/src/trigger/common/cron.ts +2 -1
  115. package/src/trigger/common/integration.ts +2 -1
  116. package/src/trigger/common/model.ts +87 -0
  117. package/src/trigger/common/poll.ts +2 -1
  118. package/src/trigger/common/webhook.ts +3 -1
  119. package/src/trigger/providers/fieldDescriptions.ts +1 -1
  120. package/src/trigger/providers/localization.ts +9 -2
  121. package/src/trigger/providers/remote.ts +1 -1
  122. package/src/types/index.ts +44 -15
  123. package/src/workflow/common/workflow.ts +3 -44
  124. package/src/execution/common/runtime.ts +0 -563
  125. /package/src/{control → connector}/common/providerIconSprite.ts +0 -0
@@ -0,0 +1,130 @@
1
+ import type { JsonValue, Port, PortDefinition } from '../../types/index.ts'
2
+
3
+ export type { JsonValue, Port, PortDefinition } from '../../types/index.ts'
4
+
5
+ /** Primitives shared by every node family. Node unions and graph operations belong to change.ts. */
6
+
7
+ export interface RetryConfiguration {
8
+ readonly maxRetries: number
9
+ readonly intervalMs: number
10
+ }
11
+
12
+ export interface InputPortDefinition extends PortDefinition {
13
+ readonly value?: JsonValue
14
+ }
15
+
16
+ export interface InputPort extends InputPortDefinition {
17
+ readonly handle: string
18
+ }
19
+
20
+ export interface Group {
21
+ readonly collapsed?: boolean
22
+ readonly group: string
23
+ }
24
+
25
+ export function portsByHandle<Value extends Port>(ports: readonly (Value | Group)[]): Readonly<Record<string, Value>> {
26
+ return Object.fromEntries(ports.flatMap((port) => ('handle' in port ? [[port.handle, port]] : [])))
27
+ }
28
+
29
+ export interface NodeSource {
30
+ readonly system?: true
31
+ readonly field?: string
32
+ readonly kind: 'node'
33
+ readonly nodeId: string
34
+ readonly output: string
35
+ }
36
+
37
+ export interface BindingSource {
38
+ readonly bindingId: string
39
+ readonly kind: 'binding'
40
+ }
41
+
42
+ export type FixedInputValue = { readonly kind: 'unset' } | { readonly kind: 'value'; readonly value: JsonValue }
43
+ export type InputValues = Readonly<Record<string, FixedInputValue>>
44
+
45
+ export type InputMapping = FixedInputValue | { readonly kind: 'sources'; readonly sources: readonly (BindingSource | NodeSource)[] }
46
+
47
+ export interface GraphNodeBase {
48
+ readonly description?: string
49
+ readonly icon?: string
50
+ readonly inputs: Readonly<Record<string, InputMapping>>
51
+ readonly name?: string
52
+ readonly maxExecutions?: number
53
+ readonly timeoutMs?: number
54
+ readonly retry?: RetryConfiguration
55
+ readonly onError?: 'stop' | 'error'
56
+ }
57
+
58
+ export type Source = BindingSource | NodeSource
59
+
60
+ export interface WorkflowCapability {
61
+ readonly kind: 'workflow'
62
+ readonly name: string
63
+ readonly flowId: string
64
+ readonly revisionId: string
65
+ readonly inputs: readonly Port[]
66
+ readonly outputs: readonly Port[] | null
67
+ }
68
+
69
+ /** Hosts that Code may call through `context.fetch`; requires Flow model 12 and Engine v9. */
70
+ export interface HttpCapability {
71
+ readonly kind: 'http'
72
+ readonly hosts: readonly string[]
73
+ }
74
+
75
+ export type TaskCapability = ConnectorCapability | WorkflowCapability | HttpCapability
76
+
77
+ export interface ConnectorAccessCapability {
78
+ readonly actionHints?: readonly string[]
79
+ readonly connectionHints?: readonly {
80
+ readonly action: string
81
+ readonly connectionId: string
82
+ readonly alias?: string
83
+ }[]
84
+ readonly kind: 'connector'
85
+ }
86
+
87
+ export interface ConnectorActionCapability {
88
+ readonly action: string
89
+ readonly connections: readonly { readonly connectionId: string; readonly alias?: string }[]
90
+ readonly connectionId?: string
91
+ readonly kind: 'connector'
92
+ }
93
+
94
+ export type ConnectorPermissionCapability =
95
+ | { readonly kind: 'connector'; readonly mode: 'shared' }
96
+ | { readonly kind: 'connector'; readonly mode: 'independent'; readonly actions: readonly { readonly action: string; readonly connectionId?: string }[] }
97
+
98
+ export type ConnectorCapability = ConnectorAccessCapability | ConnectorActionCapability | ConnectorPermissionCapability
99
+
100
+ export interface TaskDefinitionBase {
101
+ readonly inputs: readonly (InputPort | Group)[]
102
+ readonly name: string
103
+ readonly outputs: readonly (Port | Group)[]
104
+ }
105
+
106
+ export interface InlineTaskDefinition extends TaskDefinitionBase {
107
+ readonly capabilities?: readonly TaskCapability[]
108
+ readonly moduleId: string
109
+ }
110
+
111
+ /** The executor shape every managed Task shares; each node family narrows it with its own executor. */
112
+ export interface TaskExecutor {
113
+ readonly kind: string
114
+ }
115
+
116
+ export interface ManagedTaskDefinitionOf<Executor extends TaskExecutor> extends TaskDefinitionBase {
117
+ readonly executor: Executor
118
+ }
119
+
120
+ export interface WorkflowExecutor {
121
+ readonly kind: 'workflow'
122
+ readonly flowId: string
123
+ readonly revisionId: string
124
+ }
125
+
126
+ export interface CodeModule {
127
+ readonly imports: readonly string[]
128
+ readonly name: string
129
+ readonly source: string
130
+ }
@@ -1,4 +1,4 @@
1
- import type { EngineContract } from '../../execution/common/engineContract.ts'
1
+ import type { EngineContract } from '../../runtime/common/engineContract.ts'
2
2
  import type { RevisionContent } from './change.ts'
3
3
  import type { Diagnostic } from './semantics.ts'
4
4
 
@@ -1,4 +1,4 @@
1
- import type { InputMapping } from './change.ts'
1
+ import type { InputMapping } from './model.ts'
2
2
 
3
3
  export function renamedPort(
4
4
  previous: readonly ({ readonly handle: string } | { readonly group: string })[],
@@ -1,11 +1,9 @@
1
1
  import type { GraphNode } from './change.ts'
2
+ import type { RetryConfiguration } from './model.ts'
2
3
 
3
4
  import { z } from 'zod'
4
5
 
5
- export interface RetryConfiguration {
6
- readonly maxRetries: number
7
- readonly intervalMs: number
8
- }
6
+ export type { RetryConfiguration } from './model.ts'
9
7
 
10
8
  export const retryNodeTypes = ['code', 'connector', 'openapi', 'http-request', 'llm', 'decision'] as const
11
9
  export const nodeRetryLimits = { maxRetries: 5, minIntervalMs: 1000, maxIntervalMs: 60000, intervalStepMs: 1000 } as const
@@ -2,19 +2,19 @@ import { dequal } from 'dequal/lite'
2
2
  import { decisionTaskIssues } from '../../decision/common/decision.ts'
3
3
  import { httpRequestIssues, httpAuthHandles, httpRequestTask, httpRequestUrl } from '../../http/common/httpRequest.ts'
4
4
  import { openApiIssues, authHandles } from '../../openapi/common/openapi.ts'
5
- import { nodeWorkflowReferences } from '../../workflow/common/workflow.ts'
6
5
  import { isTriggerNode } from './change.ts'
6
+ import { nodeWorkflowReferences } from './workflowReferences.ts'
7
7
  export { renderPrompt } from './promptTemplate.ts'
8
8
  export { connectionUsage, removeConnectionUsage } from './connectionUsage.ts'
9
9
  import { matchesTriggerOutputs, normalizeTriggerOutputs } from '../../trigger/common/contract.ts'
10
10
  import { nodeInputMappings } from './condition.ts'
11
11
  import { connectionUsage } from './connectionUsage.ts'
12
12
  export { matchesTriggerOutputs, normalizeTriggerOutputs, triggerOutputDefinitions, triggerOutputPorts } from '../../trigger/common/contract.ts'
13
- import type { EngineContract } from '../../execution/common/engineContract.ts'
14
- import type { RuntimeProgram } from '../../execution/common/runtime.ts'
13
+ import type { EngineContract } from '../../runtime/common/engineContract.ts'
14
+ import type { RuntimeProgram } from '../../runtime/common/runtime.ts'
15
15
  import type { ConnectorActionCapability, ConnectorCapability, Graph, InputMapping, JsonValue, Port, RevisionContent, SchemaMismatch } from './change.ts'
16
16
 
17
- import { findEngineContract } from '../../execution/common/engineContract.ts'
17
+ import { findEngineContract } from '../../runtime/common/engineContract.ts'
18
18
  import { agentConfigIssues } from './agent.ts'
19
19
  import { decodeTaskCapabilities } from './change.ts'
20
20
  import { canonicalJsonBytes, canonicalModule, canonicalRevisionGraph, digestBytes } from './encoding.ts'
@@ -445,6 +445,13 @@ export async function prepareFlow(revision: RevisionContent, engineContract: str
445
445
  Object.values(revision.document.graph.nodes).some((node) => node.kind == 'task' && 'executor' in node.task && node.task.executor.kind == 'http-request')
446
446
  )
447
447
  return { kind: 'engine-unsupported' }
448
+ if (
449
+ !engine.codeFetch &&
450
+ Object.values(revision.document.graph.nodes).some(
451
+ (node) => node.kind == 'task' && 'moduleId' in node.task && node.task.capabilities?.some((item) => item.kind == 'http'),
452
+ )
453
+ )
454
+ return { kind: 'engine-unsupported' }
448
455
  if (!engine.workflowTools && Object.values(revision.document.graph.nodes).some((node) => nodeWorkflowReferences(node).some((ref) => ref.tool != null)))
449
456
  return { kind: 'engine-unsupported' }
450
457
  if (triggerId != null) {
@@ -1,4 +1,4 @@
1
- import type { InputPort, JsonValue, Port, PortDefinition } from './change.ts'
1
+ import type { InputPort, JsonValue, Port, PortDefinition } from './model.ts'
2
2
 
3
3
  import { httpHeaderNameIssue } from '../../http/common/headers.ts'
4
4
 
@@ -0,0 +1,31 @@
1
+ import type { GraphNode } from './change.ts'
2
+ import type { WorkflowCapability } from './model.ts'
3
+
4
+ /** Workflow declarations share discovery across authoring, admission and resource indexing. */
5
+ export function nodeWorkflowReferences(node: GraphNode): readonly {
6
+ readonly target: Omit<WorkflowCapability, 'kind' | 'name'>
7
+ readonly path: string
8
+ readonly tool?: string
9
+ }[] {
10
+ if (node.kind != 'task') return []
11
+ if ('moduleId' in node.task)
12
+ return (node.task.capabilities ?? []).flatMap((item, index) =>
13
+ item.kind == 'workflow' ? [{ target: item, path: `task/capabilities/${index}`, tool: item.name }] : [],
14
+ )
15
+ if (node.task.executor.kind == 'workflow')
16
+ return [
17
+ {
18
+ target: {
19
+ ...node.task.executor,
20
+ inputs: node.task.inputs.filter((port) => 'handle' in port),
21
+ outputs: node.task.outputs.filter((port) => 'handle' in port),
22
+ },
23
+ path: 'task/executor',
24
+ },
25
+ ]
26
+ if (node.task.executor.kind == 'agent')
27
+ return node.task.executor.tools.flatMap((item, index) =>
28
+ item.kind == 'workflow' ? [{ target: item, path: `task/executor/tools/${index}`, tool: item.name }] : [],
29
+ )
30
+ return []
31
+ }
@@ -1,12 +1,9 @@
1
- import type { FlowDocument } from '../../flow/common/change.ts'
1
+ import type { InputMapping } from '../../flow/common/model.ts'
2
2
 
3
- /** Never allow credentials to enter a Revision as literal node inputs. */
4
- export function assertHttpAuthBindings(document: FlowDocument): void {
5
- for (const node of Object.values(document.graph.nodes)) {
6
- if (node.kind != 'task' || !('executor' in node.task) || !['openapi', 'http-request'].includes(node.task.executor.kind)) continue
7
- for (const [handle, mapping] of Object.entries(node.inputs)) {
8
- if (handle.startsWith('auth.') && mapping.kind == 'value')
9
- throw new TypeError('HTTP authentication accepts deployment variables and upstream outputs only.')
10
- }
3
+ /** Never allow credentials to enter a Revision as literal inputs of an OpenAPI or HTTP request node. */
4
+ export function assertHttpAuthBindings(inputs: Readonly<Record<string, InputMapping>>): void {
5
+ for (const [handle, mapping] of Object.entries(inputs)) {
6
+ if (handle.startsWith('auth.') && mapping.kind == 'value')
7
+ throw new TypeError('HTTP authentication accepts deployment variables and upstream outputs only.')
11
8
  }
12
9
  }
@@ -0,0 +1,13 @@
1
+ /** Hosts of a Code `http` Capability: lowercase DNS names or IPv4 addresses; `*.example.com` matches subdomains only. */
2
+ export const httpHostPattern = /^(?:\*\.)?(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)*[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/
3
+ export const maxHttpHosts = 20
4
+
5
+ /** Wildcards must name a registrable suffix, so `*.com` is rejected. */
6
+ export function isHttpHost(value: string): boolean {
7
+ return httpHostPattern.test(value) && (!value.startsWith('*.') || value.slice(2).includes('.'))
8
+ }
9
+
10
+ export function httpHostAllowed(hosts: readonly string[], hostname: string): boolean {
11
+ const host = hostname.toLowerCase()
12
+ return hosts.some((pattern) => (pattern.startsWith('*.') ? host.endsWith(pattern.slice(1)) : host == pattern))
13
+ }
@@ -1,10 +1,12 @@
1
- import type { InputMapping, InputPort, JsonValue, ManagedTaskDefinition, TaskNode } from '../../flow/common/change.ts'
1
+ import type { InputMapping, InputPort, JsonValue, ManagedTaskDefinitionOf, TaskDefinitionBase, TaskExecutor } from '../../flow/common/model.ts'
2
2
 
3
3
  import { dequal } from 'dequal/lite'
4
4
  import { z } from 'zod'
5
5
  import { remapInputMappings, renamedPort } from '../../flow/common/portChanges.ts'
6
6
  import { httpHeaderNameIssue } from './headers.ts'
7
7
 
8
+ export { httpHostAllowed, isHttpHost, maxHttpHosts } from './hosts.ts'
9
+
8
10
  const parameterSchema = z.strictObject({ mode: z.enum(['fields', 'object']).default('fields'), names: z.array(z.string()).default([]) })
9
11
  export const httpRequestConfigSchema = z.strictObject({
10
12
  method: z.enum(['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS']).default('GET'),
@@ -35,10 +37,10 @@ export function httpAuthHandles(config: HttpRequestConfig): string[] {
35
37
  return config.authentication.type == 'none' ? [] : config.authentication.type == 'basic' ? ['auth.username', 'auth.password'] : ['auth.token']
36
38
  }
37
39
  // Preserve metadata on ports whose contracts did not change.
38
- function retain(ports: readonly InputPort[], old: ManagedTaskDefinition['inputs']): ManagedTaskDefinition['inputs'] {
40
+ function retain(ports: readonly InputPort[], old: TaskDefinitionBase['inputs']): TaskDefinitionBase['inputs'] {
39
41
  const remaining = new Map(ports.map((port) => [port.handle, port]))
40
42
  return [
41
- ...old.flatMap((item): ManagedTaskDefinition['inputs'] => {
43
+ ...old.flatMap((item): TaskDefinitionBase['inputs'] => {
42
44
  if ('group' in item) return [item]
43
45
  const next = remaining.get(item.handle)
44
46
  if (next == null) return []
@@ -51,8 +53,8 @@ function retain(ports: readonly InputPort[], old: ManagedTaskDefinition['inputs'
51
53
  export function httpRequestTask(
52
54
  config: HttpRequestConfig = httpRequestConfigSchema.parse({}),
53
55
  name = 'HTTP Request',
54
- previous?: ManagedTaskDefinition,
55
- ): ManagedTaskDefinition {
56
+ previous?: TaskDefinitionBase,
57
+ ): ManagedTaskDefinitionOf<HttpRequestExecutor> {
56
58
  const inputs: InputPort[] = [{ handle: 'url', jsonSchema: { type: 'string' }, nullable: false }]
57
59
  for (const group of ['query', 'headers'] as const) {
58
60
  if (config[group].mode == 'object') inputs.push({ handle: group, jsonSchema: group == 'query' ? httpQuerySchema : httpHeaderSchema, nullable: true })
@@ -218,18 +220,29 @@ export function parseHttpResponse(config: HttpRequestConfig, status: number, hea
218
220
  /** Only a named parameter rename carries a value; changing source modes is not a rename. */
219
221
  export function httpRequestInputs(
220
222
  mappings: Readonly<Record<string, InputMapping>>,
221
- previous: ManagedTaskDefinition,
222
- next: ManagedTaskDefinition,
223
+ previous: TaskDefinitionBase,
224
+ next: TaskDefinitionBase,
223
225
  ): Record<string, InputMapping> {
224
226
  const candidate = renamedPort(previous.inputs, next.inputs)
225
227
  const rename = candidate != null && ['query.', 'header.'].some((prefix) => candidate.every((name) => name.startsWith(prefix))) ? candidate : undefined
226
228
  return remapInputMappings(mappings, next.inputs, rename)
227
229
  }
228
230
 
231
+ /** A Task node as HTTP helpers read it; inline Tasks have no executor. */
232
+ interface HttpTaskNode {
233
+ readonly inputs: Readonly<Record<string, InputMapping>>
234
+ readonly task: TaskDefinitionBase & { readonly executor?: TaskExecutor }
235
+ }
236
+
237
+ function httpRequestExecutor(node: HttpTaskNode): HttpRequestExecutor | undefined {
238
+ const executor = node.task.executor
239
+ return executor?.kind === 'http-request' ? (executor as HttpRequestExecutor) : undefined
240
+ }
241
+
229
242
  /** The object editor assembles named inputs without changing unrelated request settings. */
230
- export function withHttpParameterFields(node: TaskNode, group: 'query' | 'headers', values: Record<string, JsonValue>): TaskNode {
231
- if (!('executor' in node.task) || node.task.executor.kind !== 'http-request') return node
232
- const config = node.task.executor
243
+ export function withHttpParameterFields<Node extends HttpTaskNode>(node: Node, group: 'query' | 'headers', values: Record<string, JsonValue>): Node {
244
+ const config = httpRequestExecutor(node)
245
+ if (config == null) return node
233
246
  const task = httpRequestTask({ ...config, [group]: { mode: 'fields', names: Object.keys(values) } }, node.task.name, node.task)
234
247
  const inputs = httpRequestInputs(node.inputs, node.task, task)
235
248
  const rename = renamedPort(node.task.inputs, task.inputs)
@@ -238,14 +251,16 @@ export function withHttpParameterFields(node: TaskNode, group: 'query' | 'header
238
251
  const existing = config[group].mode === 'fields' && (config[group].names.includes(name) || rename?.[1] === handle)
239
252
  if (inputs[handle] == null && !existing) inputs[handle] = { kind: 'value', value }
240
253
  }
241
- return { ...node, task, inputs }
254
+ // Only the HTTP Task and its input mappings change, so the node keeps its own type.
255
+ return { ...node, task, inputs } as Node
242
256
  }
243
257
 
244
- export function withHttpParameterSource(node: TaskNode, group: 'query' | 'headers', mapping?: InputMapping): TaskNode {
245
- if (!('executor' in node.task) || node.task.executor.kind !== 'http-request') return node
246
- const task = httpRequestTask({ ...node.task.executor, [group]: { mode: 'object', names: [] } }, node.task.name, node.task)
258
+ export function withHttpParameterSource<Node extends HttpTaskNode>(node: Node, group: 'query' | 'headers', mapping?: InputMapping): Node {
259
+ const config = httpRequestExecutor(node)
260
+ if (config == null) return node
261
+ const task = httpRequestTask({ ...config, [group]: { mode: 'object', names: [] } }, node.task.name, node.task)
247
262
  const inputs = httpRequestInputs(node.inputs, node.task, task)
248
263
  if (mapping == null) delete inputs[group]
249
264
  else inputs[group] = mapping
250
- return { ...node, task, inputs }
265
+ return { ...node, task, inputs } as Node
251
266
  }
@@ -1,4 +1,4 @@
1
- import type { InputPort, JsonValue, ManagedTaskDefinition, Port } from '../../flow/common/change.ts'
1
+ import type { InputPort, JsonValue, ManagedTaskDefinitionOf, Port, TaskExecutor } from '../../flow/common/model.ts'
2
2
 
3
3
  import { dequal } from 'dequal/lite'
4
4
  import { matchesSchema } from '../../flow/common/schema.ts'
@@ -224,6 +224,10 @@ function schema(doc: ObjectValue, schemaValue: JsonValue): JsonValue {
224
224
  if (typeof converted == 'boolean') return converted
225
225
  return { $schema: 'https://json-schema.org/draft/2020-12/schema', ...object(converted), ...(Object.keys(defs).length ? { $defs: defs } : {}) }
226
226
  }
227
+ function isOpenApiExecutor(executor: TaskExecutor): executor is OpenApiExecutor {
228
+ return executor.kind == 'openapi'
229
+ }
230
+
227
231
  function parameters(config: OpenApiExecutor): ObjectValue[] {
228
232
  const doc = parseOpenApi(config.document)
229
233
  const { item, op } = operation(doc, config.path, config.method)
@@ -250,7 +254,7 @@ function jsonContent(doc: ObjectValue, value: JsonValue): { schema: JsonValue; r
250
254
  export function authHandles(auth: readonly OpenApiAuth[]): string[] {
251
255
  return auth.flatMap((scheme) => (scheme.type == 'basic' ? [`auth.${scheme.id}.username`, `auth.${scheme.id}.password`] : [`auth.${scheme.id}.token`]))
252
256
  }
253
- export function openApiTask(config: OpenApiExecutor, name = 'OpenAPI'): ManagedTaskDefinition {
257
+ export function openApiTask(config: OpenApiExecutor, name = 'OpenAPI'): ManagedTaskDefinitionOf<OpenApiExecutor> {
254
258
  if (!config.path) return { name, executor: config, inputs: [], outputs: [] }
255
259
  const doc = parseOpenApi(config.document)
256
260
  const { op } = operation(doc, config.path, config.method)
@@ -318,8 +322,8 @@ export interface OpenApiFieldIssue {
318
322
  }
319
323
 
320
324
  /** The canvas validator and property panel share the same configuration contract. */
321
- export function openApiFieldIssues(task: ManagedTaskDefinition): OpenApiFieldIssue[] {
322
- if (task.executor.kind != 'openapi') return []
325
+ export function openApiFieldIssues<Executor extends TaskExecutor>(task: ManagedTaskDefinitionOf<Executor>): OpenApiFieldIssue[] {
326
+ if (!isOpenApiExecutor(task.executor)) return []
323
327
  const config = task.executor
324
328
  let field: OpenApiFieldIssue['field'] = 'sourceUrl'
325
329
  try {
@@ -342,7 +346,7 @@ export function openApiFieldIssues(task: ManagedTaskDefinition): OpenApiFieldIss
342
346
  }
343
347
  }
344
348
 
345
- export function openApiIssues(task: ManagedTaskDefinition): string[] {
349
+ export function openApiIssues(task: ManagedTaskDefinitionOf<TaskExecutor>): string[] {
346
350
  return openApiFieldIssues(task).map((issue) => issue.message)
347
351
  }
348
352
  export function selectOperation(value: JsonValue, sourceUrl: string, path: string, method: string): OpenApiExecutor {
@@ -7,25 +7,31 @@ export interface EngineContract {
7
7
  readonly webhookResponses?: boolean
8
8
  readonly workflowTools?: boolean
9
9
  readonly httpRequests?: boolean
10
+ /** Code may call `context.fetch` for hosts declared by its `http` Capability. */
11
+ readonly codeFetch?: boolean
10
12
  }
11
13
 
12
- export const currentEngineContract = 'open-flow-engine/v8'
14
+ /** v9 is a single contract: the Node compatibility builtins, `context.fetch`, and no `context.egress`. */
15
+ export const currentEngineContract = 'open-flow-engine/v9'
16
+ /** The published v8 Node compatibility contract, retained for existing Publications and Runs. */
13
17
  export const nodejsEngineContract = 'open-flow-engine/v8/nodejs-compat-v1'
14
18
 
15
- const currentContract: EngineContract = {
19
+ const platformSource = (contract: string) => `export const engineContract = ${JSON.stringify(contract)}
20
+ export function identity(value) { return value }
21
+ `
22
+
23
+ const v8Contract: EngineContract = {
16
24
  workflowTools: true,
17
25
  httpRequests: true,
18
26
  waitAutoContinue: true,
19
27
  webhookResponses: true,
20
28
  platformExports: new Set(['engineContract', 'identity']),
21
29
  platformModule: 'open-flow:platform',
22
- platformSource: `export const engineContract = ${JSON.stringify(currentEngineContract)}
23
- export function identity(value) { return value }
24
- `,
30
+ platformSource: platformSource('open-flow-engine/v8'),
25
31
  }
26
32
 
27
33
  const nodejsContract: EngineContract = {
28
- ...currentContract,
34
+ ...v8Contract,
29
35
  builtinModules: new Set(
30
36
  [
31
37
  'assert',
@@ -54,31 +60,30 @@ const nodejsContract: EngineContract = {
54
60
  'zlib',
55
61
  ].flatMap((name) => [name, `node:${name}`]),
56
62
  ),
57
- platformSource: `export const engineContract = ${JSON.stringify(nodejsEngineContract)}
58
- export function identity(value) { return value }
59
- `,
63
+ platformSource: platformSource(nodejsEngineContract),
60
64
  }
61
65
 
66
+ const currentContract: EngineContract = { ...nodejsContract, codeFetch: true, platformSource: platformSource(currentEngineContract) }
67
+
62
68
  export function findEngineContract(contract: string): EngineContract | undefined {
63
69
  if (contract == currentEngineContract) return currentContract
70
+ if (contract == 'open-flow-engine/v8') return v8Contract
64
71
  if (contract == nodejsEngineContract) return nodejsContract
65
72
  if (contract == 'open-flow-engine/v7' || contract == 'open-flow-engine/v7/nodejs-compat-v1')
66
73
  return {
67
- ...(contract.endsWith('/nodejs-compat-v1') ? nodejsContract : currentContract),
74
+ ...(contract.endsWith('/nodejs-compat-v1') ? nodejsContract : v8Contract),
68
75
  workflowTools: false,
69
76
  httpRequests: false,
70
- platformSource: `export const engineContract = ${JSON.stringify(contract)}\nexport function identity(value) { return value }\n`,
77
+ platformSource: platformSource(contract),
71
78
  }
72
79
  if (contract == 'open-flow-engine/v6' || contract == 'open-flow-engine/v6/nodejs-compat-v1')
73
80
  return {
74
- ...(contract == 'open-flow-engine/v6' ? currentContract : nodejsContract),
81
+ ...(contract == 'open-flow-engine/v6' ? v8Contract : nodejsContract),
75
82
  workflowTools: false,
76
83
  httpRequests: false,
77
84
  waitAutoContinue: false,
78
85
  webhookResponses: false,
79
- platformSource: `export const engineContract = ${JSON.stringify(contract)}
80
- export function identity(value) { return value }
81
- `,
86
+ platformSource: platformSource(contract),
82
87
  }
83
88
  return undefined
84
89
  }