@scfloworg/actions 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +60 -2
- package/dist/batch.d.ts +44 -0
- package/dist/batch.d.ts.map +1 -0
- package/dist/batch.js +132 -0
- package/dist/batch.js.map +1 -0
- package/dist/describe.d.ts +27 -0
- package/dist/describe.d.ts.map +1 -0
- package/dist/describe.js +104 -0
- package/dist/describe.js.map +1 -0
- package/dist/errors.d.ts +30 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +43 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/json.d.ts +11 -0
- package/dist/json.d.ts.map +1 -0
- package/dist/json.js +44 -0
- package/dist/json.js.map +1 -0
- package/dist/parameters.d.ts +31 -0
- package/dist/parameters.d.ts.map +1 -0
- package/dist/parameters.js +141 -0
- package/dist/parameters.js.map +1 -0
- package/dist/registry.d.ts +38 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +113 -0
- package/dist/registry.js.map +1 -0
- package/dist/tools.d.ts +51 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/tools.js +106 -0
- package/dist/tools.js.map +1 -0
- package/dist/types.d.ts +118 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +7 -0
- package/dist/types.js.map +1 -0
- package/docs/actions.md +212 -0
- package/package.json +39 -4
package/dist/types.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The types of the action contract (`packages/core/docs/spec/meta-model.md`,
|
|
3
|
+
* section Action). They hold no model: an action is an id, a description for
|
|
4
|
+
* people and AI, its parameters as JSON Schema and a body.
|
|
5
|
+
*/
|
|
6
|
+
export {};
|
|
7
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG"}
|
package/docs/actions.md
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
# Actions
|
|
2
|
+
|
|
3
|
+
The neutral action package holds the part of the action contract that does not
|
|
4
|
+
need the engine. The rules of actions are in the specification
|
|
5
|
+
([Action](../../core/docs/spec/meta-model.md#action),
|
|
6
|
+
[Script](../../core/docs/spec/model-command-language.md#script) and
|
|
7
|
+
[Action Package](../../core/docs/spec/typescript-reference-implementation.md#action-package));
|
|
8
|
+
this page gives the use of the package.
|
|
9
|
+
|
|
10
|
+
## Actions
|
|
11
|
+
|
|
12
|
+
```ts
|
|
13
|
+
interface ActionDefinition {
|
|
14
|
+
id: string; // namespace/name, both identifiers
|
|
15
|
+
title?: string;
|
|
16
|
+
description?: string;
|
|
17
|
+
kind?: "read" | "action"; // action by default
|
|
18
|
+
destructive?: boolean;
|
|
19
|
+
exposure?: "visible" | "hidden"; // visible by default
|
|
20
|
+
parameters?: JsonSchema | string; // a JSON Schema object, or its JSON text
|
|
21
|
+
writes?: readonly string[]; // kept for the engine
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface Action<C = RunTrace> extends ActionDefinition {
|
|
25
|
+
run(params: any, context: C): unknown; // sync or async
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
A definition is the declaration of a manifest: a module lists it without code.
|
|
30
|
+
An `Action` adds its body, which receives the parameters after their check and
|
|
31
|
+
the context its host builds. Without a host context, the body receives its
|
|
32
|
+
`RunTrace`.
|
|
33
|
+
|
|
34
|
+
## Parameters
|
|
35
|
+
|
|
36
|
+
`parameters` is a JSON Schema object, draft 2020-12, the version of MCP tools,
|
|
37
|
+
checked by Ajv before the body runs, so a body checks nothing by hand.
|
|
38
|
+
|
|
39
|
+
- Its `type` is `object`. Absent, the action takes an empty object, and no
|
|
40
|
+
parameters (`undefined`) stand for `{}`.
|
|
41
|
+
- An unknown keyword is refused when the action is registered, with
|
|
42
|
+
`constraint_violation`, as a schema that is not valid. `format` is an
|
|
43
|
+
annotation: no format vocabulary is loaded.
|
|
44
|
+
- `default` fills an absent property. The body receives a copy: the parameters
|
|
45
|
+
of the caller do not change.
|
|
46
|
+
- Parameters that break the schema are refused with `invalid_parameters`; its
|
|
47
|
+
message lists every breach.
|
|
48
|
+
|
|
49
|
+
**`ref`.** The keyword `ref` names a schema of the model: the value is the id of
|
|
50
|
+
an instance of it, or the exact `display_name` of one instance, in any
|
|
51
|
+
language, and the body receives the key. The package reads no instance, so the
|
|
52
|
+
host gives a resolver:
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
type RefResolver = (schema: string, value: string) => RefMatches | Promise<RefMatches>;
|
|
56
|
+
interface RefMatches {
|
|
57
|
+
id: string | null; // the key of the instance whose id is the value
|
|
58
|
+
names: readonly string[]; // the keys of the instances whose display_name is the value
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
The package applies the rule: an id wins over a name that equals it, one name
|
|
63
|
+
gives its key, and no instance or several are refused with
|
|
64
|
+
`invalid_parameters`. A `ref` is found wherever it sits in the schema, in an
|
|
65
|
+
array or a nested object included. Without a resolver, a `ref` value reaches
|
|
66
|
+
the body as it is.
|
|
67
|
+
|
|
68
|
+
`validateParameters(definition, params, resolveRef?)` runs this check without a
|
|
69
|
+
registry.
|
|
70
|
+
|
|
71
|
+
## The registry
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
const registry = createActionRegistry<C>();
|
|
75
|
+
registry.register(action);
|
|
76
|
+
registry.get(id); registry.list();
|
|
77
|
+
const { result, entry } = await registry.run(id, params, host);
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
- `register` refuses an id that is not a namespace, a slash and a name with
|
|
81
|
+
`constraint_violation`, and an id already registered with `duplicate_name`.
|
|
82
|
+
It compiles the parameters once.
|
|
83
|
+
- `run` refuses an unknown id with `unknown_action`. It checks the parameters,
|
|
84
|
+
runs the body and gives its result with the run entry of the call. A refused
|
|
85
|
+
call throws its exception as it is.
|
|
86
|
+
- **It opens no transaction.** The host runs the call inside the transaction of
|
|
87
|
+
the action, and rolls it back when the call throws.
|
|
88
|
+
|
|
89
|
+
The host of a run:
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
interface RunHost<C> {
|
|
93
|
+
context?(trace: RunTrace): C; // the context of each body, built on its trace
|
|
94
|
+
resolveRef?: RefResolver;
|
|
95
|
+
onEntry?(entry: RunEntry): void; // each top-level entry, done or refused
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
interface RunTrace {
|
|
99
|
+
action: string;
|
|
100
|
+
command(command, result): void; // records a command the body emitted
|
|
101
|
+
call(id, params?): Promise<unknown>; // runs another action, recorded as a step
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
The engine builds the context of a body on the trace: each command it runs for
|
|
106
|
+
the body is recorded with `trace.command`, and its `call` goes through
|
|
107
|
+
`trace.call`, so the run log holds what the body did. Which actions a body may
|
|
108
|
+
call, and which schemas it may write, are checked by the engine.
|
|
109
|
+
|
|
110
|
+
## The run log
|
|
111
|
+
|
|
112
|
+
A run entry follows `run_entry` of the specification:
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
interface RunEntry {
|
|
116
|
+
action: string;
|
|
117
|
+
parameters: string; // canonical JSON, after the check, refs resolved
|
|
118
|
+
steps: RunStep[]; // { command, result } or { call: RunEntry }
|
|
119
|
+
outcome: "done" | "refused";
|
|
120
|
+
error?: string; // the code of the exception of a refused call
|
|
121
|
+
result?: string; // canonical JSON; absent when the body returned none
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
- Every call has an entry, a refused one included. A call refused by its
|
|
126
|
+
parameters records the parameters it was given.
|
|
127
|
+
- A nested call is a step of the entry that makes it, refused or not.
|
|
128
|
+
- A command is recorded as the body emitted it, so `${now()}` and the keys of
|
|
129
|
+
the engine stay placeholders, and its result in canonical JSON.
|
|
130
|
+
- `canonicalJson(value)` writes the canonical form: the keys of every object
|
|
131
|
+
sorted and no white space. A replay compares steps in this form.
|
|
132
|
+
- `createRunLog()` keeps entries in memory: pass `log.record` as `onEntry`.
|
|
133
|
+
|
|
134
|
+
## Batches
|
|
135
|
+
|
|
136
|
+
```ts
|
|
137
|
+
const { result, entries } = await runBatch(registry, {
|
|
138
|
+
commands: [
|
|
139
|
+
{ call: "scflow/create_actor", with: { display_name: "Plant", type: "plant" }, as: "plant" },
|
|
140
|
+
{ call: "carbon/update_actor_emissions", with: { id: "${plant.id}", co2: 12 } },
|
|
141
|
+
],
|
|
142
|
+
result: "${plant}",
|
|
143
|
+
}, host);
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
The package runs a batch whose steps are calls, each with `with` and `as` only.
|
|
147
|
+
|
|
148
|
+
- The batch is checked whole first: any other step, such as a command, `when`,
|
|
149
|
+
`each` or `fail`, and a batch with `parameters`, are refused with
|
|
150
|
+
`invalid_script_step`, before any call. The engine runs such a batch through
|
|
151
|
+
its script runner.
|
|
152
|
+
- Before each call, the placeholders of `with` are resolved against the results
|
|
153
|
+
named so far, by the rules of placeholders on arrays: an index, `*` for every
|
|
154
|
+
entry, or entry `0` by default. A placeholder that is the whole string gives
|
|
155
|
+
the value of any type, one inside text gives its text, and a value that
|
|
156
|
+
resolves to nothing leaves its property out.
|
|
157
|
+
- A placeholder whose first segment is not an `as` name set yet is refused with
|
|
158
|
+
`unknown_reference`. A placeholder that is not a path, such as `${now()}`, is
|
|
159
|
+
passed on to the called action.
|
|
160
|
+
- The result of the batch is its `result` placeholder, or the result of its last
|
|
161
|
+
call. `entries` holds the entry of each call, which `host.onEntry` also
|
|
162
|
+
receives.
|
|
163
|
+
- A refused call stops the batch and its exception is thrown. **The host runs
|
|
164
|
+
the batch in one transaction**, rolled back when it throws, so a batch is
|
|
165
|
+
undone as one.
|
|
166
|
+
|
|
167
|
+
## Export to AI tools
|
|
168
|
+
|
|
169
|
+
`toAiTools(actions)` maps each `visible` action to an MCP tool definition:
|
|
170
|
+
|
|
171
|
+
| Tool | From the action |
|
|
172
|
+
| --- | --- |
|
|
173
|
+
| `name` | `toolName(id)`: the slash replaced by a hyphen, `scflow/create_actor` gives `scflow-create_actor` |
|
|
174
|
+
| `title` | As it is |
|
|
175
|
+
| `description` | As it is, followed by the rule of a top-level `anyOf` of `required` alone |
|
|
176
|
+
| `inputSchema` | `parameters`, `ref` included, without `anyOf`, `oneOf` or `allOf` at its top level, or an empty object schema |
|
|
177
|
+
| `annotations.readOnlyHint` | `kind: read` |
|
|
178
|
+
| `annotations.destructiveHint` | `destructive` |
|
|
179
|
+
|
|
180
|
+
A hyphen never appears in an identifier, so `actionIdOfTool(name)` gives the id
|
|
181
|
+
back for a call that comes from an AI client. An action whose `exposure` is
|
|
182
|
+
`hidden` is left out; `toAiTool(action)` maps one action whatever its exposure.
|
|
183
|
+
|
|
184
|
+
`toAiTools(actions, { languages })` takes the ordered languages of the dataset: the description of each localized text parameter (a string or a map of language to string, in an `anyOf`) then names them and the source language, the first. The schema forbids nothing more, because the list offers and expects; left out or empty, the tools are as before. `parametersWithLanguages(action, languages)` gives the same for one action.
|
|
185
|
+
|
|
186
|
+
AI providers refuse `anyOf`, `oneOf` and `allOf` at the top level of a tool's
|
|
187
|
+
schema, and one refused tool fails the whole request. The export leaves them out,
|
|
188
|
+
and the action still checks its full `parameters` when it runs. An `anyOf` whose
|
|
189
|
+
branches each hold only a `required` of one property means "at least one of
|
|
190
|
+
these": the description states it, as `Give at least one of: color, icon.`
|
|
191
|
+
These keywords stay at any deeper level, where providers accept them.
|
|
192
|
+
|
|
193
|
+
## Hand-off to the engine
|
|
194
|
+
|
|
195
|
+
These parts are given to the package by the engine, in later pieces:
|
|
196
|
+
|
|
197
|
+
- the resolver of `ref` (`RunHost.resolveRef`) and the context of bodies built
|
|
198
|
+
on the trace (`RunHost.context`), with the action declarations per module
|
|
199
|
+
and the generated actions;
|
|
200
|
+
- the steps `when`, `each` and `fail` of a batch, and procedures, run by the
|
|
201
|
+
script runner of the engine.
|
|
202
|
+
|
|
203
|
+
The replay of a body against its run entry is `replayEntry` of
|
|
204
|
+
`@scfloworg/conformance`, which runs on this package's registry and needs no
|
|
205
|
+
engine.
|
|
206
|
+
|
|
207
|
+
## Errors
|
|
208
|
+
|
|
209
|
+
The package throws `ActionError`, whose `code` is one of `invalid_parameters`,
|
|
210
|
+
`unknown_action`, `duplicate_name`, `constraint_violation`,
|
|
211
|
+
`invalid_script_step` and `unknown_reference`, the codes the engine uses for the
|
|
212
|
+
same causes. An exception of a body is thrown as it is.
|
package/package.json
CHANGED
|
@@ -1,6 +1,41 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@scfloworg/actions",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"publishConfig": {
|
|
7
|
+
"registry": "https://registry.npmjs.org/",
|
|
8
|
+
"access": "public"
|
|
9
|
+
},
|
|
10
|
+
"description": "The neutral action package: the action type aligned on MCP tools, parameters checked by a JSON Schema validator, a registry, batches of calls, the export to AI tools and the run log. No SCFlow dependency. See README.md.",
|
|
11
|
+
"main": "dist/index.js",
|
|
12
|
+
"types": "dist/index.d.ts",
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"default": "./dist/index.js"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"dist",
|
|
21
|
+
"docs"
|
|
22
|
+
],
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "tsc -b",
|
|
25
|
+
"typecheck": "tsc --noEmit",
|
|
26
|
+
"test": "vitest run"
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"ajv": "^8.17.1"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@scfloworg/conformance": "^0.1.0",
|
|
33
|
+
"typescript": "^5.6.3",
|
|
34
|
+
"vitest": "^4.1.10"
|
|
35
|
+
},
|
|
36
|
+
"repository": {
|
|
37
|
+
"type": "git",
|
|
38
|
+
"url": "git+https://github.com/SCFlowOrg/scflow-core.git",
|
|
39
|
+
"directory": "packages/actions"
|
|
40
|
+
}
|
|
41
|
+
}
|