@alisio/alisio-code 0.5.2 → 0.7.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/dist/analysis.d.ts +24 -1
- package/dist/analysis.js +100 -1
- package/dist/eval.d.ts +101 -0
- package/dist/eval.js +690 -0
- package/dist/forge.d.ts +13 -0
- package/dist/forge.js +46 -0
- package/dist/main.js +94 -4
- package/dist/tui/artifacts.d.ts +1 -1
- package/dist/tui/artifacts.js +5 -1
- package/dist/tui/components.js +5 -3
- package/dist/tui/state.d.ts +17 -1
- package/dist/tui/state.js +63 -3
- package/package.json +7 -7
package/dist/analysis.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AnalysisStatus } from "@alisio/sdk";
|
|
1
|
+
import type { AnalysisStatus, EngineDescriptor } from "@alisio/sdk";
|
|
2
2
|
type Common = {
|
|
3
3
|
db?: string;
|
|
4
4
|
python?: string;
|
|
@@ -6,6 +6,10 @@ type Common = {
|
|
|
6
6
|
config?: string;
|
|
7
7
|
trustProject?: boolean;
|
|
8
8
|
};
|
|
9
|
+
/** `alisio forge status` reuses this for its Python lines. */
|
|
10
|
+
export declare function pythonBackendDescriptors(o: Common): Promise<EngineDescriptor[]>;
|
|
11
|
+
/** A fixed-width table of the backends: readiness, isolation, profiles and why not. */
|
|
12
|
+
export declare function backendsTable(backends: readonly EngineDescriptor[]): string;
|
|
9
13
|
/** The unified runtime state and, when Python is missing, the install guidance for this system. */
|
|
10
14
|
export declare function analysisState(o: Common): Promise<{
|
|
11
15
|
status: AnalysisStatus;
|
|
@@ -24,4 +28,23 @@ export declare function setupExtras(o: Common, extras: string | undefined): Prom
|
|
|
24
28
|
export declare function setupOci(o: Common, image: string | undefined): Promise<void>;
|
|
25
29
|
/** `alisio analysis sweep [--force]`: the retention sweep now (it also runs by itself, daily). */
|
|
26
30
|
export declare function sweep(o: Common, force: boolean): Promise<void>;
|
|
31
|
+
export interface WasmSetupDeps {
|
|
32
|
+
/** Replaceable in tests; the global `fetch` otherwise. */
|
|
33
|
+
fetch?: typeof fetch;
|
|
34
|
+
manifest?: import("@alisio/core").PyodideManifest;
|
|
35
|
+
/** Whether someone can answer a question (a terminal). */
|
|
36
|
+
interactive?: boolean;
|
|
37
|
+
ask?: (question: string) => Promise<boolean>;
|
|
38
|
+
write?: (text: string) => void;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* `alisio analysis setup --wasm [--profile analysis|science] [--yes]`: the only place the wasm
|
|
42
|
+
* backend uses the network. Downloads the pinned Pyodide distribution into
|
|
43
|
+
* `<analysis dir>/pyodide/<version>/`, verifies every hash and size, and on any mismatch deletes
|
|
44
|
+
* what it fetched and leaves the backend not ready. It never touches the venv or the OCI image.
|
|
45
|
+
*/
|
|
46
|
+
export declare function setupWasm(o: Common, flags: {
|
|
47
|
+
profile?: string;
|
|
48
|
+
yes?: boolean;
|
|
49
|
+
}, deps?: WasmSetupDeps): Promise<void>;
|
|
27
50
|
export {};
|
package/dist/analysis.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* `python_run`: the base runtime is the discovered Python with only the standard library.
|
|
6
6
|
*/
|
|
7
7
|
import { dirname, join } from "node:path";
|
|
8
|
+
import { createInterface } from "node:readline/promises";
|
|
8
9
|
async function context(o) {
|
|
9
10
|
const core = await import("@alisio/core");
|
|
10
11
|
const workspace = await core.findWorkspace(o.cwd ?? process.cwd());
|
|
@@ -20,9 +21,41 @@ async function context(o) {
|
|
|
20
21
|
const oci = () => new core.OciRuntime({ ...config.analysis.oci });
|
|
21
22
|
return { core, config, provenance, stateDir, manager, oci };
|
|
22
23
|
}
|
|
24
|
+
/** The Python backends of this configuration, described with their live readiness. */
|
|
25
|
+
function describeBackends(c) {
|
|
26
|
+
const backends = c.core.createPythonBackends({
|
|
27
|
+
runtime: c.manager,
|
|
28
|
+
oci: { runtime: c.oci, mode: () => c.config.analysis.runtime },
|
|
29
|
+
wasm: { stateRoot: c.stateDir, settings: () => c.config.analysis.wasm },
|
|
30
|
+
});
|
|
31
|
+
return (signal) => c.core.describePythonBackends(backends, signal);
|
|
32
|
+
}
|
|
33
|
+
/** `alisio forge status` reuses this for its Python lines. */
|
|
34
|
+
export async function pythonBackendDescriptors(o) {
|
|
35
|
+
return describeBackends(await context(o))();
|
|
36
|
+
}
|
|
37
|
+
/** A fixed-width table of the backends: readiness, isolation, profiles and why not. */
|
|
38
|
+
export function backendsTable(backends) {
|
|
39
|
+
const rows = backends.map((b) => [
|
|
40
|
+
b.id.replace(/^python-/, ""),
|
|
41
|
+
b.isolation,
|
|
42
|
+
b.ready ? "ready" : "not ready",
|
|
43
|
+
b.profiles?.length ? b.profiles.join(",") : "-",
|
|
44
|
+
b.capabilities.networkDeniedByOS ? "denied" : "open",
|
|
45
|
+
b.reason ?? (b.isolation === "none" ? "host (not sandboxed)" : ""),
|
|
46
|
+
]);
|
|
47
|
+
const head = ["BACKEND", "ISOLATION", "STATE", "PROFILES", "NETWORK", "NOTE"];
|
|
48
|
+
const widths = head.map((h, i) => Math.max(h.length, ...rows.map((r) => (r[i] ?? "").length)));
|
|
49
|
+
const line = (cells) => cells
|
|
50
|
+
.map((c, i) => c.padEnd(widths[i] ?? 0))
|
|
51
|
+
.join(" ")
|
|
52
|
+
.trimEnd();
|
|
53
|
+
return [line(head), ...rows.map(line)].join("\n");
|
|
54
|
+
}
|
|
23
55
|
/** The unified runtime state and, when Python is missing, the install guidance for this system. */
|
|
24
56
|
export async function analysisState(o) {
|
|
25
|
-
const
|
|
57
|
+
const ctx = await context(o);
|
|
58
|
+
const { core, config, provenance, stateDir, manager, oci } = ctx;
|
|
26
59
|
const janitor = {
|
|
27
60
|
lastSweep: () => import("node:fs/promises")
|
|
28
61
|
.then((fs) => fs.readFile(join(stateDir, "analysis", ".last-sweep"), "utf8"))
|
|
@@ -37,6 +70,7 @@ export async function analysisState(o) {
|
|
|
37
70
|
runtime: manager,
|
|
38
71
|
oci,
|
|
39
72
|
janitor,
|
|
73
|
+
backends: describeBackends(ctx),
|
|
40
74
|
enabled: config.analysis.enabled,
|
|
41
75
|
readOnly: false,
|
|
42
76
|
});
|
|
@@ -58,6 +92,8 @@ export async function analysisState(o) {
|
|
|
58
92
|
export async function printAnalysisStatus(o) {
|
|
59
93
|
const { status, guidance, ignored } = await analysisState(o);
|
|
60
94
|
console.log(JSON.stringify(status, null, 2));
|
|
95
|
+
if (status.backends?.length)
|
|
96
|
+
process.stderr.write(`\nPython backends (analysis.runtime=${status.mode}, analysis.isolation=${status.isolation ?? "preferred"}):\n${backendsTable(status.backends)}\n`);
|
|
61
97
|
if (guidance)
|
|
62
98
|
process.stderr.write(`\n${guidance}\n`);
|
|
63
99
|
if (status.mode === "oci" && status.oci.available === false)
|
|
@@ -143,3 +179,66 @@ export async function sweep(o, force) {
|
|
|
143
179
|
store.close();
|
|
144
180
|
}
|
|
145
181
|
}
|
|
182
|
+
const megabytes = (bytes) => `${Math.max(1, Math.round(bytes / (1024 * 1024)))} MB`;
|
|
183
|
+
async function confirm(question) {
|
|
184
|
+
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
185
|
+
try {
|
|
186
|
+
return /^y(es)?$/i.test((await rl.question(question)).trim());
|
|
187
|
+
}
|
|
188
|
+
finally {
|
|
189
|
+
rl.close();
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* `alisio analysis setup --wasm [--profile analysis|science] [--yes]`: the only place the wasm
|
|
194
|
+
* backend uses the network. Downloads the pinned Pyodide distribution into
|
|
195
|
+
* `<analysis dir>/pyodide/<version>/`, verifies every hash and size, and on any mismatch deletes
|
|
196
|
+
* what it fetched and leaves the backend not ready. It never touches the venv or the OCI image.
|
|
197
|
+
*/
|
|
198
|
+
export async function setupWasm(o, flags, deps = {}) {
|
|
199
|
+
const profile = flags.profile ?? "analysis";
|
|
200
|
+
if (profile !== "analysis" && profile !== "science")
|
|
201
|
+
throw new Error("Choose the profile: --profile analysis (default) or --profile science");
|
|
202
|
+
const { core, stateDir } = await context(o);
|
|
203
|
+
const manifest = deps.manifest ?? core.PYODIDE_MANIFEST;
|
|
204
|
+
const write = deps.write ?? ((text) => void process.stderr.write(text));
|
|
205
|
+
const dir = core.pyodideDir(stateDir, manifest);
|
|
206
|
+
const bytes = core.pyodideDownloadSize(manifest, profile);
|
|
207
|
+
const hosts = [
|
|
208
|
+
...new Set([...manifest.core, ...Object.values(manifest.packages)].map((f) => new URL(f.url).host)),
|
|
209
|
+
];
|
|
210
|
+
write(`Pyodide ${manifest.version} (Python ${manifest.python}), profile ${profile}: about ${megabytes(bytes)} from ${hosts.join(", ")}.\n` +
|
|
211
|
+
`It goes to ${dir}. Every file is checked against a pinned SHA-256; this command is the only time the wasm backend uses the network.\n`);
|
|
212
|
+
if (!flags.yes) {
|
|
213
|
+
const interactive = deps.interactive ?? (!!process.stdin.isTTY && !!process.stderr.isTTY);
|
|
214
|
+
if (!interactive)
|
|
215
|
+
throw new Error("This download needs your confirmation: pass --yes in a non-interactive run.");
|
|
216
|
+
const ok = await (deps.ask ?? confirm)("Download now? [y/N] ");
|
|
217
|
+
if (!ok) {
|
|
218
|
+
write("Cancelled; nothing was downloaded.\n");
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
const controller = new AbortController();
|
|
223
|
+
process.on("SIGINT", () => controller.abort(new Error("Interrupted")));
|
|
224
|
+
try {
|
|
225
|
+
const result = await core.installPyodide({
|
|
226
|
+
dir,
|
|
227
|
+
profile,
|
|
228
|
+
manifest,
|
|
229
|
+
signal: controller.signal,
|
|
230
|
+
...(deps.fetch ? { fetch: deps.fetch } : {}),
|
|
231
|
+
onProgress: (e) => write(` [${e.index}/${e.total}] ${e.file}\n`),
|
|
232
|
+
});
|
|
233
|
+
write(`Pyodide ${manifest.version} is ready (profile ${result.ok ? result.profile : profile}). ` +
|
|
234
|
+
'With analysis.runtime "auto" it is used when no container is ready; set analysis.runtime to "wasm" to require it.\n');
|
|
235
|
+
}
|
|
236
|
+
catch (error) {
|
|
237
|
+
const integrity = error instanceof core.PyodideIntegrityError;
|
|
238
|
+
write(`${error instanceof Error ? error.message : String(error)}\n` +
|
|
239
|
+
(integrity
|
|
240
|
+
? "Nothing from this download was kept. The wasm backend is not ready.\n"
|
|
241
|
+
: "Nothing from this download was kept.\n"));
|
|
242
|
+
process.exitCode = 1;
|
|
243
|
+
}
|
|
244
|
+
}
|
package/dist/eval.d.ts
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `alisio eval list|run|record|compare|variants|export` (specs/010 5.4, Phases 3-4). Every function
|
|
3
|
+
* returns the exit code and talks only through the injected `EvalEnv`, so tests drive them without
|
|
4
|
+
* a process:
|
|
5
|
+
*
|
|
6
|
+
* 0 completed (independent of which tasks passed) 1 usage or infrastructure error
|
|
7
|
+
* 2 a replay diverged from its cassette 3 `compare` of incompatible suites
|
|
8
|
+
*
|
|
9
|
+
* `record` runs each task once live and writes `<suite>/cassettes/<taskId>.cassette.jsonl`;
|
|
10
|
+
* `run --mode replay` serves those cassettes (no provider, no credentials, no network). `export`
|
|
11
|
+
* writes one local, scrubbed JSON bundle of a stored run (nothing is uploaded). Reports go to `<stateHome>/eval/<runId>/` (or `--out DIR`): report.json, report.md, runs.jsonl.
|
|
12
|
+
* A suite given with `--suite-dir` is third-party code: it needs `--yes` (or an interactive
|
|
13
|
+
* confirmation) because a subprocess is not a sandbox.
|
|
14
|
+
*/
|
|
15
|
+
import type { BuiltinPlugin, Config, EvalApplication } from "@alisio/core";
|
|
16
|
+
import type { ModelProvider } from "@alisio/sdk";
|
|
17
|
+
/** The part of the command line `eval` reads, global flags included. */
|
|
18
|
+
export interface EvalFlags {
|
|
19
|
+
json?: boolean;
|
|
20
|
+
allowWrite?: boolean;
|
|
21
|
+
allowProcess?: boolean;
|
|
22
|
+
allowExternal?: boolean;
|
|
23
|
+
allowAnalysis?: boolean;
|
|
24
|
+
readOnly?: boolean;
|
|
25
|
+
disablePlugin?: string[];
|
|
26
|
+
suiteDir?: string;
|
|
27
|
+
tasks?: string;
|
|
28
|
+
k?: string;
|
|
29
|
+
seed?: string;
|
|
30
|
+
mode?: string;
|
|
31
|
+
keep?: boolean;
|
|
32
|
+
out?: string;
|
|
33
|
+
allowSuiteFull?: boolean;
|
|
34
|
+
yes?: boolean;
|
|
35
|
+
inputRate?: string;
|
|
36
|
+
outputRate?: string;
|
|
37
|
+
variants?: string;
|
|
38
|
+
md?: boolean;
|
|
39
|
+
cassetteDir?: string;
|
|
40
|
+
force?: boolean;
|
|
41
|
+
}
|
|
42
|
+
/** What a live run needs, resolved BEFORE any task starts (the engine takes a provider). */
|
|
43
|
+
export interface EvalRuntime {
|
|
44
|
+
provider: ModelProvider;
|
|
45
|
+
/** The effective configuration (variant overrides are applied on top of it). */
|
|
46
|
+
config: Config;
|
|
47
|
+
builtins: BuiltinPlugin[];
|
|
48
|
+
/** Exact credential values of the run's provider profile (a cassette holding one is refused). */
|
|
49
|
+
secrets?: string[];
|
|
50
|
+
resolveAgent?: (app: EvalApplication, agent: string) => ReturnType<NonNullable<ResolveAgent>>;
|
|
51
|
+
close(): Promise<void>;
|
|
52
|
+
}
|
|
53
|
+
type ResolveAgent = NonNullable<import("@alisio/core").RunEvaluationOptions["resolveAgent"]>;
|
|
54
|
+
export interface EvalEnv {
|
|
55
|
+
out(text: string): void;
|
|
56
|
+
err(text: string): void;
|
|
57
|
+
/** `<stateHome>`: reports default to `<stateDir>/eval/<runId>/`. */
|
|
58
|
+
stateDir: string;
|
|
59
|
+
/** Directory of the bundled suites: `tests/fixtures/eval` of a source checkout, absent elsewhere. */
|
|
60
|
+
bundledDir?: string;
|
|
61
|
+
interactive: boolean;
|
|
62
|
+
confirm(question: string): Promise<boolean>;
|
|
63
|
+
/**
|
|
64
|
+
* `requireProvider: false` (replay) must not fail when no provider is configured: the cassette
|
|
65
|
+
* is the provider, and the returned `provider` is then ignored.
|
|
66
|
+
*/
|
|
67
|
+
openRuntime(flags: EvalFlags, options?: {
|
|
68
|
+
requireProvider?: boolean;
|
|
69
|
+
}): Promise<EvalRuntime>;
|
|
70
|
+
newRunId(): string;
|
|
71
|
+
/** Parent of the per-run temporary directories (tests); default `os.tmpdir()`. */
|
|
72
|
+
tmpRoot?: string;
|
|
73
|
+
}
|
|
74
|
+
export declare function evalList(env: EvalEnv, flags: EvalFlags): Promise<number>;
|
|
75
|
+
export declare function evalRun(env: EvalEnv, flags: EvalFlags, suiteName: string): Promise<number>;
|
|
76
|
+
/**
|
|
77
|
+
* Runs each selected task ONCE live and writes its cassette. A task whose run errored, whose
|
|
78
|
+
* recording is unstable, or whose cassette holds a secret or is over the size limits writes
|
|
79
|
+
* nothing and makes the command exit 1; the other tasks are still recorded.
|
|
80
|
+
*/
|
|
81
|
+
export declare function evalRecord(env: EvalEnv, flags: EvalFlags, suiteName: string): Promise<number>;
|
|
82
|
+
export declare function evalCompare(env: EvalEnv, flags: EvalFlags, pathA: string, pathB: string): Promise<number>;
|
|
83
|
+
export declare function evalVariants(env: EvalEnv, flags: EvalFlags, suiteName: string): Promise<number>;
|
|
84
|
+
/**
|
|
85
|
+
* Writes ONE shareable JSON bundle `{schemaVersion, report, runs, markdown}` of the stored run
|
|
86
|
+
* `<stateDir>/eval/<runId>/`, scrubbed of absolute paths and secrets. A secret hit refuses it
|
|
87
|
+
* (exit 1). The file is local: nothing is uploaded.
|
|
88
|
+
*/
|
|
89
|
+
export declare function evalExport(env: EvalEnv, flags: EvalFlags, runId: string): Promise<number>;
|
|
90
|
+
type CliAppOptions = import("@alisio/core").AppOptions;
|
|
91
|
+
/**
|
|
92
|
+
* The real environment of `alisio eval`: the provider is resolved from the same sources as
|
|
93
|
+
* `alisio run` (flags, saved profile, trusted config) through a throwaway Application that only
|
|
94
|
+
* lends its provider and configuration. It does not carry the invoker's `--read-only`, which
|
|
95
|
+
* would stop the provider plugins from loading; the permissions the runs get come from
|
|
96
|
+
* `InvokerPermissions` instead. Its in-memory database never touches the user's sessions.
|
|
97
|
+
*/
|
|
98
|
+
export declare function realEvalEnv(appOptions: (flags: EvalFlags) => Promise<CliAppOptions>, defaults: () => Promise<{
|
|
99
|
+
builtins: BuiltinPlugin[];
|
|
100
|
+
}>): Promise<EvalEnv>;
|
|
101
|
+
export {};
|