@llm4ts/shell 2.14.0 → 2.16.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/flows/epic-stories-board.js +18 -0
- package/flows/epic-stories.js +3 -179
- package/flows/lib/epic-stories.js +201 -2
- package/flows/lib/story-board.js +232 -0
- package/kits/soap-ace/README.md +50 -9
- package/kits/soap-ace/fixtures/demo-bank-soap/RUNBOOK.md +16 -0
- package/kits/soap-ace/flows/lib/soap/Call.js +10 -6
- package/kits/soap-ace/flows/lib/soap/Explore.js +476 -0
- package/kits/soap-ace/flows/soap-explore.js +190 -0
- package/package.json +4 -4
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
// One pass from a WSDL to a design draft: discover, a request for every operation, a read-only probe with values chained between calls, analyses, the 1:1 mapping, and a drafted REST design; stops only where a person decides.
|
|
2
|
+
//
|
|
3
|
+
// The task text is the WSDL location (path or http(s) URL), as for
|
|
4
|
+
// soap-discover; without one it continues from the discovered service:
|
|
5
|
+
//
|
|
6
|
+
// llm4ts run soap-explore --repo ~/work/conti-api ./wsdl/DemoBank.wsdl
|
|
7
|
+
// llm4ts run soap-explore --repo . # rerun: continue where it stopped
|
|
8
|
+
// llm4ts run soap-explore --repo . -- --refresh # call the probed operations again
|
|
9
|
+
//
|
|
10
|
+
// Steps, each keeping what already exists (so a rerun continues):
|
|
11
|
+
//
|
|
12
|
+
// 1. discover catalog.json/.md; operations.md with proposed classes
|
|
13
|
+
// 2. requests samples/<op>/happy-path.request.yaml for every operation
|
|
14
|
+
// 3. probe calls read operations only, producers first; values a
|
|
15
|
+
// response returns seed the next request (live in memory,
|
|
16
|
+
// masked on disk). Before operations.md is confirmed an
|
|
17
|
+
// operation is probed only in dev/test when the name
|
|
18
|
+
// heuristic and the judgment seat (LLM4TS_JUDGMENT_PROVIDER)
|
|
19
|
+
// both say read. Mutating operations are never called.
|
|
20
|
+
// 4. evidence analysis/<op>.md, design/mapping.json/.md
|
|
21
|
+
// 5. design the reasoning seat (LLM4TS_REASONER, default claude)
|
|
22
|
+
// drafts design/api-design.md, check, one revise round;
|
|
23
|
+
// an existing design is only checked. --no-design skips.
|
|
24
|
+
// 6. report explore.md: per operation what happened, and the next
|
|
25
|
+
// command for every gap
|
|
26
|
+
//
|
|
27
|
+
// Calls need auth.json (at least {"environment": "test"}); explore never
|
|
28
|
+
// writes it. Approval stays human: operations.md `Status: confirmed`,
|
|
29
|
+
// api-design.md `Status: approved`. LLM4TS_SOAP_SERVICE names the service
|
|
30
|
+
// (and the auth.json used to fetch a URL); LLM4TS_SOAP_STUB=<dir> answers
|
|
31
|
+
// calls from <dir>/<operation>.xml instead of the network.
|
|
32
|
+
import { readFile } from "node:fs/promises";
|
|
33
|
+
import { isAbsolute, resolve } from "node:path";
|
|
34
|
+
import { fileURLToPath } from "node:url";
|
|
35
|
+
import * as Effect from "effect/Effect";
|
|
36
|
+
import { FlowAborted, Info, makeNodeWorkspace, mock, resolveFlowInput, runFlowMain, runNode, ScriptUsage } from "@llm4ts/runner";
|
|
37
|
+
import { coderFor, judgmentConnectorFromEnvironment } from "@llm4ts/runner/Connectors";
|
|
38
|
+
import { nodeSecretSource, resolveSide } from "./lib/soap/Auth.js";
|
|
39
|
+
import { heuristicClass, judgeClass } from "./lib/soap/Classification.js";
|
|
40
|
+
import { discoverService, loadAuthProfile, loadCatalog, loadOperationsFile } from "./lib/soap/Discover.js";
|
|
41
|
+
import { designStep, explorePaths, exploreService, renderExploreReport, writeEvidence } from "./lib/soap/Explore.js";
|
|
42
|
+
import { makeDirectoryStubTransport } from "./lib/soap/Stub.js";
|
|
43
|
+
import { makeNodeSoapTransport, makeTransportDocumentLoader } from "./lib/soap/Transport.js";
|
|
44
|
+
import { DocumentLoader } from "./lib/soap/Wsdl.js";
|
|
45
|
+
const usage = 'usage: soap-explore [--refresh] [--no-design] "<wsdl path or URL>"';
|
|
46
|
+
const isUrl = (location) => /^https?:\/\//i.test(location);
|
|
47
|
+
const parseExploreFlags = (argv) => Effect.gen(function* () {
|
|
48
|
+
let refresh = false;
|
|
49
|
+
let design = true;
|
|
50
|
+
const rest = [];
|
|
51
|
+
for (const argument of argv) {
|
|
52
|
+
if (argument === "--refresh")
|
|
53
|
+
refresh = true;
|
|
54
|
+
else if (argument === "--no-design")
|
|
55
|
+
design = false;
|
|
56
|
+
else if (argument.startsWith("--refresh=") || argument.startsWith("--no-design="))
|
|
57
|
+
return yield* ScriptUsage.make({ message: `${argument} takes no value\n${usage}` });
|
|
58
|
+
else
|
|
59
|
+
rest.push(argument);
|
|
60
|
+
}
|
|
61
|
+
return { refresh, design, rest };
|
|
62
|
+
});
|
|
63
|
+
const styleGuide = () => Effect.tryPromise({
|
|
64
|
+
try: () => readFile(fileURLToPath(new URL("../packs/ace12-rest/api-style.md", import.meta.url)), "utf8"),
|
|
65
|
+
catch: () => FlowAborted.make({ message: "cannot read the kit's packs/ace12-rest/api-style.md" })
|
|
66
|
+
});
|
|
67
|
+
const program = Effect.gen(function* () {
|
|
68
|
+
const flags = yield* parseExploreFlags(process.argv.slice(2));
|
|
69
|
+
const input = yield* resolveFlowInput("", flags.rest);
|
|
70
|
+
const raw = input.prompt.trim();
|
|
71
|
+
const location = raw === "" ? undefined : isUrl(raw) || isAbsolute(raw) ? raw : resolve(input.workspace, raw);
|
|
72
|
+
const environment = process.env;
|
|
73
|
+
const requested = environment.LLM4TS_SOAP_SERVICE?.trim() || undefined;
|
|
74
|
+
const judgment = yield* judgmentConnectorFromEnvironment();
|
|
75
|
+
const reasonerName = (environment.LLM4TS_REASONER ?? "claude").trim() || "claude";
|
|
76
|
+
const reasoner = flags.design ? coderFor(reasonerName) : undefined;
|
|
77
|
+
if (flags.design && reasoner === undefined) {
|
|
78
|
+
return yield* ScriptUsage.make({ message: `unknown LLM4TS_REASONER '${reasonerName}'` });
|
|
79
|
+
}
|
|
80
|
+
yield* runNode({
|
|
81
|
+
workDir: input.workDir,
|
|
82
|
+
workspace: input.workspace,
|
|
83
|
+
userPrompt: input.prompt,
|
|
84
|
+
coder: mock,
|
|
85
|
+
...(reasoner === undefined ? {} : { reasoning: reasoner }),
|
|
86
|
+
...(judgment === undefined ? {} : { judgment }),
|
|
87
|
+
environment
|
|
88
|
+
}, (context) => Effect.gen(function* () {
|
|
89
|
+
const say = (message) => context.events.publish(Info.make({ message }));
|
|
90
|
+
const workspace = yield* makeNodeWorkspace(input.workDir);
|
|
91
|
+
const seat = judgment === undefined ? undefined : context.judgment;
|
|
92
|
+
// 1. discover (or continue from the discovered service)
|
|
93
|
+
let service;
|
|
94
|
+
let catalog;
|
|
95
|
+
let judged;
|
|
96
|
+
if (location !== undefined) {
|
|
97
|
+
const fetchProfile = requested === undefined ? undefined : yield* loadAuthProfile(workspace, requested);
|
|
98
|
+
const fetchSide = yield* resolveSide(nodeSecretSource(environment), fetchProfile?.fetch);
|
|
99
|
+
const discovered = yield* discoverService({
|
|
100
|
+
workspace,
|
|
101
|
+
location,
|
|
102
|
+
...(requested === undefined ? {} : { service: requested }),
|
|
103
|
+
...(seat === undefined ? {} : { judgment: seat })
|
|
104
|
+
}).pipe(Effect.provideService(DocumentLoader, makeTransportDocumentLoader(makeNodeSoapTransport(), fetchSide)));
|
|
105
|
+
service = discovered.service;
|
|
106
|
+
catalog = discovered.catalog;
|
|
107
|
+
// Classes judged at discovery are reused; otherwise judged below.
|
|
108
|
+
if (discovered.proposals !== undefined && seat !== undefined) {
|
|
109
|
+
judged = new Map(discovered.proposals.map((proposal) => [proposal.operation, proposal.judged]));
|
|
110
|
+
}
|
|
111
|
+
yield* say(`discovered ${service}: ${catalog.operations.length} operations, ${catalog.openQuestions.length} open questions`);
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
const services = (yield* workspace.discover(".llm4ts/soap/*/catalog.json")).map((path) => path.split("/")[2] ?? "");
|
|
115
|
+
const picked = requested ?? (services.length === 1 ? services[0] : undefined);
|
|
116
|
+
if (picked === undefined) {
|
|
117
|
+
return yield* FlowAborted.make({
|
|
118
|
+
message: services.length === 0
|
|
119
|
+
? `nothing discovered here; give the WSDL\n${usage}`
|
|
120
|
+
: `several services (${services.join(", ")}); set LLM4TS_SOAP_SERVICE`
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
service = picked;
|
|
124
|
+
catalog = yield* loadCatalog(workspace, service);
|
|
125
|
+
yield* say(`continuing ${service}: ${catalog.operations.length} operations`);
|
|
126
|
+
}
|
|
127
|
+
const profile = yield* loadAuthProfile(workspace, service);
|
|
128
|
+
const operations = yield* loadOperationsFile(workspace, service);
|
|
129
|
+
// Provisional probing asks the judgment only where the listed class
|
|
130
|
+
// and the heuristic already say read.
|
|
131
|
+
if (seat !== undefined && judged === undefined && operations?.confirmed !== true) {
|
|
132
|
+
const candidates = catalog.operations.filter((operation) => operations?.classes.get(operation.name) === "read" &&
|
|
133
|
+
heuristicClass(operation.name).class === "read");
|
|
134
|
+
judged = new Map(yield* Effect.forEach(candidates, (operation) => Effect.map(judgeClass(seat, catalog, operation), (answer) => [operation.name, answer])));
|
|
135
|
+
}
|
|
136
|
+
// 2-3. requests and the probe
|
|
137
|
+
const stub = environment.LLM4TS_SOAP_STUB?.trim();
|
|
138
|
+
const transport = stub === undefined || stub === ""
|
|
139
|
+
? makeNodeSoapTransport()
|
|
140
|
+
: makeDirectoryStubTransport(resolve(input.workspace, stub), catalog);
|
|
141
|
+
if (stub !== undefined && stub !== "")
|
|
142
|
+
yield* say(`LLM4TS_SOAP_STUB: answering from ${stub}, not the network`);
|
|
143
|
+
const result = yield* exploreService({
|
|
144
|
+
workspace,
|
|
145
|
+
catalog,
|
|
146
|
+
service,
|
|
147
|
+
profile,
|
|
148
|
+
operations,
|
|
149
|
+
judged,
|
|
150
|
+
secrets: nodeSecretSource(environment),
|
|
151
|
+
transport,
|
|
152
|
+
refresh: flags.refresh,
|
|
153
|
+
progress: (line) => say(` ${line}`)
|
|
154
|
+
});
|
|
155
|
+
const probed = result.decisions.filter((decision) => decision.probe).length;
|
|
156
|
+
yield* say(`probe: ${probed} of ${catalog.operations.length} operations callable now${profile === undefined ? " (no auth.json)" : ""}`);
|
|
157
|
+
// 4. evidence
|
|
158
|
+
const evidence = yield* writeEvidence(workspace, catalog, service);
|
|
159
|
+
// 5. design
|
|
160
|
+
const design = yield* designStep({
|
|
161
|
+
workspace,
|
|
162
|
+
catalog,
|
|
163
|
+
service,
|
|
164
|
+
operations,
|
|
165
|
+
evidence,
|
|
166
|
+
reasoning: flags.design ? context.reasoning : { off: "--no-design" },
|
|
167
|
+
style: yield* styleGuide()
|
|
168
|
+
});
|
|
169
|
+
yield* say(design.kind === "skipped" || design.kind === "failed"
|
|
170
|
+
? `design: not drafted (${design.kind === "skipped" ? design.reason : design.detail})`
|
|
171
|
+
: `design: ${design.kind}, ${design.approved ? "approved" : "proposed"}, ${design.errors} errors, ${design.warnings} warnings`);
|
|
172
|
+
// 6. report
|
|
173
|
+
const report = renderExploreReport({
|
|
174
|
+
service,
|
|
175
|
+
catalog,
|
|
176
|
+
profile,
|
|
177
|
+
operations,
|
|
178
|
+
result,
|
|
179
|
+
design,
|
|
180
|
+
evidence
|
|
181
|
+
});
|
|
182
|
+
const path = explorePaths(service).report;
|
|
183
|
+
yield* workspace.write(path, report);
|
|
184
|
+
const next = report.split("## Next")[1]?.trim().split("\n") ?? [];
|
|
185
|
+
yield* say(`wrote ${path}; next:`);
|
|
186
|
+
for (const line of next)
|
|
187
|
+
yield* say(` ${line}`);
|
|
188
|
+
}));
|
|
189
|
+
});
|
|
190
|
+
runFlowMain(program);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@llm4ts/shell",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.16.0",
|
|
4
4
|
"description": "Interactive shell and CLI for llm4ts: flow discovery, run-a-flow, and view",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -52,9 +52,9 @@
|
|
|
52
52
|
"dependencies": {
|
|
53
53
|
"@effect/platform-node": "4.0.0-rc.115",
|
|
54
54
|
"@effect/platform-node-shared": "4.0.0-rc.115",
|
|
55
|
-
"@llm4ts/core": "2.
|
|
56
|
-
"@llm4ts/flow": "2.
|
|
57
|
-
"@llm4ts/runner": "2.
|
|
55
|
+
"@llm4ts/core": "2.16.0",
|
|
56
|
+
"@llm4ts/flow": "2.16.0",
|
|
57
|
+
"@llm4ts/runner": "2.16.0"
|
|
58
58
|
},
|
|
59
59
|
"peerDependencies": {
|
|
60
60
|
"effect": "4.0.0-rc.115"
|