@oomol-lab/open-flow 0.1.0-beta.84 → 0.1.0-beta.86
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.
- package/dist/browser/{FlowCanvas-B2g7NceZ.js → FlowCanvas-CHIaMaBv.js} +112 -16
- package/dist/browser/{createResourceDialog-Bi4fUnbD.js → createResourceDialog-C0E5yPe6.js} +3 -3
- package/dist/browser/{dialog-BzoSxhXQ.js → dialog-b3hsdu8i.js} +291 -143
- package/dist/browser/{exportPng-DLdtJvg3.js → exportPng-DGHcejgy.js} +2 -2
- package/dist/browser/{flowChanges-C567g-Pc.js → flowChanges-BvSWezEb.js} +375 -83
- package/dist/browser/{flowWorkspace-DBk4A-Ld.js → flowWorkspace-BKnMRClO.js} +1619 -475
- package/dist/browser/fr-CNTSedYU.js +593 -0
- package/dist/browser/ja-CxQBHr2J.js +593 -0
- package/dist/browser/ko-D1Exiv1h.js +593 -0
- package/dist/browser/licenses.md +1 -1
- package/dist/browser/{markdownContent-DIuJThGK.js → markdownContent-BR9-1ikU.js} +1 -1
- package/dist/browser/preview.css +181 -181
- package/dist/browser/preview.js +2103 -636
- package/dist/browser/ru-BzLZv0B7.js +593 -0
- package/dist/browser/{tooltip-DGj5pudg.js → tooltip-DSBF3bRw.js} +55 -23
- package/dist/browser/workbench.css +181 -181
- package/dist/browser/workbench.js +86 -17
- package/dist/browser/{workbenchSelect-BIaEVTx6.js → workbenchSelect-Di1Bm_-d.js} +2 -2
- package/dist/browser/{wrapLayout-C4moMKgb.js → wrapLayout-B0AT8_-i.js} +1 -1
- package/dist/browser/zh-CN-Dg_xSGde.js +593 -0
- package/dist/browser/zh-TW-oxuLwsV0.js +593 -0
- package/dist/command/index.js +61 -57
- package/dist/command/licenses.md +28 -4
- package/dist/common/authoring.d.ts +5 -5
- package/dist/common/authoringExamples.d.ts +3 -2
- package/dist/common/authoringSchema.d.ts +41 -4
- package/dist/common/catalogCache.d.ts +46 -0
- package/dist/common/connector-action.js +13 -10
- package/dist/common/connector-proxy.d.ts +96 -0
- package/dist/common/connector-proxy.js +5209 -0
- package/dist/common/control-api-conformance.js +312 -46
- package/dist/common/control-api.d.ts +12 -1
- package/dist/common/control-api.js +303 -41
- package/dist/common/control-requests.d.ts +4 -3
- package/dist/common/control-requests.js +14111 -171
- package/dist/common/engine-contract.d.ts +3 -2
- package/dist/common/event-source.js +55 -23
- package/dist/common/flow-authoring-node.d.ts +2 -0
- package/dist/common/flow-authoring.js +6936 -6781
- package/dist/common/flow-change-schema.d.ts +1 -1
- package/dist/common/flow-change.d.ts +2 -2
- package/dist/common/flow-change.js +91 -36
- package/dist/common/flow-connectionUsage.d.ts +1 -1
- package/dist/common/flow-encoding.js +93 -38
- package/dist/common/flow-requestParameters.d.ts +34 -0
- package/dist/common/flow-semantics.js +9429 -9236
- package/dist/common/flow-state.js +55 -23
- package/dist/common/flow-workflowFile.d.ts +3 -1
- package/dist/common/http-request.d.ts +6 -11
- package/dist/common/http-request.js +192 -141
- package/dist/common/licenses.md +1 -1
- package/dist/common/mcp.d.ts +3 -3
- package/dist/common/mcp.js +303 -41
- package/dist/common/run-events.js +1 -0
- package/dist/common/runtime-contract.js +10 -4
- package/dist/common/runtime-nodejs-compat.js +55 -23
- package/dist/common/scheduler-checkpoint.d.ts +3 -3
- package/dist/common/scheduler-dependencies.d.ts +22 -28
- package/dist/common/scheduler-node-execution.d.ts +1 -0
- package/dist/common/scheduler.d.ts +8 -2
- package/dist/common/scheduler.js +274 -95
- package/dist/common/workflow.js +51 -19
- package/dist/common/workflowAuthoring.d.ts +411 -0
- package/dist/render/browser/licenses.md +1 -1
- package/dist/render/browser/runtime.js +289 -61
- package/dist/render/node/index.js +85 -30
- package/dist/render/node/licenses.md +1 -1
- package/package.json +1 -1
- package/skills/open-flow/references/flow-authoring.md +14 -13
- package/src/base/common/jsonSchema.ts +11 -0
- package/src/connector/common/actionSchema.ts +4 -11
- package/src/connector/common/proxy.ts +152 -0
- package/src/control/common/api.ts +99 -3
- package/src/control/common/authoring.ts +23 -9
- package/src/control/common/authoringExamples.ts +9 -0
- package/src/control/common/authoringSchema.ts +21 -12
- package/src/control/common/catalogCache.ts +145 -0
- package/src/control/common/conformance.ts +9 -3
- package/src/control/common/connectorQueries.ts +17 -2
- package/src/control/common/requests.ts +1 -0
- package/src/control/common/workflowAuthoring.ts +120 -0
- package/src/execution/common/checkpoint.ts +18 -12
- package/src/execution/common/dependencies.ts +132 -15
- package/src/execution/common/events.ts +3 -0
- package/src/execution/common/node-execution.ts +22 -15
- package/src/execution/common/scheduler.ts +20 -31
- package/src/flow/common/change.ts +2 -1
- package/src/flow/common/changeSchema.ts +11 -1
- package/src/flow/common/connectionUsage.ts +4 -2
- package/src/flow/common/errorHandling.ts +3 -1
- package/src/flow/common/nodeChanges.ts +35 -18
- package/src/flow/common/requestParameters.ts +120 -0
- package/src/flow/common/semantics.ts +51 -1
- package/src/flow/common/sourceField.ts +3 -2
- package/src/flow/common/workflowFile.ts +2 -2
- package/src/http/common/httpRequest.ts +28 -78
- package/src/runtime/common/engineContract.ts +6 -3
|
@@ -64,10 +64,27 @@ export type { WorkflowFile, WorkflowCleanupSummary } from '../../flow/common/wor
|
|
|
64
64
|
export type { WorkflowBundle, WorkflowExportPreview } from '../../flow/common/workflowBundle.ts'
|
|
65
65
|
import type { InputPortDefinition, JsonValue, PortDefinition, RevisionContent, TriggerKeySnapshot, WaitAction } from '../../flow/common/change.ts'
|
|
66
66
|
import type { FlowResourceReferences } from '../../flow/common/semantics.ts'
|
|
67
|
+
import type { ControlCacheOptions } from './catalogCache.ts'
|
|
67
68
|
|
|
69
|
+
import { ControlCatalogCache } from './catalogCache.ts'
|
|
68
70
|
import { flowCheck } from './checkDecoders.ts'
|
|
69
|
-
import {
|
|
70
|
-
|
|
71
|
+
import {
|
|
72
|
+
connection,
|
|
73
|
+
connectorAccess,
|
|
74
|
+
connectorAccessSnapshot,
|
|
75
|
+
connectorAccessCandidatesBatch,
|
|
76
|
+
connectorAction,
|
|
77
|
+
connectorActionMetadata,
|
|
78
|
+
leinaBots,
|
|
79
|
+
} from './connectorDecoders.ts'
|
|
80
|
+
import {
|
|
81
|
+
allConnectorConnectionsQuery,
|
|
82
|
+
connectorActionQuery,
|
|
83
|
+
connectorActionMetadataQuery,
|
|
84
|
+
connectorConnectionsQuery,
|
|
85
|
+
connectorProvidersQuery,
|
|
86
|
+
} from './connectorQueries.ts'
|
|
87
|
+
export type { ControlCacheDelegate, ControlCacheOptions, ControlCacheRead } from './catalogCache.ts'
|
|
71
88
|
import { requiredFields, integer, invalidResponse, jsonValue, record, string } from './decoding.ts'
|
|
72
89
|
import { errorListener, flow, flowPage, variable, variableListItem, variableReferences } from './flowDecoders.ts'
|
|
73
90
|
import { live, publication, publicationPage, publishOperation } from './publicationDecoders.ts'
|
|
@@ -726,6 +743,7 @@ export function decodeRunEvent(value: unknown) {
|
|
|
726
743
|
nodeKind !== 'approval' &&
|
|
727
744
|
nodeKind !== 'condition' &&
|
|
728
745
|
nodeKind !== 'connector' &&
|
|
746
|
+
nodeKind !== 'connector-proxy' &&
|
|
729
747
|
nodeKind !== 'javascript' &&
|
|
730
748
|
nodeKind !== 'decision' &&
|
|
731
749
|
nodeKind !== 'openapi' &&
|
|
@@ -850,10 +868,14 @@ export type ConditionalResult<T> =
|
|
|
850
868
|
export class ControlClient {
|
|
851
869
|
private readonly requestControl: ControlRequest
|
|
852
870
|
private readonly channel: 'workbench' | 'cli' | 'api'
|
|
871
|
+
private readonly catalogCache?: ControlCatalogCache
|
|
872
|
+
private readonly options: { readonly cache?: ControlCacheOptions }
|
|
853
873
|
|
|
854
|
-
constructor(requestControl: ControlRequest, channel: 'workbench' | 'cli' | 'api' = 'api') {
|
|
874
|
+
constructor(requestControl: ControlRequest, channel: 'workbench' | 'cli' | 'api' = 'api', options: { readonly cache?: ControlCacheOptions } = {}) {
|
|
875
|
+
this.options = options
|
|
855
876
|
this.channel = channel
|
|
856
877
|
this.requestControl = requestControl
|
|
878
|
+
if (options.cache != null) this.catalogCache = new ControlCatalogCache(options.cache)
|
|
857
879
|
}
|
|
858
880
|
|
|
859
881
|
async loadOpenApiDocument(url: string, signal?: AbortSignal): Promise<JsonValue> {
|
|
@@ -1140,6 +1162,14 @@ export class ControlClient {
|
|
|
1140
1162
|
}
|
|
1141
1163
|
|
|
1142
1164
|
async getTriggerCatalog(locale: string, cached?: TriggerCatalogCache, signal?: AbortSignal): Promise<TriggerCatalogCache> {
|
|
1165
|
+
if (this.catalogCache != null) {
|
|
1166
|
+
const result = await this.catalogResponse(
|
|
1167
|
+
{ path: `/v1/trigger-keys/catalog?locale=${encodeURIComponent(locale)}`, decode: decodeTriggerCatalog },
|
|
1168
|
+
300_000,
|
|
1169
|
+
signal,
|
|
1170
|
+
)
|
|
1171
|
+
return { data: result.data, etag: result.etag }
|
|
1172
|
+
}
|
|
1143
1173
|
const headers = new Headers()
|
|
1144
1174
|
if (cached?.etag) headers.set('if-none-match', cached.etag)
|
|
1145
1175
|
const response = await this.response(`/v1/trigger-keys/catalog?locale=${encodeURIComponent(locale)}`, { headers, signal }, true)
|
|
@@ -1151,6 +1181,23 @@ export class ControlClient {
|
|
|
1151
1181
|
}
|
|
1152
1182
|
|
|
1153
1183
|
async getTriggerKey(key: string, signal?: AbortSignal): Promise<TriggerKeySnapshot> {
|
|
1184
|
+
if (this.catalogCache != null)
|
|
1185
|
+
return (
|
|
1186
|
+
await this.catalogResponse(
|
|
1187
|
+
{
|
|
1188
|
+
path: `/v1/trigger-keys/${segment(key)}`,
|
|
1189
|
+
decode: (value) => {
|
|
1190
|
+
const source = record(value)
|
|
1191
|
+
if (source.version != 1) return invalidResponse()
|
|
1192
|
+
const definition = triggerKey(source.definition)
|
|
1193
|
+
if (definition.key != key) return invalidResponse('Trigger key does not match the requested definition.')
|
|
1194
|
+
return definition
|
|
1195
|
+
},
|
|
1196
|
+
},
|
|
1197
|
+
300_000,
|
|
1198
|
+
signal,
|
|
1199
|
+
)
|
|
1200
|
+
).data
|
|
1154
1201
|
const source = record(await this.request(`/v1/trigger-keys/${segment(key)}`, { signal }))
|
|
1155
1202
|
if (source.version != 1) return invalidResponse()
|
|
1156
1203
|
return triggerKey(source.definition)
|
|
@@ -1265,6 +1312,7 @@ export class ControlClient {
|
|
|
1265
1312
|
|
|
1266
1313
|
async listConnectorProviders(signal?: AbortSignal, flowId?: string, locale?: string): Promise<readonly ConnectorProvider[]> {
|
|
1267
1314
|
const query = connectorProvidersQuery(flowId, locale)
|
|
1315
|
+
if (this.catalogCache != null) return (await this.catalogResponse(query, 300_000, signal)).data
|
|
1268
1316
|
return this.connectorRequest(query.path, 'providers', signal, query.decode)
|
|
1269
1317
|
}
|
|
1270
1318
|
|
|
@@ -1350,6 +1398,21 @@ export class ControlClient {
|
|
|
1350
1398
|
return this.connectorRequest(query.path, 'actions', signal, query.decode, fresh)
|
|
1351
1399
|
}
|
|
1352
1400
|
|
|
1401
|
+
async listConnectorActionMetadata(serviceId?: string, signal?: AbortSignal, flowId?: string, locale?: string): Promise<readonly ConnectorActionMetadata[]> {
|
|
1402
|
+
return this.connectorMetadata(
|
|
1403
|
+
{ ...(serviceId == null ? {} : { service: serviceId }), ...(flowId == null ? {} : { flowId }), ...(locale == null ? {} : { locale }) },
|
|
1404
|
+
signal,
|
|
1405
|
+
)
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1408
|
+
async searchConnectorActionMetadata(query: string, signal?: AbortSignal, flowId?: string, locale?: string): Promise<readonly ConnectorActionMetadata[]> {
|
|
1409
|
+
return this.connectorMetadata({ q: query.trim(), ...(flowId == null ? {} : { flowId }), ...(locale == null ? {} : { locale }) }, signal)
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1412
|
+
async getConnectorActionMetadata(actionId: string, signal?: AbortSignal, flowId?: string, locale?: string, fresh = false): Promise<ConnectorActionMetadata> {
|
|
1413
|
+
return (await this.catalogResponse(connectorActionMetadataQuery(actionId, flowId, locale), 30_000, signal, fresh)).data
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1353
1416
|
async listAllConnectorConnections(signal?: AbortSignal, flowId?: string, fresh = false): Promise<readonly ConnectorConnection[]> {
|
|
1354
1417
|
const query = allConnectorConnectionsQuery(flowId)
|
|
1355
1418
|
return this.connectorRequest(query.path, 'connections', signal, query.decode, fresh)
|
|
@@ -1618,6 +1681,38 @@ export class ControlClient {
|
|
|
1618
1681
|
)
|
|
1619
1682
|
}
|
|
1620
1683
|
|
|
1684
|
+
private async catalogResponse<Value>(
|
|
1685
|
+
query: { readonly path: string; readonly decode: (value: unknown) => Value },
|
|
1686
|
+
maxAgeMs: number,
|
|
1687
|
+
signal?: AbortSignal,
|
|
1688
|
+
fresh = false,
|
|
1689
|
+
) {
|
|
1690
|
+
if (this.catalogCache != null)
|
|
1691
|
+
return this.catalogCache.read(query, (etag) => this.readCatalog({ path: query.path, decode: jsonValue }, etag), maxAgeMs, signal, fresh)
|
|
1692
|
+
const response = await this.readCatalog(query, null, signal)
|
|
1693
|
+
if (!response.modified) return invalidResponse()
|
|
1694
|
+
return { data: response.data, etag: response.etag }
|
|
1695
|
+
}
|
|
1696
|
+
|
|
1697
|
+
private async connectorMetadata(parameters: Readonly<Record<string, string>>, signal?: AbortSignal): Promise<readonly ConnectorActionMetadata[]> {
|
|
1698
|
+
const query = new URLSearchParams(parameters).toString()
|
|
1699
|
+
return (
|
|
1700
|
+
await this.catalogResponse(
|
|
1701
|
+
{
|
|
1702
|
+
path: `/v1/connector/action-metadata${query ? `?${query}` : ''}`,
|
|
1703
|
+
decode: (value) => {
|
|
1704
|
+
const source = record(value)
|
|
1705
|
+
requiredFields(source, ['actions', 'version'])
|
|
1706
|
+
if (source.version != 1 || !Array.isArray(source.actions)) return invalidResponse()
|
|
1707
|
+
return source.actions.map(connectorActionMetadata)
|
|
1708
|
+
},
|
|
1709
|
+
},
|
|
1710
|
+
30_000,
|
|
1711
|
+
signal,
|
|
1712
|
+
)
|
|
1713
|
+
).data
|
|
1714
|
+
}
|
|
1715
|
+
|
|
1621
1716
|
private async connectorActions(parameters: Readonly<Record<string, string>>, signal?: AbortSignal): Promise<readonly ConnectorAction[]> {
|
|
1622
1717
|
const query = Object.entries(parameters)
|
|
1623
1718
|
.map(([key, value]) => `${segment(key)}=${segment(value)}`)
|
|
@@ -1642,6 +1737,7 @@ export class ControlClient {
|
|
|
1642
1737
|
}
|
|
1643
1738
|
|
|
1644
1739
|
protected async response(path: string, init: RequestInit, allowNotModified = false): Promise<Response> {
|
|
1740
|
+
if (this.options.cache?.mode == 'offline') throw new ApiError(503, 'control.offline', 'Network requests are disabled for this Control client.')
|
|
1645
1741
|
const headers = new Headers(init.headers)
|
|
1646
1742
|
if (init.body != null) headers.set('content-type', 'application/json')
|
|
1647
1743
|
const response = await this.requestControl(path, { ...init, headers })
|
|
@@ -16,7 +16,7 @@ import type { AuthoringRead, AuthoringRequest, AuthoringSearch, AuthoringSource,
|
|
|
16
16
|
import type { AuthoringAgentTool, AuthoringCapability } from './authoringTools.ts'
|
|
17
17
|
|
|
18
18
|
import { dequal } from 'dequal/lite'
|
|
19
|
-
import {
|
|
19
|
+
import { connectorProxyConfigSchema, connectorProxyInputs, connectorProxyTask } from '../../connector/common/proxy.ts'
|
|
20
20
|
import { decisionTask } from '../../decision/common/decision.ts'
|
|
21
21
|
import { supportsNodeRetry, isTriggerNode, applyFlowChanges, decodeChangeOperations, upgradeRevisionContent } from '../../flow/common/change.ts'
|
|
22
22
|
import { nodeInputMappings } from '../../flow/common/condition.ts'
|
|
@@ -40,6 +40,7 @@ import {
|
|
|
40
40
|
updateStartInputDefs,
|
|
41
41
|
updateOutputsDefs,
|
|
42
42
|
renameFlowOutputSources,
|
|
43
|
+
setInputMappings,
|
|
43
44
|
} from '../../flow/common/nodeChanges.ts'
|
|
44
45
|
import { retryConfigurationSchema } from '../../flow/common/retry.ts'
|
|
45
46
|
import { httpRequestTask, httpRequestConfigSchema, httpRequestInputs } from '../../http/common/httpRequest.ts'
|
|
@@ -147,7 +148,7 @@ export function authoringNode(content: RevisionContent, ref: string) {
|
|
|
147
148
|
),
|
|
148
149
|
}
|
|
149
150
|
else if (task.executor.kind == 'workflow') config = { flowId: task.executor.flowId }
|
|
150
|
-
else if (task.executor.kind == 'http-request' || task.executor.kind == 'skip-duplicates') {
|
|
151
|
+
else if (task.executor.kind == 'http-request' || task.executor.kind == 'skip-duplicates' || task.executor.kind == 'connector-proxy') {
|
|
151
152
|
const { kind: _kind, ...settings } = task.executor
|
|
152
153
|
config = settings
|
|
153
154
|
} else if (task.executor.kind == 'openapi') {
|
|
@@ -319,7 +320,7 @@ export interface AuthoringHost {
|
|
|
319
320
|
workflow?(flowId: string): Promise<WorkflowContract | WorkflowCandidate>
|
|
320
321
|
id(label: string): string
|
|
321
322
|
action(id: string): Promise<ConnectorAction>
|
|
322
|
-
trigger(key: string): TriggerKeySnapshot
|
|
323
|
+
trigger(key: string): TriggerKeySnapshot | Promise<TriggerKeySnapshot>
|
|
323
324
|
openapi(url: string): Promise<JsonValue>
|
|
324
325
|
}
|
|
325
326
|
function merge(base: unknown, patch: unknown): unknown {
|
|
@@ -407,13 +408,20 @@ export async function compileAuthoring(base: RevisionContent, request: Authoring
|
|
|
407
408
|
|
|
408
409
|
async function connectorTask(config: Record<string, unknown>, name: string): Promise<ManagedTaskDefinition> {
|
|
409
410
|
const action = await host.action(String(config.action))
|
|
411
|
+
// The source owns projection. A whole-response port carries the root schema;
|
|
412
|
+
// named ports carry their field schemas, including a field named "output".
|
|
413
|
+
const wholeResponse =
|
|
414
|
+
action.outputSchema !== undefined &&
|
|
415
|
+
Object.keys(action.outputs).length == 1 &&
|
|
416
|
+
Object.hasOwn(action.outputs, 'output') &&
|
|
417
|
+
dequal(action.outputs.output!.jsonSchema, action.outputSchema)
|
|
410
418
|
return {
|
|
411
419
|
name,
|
|
412
420
|
executor: {
|
|
413
421
|
kind: 'connector',
|
|
414
422
|
action: action.actionId,
|
|
415
423
|
...(config.connectionId == null ? {} : { connectionId: String(config.connectionId) }),
|
|
416
|
-
...(
|
|
424
|
+
...(wholeResponse ? { outputMode: 'value' as const } : {}),
|
|
417
425
|
},
|
|
418
426
|
inputs: Object.entries(action.inputs).map(([handle, port]) => Object.assign({ handle }, port)),
|
|
419
427
|
outputs: Object.entries(action.outputs).map(([handle, port]) => Object.assign({ handle }, port)),
|
|
@@ -524,7 +532,10 @@ export async function compileAuthoring(base: RevisionContent, request: Authoring
|
|
|
524
532
|
} else if (type == 'decision') task = decisionTask(config.questions as Parameters<typeof decisionTask>[0], String(update.name))
|
|
525
533
|
else if (type == 'skip-duplicates')
|
|
526
534
|
task = { ...before, name: String(update.name), executor: { kind: 'skip-duplicates', ...skipDuplicatesConfigSchema.parse(config) } }
|
|
527
|
-
else if (type == '
|
|
535
|
+
else if (type == 'connector-proxy') {
|
|
536
|
+
task = connectorProxyTask(connectorProxyConfigSchema.parse(config), String(update.name), before)
|
|
537
|
+
next = { ...next, inputs: connectorProxyInputs(node.inputs, before, task) } as GraphNode
|
|
538
|
+
} else if (type == 'http-request') {
|
|
528
539
|
task = httpRequestTask(httpRequestConfigSchema.parse(config), String(update.name), before)
|
|
529
540
|
next = { ...next, inputs: httpRequestInputs(node.inputs, before, task) } as GraphNode
|
|
530
541
|
} else if (type == 'openapi') {
|
|
@@ -677,7 +688,7 @@ export async function compileAuthoring(base: RevisionContent, request: Authoring
|
|
|
677
688
|
replace(ref, { ...node, config })
|
|
678
689
|
return
|
|
679
690
|
}
|
|
680
|
-
if (!Object.hasOwn(inputDefinitions(node), input)) {
|
|
691
|
+
if (!Object.hasOwn(inputDefinitions(node), input) && !(typeOf(node) === 'connector-proxy' && input === 'body')) {
|
|
681
692
|
const type = typeOf(node)
|
|
682
693
|
if (!['agent', 'code', 'llm', 'wait', 'approval'].includes(type))
|
|
683
694
|
fail('input.not-found', `Unknown input ${input}.`, { node: ref, input, choices: Object.keys(inputDefinitions(node)) })
|
|
@@ -691,8 +702,8 @@ export async function compileAuthoring(base: RevisionContent, request: Authoring
|
|
|
691
702
|
: value.kind == 'value' || value.kind == 'unset'
|
|
692
703
|
? value
|
|
693
704
|
: { kind: 'sources', sources: (value.kind == 'sources' ? value.sources : [value]).map(source) }
|
|
694
|
-
const
|
|
695
|
-
if (
|
|
705
|
+
const changes = setInputMappings(content, ref, { [input]: mapping }) ?? []
|
|
706
|
+
if (changes.length > 0) apply(changes)
|
|
696
707
|
}
|
|
697
708
|
for (const edit of parsed.edits) {
|
|
698
709
|
try {
|
|
@@ -777,6 +788,9 @@ export async function compileAuthoring(base: RevisionContent, request: Authoring
|
|
|
777
788
|
case 'connector':
|
|
778
789
|
changes = createManagedTask(identity, await connectorTask(config, name))
|
|
779
790
|
break
|
|
791
|
+
case 'connector-proxy':
|
|
792
|
+
changes = createManagedTask(identity, connectorProxyTask(connectorProxyConfigSchema.parse(config), name))
|
|
793
|
+
break
|
|
780
794
|
case 'skip-duplicates':
|
|
781
795
|
changes = createManagedTask(identity, skipDuplicatesTask(skipDuplicatesConfigSchema.parse(config), name))
|
|
782
796
|
break
|
|
@@ -803,7 +817,7 @@ export async function compileAuthoring(base: RevisionContent, request: Authoring
|
|
|
803
817
|
break
|
|
804
818
|
case 'poll':
|
|
805
819
|
case 'integration': {
|
|
806
|
-
const definition = host.trigger(String(config.key))
|
|
820
|
+
const definition = await host.trigger(String(config.key))
|
|
807
821
|
if (definition.type != edit.type) fail('trigger.type-mismatch', 'Trigger key has a different type.')
|
|
808
822
|
changes = createProviderTrigger(nodeId, definition, { name, config: {} })
|
|
809
823
|
break
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { DraftOperation } from './draftOperations.ts'
|
|
2
2
|
|
|
3
|
+
import { connectorProxyConfigSchema, connectorProxyTask } from '../../connector/common/proxy.ts'
|
|
3
4
|
import {
|
|
4
5
|
createApproval,
|
|
5
6
|
createBuiltinTrigger,
|
|
@@ -27,6 +28,8 @@ const descriptions = {
|
|
|
27
28
|
'skip-duplicates':
|
|
28
29
|
'Skip items already recorded by successful Runs. Configure identity fields; an empty list uses each string or numeric item. History is private to the node and only commits after Run success.',
|
|
29
30
|
'http-request': 'A single HTTP request. Bind url and request inputs; outputs are body, statusCode and headers.',
|
|
31
|
+
'connector-proxy':
|
|
32
|
+
'A Provider API request through Connector Proxy. Select a Provider and account, then bind endpoint. Outputs preserve body, statusCode, headers and bodyEncoding.',
|
|
30
33
|
'openapi': 'A fixed JSON API operation. Replace the example document and URL before running.',
|
|
31
34
|
'connector':
|
|
32
35
|
'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.',
|
|
@@ -72,6 +75,12 @@ export function authoringExample(name: string): { version: 1; operations: readon
|
|
|
72
75
|
{ kind: 'graph.node.input.set', nodeId: 'http', handle: 'url', value: { kind: 'value', value: 'https://example.com' } },
|
|
73
76
|
]
|
|
74
77
|
break
|
|
78
|
+
case 'connector-proxy':
|
|
79
|
+
operations = [
|
|
80
|
+
...createManagedTask({ nodeId: 'request' }, connectorProxyTask(connectorProxyConfigSchema.parse({ providerId: 'github', connectionId: '' }))),
|
|
81
|
+
{ kind: 'graph.node.input.set', nodeId: 'request', handle: 'endpoint', value: { kind: 'value', value: '/user' } },
|
|
82
|
+
]
|
|
83
|
+
break
|
|
75
84
|
case 'openapi': {
|
|
76
85
|
const document = {
|
|
77
86
|
openapi: '3.1.0',
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { JsonValue } from '../../types/index.ts'
|
|
2
2
|
|
|
3
3
|
import { z } from 'zod'
|
|
4
|
+
import { connectorProxyConfigSchema } from '../../connector/common/proxy.ts'
|
|
4
5
|
import {
|
|
5
6
|
agentToolFields,
|
|
6
7
|
connectorAgentToolSchema,
|
|
@@ -31,6 +32,7 @@ export const authoringTypes = [
|
|
|
31
32
|
'integration',
|
|
32
33
|
'code',
|
|
33
34
|
'connector',
|
|
35
|
+
'connector-proxy',
|
|
34
36
|
'agent',
|
|
35
37
|
'llm',
|
|
36
38
|
'decision',
|
|
@@ -118,6 +120,7 @@ export const authoringConfigurations = {
|
|
|
118
120
|
'integration': z.strictObject({ key: id, connectionId: id.optional() }),
|
|
119
121
|
'code': z.strictObject({ inputs: inputFields, outputs: outputFields, capabilities: authoringCapabilities.optional() }),
|
|
120
122
|
'connector': definitions.connector.omit({ outputMode: true }).strict(),
|
|
123
|
+
'connector-proxy': connectorProxyConfigSchema,
|
|
121
124
|
'openapi': authoringOpenApiSchema,
|
|
122
125
|
'http-request': httpRequestConfigSchema,
|
|
123
126
|
'skip-duplicates': skipDuplicatesConfigSchema,
|
|
@@ -286,7 +289,8 @@ export function authoringSchema(kind?: AuthoringType) {
|
|
|
286
289
|
}
|
|
287
290
|
: {
|
|
288
291
|
type: kind,
|
|
289
|
-
onError:
|
|
292
|
+
onError:
|
|
293
|
+
kind == 'skip-duplicates' || kind == 'connector-proxy' || retryNodeTypes.some((nodeType) => nodeType == kind) ? { enum: ['stop', 'error'] } : false,
|
|
290
294
|
retry: retryNodeTypes.some((nodeType) => nodeType == kind) ? z.toJSONSchema(retryConfigurationSchema) : false,
|
|
291
295
|
inputSources: ['poll', 'integration'].includes(kind)
|
|
292
296
|
? ['value', 'unset', 'default']
|
|
@@ -311,17 +315,21 @@ export function authoringSchema(kind?: AuthoringType) {
|
|
|
311
315
|
? 'Configure model settings, message templates and history as defaults. Input bindings with the same names override these defaults; named business inputs can be bound directly. Template strings interpolate {{name}}.'
|
|
312
316
|
: kind == 'skip-duplicates'
|
|
313
317
|
? 'Bind an items array. Configure fields as dot-separated identity paths; an empty list uses each scalar item. Outputs are items and duplicates. History belongs to this node and commits only when the Run completes successfully. Fields accept strings or finite numbers; missing or complex values reject the batch.'
|
|
314
|
-
: kind == '
|
|
315
|
-
? 'Configure
|
|
316
|
-
: kind == '
|
|
317
|
-
? '
|
|
318
|
-
: kind == '
|
|
319
|
-
? 'Select
|
|
320
|
-
: kind == '
|
|
321
|
-
? '
|
|
322
|
-
: kind == '
|
|
323
|
-
?
|
|
324
|
-
:
|
|
318
|
+
: kind == 'connector-proxy'
|
|
319
|
+
? 'Call a Provider API using its Connector account. Configure providerId, connectionId, method and query/headers modes and names. Bind endpoint, query.<name>/header.<name> or complete query/headers objects, and optional body. Setting or binding body automatically selects JSON; clearing it omits the body. Explicit null is a JSON body. Outputs: body, statusCode, headers, bodyEncoding. Proxy permissions are independent of Action permissions. Upstream HTTP errors remain response data when Connector returns a successful proxy result. Unsupported Providers fail at execution. Use connectionId: "" to defer account configuration. Retries are not supported.'
|
|
320
|
+
: kind == 'http-request'
|
|
321
|
+
? 'Configure method, query/headers modes and names, bodyFormat, authentication, responseFormat and statusPolicy. Bind url, query.<name>/header.<name> or whole query/headers objects, body and auth.token (Basic: auth.username/auth.password). Credentials accept variables or upstream outputs only. Outputs: body, statusCode, headers. Authentication is replaced as a complete choice.'
|
|
322
|
+
: kind == 'openapi'
|
|
323
|
+
? 'Select an operation; the tool resolves its ports and authentication. Read authenticationChoices from node details. authentication is replaced as a complete choice. Bind advertised credentials through inputs.'
|
|
324
|
+
: kind == 'workflow'
|
|
325
|
+
? 'Select another workflow with config.flowId. A workflow cannot call itself, including in a disconnected node. Owned unpublished or unavailable targets may be saved as incomplete drafts. Use node.refresh to load the current published interface.'
|
|
326
|
+
: kind == 'connector'
|
|
327
|
+
? 'Select an Action and account. The platform derives ports from its schema. Open or dynamic response objects expose the complete response as one output; closed objects expose named fields. Read the created node for output names. Bind the whole response for dynamic fields; declared direct properties also support output field selection. Local edits preserve saved ports and response mapping.'
|
|
328
|
+
: kind == 'agent'
|
|
329
|
+
? 'Tools accept Connector Actions or {kind: "workflow", flowId, name, description, approval, inputs}. Workflow parameter sources use model, input or value. The platform derives the interface. Refresh a workflow tool with node.refresh and tool: its name.'
|
|
330
|
+
: kind == 'code'
|
|
331
|
+
? codeGuidance
|
|
332
|
+
: 'Supply business configuration and input data. Fixed definitions are assembled by the tool. Existing constraints and defaults are preserved on partial updates.',
|
|
325
333
|
example: {
|
|
326
334
|
op: 'node.add',
|
|
327
335
|
as: 'step',
|
|
@@ -333,6 +341,7 @@ export function authoringSchema(kind?: AuthoringType) {
|
|
|
333
341
|
'outputs': { config: { outputDefs: {} } },
|
|
334
342
|
'workflow': { config: { flowId: '<Flow ID>' } },
|
|
335
343
|
'connector': { config: { action: '<Action ID>', connectionId: '<Connection ID>' } },
|
|
344
|
+
'connector-proxy': { config: { providerId: 'github', connectionId: '' }, inputs: { endpoint: { kind: 'value', value: '/user' } } },
|
|
336
345
|
'poll': { config: { key: '<Trigger key>', schedule: [{ type: 'every', unit: 'minute', value: 5 }] } },
|
|
337
346
|
'integration': { config: { key: '<Trigger key>' } },
|
|
338
347
|
'skip-duplicates': { config: { fields: ['id'] }, inputs: { items: { kind: 'value', value: [{ id: 'a' }, { id: 'b' }] } } },
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import type { JsonValue } from '../../flow/common/change.ts'
|
|
2
|
+
|
|
3
|
+
import { jsonValue } from './decoding.ts'
|
|
4
|
+
import { ApiError } from './errors.ts'
|
|
5
|
+
|
|
6
|
+
/** Storage is host-owned. Keys and records are opaque to the delegate. */
|
|
7
|
+
export interface ControlCacheDelegate {
|
|
8
|
+
get(key: string): Promise<unknown>
|
|
9
|
+
set(key: string, value: JsonValue): Promise<void>
|
|
10
|
+
delete(key: string): Promise<void>
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface ControlCacheRead {
|
|
14
|
+
readonly path: string
|
|
15
|
+
readonly source: 'cache' | 'network' | 'revalidated'
|
|
16
|
+
readonly body: JsonValue
|
|
17
|
+
readonly etag: string | null
|
|
18
|
+
readonly validatedAt: number
|
|
19
|
+
readonly stale: boolean
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface ControlCacheOptions {
|
|
23
|
+
/** Non-secret identity of the deployment and effective caller/resource scope. */
|
|
24
|
+
readonly scope: string
|
|
25
|
+
readonly delegate: ControlCacheDelegate
|
|
26
|
+
readonly mode?: 'default' | 'refresh' | 'offline'
|
|
27
|
+
readonly maxAgeMs?: number
|
|
28
|
+
readonly onRead?: (read: ControlCacheRead) => void
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface CachedResponse {
|
|
32
|
+
readonly version: 1
|
|
33
|
+
readonly body: JsonValue
|
|
34
|
+
readonly etag: string | null
|
|
35
|
+
readonly validatedAt: number
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
interface CatalogQuery<Value> {
|
|
39
|
+
readonly path: string
|
|
40
|
+
readonly decode: (value: unknown) => Value
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
type CatalogLoad = (
|
|
44
|
+
etag: string | null,
|
|
45
|
+
) => Promise<{ readonly modified: true; readonly data: JsonValue; readonly etag: string | null } | { readonly modified: false; readonly etag: string | null }>
|
|
46
|
+
|
|
47
|
+
function cachedResponse(value: unknown): CachedResponse | undefined {
|
|
48
|
+
if (value == null || typeof value != 'object' || Array.isArray(value)) return
|
|
49
|
+
const entry = value as Record<string, unknown>
|
|
50
|
+
if (
|
|
51
|
+
entry.version !== 1 ||
|
|
52
|
+
!Object.hasOwn(entry, 'body') ||
|
|
53
|
+
!(entry.etag === null || typeof entry.etag == 'string') ||
|
|
54
|
+
typeof entry.validatedAt != 'number' ||
|
|
55
|
+
!Number.isFinite(entry.validatedAt) ||
|
|
56
|
+
entry.validatedAt < 0
|
|
57
|
+
)
|
|
58
|
+
return
|
|
59
|
+
return { version: 1, body: jsonValue(entry.body), etag: entry.etag, validatedAt: entry.validatedAt }
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Cancellation belongs to each waiter, not the shared metadata request. */
|
|
63
|
+
function wait<Value>(promise: Promise<Value>, signal?: AbortSignal): Promise<Value> {
|
|
64
|
+
if (signal == null) return promise
|
|
65
|
+
if (signal.aborted) {
|
|
66
|
+
void promise.catch(() => {})
|
|
67
|
+
return Promise.reject(signal.reason)
|
|
68
|
+
}
|
|
69
|
+
return new Promise((resolve, reject) => {
|
|
70
|
+
const abort = () => reject(signal.reason)
|
|
71
|
+
signal.addEventListener('abort', abort, { once: true })
|
|
72
|
+
promise.then(resolve, reject).finally(() => signal.removeEventListener('abort', abort))
|
|
73
|
+
})
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Complete Flow API responses, never a merged store of catalog entities. */
|
|
77
|
+
export class ControlCatalogCache {
|
|
78
|
+
readonly #pending = new Map<string, { readonly fresh: boolean; readonly promise: Promise<ControlCacheRead> }>()
|
|
79
|
+
private readonly options: ControlCacheOptions
|
|
80
|
+
|
|
81
|
+
constructor(options: ControlCacheOptions) {
|
|
82
|
+
this.options = options
|
|
83
|
+
if (!options.scope.trim()) throw new TypeError('Control cache scope must be nonempty.')
|
|
84
|
+
if (options.maxAgeMs != null && (!Number.isFinite(options.maxAgeMs) || options.maxAgeMs < 0))
|
|
85
|
+
throw new TypeError('Control cache maxAgeMs must be finite and nonnegative.')
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async read<Value>(query: CatalogQuery<Value>, load: CatalogLoad, maxAgeMs: number, signal?: AbortSignal, fresh = false) {
|
|
89
|
+
signal?.throwIfAborted()
|
|
90
|
+
const key = JSON.stringify(['open-flow:catalog', 1, this.options.scope, query.path])
|
|
91
|
+
const result = await wait(this.#request(key, query, load, this.options.maxAgeMs ?? maxAgeMs, fresh || this.options.mode == 'refresh'), signal)
|
|
92
|
+
const data = query.decode(structuredClone(result.body))
|
|
93
|
+
try {
|
|
94
|
+
this.options.onRead?.({ ...result, body: structuredClone(result.body) })
|
|
95
|
+
} catch {}
|
|
96
|
+
return { data, etag: result.etag }
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
#request<Value>(key: string, query: CatalogQuery<Value>, load: CatalogLoad, maxAgeMs: number, fresh: boolean): Promise<ControlCacheRead> {
|
|
100
|
+
const previous = this.#pending.get(key)
|
|
101
|
+
if (previous != null) {
|
|
102
|
+
if (!fresh || previous.fresh) return previous.promise
|
|
103
|
+
// An explicit refresh must validate after a preceding ordinary lookup.
|
|
104
|
+
return previous.promise.catch(() => undefined).then(() => this.#request(key, query, load, maxAgeMs, true))
|
|
105
|
+
}
|
|
106
|
+
const promise = this.#load(key, query, load, maxAgeMs, fresh).finally(() => {
|
|
107
|
+
if (this.#pending.get(key)?.promise === promise) this.#pending.delete(key)
|
|
108
|
+
})
|
|
109
|
+
this.#pending.set(key, { fresh, promise })
|
|
110
|
+
return promise
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async #load<Value>(key: string, query: CatalogQuery<Value>, load: CatalogLoad, maxAgeMs: number, fresh: boolean): Promise<ControlCacheRead> {
|
|
114
|
+
let cached: CachedResponse | undefined
|
|
115
|
+
try {
|
|
116
|
+
const stored = await this.options.delegate.get(key)
|
|
117
|
+
cached = cachedResponse(stored)
|
|
118
|
+
if (cached != null) query.decode(structuredClone(cached.body))
|
|
119
|
+
if (stored !== undefined && cached == null) await this.options.delegate.delete(key)
|
|
120
|
+
} catch {
|
|
121
|
+
cached = undefined
|
|
122
|
+
try {
|
|
123
|
+
await this.options.delegate.delete(key)
|
|
124
|
+
} catch {}
|
|
125
|
+
}
|
|
126
|
+
const now = Date.now()
|
|
127
|
+
const stale = cached == null || cached.validatedAt > now || now - cached.validatedAt >= maxAgeMs
|
|
128
|
+
if (cached != null && (this.options.mode == 'offline' || (!fresh && !stale))) return { ...cached, path: query.path, source: 'cache', stale }
|
|
129
|
+
if (this.options.mode == 'offline') throw new ApiError(503, 'catalog.cache-miss', `No compatible cached response for ${query.path}.`)
|
|
130
|
+
|
|
131
|
+
const result = await load(cached?.etag ?? null)
|
|
132
|
+
if (!result.modified && cached == null) throw new ApiError(502, 'response.invalid', 'A catalog returned 304 without a cached response.')
|
|
133
|
+
const entry: CachedResponse = {
|
|
134
|
+
version: 1,
|
|
135
|
+
body: result.modified ? result.data : cached!.body,
|
|
136
|
+
etag: result.etag ?? (result.modified ? null : cached!.etag),
|
|
137
|
+
validatedAt: Date.now(),
|
|
138
|
+
}
|
|
139
|
+
query.decode(structuredClone(entry.body))
|
|
140
|
+
try {
|
|
141
|
+
await this.options.delegate.set(key, structuredClone({ ...entry }))
|
|
142
|
+
} catch {}
|
|
143
|
+
return { ...entry, path: query.path, source: result.modified ? 'network' : 'revalidated', stale: false }
|
|
144
|
+
}
|
|
145
|
+
}
|
|
@@ -31,6 +31,10 @@ function equal(actual: unknown, expected: unknown, message: string): void {
|
|
|
31
31
|
if (!dequal(actual, expected)) fail(`${message}: expected ${JSON.stringify(expected)}, received ${JSON.stringify(actual)}.`)
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
function oneOf(actual: unknown, expected: readonly unknown[], message: string): void {
|
|
35
|
+
if (!expected.some((value) => dequal(actual, value))) fail(`${message}: expected one of ${JSON.stringify(expected)}, received ${JSON.stringify(actual)}.`)
|
|
36
|
+
}
|
|
37
|
+
|
|
34
38
|
function record(value: unknown, message: string): RecordValue {
|
|
35
39
|
if (value == null || typeof value != 'object' || Array.isArray(value)) fail(`${message}: expected an object.`)
|
|
36
40
|
return value as RecordValue
|
|
@@ -940,9 +944,11 @@ export const controlApiConformanceCases: readonly ControlApiConformanceCase[] =
|
|
|
940
944
|
}
|
|
941
945
|
return current
|
|
942
946
|
}
|
|
947
|
+
// Deployments may keep an idle Wait session in memory or freeze it immediately.
|
|
948
|
+
const pausedStatuses = ['running', 'waiting']
|
|
943
949
|
const hasWait = (value: RecordValue) => list(value.waits, 'Waits').length > 0
|
|
944
950
|
let detail = await awaitRun(run, hasWait, 'Wait Run to pause')
|
|
945
|
-
|
|
951
|
+
oneOf(detail.status, pausedStatuses, 'Wait Run status')
|
|
946
952
|
const waiting = record(list(detail.waits, 'Waits')[0], 'Active Wait')
|
|
947
953
|
equal(waiting.actions, ['approve', 'reject'], 'Wait actions')
|
|
948
954
|
equal(waiting.nodeId, 'approval', 'Wait node')
|
|
@@ -972,7 +978,7 @@ export const controlApiConformanceCases: readonly ControlApiConformanceCase[] =
|
|
|
972
978
|
const approved = await json(await resolveWait('approve'), 200, 'Approve Wait')
|
|
973
979
|
equal(approved.action, 'approve', 'Approved action')
|
|
974
980
|
equal(approved.resolutionAccepted, true, 'Approved resolution')
|
|
975
|
-
|
|
981
|
+
oneOf(approved.status, ['running', 'queued'], 'Resolved Run status')
|
|
976
982
|
requiredString(approved.resolvedAt, 'Wait resolution time')
|
|
977
983
|
const rejected = await json(await resolveWait('reject'), 200, 'Reject resolved Wait')
|
|
978
984
|
equal(rejected.action, 'approve', 'Winning action')
|
|
@@ -995,7 +1001,7 @@ export const controlApiConformanceCases: readonly ControlApiConformanceCase[] =
|
|
|
995
1001
|
const canceled = await create('wait-run-cancel', 'Create cancelable Wait Run')
|
|
996
1002
|
const canceledRunId = requiredString(canceled.runId, 'Cancelable Wait Run id')
|
|
997
1003
|
const canceledDetail = await awaitRun(canceled, hasWait, 'Wait cancelable Run to pause')
|
|
998
|
-
|
|
1004
|
+
oneOf(canceledDetail.status, pausedStatuses, 'Cancelable Wait status')
|
|
999
1005
|
equal(
|
|
1000
1006
|
(
|
|
1001
1007
|
await json(
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { ConnectorAction, ConnectorConnection, ConnectorProvider } from './api.ts'
|
|
1
|
+
import type { ConnectorAction, ConnectorActionMetadata, ConnectorConnection, ConnectorProvider } from './api.ts'
|
|
2
2
|
|
|
3
|
-
import { connection, connectorAction, connectorProvider } from './connectorDecoders.ts'
|
|
3
|
+
import { connection, connectorAction, connectorActionMetadata, connectorProvider } from './connectorDecoders.ts'
|
|
4
4
|
import { requiredFields, invalidResponse, record, string } from './decoding.ts'
|
|
5
5
|
|
|
6
6
|
export interface RequestQuery<Value> {
|
|
@@ -36,6 +36,21 @@ export function connectorActionQuery(actionId: string, flowId?: string, locale?:
|
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
export function connectorActionMetadataQuery(actionId: string, flowId?: string, locale?: string): RequestQuery<ConnectorActionMetadata> {
|
|
40
|
+
const parameters = new URLSearchParams({ ...(flowId == null ? {} : { flowId }), ...(locale == null ? {} : { locale }) }).toString()
|
|
41
|
+
return {
|
|
42
|
+
path: `/v1/connector/action-metadata/${encodeURIComponent(actionId)}${parameters ? `?${parameters}` : ''}`,
|
|
43
|
+
decode: (value) => {
|
|
44
|
+
const source = record(value)
|
|
45
|
+
requiredFields(source, ['action', 'version'])
|
|
46
|
+
if (source.version != 1) return invalidResponse()
|
|
47
|
+
const action = connectorActionMetadata(source.action)
|
|
48
|
+
if (action.actionId != actionId) return invalidResponse('action.actionId: does not match the requested Action.')
|
|
49
|
+
return action
|
|
50
|
+
},
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
39
54
|
export function allConnectorConnectionsQuery(flowId?: string): RequestQuery<readonly ConnectorConnection[]> {
|
|
40
55
|
return {
|
|
41
56
|
path: `/v1/connector/connections${scope(flowId)}`,
|
|
@@ -2,6 +2,7 @@ import { canvasCommandSchema } from '../../canvas/common/commands.ts'
|
|
|
2
2
|
export { canvasCommandSchema } from '../../canvas/common/commands.ts'
|
|
3
3
|
import { authoringRequestSchema, authoringReadSchema, authoringSearchSchema, authoringSchemaQuery, authoringCheckSchema } from './authoringSchema.ts'
|
|
4
4
|
export * from './authoring.ts'
|
|
5
|
+
export { createWorkflowAuthoring, type WorkflowAuthoringOptions } from './workflowAuthoring.ts'
|
|
5
6
|
import type { JsonValue } from '../../flow/common/change.ts'
|
|
6
7
|
import type { DraftOperation } from './draftOperations.ts'
|
|
7
8
|
|