@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.
- package/CHANGELOG.md +205 -0
- package/LICENSE +21 -0
- package/README.md +130 -0
- package/dist/cjs/agent.d.ts +10 -0
- package/dist/cjs/agent.js +38 -0
- package/dist/cjs/build-manifest.d.ts +15 -0
- package/dist/cjs/build-manifest.js +170 -0
- package/dist/cjs/context.d.ts +47 -0
- package/dist/cjs/context.js +21 -0
- package/dist/cjs/directives.d.ts +95 -0
- package/dist/cjs/directives.js +74 -0
- package/dist/cjs/errors.d.ts +19 -0
- package/dist/cjs/errors.js +49 -0
- package/dist/cjs/index.d.ts +17 -0
- package/dist/cjs/index.js +41 -0
- package/dist/cjs/introspection.d.ts +11 -0
- package/dist/cjs/introspection.js +62 -0
- package/dist/cjs/manifest.d.ts +56 -0
- package/dist/cjs/manifest.js +27 -0
- package/dist/cjs/step.d.ts +42 -0
- package/dist/cjs/step.js +15 -0
- package/dist/esm/agent.d.ts +10 -0
- package/dist/esm/agent.js +33 -0
- package/dist/esm/build-manifest.d.ts +15 -0
- package/dist/esm/build-manifest.js +165 -0
- package/dist/esm/context.d.ts +47 -0
- package/dist/esm/context.js +17 -0
- package/dist/esm/directives.d.ts +95 -0
- package/dist/esm/directives.js +61 -0
- package/dist/esm/errors.d.ts +19 -0
- package/dist/esm/errors.js +42 -0
- package/dist/esm/index.d.ts +17 -0
- package/dist/esm/index.js +9 -0
- package/dist/esm/introspection.d.ts +11 -0
- package/dist/esm/introspection.js +56 -0
- package/dist/esm/manifest.d.ts +56 -0
- package/dist/esm/manifest.js +24 -0
- package/dist/esm/package.json +1 -0
- package/dist/esm/step.d.ts +42 -0
- package/dist/esm/step.js +12 -0
- package/dist/tsconfig.cjs.tsbuildinfo +1 -0
- package/dist/tsconfig.esm.tsbuildinfo +1 -0
- 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;
|