@sapiom/agent 0.5.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.
Files changed (43) hide show
  1. package/CHANGELOG.md +205 -0
  2. package/LICENSE +21 -0
  3. package/README.md +130 -0
  4. package/dist/cjs/agent.d.ts +10 -0
  5. package/dist/cjs/agent.js +38 -0
  6. package/dist/cjs/build-manifest.d.ts +15 -0
  7. package/dist/cjs/build-manifest.js +170 -0
  8. package/dist/cjs/context.d.ts +47 -0
  9. package/dist/cjs/context.js +21 -0
  10. package/dist/cjs/directives.d.ts +95 -0
  11. package/dist/cjs/directives.js +74 -0
  12. package/dist/cjs/errors.d.ts +19 -0
  13. package/dist/cjs/errors.js +49 -0
  14. package/dist/cjs/index.d.ts +17 -0
  15. package/dist/cjs/index.js +41 -0
  16. package/dist/cjs/introspection.d.ts +11 -0
  17. package/dist/cjs/introspection.js +62 -0
  18. package/dist/cjs/manifest.d.ts +56 -0
  19. package/dist/cjs/manifest.js +27 -0
  20. package/dist/cjs/step.d.ts +42 -0
  21. package/dist/cjs/step.js +15 -0
  22. package/dist/esm/agent.d.ts +10 -0
  23. package/dist/esm/agent.js +33 -0
  24. package/dist/esm/build-manifest.d.ts +15 -0
  25. package/dist/esm/build-manifest.js +165 -0
  26. package/dist/esm/context.d.ts +47 -0
  27. package/dist/esm/context.js +17 -0
  28. package/dist/esm/directives.d.ts +95 -0
  29. package/dist/esm/directives.js +61 -0
  30. package/dist/esm/errors.d.ts +19 -0
  31. package/dist/esm/errors.js +42 -0
  32. package/dist/esm/index.d.ts +17 -0
  33. package/dist/esm/index.js +9 -0
  34. package/dist/esm/introspection.d.ts +11 -0
  35. package/dist/esm/introspection.js +56 -0
  36. package/dist/esm/manifest.d.ts +56 -0
  37. package/dist/esm/manifest.js +24 -0
  38. package/dist/esm/package.json +1 -0
  39. package/dist/esm/step.d.ts +42 -0
  40. package/dist/esm/step.js +12 -0
  41. package/dist/tsconfig.cjs.tsbuildinfo +1 -0
  42. package/dist/tsconfig.esm.tsbuildinfo +1 -0
  43. package/package.json +74 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,205 @@
