@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.
@@ -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.14.0",
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.14.0",
56
- "@llm4ts/flow": "2.14.0",
57
- "@llm4ts/runner": "2.14.0"
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"