@codemation/core 0.8.0 → 0.10.0
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/CHANGELOG.md +390 -0
- package/dist/{EngineRuntimeRegistration.types-BP6tsaNP.d.ts → EngineRuntimeRegistration.types-D1fyApMI.d.ts} +2 -2
- package/dist/{EngineWorkflowRunnerService-DzOCa1BW.d.cts → EngineRuntimeRegistration.types-pB3FnzqR.d.cts} +17 -17
- package/dist/{InMemoryRunDataFactory-1iz7_SnO.d.cts → InMemoryRunDataFactory-Xw7v4-sj.d.cts} +31 -29
- package/dist/InMemoryRunEventBusRegistry-VM3OWnHo.cjs +47 -0
- package/dist/InMemoryRunEventBusRegistry-VM3OWnHo.cjs.map +1 -0
- package/dist/InMemoryRunEventBusRegistry-sM4z4n_i.js +41 -0
- package/dist/InMemoryRunEventBusRegistry-sM4z4n_i.js.map +1 -0
- package/dist/{RunIntentService-BqhmdoA1.d.ts → RunIntentService-BE9CAkbf.d.ts} +966 -471
- package/dist/{RunIntentService-S-1lW-gS.d.cts → RunIntentService-siBSjaaY.d.cts} +859 -493
- package/dist/bootstrap/index.cjs +5 -2
- package/dist/bootstrap/index.d.cts +212 -135
- package/dist/bootstrap/index.d.ts +4 -4
- package/dist/bootstrap/index.js +3 -3
- package/dist/{bootstrap-BaN6hZ5I.cjs → bootstrap-Cm5ruQxx.cjs} +263 -12
- package/dist/bootstrap-Cm5ruQxx.cjs.map +1 -0
- package/dist/bootstrap-D3r505ko.js +454 -0
- package/dist/bootstrap-D3r505ko.js.map +1 -0
- package/dist/{index-CVs9rVhl.d.ts → index-DeLl1Tne.d.ts} +632 -230
- package/dist/index.cjs +323 -176
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +544 -91
- package/dist/index.d.ts +3 -3
- package/dist/index.js +299 -166
- package/dist/index.js.map +1 -1
- package/dist/{runtime-DUW6tIJ1.js → runtime-BGNbRnqs.js} +934 -75
- package/dist/runtime-BGNbRnqs.js.map +1 -0
- package/dist/{runtime-Dvo2ru5A.cjs → runtime-DKXJwTNv.cjs} +1028 -73
- package/dist/runtime-DKXJwTNv.cjs.map +1 -0
- package/dist/testing.cjs +5 -5
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.d.cts +2 -2
- package/dist/testing.d.ts +2 -2
- package/dist/testing.js +4 -4
- package/dist/testing.js.map +1 -1
- package/package.json +7 -2
- package/src/ai/AiHost.ts +42 -14
- package/src/authoring/DefinedCollectionRegistry.ts +17 -0
- package/src/authoring/defineCollection.types.ts +181 -0
- package/src/authoring/definePollingTrigger.types.ts +396 -0
- package/src/authoring/definePollingTriggerInternals.ts +74 -0
- package/src/authoring/index.ts +19 -0
- package/src/bootstrap/index.ts +9 -0
- package/src/bootstrap/runtime/EngineRuntimeRegistrar.ts +21 -14
- package/src/browser.ts +1 -0
- package/src/contracts/CodemationTelemetryAttributeNames.ts +6 -0
- package/src/contracts/NoOpNodeExecutionTelemetry.ts +2 -11
- package/src/contracts/NoOpTelemetrySpanScope.ts +46 -10
- package/src/contracts/assertionTypes.ts +63 -0
- package/src/contracts/baseTypes.ts +12 -0
- package/src/contracts/collectionTypes.ts +44 -0
- package/src/contracts/credentialTypes.ts +23 -1
- package/src/contracts/executionPersistenceContracts.ts +30 -0
- package/src/contracts/index.ts +4 -0
- package/src/contracts/runTypes.ts +37 -1
- package/src/contracts/runtimeTypes.ts +42 -0
- package/src/contracts/telemetryTypes.ts +8 -0
- package/src/contracts/testTriggerTypes.ts +66 -0
- package/src/contracts/workflowTypes.ts +36 -7
- package/src/contracts.ts +59 -0
- package/src/events/ConnectionInvocationEventPublisher.ts +46 -0
- package/src/events/index.ts +1 -0
- package/src/events/runEvents.ts +74 -0
- package/src/execution/ChildExecutionScopeFactory.ts +55 -0
- package/src/execution/DefaultExecutionContextFactory.ts +6 -0
- package/src/execution/ExecutionTelemetryCostTrackingDecoratorFactory.ts +18 -0
- package/src/execution/NodeExecutor.ts +10 -2
- package/src/execution/NodeInstanceFactory.ts +13 -1
- package/src/execution/NodeInstantiationError.ts +16 -0
- package/src/execution/NodeRunStateWriter.ts +7 -0
- package/src/execution/NodeRunStateWriterFactory.ts +7 -0
- package/src/execution/WorkflowRunExecutionContextFactory.ts +3 -0
- package/src/execution/index.ts +2 -0
- package/src/index.ts +8 -0
- package/src/orchestration/AbortControllerFactory.ts +9 -0
- package/src/orchestration/NodeExecutionRequestHandlerService.ts +1 -0
- package/src/orchestration/RunContinuationService.ts +3 -0
- package/src/orchestration/RunStartService.ts +122 -3
- package/src/orchestration/TestSuiteOrchestrator.ts +350 -0
- package/src/orchestration/TestSuiteRunIdFactory.ts +11 -0
- package/src/orchestration/TriggerRuntimeService.ts +34 -7
- package/src/orchestration/index.ts +9 -0
- package/src/runtime/EngineFactory.ts +12 -0
- package/src/testing/WorkflowTestKitNodeRegistrationContextFactory.ts +1 -3
- package/src/triggers/polling/PollingTriggerDedupWindow.ts +23 -0
- package/src/triggers/polling/PollingTriggerLogger.ts +18 -0
- package/src/triggers/polling/PollingTriggerRuntime.ts +122 -0
- package/src/triggers/polling/index.ts +5 -0
- package/src/types/index.ts +12 -9
- package/src/workflow/definition/NodeIterationIdFactory.ts +26 -0
- package/src/workflow/dsl/NodeIdSlugifier.ts +18 -0
- package/src/workflow/dsl/WorkflowBuilder.ts +71 -3
- package/src/workflow/dsl/WorkflowDefinitionError.ts +15 -0
- package/src/workflow/index.ts +3 -0
- package/dist/InMemoryRunEventBusRegistry-B0_C4OnP.cjs +0 -262
- package/dist/InMemoryRunEventBusRegistry-B0_C4OnP.cjs.map +0 -1
- package/dist/InMemoryRunEventBusRegistry-C2U83Hmv.js +0 -238
- package/dist/InMemoryRunEventBusRegistry-C2U83Hmv.js.map +0 -1
- package/dist/bootstrap-BaN6hZ5I.cjs.map +0 -1
- package/dist/bootstrap-d_BMaDT4.js +0 -221
- package/dist/bootstrap-d_BMaDT4.js.map +0 -1
- package/dist/runtime-DUW6tIJ1.js.map +0 -1
- package/dist/runtime-Dvo2ru5A.cjs.map +0 -1
|
@@ -8,9 +8,7 @@ export class WorkflowTestKitNodeRegistrationContextFactory {
|
|
|
8
8
|
create(dependencyContainer: DependencyContainer): DefinedNodeRegistrationContext {
|
|
9
9
|
return {
|
|
10
10
|
registerNode<TValue>(token: TypeToken<TValue>, implementation?: TypeToken<TValue>) {
|
|
11
|
-
dependencyContainer.
|
|
12
|
-
useClass: (implementation ?? token) as never,
|
|
13
|
-
});
|
|
11
|
+
dependencyContainer.registerSingleton(token as never, (implementation ?? token) as never);
|
|
14
12
|
},
|
|
15
13
|
};
|
|
16
14
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Merges processed-ID windows for polling triggers, capping the total to avoid unbounded growth.
|
|
3
|
+
* Plugin code receives an instance of this class via {@link PollingTriggerHandle.dedup}.
|
|
4
|
+
*/
|
|
5
|
+
export class PollingTriggerDedupWindow {
|
|
6
|
+
static readonly defaultCapN = 2000;
|
|
7
|
+
|
|
8
|
+
merge(
|
|
9
|
+
previous: ReadonlyArray<string>,
|
|
10
|
+
incoming: ReadonlyArray<string>,
|
|
11
|
+
capN: number = PollingTriggerDedupWindow.defaultCapN,
|
|
12
|
+
): ReadonlyArray<string> {
|
|
13
|
+
const merged = new Set(previous);
|
|
14
|
+
for (const id of incoming) {
|
|
15
|
+
merged.add(id);
|
|
16
|
+
}
|
|
17
|
+
const result = [...merged];
|
|
18
|
+
if (result.length <= capN) {
|
|
19
|
+
return result;
|
|
20
|
+
}
|
|
21
|
+
return result.slice(result.length - capN);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal logger surface for the polling-trigger runtime.
|
|
3
|
+
* Hosts supply this via {@link EngineDeps.pollingTriggerLogger};
|
|
4
|
+
* when absent the runtime is silent.
|
|
5
|
+
*/
|
|
6
|
+
export interface PollingTriggerLogger {
|
|
7
|
+
info(message: string): void;
|
|
8
|
+
warn(message: string): void;
|
|
9
|
+
error(message: string, exception?: Error): void;
|
|
10
|
+
debug(message: string): void;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export class NoOpPollingTriggerLogger implements PollingTriggerLogger {
|
|
14
|
+
info(): void {}
|
|
15
|
+
warn(): void {}
|
|
16
|
+
error(): void {}
|
|
17
|
+
debug(): void {}
|
|
18
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import type { Items, TriggerInstanceId, TriggerSetupStateRepository } from "../../types";
|
|
2
|
+
import type { PollingTriggerLogger } from "./PollingTriggerLogger";
|
|
3
|
+
|
|
4
|
+
export interface PollingRunCycleArgs<TState> {
|
|
5
|
+
previousState: TState | undefined;
|
|
6
|
+
signal: AbortSignal;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface PollingRunCycleResult<TState, TItem> {
|
|
10
|
+
items: Items<TItem>;
|
|
11
|
+
nextState: TState;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface PollingTriggerStartArgs<TState, TItem> {
|
|
15
|
+
trigger: TriggerInstanceId;
|
|
16
|
+
intervalMs: number;
|
|
17
|
+
seedState?: TState;
|
|
18
|
+
runCycle: (cycleCtx: PollingRunCycleArgs<TState>) => Promise<PollingRunCycleResult<TState, TItem>>;
|
|
19
|
+
emit: (items: Items) => Promise<void>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Generic polling-trigger runtime. Owns the set-interval loop, overlap guard, and persistence.
|
|
24
|
+
* Constructed by {@link import("../../runtime/EngineFactory").EngineFactory} and exposed to plugin
|
|
25
|
+
* authors via {@link import("../../contracts/runtimeTypes").TriggerSetupContext}.polling.
|
|
26
|
+
*/
|
|
27
|
+
export class PollingTriggerRuntime {
|
|
28
|
+
private readonly activeTriggers = new Set<string>();
|
|
29
|
+
private readonly intervalsByTrigger = new Map<string, ReturnType<typeof setInterval>>();
|
|
30
|
+
private readonly busyTriggers = new Set<string>();
|
|
31
|
+
|
|
32
|
+
constructor(
|
|
33
|
+
private readonly triggerSetupStateRepository: TriggerSetupStateRepository,
|
|
34
|
+
private readonly logger: PollingTriggerLogger,
|
|
35
|
+
) {}
|
|
36
|
+
|
|
37
|
+
async start<TState, TItem>(args: PollingTriggerStartArgs<TState, TItem>): Promise<TState | undefined> {
|
|
38
|
+
let first: TState | undefined;
|
|
39
|
+
try {
|
|
40
|
+
first = await this.runCycle(args, { seedState: args.seedState });
|
|
41
|
+
} catch (err: unknown) {
|
|
42
|
+
this.logError(`Polling trigger initial cycle failed for ${this.describe(args.trigger)}`, err);
|
|
43
|
+
}
|
|
44
|
+
this.ensureLoop(args);
|
|
45
|
+
return first;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async stop(trigger: TriggerInstanceId): Promise<void> {
|
|
49
|
+
const key = this.toKey(trigger);
|
|
50
|
+
const interval = this.intervalsByTrigger.get(key);
|
|
51
|
+
if (interval !== undefined) {
|
|
52
|
+
clearInterval(interval);
|
|
53
|
+
this.intervalsByTrigger.delete(key);
|
|
54
|
+
}
|
|
55
|
+
this.busyTriggers.delete(key);
|
|
56
|
+
this.activeTriggers.delete(key);
|
|
57
|
+
this.logger.debug(`Polling trigger stopped for ${this.describe(trigger)}`);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
private ensureLoop<TState, TItem>(args: PollingTriggerStartArgs<TState, TItem>): void {
|
|
61
|
+
const key = this.toKey(args.trigger);
|
|
62
|
+
if (this.activeTriggers.has(key)) {
|
|
63
|
+
this.logger.debug(`Polling trigger already active for ${this.describe(args.trigger)}`);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
this.activeTriggers.add(key);
|
|
67
|
+
const intervalMs = Math.max(args.intervalMs, 25);
|
|
68
|
+
const interval = setInterval(() => {
|
|
69
|
+
void this.runCycle(args, { seedState: undefined }).catch((err: unknown) => {
|
|
70
|
+
this.logError(`Polling trigger cycle failed for ${this.describe(args.trigger)}`, err);
|
|
71
|
+
});
|
|
72
|
+
}, intervalMs);
|
|
73
|
+
this.intervalsByTrigger.set(key, interval);
|
|
74
|
+
this.logger.info(`Polling trigger started for ${this.describe(args.trigger)} (interval ${intervalMs}ms)`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
private async runCycle<TState, TItem>(
|
|
78
|
+
args: PollingTriggerStartArgs<TState, TItem>,
|
|
79
|
+
opts: { seedState: TState | undefined },
|
|
80
|
+
): Promise<TState | undefined> {
|
|
81
|
+
const key = this.toKey(args.trigger);
|
|
82
|
+
if (this.busyTriggers.has(key)) {
|
|
83
|
+
this.logger.debug(`Polling trigger skipping overlapping tick for ${this.describe(args.trigger)}`);
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
this.busyTriggers.add(key);
|
|
87
|
+
try {
|
|
88
|
+
const loaded = await this.triggerSetupStateRepository.load(args.trigger);
|
|
89
|
+
const previousState = loaded !== undefined ? (loaded.state as TState | undefined) : opts.seedState;
|
|
90
|
+
const controller = new AbortController();
|
|
91
|
+
const { items, nextState } = await args.runCycle({ previousState, signal: controller.signal });
|
|
92
|
+
await this.triggerSetupStateRepository.save({
|
|
93
|
+
trigger: args.trigger,
|
|
94
|
+
updatedAt: new Date().toISOString(),
|
|
95
|
+
state: nextState as never,
|
|
96
|
+
});
|
|
97
|
+
if (items.length > 0) {
|
|
98
|
+
this.logger.info(`Polling trigger emitting ${items.length} item(s) for ${this.describe(args.trigger)}`);
|
|
99
|
+
await args.emit(items);
|
|
100
|
+
}
|
|
101
|
+
return nextState;
|
|
102
|
+
} finally {
|
|
103
|
+
this.busyTriggers.delete(key);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
private toKey(trigger: TriggerInstanceId): string {
|
|
108
|
+
return `${trigger.workflowId}:${trigger.nodeId}`;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
private describe(trigger: TriggerInstanceId): string {
|
|
112
|
+
return `${trigger.workflowId}.${trigger.nodeId}`;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
private logError(message: string, error: unknown): void {
|
|
116
|
+
if (error instanceof Error) {
|
|
117
|
+
this.logger.error(message, error);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
this.logger.error(`${message}: ${String(error)}`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { PollingTriggerDedupWindow } from "./PollingTriggerDedupWindow";
|
|
2
|
+
export { PollingTriggerRuntime } from "./PollingTriggerRuntime";
|
|
3
|
+
export type { PollingTriggerLogger } from "./PollingTriggerLogger";
|
|
4
|
+
export { NoOpPollingTriggerLogger } from "./PollingTriggerLogger";
|
|
5
|
+
export type { PollingRunCycleArgs, PollingRunCycleResult, PollingTriggerStartArgs } from "./PollingTriggerRuntime";
|
package/src/types/index.ts
CHANGED
|
@@ -1,22 +1,25 @@
|
|
|
1
|
+
// Re-export pure-type contracts first (available via @codemation/core/contracts subpath)
|
|
2
|
+
export * from "../contracts";
|
|
3
|
+
export type { CollectionStore, CollectionsContext } from "../contracts/collectionTypes";
|
|
4
|
+
|
|
5
|
+
// Additional runtime exports not included in contracts (factory classes, DSL builders, etc.)
|
|
1
6
|
export * from "../contracts/emitPorts";
|
|
2
7
|
export * from "../contracts/itemMeta";
|
|
3
|
-
export * from "../contracts/params";
|
|
4
8
|
export * from "../contracts/itemExpr";
|
|
5
|
-
export * from "../contracts/retryPolicySpec.types";
|
|
6
9
|
export * from "../contracts/NoRetryPolicy";
|
|
7
10
|
export * from "../contracts/RetryPolicy";
|
|
8
11
|
export * from "../contracts/ExpRetryPolicy";
|
|
9
12
|
export * from "../contracts/credentialTypes";
|
|
10
|
-
export * from "../contracts/CostCatalogContract";
|
|
11
13
|
export * from "../contracts/CostTrackingTelemetryContract";
|
|
12
14
|
export * from "../contracts/NoOpCostTrackingTelemetry";
|
|
13
15
|
export * from "../contracts/NoOpCostTrackingTelemetryFactory";
|
|
14
|
-
export * from "../contracts/
|
|
15
|
-
export * from "../contracts/runtimeTypes";
|
|
16
|
-
export * from "../contracts/telemetryTypes";
|
|
16
|
+
export * from "../contracts/NoOpExecutionTelemetryFactory";
|
|
17
17
|
export * from "../contracts/runFinishedAtFactory";
|
|
18
|
-
export * from "../contracts/runTypes";
|
|
19
|
-
export * from "../contracts/webhookTypes";
|
|
20
|
-
export * from "../contracts/workflowTypes";
|
|
21
18
|
export * from "../contracts/workflowActivationPolicy";
|
|
19
|
+
// telemetryTypes and workflowTypes also have runtime exports (No-Op telemetry classes,
|
|
20
|
+
// attribute-name registries, `nodeRef` factory, unique-symbol type tags) — `contracts.ts`
|
|
21
|
+
// already re-exports them with `export type *` for the slim subpath; re-export with the
|
|
22
|
+
// full `export *` here so back-compat for `@codemation/core` (root) consumers is preserved.
|
|
23
|
+
export * from "../contracts/telemetryTypes";
|
|
24
|
+
export * from "../contracts/workflowTypes";
|
|
22
25
|
export * from "../workflow";
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
import type { NodeId } from "../../types";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Unique ids for one per-item iteration of a runnable node's execute loop.
|
|
7
|
+
*
|
|
8
|
+
* Activations are per-batch (one scheduled execution of a node, possibly with N items).
|
|
9
|
+
* Iterations refine that to one identifier per item-index inside the batch loop, so per-item
|
|
10
|
+
* connection invocations and telemetry can be grouped without time-window heuristics.
|
|
11
|
+
*/
|
|
12
|
+
export class NodeIterationIdFactory {
|
|
13
|
+
static create(): string {
|
|
14
|
+
return `iter_${randomUUID()}`;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Deterministic id for tests when a stable sequence is needed. */
|
|
18
|
+
static createForTest(seed: string, sequence: number): string {
|
|
19
|
+
return `iter_${seed}_${sequence}`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Deterministic id derived from a connection node id (for sub-agent / tool-call scopes). */
|
|
23
|
+
static createForConnection(connectionNodeId: NodeId, sequence: number): string {
|
|
24
|
+
return `iter_${connectionNodeId}_${sequence}`;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Converts a human-readable node label into a stable, URL-safe identifier segment.
|
|
3
|
+
*
|
|
4
|
+
* Rules:
|
|
5
|
+
* - Lowercase the entire string.
|
|
6
|
+
* - Replace every run of characters outside `[a-z0-9]` with a single `-`.
|
|
7
|
+
* - Strip any leading or trailing `-` characters.
|
|
8
|
+
* - Return `""` for blank/empty input.
|
|
9
|
+
*/
|
|
10
|
+
export const NodeIdSlugifier = {
|
|
11
|
+
slugify(label: string): string {
|
|
12
|
+
if (!label) return "";
|
|
13
|
+
return label
|
|
14
|
+
.toLowerCase()
|
|
15
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
16
|
+
.replace(/^-+|-+$/g, "");
|
|
17
|
+
},
|
|
18
|
+
};
|
|
@@ -10,13 +10,22 @@ import type {
|
|
|
10
10
|
WorkflowId,
|
|
11
11
|
} from "../../types";
|
|
12
12
|
|
|
13
|
+
import { AgentConfigInspector } from "../../ai/AgentConfigInspectorFactory";
|
|
14
|
+
import { AgentConnectionNodeCollector } from "../../ai/AgentConnectionNodeCollector";
|
|
13
15
|
import { ChainCursor } from "./ChainCursorResolver";
|
|
16
|
+
import { NodeIdSlugifier } from "./NodeIdSlugifier";
|
|
17
|
+
import { WorkflowDefinitionError } from "./WorkflowDefinitionError";
|
|
14
18
|
import type { AnyRunnableNodeConfig, AnyTriggerNodeConfig } from "./workflowBuilderTypes";
|
|
15
19
|
|
|
20
|
+
type NodeIdEntry = Readonly<{
|
|
21
|
+
nodeId: string;
|
|
22
|
+
tokenName: string;
|
|
23
|
+
label: string;
|
|
24
|
+
}>;
|
|
25
|
+
|
|
16
26
|
export class WorkflowBuilder {
|
|
17
27
|
private readonly nodes: NodeDefinition[] = [];
|
|
18
28
|
private readonly edges: WorkflowDefinition["edges"] = [];
|
|
19
|
-
private seq = 0;
|
|
20
29
|
|
|
21
30
|
constructor(
|
|
22
31
|
private readonly meta: { id: WorkflowId; name: string },
|
|
@@ -24,8 +33,7 @@ export class WorkflowBuilder {
|
|
|
24
33
|
) {}
|
|
25
34
|
|
|
26
35
|
private add(config: NodeConfigBase): NodeRef {
|
|
27
|
-
const
|
|
28
|
-
const id = config.id ?? `${tokenName}:${++this.seq}`;
|
|
36
|
+
const id = config.id ?? NodeIdSlugifier.slugify(config.name ?? "");
|
|
29
37
|
this.nodes.push({ id, kind: config.kind, type: config.type, name: config.name, config });
|
|
30
38
|
return { id, kind: config.kind, name: config.name };
|
|
31
39
|
}
|
|
@@ -45,8 +53,68 @@ export class WorkflowBuilder {
|
|
|
45
53
|
}
|
|
46
54
|
|
|
47
55
|
build(): WorkflowDefinition {
|
|
56
|
+
this.validateNodeIds();
|
|
48
57
|
return { ...this.meta, nodes: this.nodes, edges: this.edges };
|
|
49
58
|
}
|
|
59
|
+
|
|
60
|
+
private validateNodeIds(): void {
|
|
61
|
+
const entries: NodeIdEntry[] = [];
|
|
62
|
+
|
|
63
|
+
for (const node of this.nodes) {
|
|
64
|
+
const tokenName = typeof node.type === "function" ? node.type.name : String(node.type);
|
|
65
|
+
entries.push({ nodeId: node.id, tokenName, label: node.name ?? "" });
|
|
66
|
+
|
|
67
|
+
if (AgentConfigInspector.isAgentNodeConfig(node.config)) {
|
|
68
|
+
for (const child of AgentConnectionNodeCollector.collect(node.id, node.config)) {
|
|
69
|
+
entries.push({ nodeId: child.nodeId, tokenName: child.typeName, label: child.name });
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const emptyIds: NodeIdEntry[] = [];
|
|
75
|
+
const seenIds = new Map<string, NodeIdEntry>();
|
|
76
|
+
const duplicateIds: NodeIdEntry[] = [];
|
|
77
|
+
|
|
78
|
+
for (const entry of entries) {
|
|
79
|
+
if (!entry.nodeId) {
|
|
80
|
+
emptyIds.push(entry);
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
const existing = seenIds.get(entry.nodeId);
|
|
84
|
+
if (existing) {
|
|
85
|
+
if (!duplicateIds.includes(existing)) {
|
|
86
|
+
duplicateIds.push(existing);
|
|
87
|
+
}
|
|
88
|
+
duplicateIds.push(entry);
|
|
89
|
+
} else {
|
|
90
|
+
seenIds.set(entry.nodeId, entry);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (emptyIds.length === 0 && duplicateIds.length === 0) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const lines: string[] = ["WorkflowBuilder.build() found invalid node ids:"];
|
|
99
|
+
|
|
100
|
+
if (emptyIds.length > 0) {
|
|
101
|
+
lines.push(" Empty ids (label is blank and no explicit id was given):");
|
|
102
|
+
for (const e of emptyIds) {
|
|
103
|
+
lines.push(` - type "${e.tokenName}" label "${e.label}"`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (duplicateIds.length > 0) {
|
|
108
|
+
lines.push(" Duplicate ids:");
|
|
109
|
+
for (const e of duplicateIds) {
|
|
110
|
+
lines.push(` - id "${e.nodeId}" type "${e.tokenName}" label "${e.label}"`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
lines.push(" Fix: set an explicit `id:` on each offending node config.");
|
|
115
|
+
|
|
116
|
+
throw new WorkflowDefinitionError(lines.join("\n"));
|
|
117
|
+
}
|
|
50
118
|
}
|
|
51
119
|
|
|
52
120
|
export { ChainCursor } from "./ChainCursorResolver";
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thrown by {@link WorkflowBuilder.build} when the workflow definition is structurally invalid.
|
|
3
|
+
*
|
|
4
|
+
* Common causes:
|
|
5
|
+
* - A node has an empty effective id (label is blank and no explicit `id` was given).
|
|
6
|
+
* - Two or more nodes share the same effective id (label slugs collide or explicit ids clash).
|
|
7
|
+
*
|
|
8
|
+
* Fix: provide an explicit `id:` on the offending node configs.
|
|
9
|
+
*/
|
|
10
|
+
export class WorkflowDefinitionError extends Error {
|
|
11
|
+
constructor(message: string) {
|
|
12
|
+
super(message);
|
|
13
|
+
this.name = "WorkflowDefinitionError";
|
|
14
|
+
}
|
|
15
|
+
}
|
package/src/workflow/index.ts
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
export { WorkflowBuilder } from "./dsl/WorkflowBuilder";
|
|
2
|
+
export { WorkflowDefinitionError } from "./dsl/WorkflowDefinitionError";
|
|
3
|
+
export { NodeIdSlugifier } from "./dsl/NodeIdSlugifier";
|
|
2
4
|
export { ChainCursor } from "./dsl/ChainCursorResolver";
|
|
3
5
|
export { WhenBuilder } from "./dsl/WhenBuilder";
|
|
4
6
|
export * from "./dsl/workflowBuilderTypes";
|
|
5
7
|
|
|
6
8
|
export { ConnectionInvocationIdFactory } from "./definition/ConnectionInvocationIdFactory";
|
|
7
9
|
export { ConnectionNodeIdFactory } from "./definition/ConnectionNodeIdFactory";
|
|
10
|
+
export { NodeIterationIdFactory } from "./definition/NodeIterationIdFactory";
|
|
8
11
|
export { WorkflowExecutableNodeClassifier } from "./definition/WorkflowExecutableNodeClassifier";
|
|
9
12
|
export * from "./definition/WorkflowExecutableNodeClassifierFactory";
|
|
10
13
|
|
|
@@ -1,262 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
//#region src/workflow/dsl/WhenBuilder.ts
|
|
3
|
-
var WhenBuilder = class WhenBuilder {
|
|
4
|
-
constructor(wf, from, branchPort) {
|
|
5
|
-
this.wf = wf;
|
|
6
|
-
this.from = from;
|
|
7
|
-
this.branchPort = branchPort;
|
|
8
|
-
}
|
|
9
|
-
addBranch(steps) {
|
|
10
|
-
const created = [];
|
|
11
|
-
let prev = null;
|
|
12
|
-
for (const cfg of steps) {
|
|
13
|
-
const ref = this.wf.add(cfg);
|
|
14
|
-
created.push(ref);
|
|
15
|
-
if (!prev) this.wf.connect(this.from, ref, this.branchPort, "in");
|
|
16
|
-
else this.wf.connect(prev, ref, "main", "in");
|
|
17
|
-
prev = ref;
|
|
18
|
-
}
|
|
19
|
-
for (const cfg of steps) {
|
|
20
|
-
const maybe = cfg;
|
|
21
|
-
if (!Array.isArray(maybe.upstreamRefs) || maybe.upstreamRefs.length === 0) continue;
|
|
22
|
-
maybe.upstreamRefs = maybe.upstreamRefs.map((r) => {
|
|
23
|
-
if (typeof r !== "string") return r;
|
|
24
|
-
const nodeId = created[parseInt(r.slice(1), 10)]?.id;
|
|
25
|
-
return nodeId ? { nodeId } : { nodeId: r };
|
|
26
|
-
});
|
|
27
|
-
}
|
|
28
|
-
return this;
|
|
29
|
-
}
|
|
30
|
-
when = (branch, steps, ...more) => {
|
|
31
|
-
const list = Array.isArray(steps) ? steps : [steps, ...more];
|
|
32
|
-
const port = branch ? "true" : "false";
|
|
33
|
-
const b = new WhenBuilder(this.wf, this.from, port);
|
|
34
|
-
b.addBranch(list);
|
|
35
|
-
return b;
|
|
36
|
-
};
|
|
37
|
-
build() {
|
|
38
|
-
return this.wf.build();
|
|
39
|
-
}
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
//#endregion
|
|
43
|
-
//#region src/workflow/dsl/ChainCursorResolver.ts
|
|
44
|
-
var ChainCursor = class ChainCursor {
|
|
45
|
-
constructor(wf, endpoints) {
|
|
46
|
-
this.wf = wf;
|
|
47
|
-
this.endpoints = endpoints;
|
|
48
|
-
}
|
|
49
|
-
then(config) {
|
|
50
|
-
const next = this.wf.add(config);
|
|
51
|
-
const inputPortHint = this.resolveSharedInputPortHint();
|
|
52
|
-
for (const e of this.endpoints) this.wf.connect(e.node, next, e.output);
|
|
53
|
-
return new ChainCursor(this.wf, [{
|
|
54
|
-
node: next,
|
|
55
|
-
output: "main",
|
|
56
|
-
...inputPortHint ? { inputPortHint } : {}
|
|
57
|
-
}]);
|
|
58
|
-
}
|
|
59
|
-
thenIntoInputHints(config) {
|
|
60
|
-
const next = this.wf.add(config);
|
|
61
|
-
for (const e of this.endpoints) this.wf.connect(e.node, next, e.output, e.inputPortHint ?? "in");
|
|
62
|
-
return new ChainCursor(this.wf, [{
|
|
63
|
-
node: next,
|
|
64
|
-
output: "main"
|
|
65
|
-
}]);
|
|
66
|
-
}
|
|
67
|
-
when = ((arg1, steps, ...more) => {
|
|
68
|
-
if (this.endpoints.length !== 1) throw new Error("ChainCursor.when(...) is only supported from a single cursor endpoint");
|
|
69
|
-
const cursor = this.endpoints[0].node;
|
|
70
|
-
if (typeof arg1 === "boolean") {
|
|
71
|
-
const list = Array.isArray(steps) ? steps : steps ? [steps, ...more] : more;
|
|
72
|
-
const port = arg1 ? "true" : "false";
|
|
73
|
-
const b = new WhenBuilder(this.wf, cursor, port);
|
|
74
|
-
b.addBranch(list);
|
|
75
|
-
return b;
|
|
76
|
-
}
|
|
77
|
-
const branches = arg1;
|
|
78
|
-
const wfAny = this.wf;
|
|
79
|
-
const buildBranch = (port, branchSteps) => {
|
|
80
|
-
const list = branchSteps ?? [];
|
|
81
|
-
let prev = null;
|
|
82
|
-
for (const cfg of list) {
|
|
83
|
-
const ref = wfAny.add(cfg);
|
|
84
|
-
if (!prev) wfAny.connect(cursor, ref, port, "in");
|
|
85
|
-
else wfAny.connect(prev, ref, "main", "in");
|
|
86
|
-
prev = ref;
|
|
87
|
-
}
|
|
88
|
-
if (!prev) return {
|
|
89
|
-
end: cursor,
|
|
90
|
-
endOutput: port,
|
|
91
|
-
inputPortHint: port
|
|
92
|
-
};
|
|
93
|
-
return {
|
|
94
|
-
end: prev,
|
|
95
|
-
endOutput: "main",
|
|
96
|
-
inputPortHint: port
|
|
97
|
-
};
|
|
98
|
-
};
|
|
99
|
-
const t = buildBranch("true", branches.true);
|
|
100
|
-
const f = buildBranch("false", branches.false);
|
|
101
|
-
return new ChainCursor(this.wf, [{
|
|
102
|
-
node: t.end,
|
|
103
|
-
output: t.endOutput,
|
|
104
|
-
inputPortHint: t.inputPortHint
|
|
105
|
-
}, {
|
|
106
|
-
node: f.end,
|
|
107
|
-
output: f.endOutput,
|
|
108
|
-
inputPortHint: f.inputPortHint
|
|
109
|
-
}]);
|
|
110
|
-
});
|
|
111
|
-
route(branches) {
|
|
112
|
-
if (this.endpoints.length !== 1) throw new Error("ChainCursor.route(...) is only supported from a single cursor endpoint");
|
|
113
|
-
const cursor = this.endpoints[0];
|
|
114
|
-
const nextEndpoints = [];
|
|
115
|
-
for (const [port, branchFactory] of Object.entries(branches)) {
|
|
116
|
-
if (!branchFactory) continue;
|
|
117
|
-
const builtBranch = branchFactory(new ChainCursor(this.wf, [{
|
|
118
|
-
node: cursor.node,
|
|
119
|
-
output: port,
|
|
120
|
-
inputPortHint: port
|
|
121
|
-
}]));
|
|
122
|
-
if (!builtBranch) continue;
|
|
123
|
-
nextEndpoints.push(...builtBranch.endpoints);
|
|
124
|
-
}
|
|
125
|
-
return new ChainCursor(this.wf, nextEndpoints);
|
|
126
|
-
}
|
|
127
|
-
build() {
|
|
128
|
-
return this.wf.build();
|
|
129
|
-
}
|
|
130
|
-
resolveSharedInputPortHint() {
|
|
131
|
-
const first = this.endpoints[0]?.inputPortHint;
|
|
132
|
-
if (!first) return;
|
|
133
|
-
return this.endpoints.every((endpoint) => endpoint.inputPortHint === first) ? first : void 0;
|
|
134
|
-
}
|
|
135
|
-
};
|
|
136
|
-
|
|
137
|
-
//#endregion
|
|
138
|
-
//#region src/workflow/dsl/WorkflowBuilder.ts
|
|
139
|
-
var WorkflowBuilder = class {
|
|
140
|
-
nodes = [];
|
|
141
|
-
edges = [];
|
|
142
|
-
seq = 0;
|
|
143
|
-
constructor(meta, options) {
|
|
144
|
-
this.meta = meta;
|
|
145
|
-
this.options = options;
|
|
146
|
-
}
|
|
147
|
-
add(config) {
|
|
148
|
-
const tokenName = typeof config.type === "function" ? config.type.name : String(config.type);
|
|
149
|
-
const id = config.id ?? `${tokenName}:${++this.seq}`;
|
|
150
|
-
this.nodes.push({
|
|
151
|
-
id,
|
|
152
|
-
kind: config.kind,
|
|
153
|
-
type: config.type,
|
|
154
|
-
name: config.name,
|
|
155
|
-
config
|
|
156
|
-
});
|
|
157
|
-
return {
|
|
158
|
-
id,
|
|
159
|
-
kind: config.kind,
|
|
160
|
-
name: config.name
|
|
161
|
-
};
|
|
162
|
-
}
|
|
163
|
-
connect(from, to, fromOutput = "main", toInput = "in") {
|
|
164
|
-
this.edges.push({
|
|
165
|
-
from: {
|
|
166
|
-
nodeId: from.id,
|
|
167
|
-
output: fromOutput
|
|
168
|
-
},
|
|
169
|
-
to: {
|
|
170
|
-
nodeId: to.id,
|
|
171
|
-
input: toInput
|
|
172
|
-
}
|
|
173
|
-
});
|
|
174
|
-
}
|
|
175
|
-
trigger(config) {
|
|
176
|
-
const ref = this.add(config);
|
|
177
|
-
return new ChainCursor(this, [{
|
|
178
|
-
node: ref,
|
|
179
|
-
output: "main"
|
|
180
|
-
}]);
|
|
181
|
-
}
|
|
182
|
-
start(config) {
|
|
183
|
-
const ref = this.add(config);
|
|
184
|
-
return new ChainCursor(this, [{
|
|
185
|
-
node: ref,
|
|
186
|
-
output: "main"
|
|
187
|
-
}]);
|
|
188
|
-
}
|
|
189
|
-
build() {
|
|
190
|
-
return {
|
|
191
|
-
...this.meta,
|
|
192
|
-
nodes: this.nodes,
|
|
193
|
-
edges: this.edges
|
|
194
|
-
};
|
|
195
|
-
}
|
|
196
|
-
};
|
|
197
|
-
|
|
198
|
-
//#endregion
|
|
199
|
-
//#region src/events/InMemoryRunEventSubscription.ts
|
|
200
|
-
var InMemoryRunEventSubscription = class {
|
|
201
|
-
constructor(onClose) {
|
|
202
|
-
this.onClose = onClose;
|
|
203
|
-
}
|
|
204
|
-
async close() {
|
|
205
|
-
this.onClose();
|
|
206
|
-
}
|
|
207
|
-
};
|
|
208
|
-
|
|
209
|
-
//#endregion
|
|
210
|
-
//#region src/events/InMemoryRunEventBusRegistry.ts
|
|
211
|
-
var InMemoryRunEventBus = class {
|
|
212
|
-
globalListeners = /* @__PURE__ */ new Set();
|
|
213
|
-
listenersByWorkflowId = /* @__PURE__ */ new Map();
|
|
214
|
-
async publish(event) {
|
|
215
|
-
for (const listener of this.globalListeners) listener(event);
|
|
216
|
-
for (const listener of this.listenersByWorkflowId.get(event.workflowId) ?? []) listener(event);
|
|
217
|
-
}
|
|
218
|
-
async subscribe(onEvent) {
|
|
219
|
-
this.globalListeners.add(onEvent);
|
|
220
|
-
return new InMemoryRunEventSubscription(() => {
|
|
221
|
-
this.globalListeners.delete(onEvent);
|
|
222
|
-
});
|
|
223
|
-
}
|
|
224
|
-
async subscribeToWorkflow(workflowId, onEvent) {
|
|
225
|
-
const existing = this.listenersByWorkflowId.get(workflowId) ?? /* @__PURE__ */ new Set();
|
|
226
|
-
existing.add(onEvent);
|
|
227
|
-
this.listenersByWorkflowId.set(workflowId, existing);
|
|
228
|
-
return new InMemoryRunEventSubscription(() => {
|
|
229
|
-
const listeners = this.listenersByWorkflowId.get(workflowId);
|
|
230
|
-
if (!listeners) return;
|
|
231
|
-
listeners.delete(onEvent);
|
|
232
|
-
if (listeners.size === 0) this.listenersByWorkflowId.delete(workflowId);
|
|
233
|
-
});
|
|
234
|
-
}
|
|
235
|
-
};
|
|
236
|
-
|
|
237
|
-
//#endregion
|
|
238
|
-
Object.defineProperty(exports, 'ChainCursor', {
|
|
239
|
-
enumerable: true,
|
|
240
|
-
get: function () {
|
|
241
|
-
return ChainCursor;
|
|
242
|
-
}
|
|
243
|
-
});
|
|
244
|
-
Object.defineProperty(exports, 'InMemoryRunEventBus', {
|
|
245
|
-
enumerable: true,
|
|
246
|
-
get: function () {
|
|
247
|
-
return InMemoryRunEventBus;
|
|
248
|
-
}
|
|
249
|
-
});
|
|
250
|
-
Object.defineProperty(exports, 'WhenBuilder', {
|
|
251
|
-
enumerable: true,
|
|
252
|
-
get: function () {
|
|
253
|
-
return WhenBuilder;
|
|
254
|
-
}
|
|
255
|
-
});
|
|
256
|
-
Object.defineProperty(exports, 'WorkflowBuilder', {
|
|
257
|
-
enumerable: true,
|
|
258
|
-
get: function () {
|
|
259
|
-
return WorkflowBuilder;
|
|
260
|
-
}
|
|
261
|
-
});
|
|
262
|
-
//# sourceMappingURL=InMemoryRunEventBusRegistry-B0_C4OnP.cjs.map
|