1
+ # @sapiom/orchestration
2
+
3
+ ## 0.5.0
4
+
5
+ ### Minor Changes
6
+
7
+ - cc1261e: Rename the composition SDK to **agents** and the coding/LLM capability to **models**.
8
+
9
+ **Breaking — the package names changed. Install the new names; the old ones are deprecated.**
10
+
11
+ - Packages: `@sapiom/orchestration` → `@sapiom/agent`, `@sapiom/orchestration-core` → `@sapiom/agent-core`, `@sapiom/orchestration-runtime` → `@sapiom/agent-runtime`. (`@sapiom/create-orchestration` is retired — scaffold with the CLI or the developer MCP.)
12
+ - API: `defineOrchestration` → `defineAgent`; `Orchestration*` types/errors → `Agent*`.
13
+ - `@sapiom/tools`: the `agent` capability namespace is now `models` (e.g. `sapiom.models.coding`); the `orchestrations` namespace is now `agents`.
14
+ - CLI: `sapiom orchestrations …` → `sapiom agents …`.
15
+ - Developer MCP tools: `sapiom_dev_orchestrations_*` → `sapiom_dev_agents_*`.
16
+
17
+ ### Patch Changes
18
+
19
+ - Updated dependencies [cc1261e]
20
+ - @sapiom/tools@0.16.0
21
+
22
+ ## 0.4.9
23
+
24
+ ### Patch Changes
25
+
26
+ - Updated dependencies [8fd3f71]
27
+ - @sapiom/tools@0.15.0
28
+
29
+ ## 0.4.8
30
+
31
+ ### Patch Changes
32
+
33
+ - Updated dependencies [aaf633c]
34
+ - @sapiom/tools@0.14.0
35
+
36
+ ## 0.4.7
37
+
38
+ ### Patch Changes
39
+
40
+ - Updated dependencies [cc2bde2]
41
+ - @sapiom/tools@0.13.0
42
+
43
+ ## 0.4.6
44
+
45
+ ### Patch Changes
46
+
47
+ - Updated dependencies [019ef30]
48
+ - @sapiom/tools@0.12.0
49
+
50
+ ## 0.4.5
51
+
52
+ ### Patch Changes
53
+
54
+ - Updated dependencies [84e44e2]
55
+ - @sapiom/tools@0.11.0
56
+
57
+ ## 0.4.4
58
+
59
+ ### Patch Changes
60
+
61
+ - Updated dependencies [6ebf569]
62
+ - @sapiom/tools@0.10.0
63
+
64
+ ## 0.4.3
65
+
66
+ ### Patch Changes
67
+
68
+ - Updated dependencies [30bac1c]
69
+ - Updated dependencies [30bac1c]
70
+ - Updated dependencies [0361fa7]
71
+ - @sapiom/tools@0.9.0
72
+
73
+ ## 0.4.2
74
+
75
+ ### Patch Changes
76
+
77
+ - Updated dependencies [2b94dff]
78
+ - Updated dependencies [ac71754]
79
+ - Updated dependencies [f078ed5]
80
+ - @sapiom/tools@0.8.0
81
+
82
+ ## 0.4.1
83
+
84
+ ### Patch Changes
85
+
86
+ - Updated dependencies [a3cc368]
87
+ - @sapiom/tools@0.7.0
88
+
89
+ ## 0.4.0
90
+
91
+ ### Minor Changes
92
+
93
+ - 56fd77d: Broaden `zod` peer support to `^3.25.76 || ^4.0.0` (was `^4.0.0`).
94
+
95
+ The package now uses zod's v4 API via the `zod/v4` subpath internally (types, `z.toJSONSchema`) instead of importing from `zod` directly. That subpath ships in **both** zod 3.25.x and zod 4.x, so the package can be consumed on either — restoring support for zod-3.25 projects (which `^4.0.0` had excluded) while keeping zod 4 working. Author step schemas with `import { z } from "zod/v4"` (equivalent to `import { z } from "zod"` on zod 4).
96
+
97
+ Non-breaking for existing zod-4 consumers: `zod/v4` and `zod` resolve to the same v4 implementation there.
98
+
99
+ ## 0.3.0
100
+
101
+ ### Minor Changes
102
+
103
+ - f41ab95: Declare `zod` as a `peerDependency` (`^4.0.0`) instead of a direct dependency, and remove the `z` / `ZodType` compatibility re-exports from the package entry.
104
+
105
+ Step `inputSchema`s are zod schemas authored in the consumer's project and passed into this package, so there must be a single shared `zod` instance — bundling our own copy could otherwise cause type and `instanceof` mismatches against the consumer's `zod`. zod 4 is required; npm 7+ and pnpm install the peer automatically (consumers on older package managers should add `zod` alongside this package).
106
+
107
+ The previous `export { z } from "zod"` shim only worked because the package bundled its own `zod`; with `zod` as a peer it would just re-export the consumer's own instance, so it has been removed. Import `z` from `zod` directly.
108
+
109
+ ## 0.2.0
110
+
111
+ ### Minor Changes
112
+
113
+ - b2c5612: Move the orchestration authoring SDK onto zod 4 via the bare `zod` import (no
114
+ more `zod/v4` subpath), so installing is just:
115
+
116
+ ```sh
117
+ npm install @sapiom/orchestration
118
+ ```
119
+
120
+ `zod` is now a regular dependency rather than a peer. Author your step schemas
121
+ with your own `import { z } from "zod"` as usual; a compatibility re-export
122
+ (`import { z } from "@sapiom/orchestration"`) is available for projects pinned
123
+ to an incompatible zod. Scaffolded projects now pin zod 4.
124
+
125
+ ### Patch Changes
126
+
127
+ - Updated dependencies [b2c5612]
128
+ - @sapiom/tools@0.6.0
129
+
130
+ ## 0.1.9
131
+
132
+ ### Patch Changes
133
+
134
+ - Updated dependencies [5c974b1]
135
+ - @sapiom/tools@0.5.0
136
+
137
+ ## 0.1.8
138
+
139
+ ### Patch Changes
140
+
141
+ - Updated dependencies [e17b2d1]
142
+ - @sapiom/tools@0.4.0
143
+
144
+ ## 0.1.7
145
+
146
+ ### Patch Changes
147
+
148
+ - Updated dependencies [704c9ac]
149
+ - @sapiom/tools@0.3.0
150
+
151
+ ## 0.1.6
152
+
153
+ ### Patch Changes
154
+
155
+ - Updated dependencies [7f6859e]
156
+ - @sapiom/tools@0.2.0
157
+
158
+ ## 0.1.5
159
+
160
+ ### Patch Changes
161
+
162
+ - 363270f: Generated step-input JSON Schemas no longer reject fields the schema doesn't declare.
163
+
164
+ `z.toJSONSchema()` (Zod v4) marks every object as closed (`additionalProperties: false`), but a `z.object()` ignores keys it doesn't name when it parses, rather than rejecting them. `buildManifest` now strips the closed-object marker from the schemas it emits so the two behaviors match — a step keeps validating successfully when an input it receives carries extra fields the step's `inputSchema` doesn't name. Typed catchalls (`z.object().catchall(...)`) are preserved.
165
+
166
+ Additive and non-breaking. If you previously added fields to a step's `inputSchema` only to admit extra incoming payload fields, that workaround is no longer required (though it remains harmless).
167
+
168
+ ## 0.1.4
169
+
170
+ ### Patch Changes
171
+
172
+ - 2f957ca: Depend on `@sapiom/tools` with a caret range (`^0.1`) instead of an exact
173
+ version. The dependency was declared `workspace:*`, which publishes as the exact
174
+ resolved version — so `@sapiom/orchestration@0.1.2` carried a hard `0.1.2` pin and
175
+ forced a second copy of `@sapiom/tools` whenever a project used a newer patch
176
+ (e.g. tools `0.1.3`), producing duplicate nominal types and `tsc` errors. A caret
177
+ range lets the consumer's own `@sapiom/tools` (any `0.1.x`) satisfy and dedupe to
178
+ a single copy, while still pulling tools in transitively so authoring types
179
+ resolve out of the box.
180
+
181
+ ## 0.1.3
182
+
183
+ ### Patch Changes
184
+
185
+ - Updated dependencies [2126d96]
186
+ - @sapiom/tools@0.1.3
187
+
188
+ ## 0.1.2
189
+
190
+ ### Patch Changes
191
+
192
+ - be3886e: Add the dispatch→pause→resume authoring surface for long-running capabilities.
193
+
194
+ `@sapiom/tools`: new `DispatchHandle` contract + `CODING_RESULT_SIGNAL`; coding-run
195
+ handles now carry a `dispatch` member, and `launch` forwards the engine-injected
196
+ `SAPIOM_CAPABILITY_RESUME_TOKEN` as the `x-sapiom-workflow-token` header.
197
+
198
+ `@sapiom/orchestration`: `pauseUntilSignal` accepts a `DispatchHandle |
199
+ Promise<DispatchHandle>` so a step can pause on a launched capability with
200
+ `pauseUntilSignal(ctx.sapiom.agent.coding.launch(...), { resumeStep })`.
201
+
202
+ Additive and non-breaking — standalone `agent.coding.launch` is unchanged.
203
+
204
+ - Updated dependencies [be3886e]
205
+ - @sapiom/tools@0.1.2
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Sapiom
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,130 @@
1
+ # @sapiom/agent
2
+
3
+ The versioned public contract for authoring Sapiom orchestrations.
4
+
5
+ A lean, dependency-light package (types + a small protocol runtime) shared by
6
+ three consumers:
7
+
8
+ - **Customer orchestration definitions** — authored against this package's types and
9
+ compiled by the build.
10
+ - **The sandbox step-runner** — reads a step's input, builds `ctx`, runs one step.
11
+ - **The engine** — uses the directive guards + manifest schema; it never runs
12
+ customer code, only validates the pure-data completion payload.
13
+
14
+ ## Install
15
+
16
+ ```sh
17
+ npm install @sapiom/agent
18
+ ```
19
+
20
+ ## Authoring surface
21
+
22
+ ```ts
23
+ import {
24
+ defineAgent,
25
+ defineStep,
26
+ goto,
27
+ terminate,
28
+ } from "@sapiom/agent";
29
+
30
+ const start = defineStep({
31
+ name: "start",
32
+ next: ["finish"],
33
+ async run(input, ctx) {
34
+ return goto("finish", { greeting: `hello ${input.name}` });
35
+ },
36
+ });
37
+
38
+ const finish = defineStep({
39
+ name: "finish",
40
+ next: [],
41
+ terminal: true,
42
+ async run() {
43
+ return terminate({ done: true });
44
+ },
45
+ });
46
+
47
+ export const hello = defineAgent({
48
+ name: "hello",
49
+ entry: "start",
50
+ steps: { start, finish },
51
+ });
52
+ ```
53
+
54
+ A step declares the transitions it may take (`next` / `terminal` / `canFail` /
55
+ `pause`); the `run` return type is derived from those declarations, so an
56
+ undeclared transition is a compile error. The build reads those same declarations
57
+ to render the orchestration graph without executing anything.
58
+
59
+ ## Pausing on a long-running capability
60
+
61
+ Some `ctx.sapiom` capabilities are **dispatched**: you launch them, they run far
62
+ past one step's budget, and they report back when they finish (a coding agent
63
+ today; more below). A step can't inline-`await` one — it pauses, and a later step
64
+ resumes with the result. `pauseUntilSignal` accepts the launch handle (or the
65
+ launch promise itself) and reads everything it needs off it:
66
+
67
+ ```ts
68
+ import { defineStep, pauseUntilSignal, terminate } from "@sapiom/agent";
69
+ import { CODING_RESULT_SIGNAL } from "@sapiom/tools";
70
+
71
+ const code = defineStep({
72
+ name: "code",
73
+ next: ["review"],
74
+ // capability's exported signal constant so the decl can't drift from the handle.
75
+ pause: { signal: CODING_RESULT_SIGNAL, resumeStep: "review" },
76
+ async run(input, ctx) {
77
+ // launch returns immediately; hand it straight to pauseUntilSignal. The run
78
+ // parks at status='paused' and the dispatch loop exits.
79
+ return pauseUntilSignal(
80
+ ctx.sapiom.models.coding.launch({ task: input.task }),
81
+ {
82
+ resumeStep: "review",
83
+ },
84
+ );
85
+ },
86
+ });
87
+
88
+ const review = defineStep({
89
+ name: "review",
90
+ next: [],
91
+ terminal: true,
92
+ // Give this step an `inputSchema` (a zod schema for the capability's result
93
+ // shape) to type + validate what it receives.
94
+ async run(result, ctx) {
95
+ // Fires on success OR failure — branch on the terminal result.
96
+ return terminate({ ok: result.status === "completed" });
97
+ },
98
+ });
99
+ ```
100
+
101
+ Things to know:
102
+
103
+ - **The pause is a real suspend across processes**, so the launch and the resume
104
+ are two steps — you can't fold them into one inline `await`.
105
+ - **The resumed step receives the capability's result as its input.** Declare its
106
+ `inputSchema` to type + validate it (each capability documents its result shape).
107
+ - **Pass the launch promise directly** for the one-liner above, or `await` it first
108
+ when you need the handle — to stash the run id in `ctx.shared`, or to `try/catch`
109
+ a launch failure and route somewhere other than a retry. Awaiting doesn't lose
110
+ the pause; the resolved handle still flows into `pauseUntilSignal`:
111
+
112
+ ```ts
113
+ async run(input, ctx) {
114
+ const run = await ctx.sapiom.models.coding.launch({ task: input.task });
115
+ ctx.shared.set("codingRunId", run.runId); // readable from the resumed step
116
+ return pauseUntilSignal(run, { resumeStep: "review" });
117
+ }
118
+ ```
119
+ - **Outside a workflow nothing changes** — `await launch().wait()` the capability as
120
+ usual; the pause wiring only engages when a step pauses on the handle.
121
+
122
+ ### Compatible capabilities
123
+
124
+ Any capability whose `launch` returns a `DispatchHandle` (a `dispatch` member) is
125
+ pausable; each ships a stable result-signal constant for the `pause` decl. This
126
+ list grows as capabilities land:
127
+
128
+ | Capability | Launch | Pause signal |
129
+ | ------------ | ----------------------------------- | ---------------------------------------- |
130
+ | Coding agent | `ctx.sapiom.models.coding.launch(…)` | `CODING_RESULT_SIGNAL` (`@sapiom/tools`) |
@@ -0,0 +1,10 @@
1
+ import type { StepDefinition } from './step.js';
2
+ export declare const AGENT_DEFINITION_BRAND: unique symbol;
3
+ export interface AgentDefinition<TInput = unknown, TShared extends Record<string, unknown> = Record<string, unknown>> {
4
+ readonly name: string;
5
+ readonly entry: string;
6
+ readonly steps: Readonly<Record<string, StepDefinition<TShared>>>;
7
+ readonly __inputType?: TInput;
8
+ }
9
+ export declare function isAgentDefinition(val: unknown): val is AgentDefinition;
10
+ export declare function defineAgent<TInput = unknown, TShared extends Record<string, unknown> = Record<string, unknown>>(def: AgentDefinition<TInput, TShared>): AgentDefinition<TInput, TShared>;
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AGENT_DEFINITION_BRAND = void 0;
4
+ exports.isAgentDefinition = isAgentDefinition;
5
+ exports.defineAgent = defineAgent;
6
+ const errors_js_1 = require("./errors.js");
7
+ exports.AGENT_DEFINITION_BRAND = Symbol.for('sapiom.models.definition');
8
+ function isAgentDefinition(val) {
9
+ if (val === null || typeof val !== 'object')
10
+ return false;
11
+ return val[exports.AGENT_DEFINITION_BRAND] === 1;
12
+ }
13
+ function defineAgent(def) {
14
+ if (!def.name) {
15
+ throw new Error('Agent definition must have a non-empty name');
16
+ }
17
+ if (!def.entry) {
18
+ throw new Error(`Agent '${def.name}' must declare an entry step`);
19
+ }
20
+ if (!def.steps[def.entry]) {
21
+ throw new errors_js_1.UnknownStepError(def.entry);
22
+ }
23
+ for (const [key, step] of Object.entries(def.steps)) {
24
+ if (!step) {
25
+ throw new Error(`Agent '${def.name}' has null/undefined step at key '${key}'`);
26
+ }
27
+ if (step.name !== key) {
28
+ throw new Error(`Agent '${def.name}' step name mismatch at key '${key}': step.name='${step.name}'`);
29
+ }
30
+ }
31
+ Object.defineProperty(def, exports.AGENT_DEFINITION_BRAND, {
32
+ value: 1,
33
+ enumerable: false,
34
+ writable: false,
35
+ configurable: false,
36
+ });
37
+ return def;
38
+ }
@@ -0,0 +1,15 @@
1
+ import { type AgentManifest } from "./manifest.js";
2
+ import type { AgentDefinition } from "./agent.js";
3
+ export declare function buildManifest(def: AgentDefinition, opts: {
4
+ sdkVersion: string;
5
+ artifact: {
6
+ sha256: string;
7
+ entryFile: string;
8
+ };
9
+ }): AgentManifest;
10
+ export interface GraphValidation {
11
+ readonly errors: string[];
12
+ readonly warnings: string[];
13
+ }
14
+ export declare function validateGraph(manifest: AgentManifest): GraphValidation;
15
+ export declare function assertValidGraph(manifest: AgentManifest): string[];
@@ -0,0 +1,170 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildManifest = buildManifest;
4
+ exports.validateGraph = validateGraph;
5
+ exports.assertValidGraph = assertValidGraph;
6
+ const introspection_js_1 = require("./introspection.js");
7
+ const manifest_js_1 = require("./manifest.js");
8
+ function buildManifest(def, opts) {
9
+ const steps = {};
10
+ for (const [stepName, step] of Object.entries(def.steps)) {
11
+ let inputSchema = null;
12
+ if (step.inputSchema) {
13
+ const raw = (0, introspection_js_1.zodToJsonSchema)(step.inputSchema);
14
+ inputSchema = relaxAdditionalProperties(dropDefaultedFromRequired(raw));
15
+ }
16
+ steps[stepName] = {
17
+ timeoutMs: step.timeoutMs ?? null,
18
+ inputSchema,
19
+ transitions: transitionsFor(step),
20
+ };
21
+ }
22
+ return {
23
+ protocol: manifest_js_1.MANIFEST_PROTOCOL,
24
+ name: def.name,
25
+ entry: def.entry,
26
+ sdkVersion: opts.sdkVersion,
27
+ artifact: opts.artifact,
28
+ steps,
29
+ };
30
+ }
31
+ function transitionsFor(step) {
32
+ const transitions = [];
33
+ for (const target of step.next ?? []) {
34
+ transitions.push({ kind: "continue", target });
35
+ }
36
+ if (step.pause) {
37
+ transitions.push({
38
+ kind: "pause",
39
+ signal: step.pause.signal,
40
+ resumeStep: step.pause.resumeStep,
41
+ });
42
+ }
43
+ if (step.terminal)
44
+ transitions.push({ kind: "terminate" });
45
+ if (step.canFail)
46
+ transitions.push({ kind: "fail" });
47
+ return transitions;
48
+ }
49
+ function validateGraph(manifest) {
50
+ const errors = [];
51
+ const names = new Set(Object.keys(manifest.steps));
52
+ if (!names.has(manifest.entry)) {
53
+ errors.push(`entry step '${manifest.entry}' is not in the steps map`);
54
+ }
55
+ const forward = buildForwardEdges(manifest, names, errors);
56
+ const warnings = [];
57
+ if (names.has(manifest.entry)) {
58
+ const reachable = bfs([manifest.entry], forward);
59
+ for (const name of names) {
60
+ if (!reachable.has(name))
61
+ warnings.push(`step '${name}' is unreachable from entry '${manifest.entry}'`);
62
+ }
63
+ }
64
+ warnings.push(...terminalReachabilityWarnings(manifest, names, forward));
65
+ return { errors, warnings };
66
+ }
67
+ function assertValidGraph(manifest) {
68
+ const { errors, warnings } = validateGraph(manifest);
69
+ if (errors.length > 0) {
70
+ throw new Error(`Invalid workflow graph for '${manifest.name}':\n - ${errors.join("\n - ")}`);
71
+ }
72
+ return warnings;
73
+ }
74
+ function buildForwardEdges(manifest, names, errors) {
75
+ const forward = new Map();
76
+ for (const [name, step] of Object.entries(manifest.steps)) {
77
+ const targets = new Set();
78
+ for (const t of step.transitions) {
79
+ if (t.kind === "continue") {
80
+ if (names.has(t.target))
81
+ targets.add(t.target);
82
+ else
83
+ errors.push(`step '${name}' has a continue target '${t.target}' that is not in the steps map`);
84
+ }
85
+ else if (t.kind === "pause" && names.has(t.resumeStep)) {
86
+ targets.add(t.resumeStep);
87
+ }
88
+ }
89
+ if (step.transitions.length === 0) {
90
+ errors.push(`step '${name}' is a dead-end: it declares no transitions (next/terminal/canFail/pause)`);
91
+ }
92
+ forward.set(name, targets);
93
+ }
94
+ return forward;
95
+ }
96
+ function terminalReachabilityWarnings(manifest, names, forward) {
97
+ const sinks = Object.entries(manifest.steps)
98
+ .filter(([, s]) => s.transitions.some((t) => t.kind === "terminate" || t.kind === "fail"))
99
+ .map(([name]) => name);
100
+ if (sinks.length === 0) {
101
+ return [
102
+ "no step can terminate or fail — the workflow has no terminal state",
103
+ ];
104
+ }
105
+ const reverse = new Map();
106
+ for (const name of names)
107
+ reverse.set(name, new Set());
108
+ for (const [name, targets] of forward) {
109
+ for (const t of targets)
110
+ reverse.get(t)?.add(name);
111
+ }
112
+ const canReach = bfs(sinks, reverse);
113
+ const warnings = [];
114
+ for (const name of names) {
115
+ if (!canReach.has(name)) {
116
+ warnings.push(`step '${name}' cannot reach any terminate/fail (possible unbounded loop or missing terminal)`);
117
+ }
118
+ }
119
+ return warnings;
120
+ }
121
+ function bfs(starts, edges) {
122
+ const seen = new Set();
123
+ const queue = [...starts];
124
+ while (queue.length > 0) {
125
+ const cur = queue.shift();
126
+ if (seen.has(cur))
127
+ continue;
128
+ seen.add(cur);
129
+ for (const next of edges.get(cur) ?? [])
130
+ if (!seen.has(next))
131
+ queue.push(next);
132
+ }
133
+ return seen;
134
+ }
135
+ function relaxAdditionalProperties(value) {
136
+ if (Array.isArray(value)) {
137
+ return value.map(relaxAdditionalProperties);
138
+ }
139
+ if (value && typeof value === "object") {
140
+ const out = {};
141
+ for (const [key, v] of Object.entries(value)) {
142
+ if (key === "additionalProperties" && v === false) {
143
+ continue;
144
+ }
145
+ out[key] = relaxAdditionalProperties(v);
146
+ }
147
+ return out;
148
+ }
149
+ return value;
150
+ }
151
+ function dropDefaultedFromRequired(schema) {
152
+ const required = schema.required;
153
+ const properties = schema.properties;
154
+ if (!Array.isArray(required) ||
155
+ !properties ||
156
+ typeof properties !== "object") {
157
+ return schema;
158
+ }
159
+ const props = properties;
160
+ const filtered = required.filter((key) => {
161
+ if (typeof key !== "string")
162
+ return true;
163
+ const prop = props[key];
164
+ return !(prop && typeof prop === "object" && "default" in prop);
165
+ });
166
+ if (filtered.length === required.length) {
167
+ return schema;
168
+ }
169
+ return { ...schema, required: filtered };
170
+ }
@@ -0,0 +1,47 @@
1
+ import type { Sapiom } from '@sapiom/tools';
2
+ import type { NextStepDirective } from './directives.js';
3
+ export interface StepLogger {
4
+ info(message: string, meta?: Record<string, unknown>): void;
5
+ warn(message: string, meta?: Record<string, unknown>): void;
6
+ error(message: string, meta?: Record<string, unknown>): void;
7
+ debug(message: string, meta?: Record<string, unknown>): void;
8
+ }
9
+ export type FinishedStepStatus = 'dispatched' | 'succeeded' | 'failed';
10
+ export interface AgentExecutionContext<TShared extends Record<string, unknown> = Record<string, unknown>> {
11
+ readonly executionId: string;
12
+ readonly workflowName: string;
13
+ readonly organizationId: string | null;
14
+ readonly tenantId: string | null;
15
+ readonly input: unknown;
16
+ readonly shared: TypedContextStore<TShared>;
17
+ readonly history: readonly StepExecutionRecord[];
18
+ readonly attempts: number;
19
+ readonly logger: StepLogger;
20
+ readonly sapiom: Sapiom;
21
+ }
22
+ export interface TypedContextStore<TShared extends Record<string, unknown>> {
23
+ get<K extends keyof TShared>(key: K): TShared[K] | undefined;
24
+ set<K extends keyof TShared>(key: K, value: TShared[K]): void;
25
+ has<K extends keyof TShared>(key: K): boolean;
26
+ snapshot(): Partial<TShared>;
27
+ }
28
+ export interface StepExecutionRecord {
29
+ readonly stepName: string;
30
+ readonly attempt: number;
31
+ readonly status: FinishedStepStatus;
32
+ readonly input: unknown;
33
+ readonly output: unknown;
34
+ readonly error: unknown;
35
+ readonly nextDirective: NextStepDirective | null;
36
+ readonly sharedStateAfter: Record<string, unknown> | null;
37
+ readonly startedAt: Date;
38
+ readonly finishedAt: Date;
39
+ }
40
+ export declare class InMemoryContextStore<TShared extends Record<string, unknown>> implements TypedContextStore<TShared> {
41
+ private state;
42
+ constructor(initial?: Partial<TShared>);
43
+ get<K extends keyof TShared>(key: K): TShared[K] | undefined;
44
+ set<K extends keyof TShared>(key: K, value: TShared[K]): void;
45
+ has<K extends keyof TShared>(key: K): boolean;
46
+ snapshot(): Partial<TShared>;
47
+ }
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.InMemoryContextStore = void 0;
4
+ class InMemoryContextStore {
5
+ constructor(initial = {}) {
6
+ this.state = { ...initial };
7
+ }
8
+ get(key) {
9
+ return this.state[key];
10
+ }
11
+ set(key, value) {
12
+ this.state[key] = value;
13
+ }
14
+ has(key) {
15
+ return key in this.state;
16
+ }
17
+ snapshot() {
18
+ return { ...this.state };
19
+ }
20
+ }
21
+ exports.InMemoryContextStore = InMemoryContextStore;