@alexkroman1/aai-cli 3.2.0 → 5.0.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/{_agent-4rsUGPhx.mjs → _agent-D39qatZJ.mjs} +1 -1
- package/dist/_bundler-DdmuZzeR.mjs +92 -0
- package/dist/{_config-BWYgLjiI.mjs → _config-Czjhvaax.mjs} +5 -21
- package/dist/{_dev-server-C8lcQOXt.mjs → _dev-server-BCbEUhmv.mjs} +42 -140
- package/dist/{_init-C3xJKK3L.mjs → _init-H-lw7czL.mjs} +3 -5
- package/dist/{_server-common-Bv-4Rskq.mjs → _server-common-B75oco06.mjs} +10 -4
- package/dist/_typecheck-gate-B1GcKZX-.mjs +17 -0
- package/dist/{_ui-CKEIHAtB.mjs → _ui-DXZ9prrM.mjs} +2 -2
- package/dist/cli.mjs +34 -20
- package/dist/{delete-BHSeDVBT.mjs → delete-K24hW1ab.mjs} +2 -2
- package/dist/{deploy-CQ69yWAK.mjs → deploy-D-dnt9L8.mjs} +19 -10
- package/dist/{dev-DlseKVVF.mjs → dev-C8o-p13B.mjs} +4 -4
- package/dist/{init-mdG0rdRZ.mjs → init-B53rR3yD.mjs} +7 -7
- package/dist/{secret-CJcR121q.mjs → secret-C4unwCEw.mjs} +2 -2
- package/dist/{storage-D0odnyBO.mjs → storage-tZM1J8qj.mjs} +2 -2
- package/dist/{test-B3BkLNn_.mjs → test-CgWhus_Z.mjs} +1 -1
- package/dist/typecheck.mjs +129 -0
- package/dist/worker-bundler.mjs +63 -29
- package/package.json +10 -5
- package/dist/_bundler-DnZnKsIU.mjs +0 -96
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { a as serverOrigin, i as readProjectConfig, n as ensureApiKey, r as readGlobalConfig, t as approveServer } from "./_config-
|
|
2
|
+
import { a as serverOrigin, i as readProjectConfig, n as ensureApiKey, r as readGlobalConfig, t as approveServer } from "./_config-Czjhvaax.mjs";
|
|
3
3
|
import { existsSync } from "node:fs";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { n as log, u as ok } from "./_ui-DXZ9prrM.mjs";
|
|
3
|
+
import { c as validateAgentExport } from "./_utils-C502jKo8.mjs";
|
|
4
|
+
import { t as buildClient } from "./client-bundler-DO3GgO6p.mjs";
|
|
5
|
+
import { buildWorker } from "./worker-bundler.mjs";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import { pathToFileURL } from "node:url";
|
|
8
|
+
import { mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
9
|
+
import { hash } from "node:crypto";
|
|
10
|
+
import { tmpdir } from "node:os";
|
|
11
|
+
//#region _bundler.ts
|
|
12
|
+
/**
|
|
13
|
+
* Bundle an agent directory: build agent.ts into worker ESM + client files.
|
|
14
|
+
*
|
|
15
|
+
* agent.ts is the single entry point: `export default agent({...})`. The
|
|
16
|
+
* worker self-describes (it exports `__aaiConfig` — see `worker-bundler.ts`),
|
|
17
|
+
* so nothing here evaluates the bundle: the server extracts the config inside
|
|
18
|
+
* a guest sandbox at deploy time.
|
|
19
|
+
*/
|
|
20
|
+
async function buildAgentBundle(cwd, opts = {}) {
|
|
21
|
+
const [worker, clientFiles] = await Promise.all([buildWorker(cwd, opts), buildClient(cwd)]);
|
|
22
|
+
return {
|
|
23
|
+
worker,
|
|
24
|
+
clientFiles
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Import the worker ESM from a uniquely named temp file and return the
|
|
29
|
+
* AgentDef default export. A real `file:` URL, not a `data:` URL: deploy
|
|
30
|
+
* bundles ship the SDK runtime, whose CJS interop calls
|
|
31
|
+
* `createRequire(import.meta.url)` — which rejects anything that isn't a
|
|
32
|
+
* file URL or absolute path. (The guest harness imports bundles the same
|
|
33
|
+
* way, for the same reason.) The file is removed after import; the module
|
|
34
|
+
* lives on in memory.
|
|
35
|
+
*
|
|
36
|
+
* Each call imports a unique URL, and Node's ESM registry never evicts — so
|
|
37
|
+
* every call retains one bundle for the process lifetime. That is fine for
|
|
38
|
+
* one-shot commands (`aai build`); long-lived callers must go through
|
|
39
|
+
* `createWorkerEvaluator` to at least dedupe identical builds. Evaluating in
|
|
40
|
+
* a discardable context is not an option: tool `execute` functions from the
|
|
41
|
+
* returned AgentDef are called in-process by the dev runtime, which rules
|
|
42
|
+
* out worker threads, and `node:vm` ESM evaluation is still flagged
|
|
43
|
+
* experimental.
|
|
44
|
+
*/
|
|
45
|
+
async function evalWorkerBundle(code) {
|
|
46
|
+
const dir = await mkdtemp(path.join(tmpdir(), "aai-worker-"));
|
|
47
|
+
const file = path.join(dir, "worker.mjs");
|
|
48
|
+
let mod;
|
|
49
|
+
try {
|
|
50
|
+
await writeFile(file, code, "utf-8");
|
|
51
|
+
mod = await import(pathToFileURL(file).href);
|
|
52
|
+
} finally {
|
|
53
|
+
await rm(dir, {
|
|
54
|
+
recursive: true,
|
|
55
|
+
force: true
|
|
56
|
+
}).catch(() => void 0);
|
|
57
|
+
}
|
|
58
|
+
const agentDef = mod.default ?? mod;
|
|
59
|
+
validateAgentExport(agentDef);
|
|
60
|
+
return agentDef;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Memoizing wrapper around `evalWorkerBundle` for long-lived callers (the
|
|
64
|
+
* dev server): byte-identical worker code returns the previously evaluated
|
|
65
|
+
* AgentDef without touching the ESM registry. No-op saves and formatter
|
|
66
|
+
* churn are the common watcher events, so this caps the registry leak (see
|
|
67
|
+
* `evalWorkerBundle`) to genuinely-new bundles — the residual one-module-per-
|
|
68
|
+
* distinct-build leak is accepted for the reasons documented there.
|
|
69
|
+
*/
|
|
70
|
+
function createWorkerEvaluator() {
|
|
71
|
+
let lastHash;
|
|
72
|
+
let lastAgentDef;
|
|
73
|
+
return async (code) => {
|
|
74
|
+
const codeHash = hash("sha256", code);
|
|
75
|
+
if (lastAgentDef && codeHash === lastHash) return lastAgentDef;
|
|
76
|
+
const agentDef = await evalWorkerBundle(code);
|
|
77
|
+
lastHash = codeHash;
|
|
78
|
+
lastAgentDef = agentDef;
|
|
79
|
+
return agentDef;
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
async function executeBuild(cwd) {
|
|
83
|
+
const bundle = await buildAgentBundle(cwd, { minify: true });
|
|
84
|
+
const agentDef = await evalWorkerBundle(bundle.worker);
|
|
85
|
+
log.success("Build complete");
|
|
86
|
+
return ok({
|
|
87
|
+
name: agentDef.name,
|
|
88
|
+
workerBytes: bundle.worker.length
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
//#endregion
|
|
92
|
+
export { buildAgentBundle, createWorkerEvaluator, executeBuild };
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { a as unwrapCancel, n as log, o as CliError } from "./_ui-
|
|
2
|
+
import { a as unwrapCancel, n as log, o as CliError } from "./_ui-DXZ9prrM.mjs";
|
|
3
3
|
import { l as writeJson, o as readJson, r as errorMessage } from "./_utils-C502jKo8.mjs";
|
|
4
|
-
import { existsSync } from "node:fs";
|
|
5
4
|
import path from "node:path";
|
|
6
5
|
import * as p from "@clack/prompts";
|
|
7
|
-
import os from "node:os";
|
|
8
6
|
import envPaths from "env-paths";
|
|
9
7
|
import { z } from "zod";
|
|
10
8
|
//#region _config.ts
|
|
@@ -24,32 +22,18 @@ const ProjectConfigSchema = z.object({
|
|
|
24
22
|
serverUrl: z.string()
|
|
25
23
|
});
|
|
26
24
|
/**
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* Windows (no trailing `Config` segment) it does not.
|
|
30
|
-
*/
|
|
31
|
-
function legacyConfigDir() {
|
|
32
|
-
if (process.platform === "win32") return path.join(process.env.APPDATA ?? path.join(os.homedir(), "AppData", "Roaming"), "aai");
|
|
33
|
-
return path.join(process.env.XDG_CONFIG_HOME ?? path.join(os.homedir(), ".config"), "aai");
|
|
34
|
-
}
|
|
35
|
-
/**
|
|
36
|
-
* Resolve the global config directory, preferring the platform-conventional
|
|
37
|
-
* env-paths location while staying backward compatible: an existing config at
|
|
38
|
-
* the legacy path keeps winning so already-authenticated users are not
|
|
39
|
-
* silently logged out. Injectable for tests.
|
|
25
|
+
* Resolve the global config directory (the platform-conventional env-paths
|
|
26
|
+
* location).
|
|
40
27
|
*
|
|
41
28
|
* `AAI_CONFIG_DIR` overrides everything — it exists so tests (and unusual
|
|
42
29
|
* setups) can redirect ALL global-config reads and writes away from the
|
|
43
30
|
* user's real config. The test suite's `approveServer` calls used to
|
|
44
31
|
* permanently pollute `~/.config/aai/config.json` with approved origins.
|
|
45
32
|
*/
|
|
46
|
-
function getConfigDir(
|
|
47
|
-
legacy: legacyConfigDir(),
|
|
48
|
-
modern: envPaths("aai", { suffix: "" }).config
|
|
49
|
-
}, exists = existsSync) {
|
|
33
|
+
function getConfigDir() {
|
|
50
34
|
const override = process.env.AAI_CONFIG_DIR?.trim();
|
|
51
35
|
if (override) return override;
|
|
52
|
-
return
|
|
36
|
+
return envPaths("aai", { suffix: "" }).config;
|
|
53
37
|
}
|
|
54
38
|
async function readProjectConfig(agentDir) {
|
|
55
39
|
const file = path.join(agentDir, ".aai", "project.json");
|
|
@@ -1,129 +1,19 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { n as log } from "./_ui-
|
|
2
|
+
import { n as log } from "./_ui-DXZ9prrM.mjs";
|
|
3
3
|
import { r as errorMessage, t as errorCode } from "./_utils-C502jKo8.mjs";
|
|
4
4
|
import { n as fallbackHtmlPlugin } from "./client-bundler-DO3GgO6p.mjs";
|
|
5
5
|
import { buildWorker } from "./worker-bundler.mjs";
|
|
6
|
-
import { createWorkerEvaluator } from "./_bundler-
|
|
7
|
-
import { n as ensureApiKey } from "./_config-
|
|
8
|
-
import { t as resolveServerEnv } from "./_server-common-
|
|
6
|
+
import { createWorkerEvaluator } from "./_bundler-DdmuZzeR.mjs";
|
|
7
|
+
import { n as ensureApiKey } from "./_config-Czjhvaax.mjs";
|
|
8
|
+
import { t as resolveServerEnv } from "./_server-common-B75oco06.mjs";
|
|
9
9
|
import { createRequire } from "node:module";
|
|
10
10
|
import { existsSync } from "node:fs";
|
|
11
11
|
import path from "node:path";
|
|
12
|
-
import
|
|
12
|
+
import { setTimeout } from "node:timers/promises";
|
|
13
13
|
import { requiredProviderEnvVars, withHostCredentialFallback } from "@alexkroman1/aai/runtime";
|
|
14
14
|
import { watch } from "chokidar";
|
|
15
15
|
import getPort, { portNumbers } from "get-port";
|
|
16
16
|
import pDebounce from "p-debounce";
|
|
17
|
-
//#region _dev-bundler.ts
|
|
18
|
-
/**
|
|
19
|
-
* Fast worker builds for `aai dev`.
|
|
20
|
-
*
|
|
21
|
-
* The deploy path (`buildWorker` in `worker-bundler.ts`) is a full Vite
|
|
22
|
-
* pipeline pass — right for a one-shot `aai deploy`, but 1–3 s per save when
|
|
23
|
-
* the dev watcher runs it on every change. This module builds with Rolldown
|
|
24
|
-
* directly (the native bundler Vite 8 itself runs on, so it adds no install
|
|
25
|
-
* weight), skipping Vite's config/plugin/CSS machinery: a from-scratch build
|
|
26
|
-
* of a typical agent lands in tens of ms. Deploy keeps Vite untouched, so
|
|
27
|
-
* nothing that ships is produced by this path.
|
|
28
|
-
*
|
|
29
|
-
* Parity with `buildWorker` where it matters for dev:
|
|
30
|
-
*
|
|
31
|
-
* - single-file ESM output, unminified (dev builds never minify);
|
|
32
|
-
* - `node:` builtins external (Rolldown's `platform: "node"`), everything
|
|
33
|
-
* else — zod, workspace deps, local imports — bundled in;
|
|
34
|
-
* - `.md` imports resolve to their raw text (`mdPlugin` below is
|
|
35
|
-
* `rawMdPlugin`'s transform), and Vite-style `?raw` suffix imports are
|
|
36
|
-
* honored via `rawSuffixPlugin`.
|
|
37
|
-
*
|
|
38
|
-
* Known dev/deploy differences, accepted: Vite's lib build applies
|
|
39
|
-
* `define`/`import.meta.env` replacements this path does not. When a build
|
|
40
|
-
* fails for anything other than a compile error in the agent's code, the
|
|
41
|
-
* caller falls back to the cold Vite path (see `_dev-server.ts`), so a
|
|
42
|
-
* resolution gap here degrades to the old slow-but-correct behavior rather
|
|
43
|
-
* than a broken dev server.
|
|
44
|
-
*
|
|
45
|
-
* Rolldown does not touch `process.env` the way Vite's `build()` does, so
|
|
46
|
-
* the `withPreservedNodeEnv` wrapper Vite builds need is not required here.
|
|
47
|
-
*/
|
|
48
|
-
const RAW_NAMESPACE = "\0aai-raw:";
|
|
49
|
-
/**
|
|
50
|
-
* Vite serves `import x from "./file?raw"` as the file's text. Rolldown
|
|
51
|
-
* treats the suffix as part of the filename, so resolve it explicitly and
|
|
52
|
-
* load the real file as a string export.
|
|
53
|
-
*/
|
|
54
|
-
const rawSuffixPlugin = {
|
|
55
|
-
name: "aai-raw-suffix",
|
|
56
|
-
resolveId: {
|
|
57
|
-
filter: { id: /\?raw$/ },
|
|
58
|
-
handler(id, importer) {
|
|
59
|
-
const file = id.slice(0, -4);
|
|
60
|
-
const base = importer ? path.dirname(importer) : process.cwd();
|
|
61
|
-
return RAW_NAMESPACE + path.resolve(base, file);
|
|
62
|
-
}
|
|
63
|
-
},
|
|
64
|
-
load: {
|
|
65
|
-
filter: { id: new RegExp(`^${RAW_NAMESPACE}`) },
|
|
66
|
-
async handler(id) {
|
|
67
|
-
const text = await fs.readFile(id.slice(9), "utf8");
|
|
68
|
-
return `export default ${JSON.stringify(text)};`;
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
};
|
|
72
|
-
/** `.md` imports resolve to their raw text (rawMdPlugin parity). */
|
|
73
|
-
const mdPlugin = {
|
|
74
|
-
name: "aai-raw-md",
|
|
75
|
-
load: {
|
|
76
|
-
filter: { id: /\.md$/ },
|
|
77
|
-
async handler(id) {
|
|
78
|
-
const text = await fs.readFile(id, "utf8");
|
|
79
|
-
return `export default ${JSON.stringify(text)};`;
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
};
|
|
83
|
-
/**
|
|
84
|
-
* True for bundler build failures — compile/resolve errors in the code being
|
|
85
|
-
* built (Rolldown aggregates its diagnostics onto an `errors` array, the same
|
|
86
|
-
* shape esbuild used). Anything else coming out of `build()` is a
|
|
87
|
-
* bundler-infrastructure problem, which callers treat as "fall back to the
|
|
88
|
-
* cold Vite path" rather than "the user's code is broken".
|
|
89
|
-
*/
|
|
90
|
-
function isBundlerBuildFailure(err) {
|
|
91
|
-
return err instanceof Error && "errors" in err && Array.isArray(err.errors);
|
|
92
|
-
}
|
|
93
|
-
/**
|
|
94
|
-
* Create a dev builder for the agent at `cwd`.
|
|
95
|
-
*
|
|
96
|
-
* Each `build()` is a from-scratch Rolldown pass — native-code bundling is
|
|
97
|
-
* fast enough (tens of ms for a typical agent) that no incremental context
|
|
98
|
-
* is worth holding between saves. `dispose()` exists for interface parity
|
|
99
|
-
* with resource-holding builders and is a no-op.
|
|
100
|
-
*/
|
|
101
|
-
function createDevWorkerBuilder(cwd) {
|
|
102
|
-
return {
|
|
103
|
-
async build() {
|
|
104
|
-
const { rolldown } = await import("rolldown");
|
|
105
|
-
const bundle = await rolldown({
|
|
106
|
-
input: path.join(cwd, "agent.ts"),
|
|
107
|
-
cwd,
|
|
108
|
-
platform: "node",
|
|
109
|
-
logLevel: "silent",
|
|
110
|
-
plugins: [rawSuffixPlugin, mdPlugin]
|
|
111
|
-
});
|
|
112
|
-
try {
|
|
113
|
-
const file = (await bundle.generate({
|
|
114
|
-
format: "esm",
|
|
115
|
-
minify: false
|
|
116
|
-
})).output[0];
|
|
117
|
-
if (!file) throw new Error("Rolldown produced no output for agent.ts");
|
|
118
|
-
return file.code;
|
|
119
|
-
} finally {
|
|
120
|
-
await bundle.close().catch(() => void 0);
|
|
121
|
-
}
|
|
122
|
-
},
|
|
123
|
-
async dispose() {}
|
|
124
|
-
};
|
|
125
|
-
}
|
|
126
|
-
//#endregion
|
|
127
17
|
//#region _dev-server.ts
|
|
128
18
|
/**
|
|
129
19
|
* Dev server for directory-based agents.
|
|
@@ -133,12 +23,36 @@ function createDevWorkerBuilder(cwd) {
|
|
|
133
23
|
* file changes and restarts automatically. Optionally runs Vite for
|
|
134
24
|
* client SPA HMR.
|
|
135
25
|
*/
|
|
26
|
+
/**
|
|
27
|
+
* Warnings about the agent's credentials, computed against the `.env`-derived
|
|
28
|
+
* env and the shell. Pure so it is directly testable; `resolveAgentEnv` logs
|
|
29
|
+
* each entry. Three cases, in increasing subtlety:
|
|
30
|
+
*
|
|
31
|
+
* - a provider key found nowhere → the first session will fail auth;
|
|
32
|
+
* - a provider key found only in the shell → works here (the
|
|
33
|
+
* `withHostCredentialFallback` ergonomic) but is invisible to `aai deploy`,
|
|
34
|
+
* which uploads `.env` — the classic "works locally, dead on deploy";
|
|
35
|
+
* - a declared `requiredEnv` key absent from `.env` → `ctx.env` won't contain
|
|
36
|
+
* it at all: custom keys never fall back to the shell, so a shell export
|
|
37
|
+
* can't mask one that would be missing both here and after deploy.
|
|
38
|
+
*/
|
|
39
|
+
function agentEnvWarnings(agentDef, env, shellEnv = process.env) {
|
|
40
|
+
const s = (names) => names.length > 1 ? "s" : "";
|
|
41
|
+
const them = (names) => names.length > 1 ? "them" : "it";
|
|
42
|
+
const required = requiredProviderEnvVars(agentDef);
|
|
43
|
+
const warnings = [];
|
|
44
|
+
const missing = required.filter((name) => !(env[name] || shellEnv[name]));
|
|
45
|
+
if (missing.length > 0) warnings.push(`Missing provider credential${s(missing)}: ${missing.join(", ")}. Set ${them(missing)} in .env or the environment.`);
|
|
46
|
+
const shellOnly = required.filter((name) => !env[name] && shellEnv[name]);
|
|
47
|
+
if (shellOnly.length > 0) warnings.push(`${shellOnly.join(", ")} resolved from your shell, not .env — deployed agents won't have ${them(shellOnly)}. Declare ${them(shellOnly)} in .env before \`aai deploy\`.`);
|
|
48
|
+
const declared = (agentDef.requiredEnv ?? []).filter((name) => !env[name]);
|
|
49
|
+
if (declared.length > 0) warnings.push(`Missing requiredEnv key${s(declared)} declared by the agent: ${declared.join(", ")}. Set ${them(declared)} in .env — ctx.env will not contain ${them(declared)} otherwise.`);
|
|
50
|
+
return warnings;
|
|
51
|
+
}
|
|
136
52
|
async function resolveAgentEnv(root, agentDef) {
|
|
137
53
|
const env = await resolveServerEnv(root);
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
const missing = required.filter((name) => !(env[name] || process.env[name]));
|
|
141
|
-
if (missing.length > 0) log.warn(`Missing provider credential${missing.length > 1 ? "s" : ""}: ${missing.join(", ")}. Set ${missing.length > 1 ? "them" : "it"} in .env or the environment.`);
|
|
54
|
+
if (requiredProviderEnvVars(agentDef).includes("ASSEMBLYAI_API_KEY") && !env.ASSEMBLYAI_API_KEY) env.ASSEMBLYAI_API_KEY = await ensureApiKey();
|
|
55
|
+
for (const warning of agentEnvWarnings(agentDef, env)) log.warn(warning);
|
|
142
56
|
return env;
|
|
143
57
|
}
|
|
144
58
|
/**
|
|
@@ -170,23 +84,14 @@ function devBindHost() {
|
|
|
170
84
|
* stay in Node's ESM registry, so edits to them are ignored on reload.
|
|
171
85
|
* Bundling picks them up.
|
|
172
86
|
*
|
|
173
|
-
* The bundle comes from the
|
|
174
|
-
*
|
|
175
|
-
*
|
|
176
|
-
*
|
|
177
|
-
* failure falls back to the cold Vite build so a Rolldown-specific gap can't
|
|
178
|
-
* take the dev loop down. Evaluation goes through the memoizing evaluator so
|
|
87
|
+
* The bundle comes from the same Vite pass deploy runs (`buildWorker`), so
|
|
88
|
+
* dev and deploy can't drift; a warm rebuild is well under 100ms. Compile
|
|
89
|
+
* errors in the agent's code propagate — the restart loop reports them and
|
|
90
|
+
* keeps the old server. Evaluation goes through the memoizing evaluator so
|
|
179
91
|
* a no-op save doesn't leak another module into the ESM registry.
|
|
180
92
|
*/
|
|
181
|
-
async function
|
|
182
|
-
|
|
183
|
-
try {
|
|
184
|
-
code = await builder.build();
|
|
185
|
-
} catch (err) {
|
|
186
|
-
if (isBundlerBuildFailure(err)) throw err;
|
|
187
|
-
code = await buildWorker(cwd);
|
|
188
|
-
}
|
|
189
|
-
return evaluate(code);
|
|
93
|
+
async function loadAgentDef(cwd, evaluate) {
|
|
94
|
+
return evaluate(await buildWorker(cwd, { runtime: false }));
|
|
190
95
|
}
|
|
191
96
|
/**
|
|
192
97
|
* True for paths that should never trigger a restart: anything inside
|
|
@@ -283,11 +188,10 @@ async function startDevServer(opts) {
|
|
|
283
188
|
const backendPort = hasClient ? await getPort({ port: portNumbers(port + 1, port + 100) }) : port;
|
|
284
189
|
const vitePort = port;
|
|
285
190
|
const clientDirOpt = hasClient ? {} : { clientDir: resolveDefaultClientDir() };
|
|
286
|
-
const
|
|
287
|
-
const evaluateWorker = createWorkerEvaluator(cwd);
|
|
191
|
+
const evaluateWorker = createWorkerEvaluator();
|
|
288
192
|
/** Full build sequence, shared by initial startup and every restart. */
|
|
289
193
|
async function buildServer() {
|
|
290
|
-
const agentDef = await
|
|
194
|
+
const agentDef = await loadAgentDef(cwd, evaluateWorker);
|
|
291
195
|
const env = await resolveAgentEnv(cwd, agentDef);
|
|
292
196
|
const providerEnv = withHostCredentialFallback(env);
|
|
293
197
|
const runtime = createRuntime({
|
|
@@ -335,7 +239,6 @@ async function startDevServer(opts) {
|
|
|
335
239
|
}
|
|
336
240
|
} catch (err) {
|
|
337
241
|
await watcher.close().catch(() => void 0);
|
|
338
|
-
await devBuilder.dispose().catch(() => void 0);
|
|
339
242
|
await viteServer?.close().catch(() => void 0);
|
|
340
243
|
throw err;
|
|
341
244
|
}
|
|
@@ -389,7 +292,7 @@ async function startDevServer(opts) {
|
|
|
389
292
|
return;
|
|
390
293
|
} catch (err) {
|
|
391
294
|
if (attempt >= LISTEN_ATTEMPTS || closed) throw err;
|
|
392
|
-
await
|
|
295
|
+
await setTimeout(LISTEN_RETRY_DELAY_MS);
|
|
393
296
|
}
|
|
394
297
|
}
|
|
395
298
|
let cleanupPromise;
|
|
@@ -397,7 +300,6 @@ async function startDevServer(opts) {
|
|
|
397
300
|
cleanupPromise ??= (async () => {
|
|
398
301
|
closed = true;
|
|
399
302
|
await watcher.close().catch(() => void 0);
|
|
400
|
-
await devBuilder.dispose().catch(() => void 0);
|
|
401
303
|
await viteServer?.close().catch(() => void 0);
|
|
402
304
|
await currentServer.close().catch(() => void 0);
|
|
403
305
|
})();
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { a as isEexist, l as writeJson, o as readJson, r as errorMessage } from "./_utils-C502jKo8.mjs";
|
|
3
|
-
import { r as isDevMode, t as getMonorepoRoot } from "./_agent-
|
|
3
|
+
import { r as isDevMode, t as getMonorepoRoot } from "./_agent-D39qatZJ.mjs";
|
|
4
4
|
import { existsSync } from "node:fs";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import fs from "node:fs/promises";
|
|
@@ -17,7 +17,7 @@ async function resolveTemplatesDir() {
|
|
|
17
17
|
root: process.env.AAI_TEMPLATES_DIR,
|
|
18
18
|
cleanup: noCleanup
|
|
19
19
|
};
|
|
20
|
-
const monorepoRoot =
|
|
20
|
+
const monorepoRoot = getMonorepoRoot();
|
|
21
21
|
if (monorepoRoot) return {
|
|
22
22
|
root: path.join(monorepoRoot, "packages", "aai-templates"),
|
|
23
23
|
cleanup: noCleanup
|
|
@@ -117,9 +117,7 @@ aai secret delete MY_KEY # Remove a secret
|
|
|
117
117
|
const WORKSPACE_PKG_DIRS = {
|
|
118
118
|
"@alexkroman1/aai": "aai",
|
|
119
119
|
"@alexkroman1/aai-cli": "aai-cli",
|
|
120
|
-
"@alexkroman1/aai-ui": "aai-ui"
|
|
121
|
-
"aai-server": "aai-server",
|
|
122
|
-
"aai-templates": "aai-templates"
|
|
120
|
+
"@alexkroman1/aai-ui": "aai-ui"
|
|
123
121
|
};
|
|
124
122
|
/** Rewrite workspace deps to link: paths so pnpm links to local source. */
|
|
125
123
|
async function patchPackageJsonForWorkspace(targetDir) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import fs from "node:fs/promises";
|
|
4
3
|
import { parseEnv } from "node:util";
|
|
4
|
+
import fs from "node:fs/promises";
|
|
5
5
|
//#region _server-common.ts
|
|
6
6
|
/**
|
|
7
7
|
* Build the `ctx.env` record that agent tools will see at runtime.
|
|
@@ -21,9 +21,15 @@ import { parseEnv } from "node:util";
|
|
|
21
21
|
*/
|
|
22
22
|
async function resolveServerEnv(cwd, baseEnv) {
|
|
23
23
|
let fileEntries = {};
|
|
24
|
-
if (cwd)
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
if (cwd) {
|
|
25
|
+
let content = null;
|
|
26
|
+
try {
|
|
27
|
+
content = await fs.readFile(path.join(cwd, ".env"), "utf-8");
|
|
28
|
+
} catch (err) {
|
|
29
|
+
if (err.code !== "ENOENT") throw err;
|
|
30
|
+
}
|
|
31
|
+
if (content !== null) fileEntries = parseEnv(content);
|
|
32
|
+
}
|
|
27
33
|
const source = baseEnv ?? process.env;
|
|
28
34
|
const env = {};
|
|
29
35
|
for (const [key, fileVal] of Object.entries(fileEntries)) {
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { n as log, o as CliError } from "./_ui-DXZ9prrM.mjs";
|
|
3
|
+
import { typecheckProject } from "./typecheck.mjs";
|
|
4
|
+
//#region _typecheck-gate.ts
|
|
5
|
+
/**
|
|
6
|
+
* The build/deploy typecheck gate: run the project's own `tsc --noEmit`
|
|
7
|
+
* (see `typecheck.ts`) and turn a failure into a structured CliError. The
|
|
8
|
+
* bundlers strip types unchecked, so without this a type-broken agent
|
|
9
|
+
* ships and misbehaves at runtime instead of failing here.
|
|
10
|
+
*/
|
|
11
|
+
async function assertTypechecks(cwd) {
|
|
12
|
+
log.step("Type checking…");
|
|
13
|
+
const result = await typecheckProject(cwd);
|
|
14
|
+
if (!result.ok) throw new CliError("typecheck_failed", result.output, "Fix the type errors, or pass --skipTypecheck to build anyway");
|
|
15
|
+
}
|
|
16
|
+
//#endregion
|
|
17
|
+
export { assertTypechecks };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { styleText } from "node:util";
|
|
2
3
|
import * as p from "@clack/prompts";
|
|
3
|
-
import pc from "picocolors";
|
|
4
4
|
//#region _output.ts
|
|
5
5
|
/**
|
|
6
6
|
* Determine output mode from CLI flags and TTY state.
|
|
@@ -93,7 +93,7 @@ function unwrapCancel(result) {
|
|
|
93
93
|
}
|
|
94
94
|
/** Format a URL for display. */
|
|
95
95
|
function fmtUrl(url) {
|
|
96
|
-
return
|
|
96
|
+
return styleText("cyanBright", url);
|
|
97
97
|
}
|
|
98
98
|
/**
|
|
99
99
|
* Parse and validate a port string. Returns the numeric port or throws.
|
package/dist/cli.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { c as getOutputMode, d as writeLine, i as silenceOutput, l as installStdoutGuard, n as log, o as CliError, s as fail } from "./_ui-
|
|
2
|
+
import { c as getOutputMode, d as writeLine, i as silenceOutput, l as installStdoutGuard, n as log, o as CliError, s as fail } from "./_ui-DXZ9prrM.mjs";
|
|
3
3
|
import { i as fileExists, r as errorMessage, s as resolveCwd } from "./_utils-C502jKo8.mjs";
|
|
4
4
|
import { existsSync, readFileSync } from "node:fs";
|
|
5
5
|
import path from "node:path";
|
|
@@ -108,10 +108,6 @@ const init = defineCommand({
|
|
|
108
108
|
server: sharedArgs.server,
|
|
109
109
|
yes: sharedArgs.yes,
|
|
110
110
|
json: sharedArgs.json,
|
|
111
|
-
skipApi: {
|
|
112
|
-
type: "boolean",
|
|
113
|
-
description: "Deprecated no-op: the API key is now only requested when deploying"
|
|
114
|
-
},
|
|
115
111
|
skipDeploy: {
|
|
116
112
|
type: "boolean",
|
|
117
113
|
description: "Skip deploy after scaffolding"
|
|
@@ -119,7 +115,7 @@ const init = defineCommand({
|
|
|
119
115
|
},
|
|
120
116
|
async run({ args }) {
|
|
121
117
|
await runCommand(args, async (mode) => {
|
|
122
|
-
const { executeInit } = await import("./init-
|
|
118
|
+
const { executeInit } = await import("./init-B53rR3yD.mjs");
|
|
123
119
|
return executeInit({
|
|
124
120
|
dir: args.dir,
|
|
125
121
|
force: args.force,
|
|
@@ -143,7 +139,7 @@ const dev = defineCommand({
|
|
|
143
139
|
async run({ args }) {
|
|
144
140
|
await runCommand(args, async () => {
|
|
145
141
|
const cwd = await setup({ agent: true });
|
|
146
|
-
const { executeDev } = await import("./dev-
|
|
142
|
+
const { executeDev } = await import("./dev-C8o-p13B.mjs");
|
|
147
143
|
return executeDev({
|
|
148
144
|
cwd,
|
|
149
145
|
port: args.port
|
|
@@ -160,7 +156,7 @@ const test = defineCommand({
|
|
|
160
156
|
async run({ args }) {
|
|
161
157
|
await runCommand(args, async () => {
|
|
162
158
|
const cwd = await setup();
|
|
163
|
-
const { executeTest } = await import("./test-
|
|
159
|
+
const { executeTest } = await import("./test-CgWhus_Z.mjs");
|
|
164
160
|
return executeTest(cwd);
|
|
165
161
|
});
|
|
166
162
|
}
|
|
@@ -175,13 +171,17 @@ const build = defineCommand({
|
|
|
175
171
|
skipTests: {
|
|
176
172
|
type: "boolean",
|
|
177
173
|
description: "Skip running tests before build"
|
|
174
|
+
},
|
|
175
|
+
skipTypecheck: {
|
|
176
|
+
type: "boolean",
|
|
177
|
+
description: "Skip type checking before build"
|
|
178
178
|
}
|
|
179
179
|
},
|
|
180
180
|
async run({ args }) {
|
|
181
181
|
await runCommand(args, async () => {
|
|
182
182
|
const cwd = await setup({ agent: true });
|
|
183
183
|
if (!args.skipTests) {
|
|
184
|
-
const { classifyVitestError, runVitest } = await import("./test-
|
|
184
|
+
const { classifyVitestError, runVitest } = await import("./test-CgWhus_Z.mjs");
|
|
185
185
|
const toCliError = (err) => {
|
|
186
186
|
const { code, message } = classifyVitestError(err);
|
|
187
187
|
return new CliError(code, message, "Re-run with --skipTests to build without tests", { cause: err });
|
|
@@ -192,7 +192,11 @@ const build = defineCommand({
|
|
|
192
192
|
throw toCliError(err);
|
|
193
193
|
}
|
|
194
194
|
}
|
|
195
|
-
|
|
195
|
+
if (!args.skipTypecheck) {
|
|
196
|
+
const { assertTypechecks } = await import("./_typecheck-gate-B1GcKZX-.mjs");
|
|
197
|
+
await assertTypechecks(cwd);
|
|
198
|
+
}
|
|
199
|
+
const { executeBuild } = await import("./_bundler-DdmuZzeR.mjs");
|
|
196
200
|
return executeBuild(cwd);
|
|
197
201
|
});
|
|
198
202
|
}
|
|
@@ -204,15 +208,25 @@ const deploy = defineCommand({
|
|
|
204
208
|
},
|
|
205
209
|
args: {
|
|
206
210
|
server: sharedArgs.server,
|
|
207
|
-
json: sharedArgs.json
|
|
211
|
+
json: sharedArgs.json,
|
|
212
|
+
allowMissingSecrets: {
|
|
213
|
+
type: "boolean",
|
|
214
|
+
description: "Deploy even when the agent's providers are missing credentials (the server warns instead of rejecting; set them afterwards with `aai secret put`)"
|
|
215
|
+
},
|
|
216
|
+
skipTypecheck: {
|
|
217
|
+
type: "boolean",
|
|
218
|
+
description: "Skip type checking before deploy"
|
|
219
|
+
}
|
|
208
220
|
},
|
|
209
221
|
async run({ args }) {
|
|
210
222
|
await runCommand(args, async () => {
|
|
211
223
|
const cwd = await setup({ agent: true });
|
|
212
|
-
const { executeDeploy } = await import("./deploy-
|
|
224
|
+
const { executeDeploy } = await import("./deploy-D-dnt9L8.mjs");
|
|
213
225
|
return executeDeploy({
|
|
214
226
|
cwd,
|
|
215
|
-
...args.server ? { server: args.server } : {}
|
|
227
|
+
...args.server ? { server: args.server } : {},
|
|
228
|
+
...args.allowMissingSecrets ? { allowMissingSecrets: true } : {},
|
|
229
|
+
...args.skipTypecheck ? { skipTypecheck: true } : {}
|
|
216
230
|
});
|
|
217
231
|
});
|
|
218
232
|
}
|
|
@@ -229,7 +243,7 @@ const del = defineCommand({
|
|
|
229
243
|
async run({ args }) {
|
|
230
244
|
await runCommand(args, async () => {
|
|
231
245
|
const cwd = await setup();
|
|
232
|
-
const { executeDelete } = await import("./delete-
|
|
246
|
+
const { executeDelete } = await import("./delete-K24hW1ab.mjs");
|
|
233
247
|
return executeDelete({
|
|
234
248
|
cwd,
|
|
235
249
|
...args.server ? { server: args.server } : {}
|
|
@@ -260,7 +274,7 @@ const secret = defineCommand({
|
|
|
260
274
|
async run({ args }) {
|
|
261
275
|
await runCommand(args, async (mode) => {
|
|
262
276
|
const cwd = await setup();
|
|
263
|
-
const { executeSecretPut, readStdin } = await import("./secret-
|
|
277
|
+
const { executeSecretPut, readStdin } = await import("./secret-C4unwCEw.mjs");
|
|
264
278
|
const value = mode === "json" ? await readStdin() : void 0;
|
|
265
279
|
if (mode === "json" && !value) throw new CliError("no_input", "No value provided", "Pipe secret value to stdin");
|
|
266
280
|
return executeSecretPut(cwd, args.name, value, args.server);
|
|
@@ -284,7 +298,7 @@ const secret = defineCommand({
|
|
|
284
298
|
async run({ args }) {
|
|
285
299
|
await runCommand(args, async () => {
|
|
286
300
|
const cwd = await setup();
|
|
287
|
-
const { executeSecretDelete } = await import("./secret-
|
|
301
|
+
const { executeSecretDelete } = await import("./secret-C4unwCEw.mjs");
|
|
288
302
|
return executeSecretDelete(cwd, args.name, args.server);
|
|
289
303
|
});
|
|
290
304
|
}
|
|
@@ -301,7 +315,7 @@ const secret = defineCommand({
|
|
|
301
315
|
async run({ args }) {
|
|
302
316
|
await runCommand(args, async () => {
|
|
303
317
|
const cwd = await setup();
|
|
304
|
-
const { executeSecretList } = await import("./secret-
|
|
318
|
+
const { executeSecretList } = await import("./secret-C4unwCEw.mjs");
|
|
305
319
|
return executeSecretList(cwd, args.server);
|
|
306
320
|
});
|
|
307
321
|
}
|
|
@@ -336,7 +350,7 @@ const storage = defineCommand({
|
|
|
336
350
|
},
|
|
337
351
|
async run({ args }) {
|
|
338
352
|
await runCommand(args, async () => {
|
|
339
|
-
const { executeStorageStatus } = await import("./storage-
|
|
353
|
+
const { executeStorageStatus } = await import("./storage-tZM1J8qj.mjs");
|
|
340
354
|
return executeStorageStatus(resolveStorageCwd(args.dir), args.server);
|
|
341
355
|
});
|
|
342
356
|
}
|
|
@@ -353,7 +367,7 @@ const storage = defineCommand({
|
|
|
353
367
|
},
|
|
354
368
|
async run({ args }) {
|
|
355
369
|
await runCommand(args, async () => {
|
|
356
|
-
const { executeStorageEnable } = await import("./storage-
|
|
370
|
+
const { executeStorageEnable } = await import("./storage-tZM1J8qj.mjs");
|
|
357
371
|
return executeStorageEnable(resolveStorageCwd(args.dir), args.server);
|
|
358
372
|
});
|
|
359
373
|
}
|
|
@@ -375,7 +389,7 @@ const storage = defineCommand({
|
|
|
375
389
|
},
|
|
376
390
|
async run({ args }) {
|
|
377
391
|
await runCommand(args, async () => {
|
|
378
|
-
const { executeStorageDisable } = await import("./storage-
|
|
392
|
+
const { executeStorageDisable } = await import("./storage-tZM1J8qj.mjs");
|
|
379
393
|
return executeStorageDisable(resolveStorageCwd(args.dir), {
|
|
380
394
|
server: args.server,
|
|
381
395
|
force: args.force
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { n as log, u as ok } from "./_ui-
|
|
3
|
-
import { n as getServerInfo } from "./_agent-
|
|
2
|
+
import { n as log, u as ok } from "./_ui-DXZ9prrM.mjs";
|
|
3
|
+
import { n as getServerInfo } from "./_agent-D39qatZJ.mjs";
|
|
4
4
|
import { t as apiRequest } from "./_api-client-a6cPMebU.mjs";
|
|
5
5
|
//#region delete.ts
|
|
6
6
|
async function runDelete(opts) {
|
|
@@ -1,22 +1,23 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { n as log, t as fmtUrl, u as ok } from "./_ui-
|
|
2
|
+
import { n as log, t as fmtUrl, u as ok } from "./_ui-DXZ9prrM.mjs";
|
|
3
3
|
import { r as errorMessage } from "./_utils-C502jKo8.mjs";
|
|
4
|
-
import { buildAgentBundle } from "./_bundler-
|
|
5
|
-
import { o as writeProjectConfig } from "./_config-
|
|
6
|
-
import { t as resolveServerEnv } from "./_server-common-
|
|
7
|
-
import { i as resolveDeployTarget } from "./_agent-
|
|
4
|
+
import { buildAgentBundle } from "./_bundler-DdmuZzeR.mjs";
|
|
5
|
+
import { o as writeProjectConfig } from "./_config-Czjhvaax.mjs";
|
|
6
|
+
import { t as resolveServerEnv } from "./_server-common-B75oco06.mjs";
|
|
7
|
+
import { i as resolveDeployTarget } from "./_agent-D39qatZJ.mjs";
|
|
8
|
+
import { assertTypechecks } from "./_typecheck-gate-B1GcKZX-.mjs";
|
|
8
9
|
import { t as apiRequest } from "./_api-client-a6cPMebU.mjs";
|
|
9
10
|
import { gzipSync } from "node:zlib";
|
|
10
11
|
//#region _deploy.ts
|
|
11
12
|
async function runDeploy(opts) {
|
|
12
13
|
const body = gzipSync(JSON.stringify({
|
|
13
14
|
...opts.slug ? { slug: opts.slug } : {},
|
|
15
|
+
...opts.allowMissingSecrets ? { credentialPolicy: "warn" } : {},
|
|
14
16
|
env: opts.env,
|
|
15
17
|
worker: opts.bundle.worker,
|
|
16
|
-
clientFiles: opts.bundle.clientFiles
|
|
17
|
-
agentConfig: opts.bundle.agentConfig
|
|
18
|
+
clientFiles: opts.bundle.clientFiles
|
|
18
19
|
}));
|
|
19
|
-
|
|
20
|
+
const data = await apiRequest(`${opts.url}/deploy`, {
|
|
20
21
|
method: "POST",
|
|
21
22
|
body,
|
|
22
23
|
headers: {
|
|
@@ -29,13 +30,18 @@ async function runDeploy(opts) {
|
|
|
29
30
|
...opts.slug ? {} : { retry: 0 },
|
|
30
31
|
...opts.retryDelay !== void 0 ? { retryDelay: opts.retryDelay } : {},
|
|
31
32
|
...opts.fetch ? { fetch: opts.fetch } : {}
|
|
32
|
-
})
|
|
33
|
+
});
|
|
34
|
+
return {
|
|
35
|
+
slug: data.slug,
|
|
36
|
+
...data.warnings ? { warnings: data.warnings } : {}
|
|
37
|
+
};
|
|
33
38
|
}
|
|
34
39
|
//#endregion
|
|
35
40
|
//#region deploy.ts
|
|
36
41
|
async function executeDeploy(opts) {
|
|
37
42
|
const { cwd } = opts;
|
|
38
43
|
const { config: projectConfig, serverUrl, apiKey } = await resolveDeployTarget(cwd, opts.server);
|
|
44
|
+
if (!opts.skipTypecheck) await assertTypechecks(cwd);
|
|
39
45
|
const bundle = await buildAgentBundle(cwd, { minify: true });
|
|
40
46
|
const slug = projectConfig?.slug;
|
|
41
47
|
const env = await resolveServerEnv(cwd);
|
|
@@ -48,6 +54,7 @@ async function executeDeploy(opts) {
|
|
|
48
54
|
...env
|
|
49
55
|
},
|
|
50
56
|
...slug ? { slug } : {},
|
|
57
|
+
...opts.allowMissingSecrets ? { allowMissingSecrets: true } : {},
|
|
51
58
|
apiKey
|
|
52
59
|
});
|
|
53
60
|
const agentUrl = `${serverUrl}/${deployed.slug}`;
|
|
@@ -63,10 +70,12 @@ async function executeDeploy(opts) {
|
|
|
63
70
|
serverUrl
|
|
64
71
|
})}`);
|
|
65
72
|
}
|
|
73
|
+
for (const warning of deployed.warnings ?? []) log.warn(warning);
|
|
66
74
|
log.success(`Deployed ${fmtUrl(agentUrl)}`);
|
|
67
75
|
return ok({
|
|
68
76
|
slug: deployed.slug,
|
|
69
|
-
url: agentUrl
|
|
77
|
+
url: agentUrl,
|
|
78
|
+
...deployed.warnings ? { warnings: deployed.warnings } : {}
|
|
70
79
|
});
|
|
71
80
|
}
|
|
72
81
|
//#endregion
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { n as log, r as parsePort, t as fmtUrl, u as ok } from "./_ui-
|
|
2
|
+
import { n as log, r as parsePort, t as fmtUrl, u as ok } from "./_ui-DXZ9prrM.mjs";
|
|
3
3
|
import { n as errorDetail } from "./_utils-C502jKo8.mjs";
|
|
4
4
|
import path from "node:path";
|
|
5
|
-
import
|
|
5
|
+
import { styleText } from "node:util";
|
|
6
6
|
//#region dev.ts
|
|
7
7
|
/**
|
|
8
8
|
* Start the dev server and return the result.
|
|
@@ -11,7 +11,7 @@ import pc from "picocolors";
|
|
|
11
11
|
async function executeDev(opts) {
|
|
12
12
|
const port = parsePort(opts.port);
|
|
13
13
|
const agentName = path.basename(path.resolve(opts.cwd));
|
|
14
|
-
const { startDevServer } = await import("./_dev-server-
|
|
14
|
+
const { startDevServer } = await import("./_dev-server-BCbEUhmv.mjs");
|
|
15
15
|
let cleanup;
|
|
16
16
|
let shuttingDown = false;
|
|
17
17
|
const onSignal = () => {
|
|
@@ -30,7 +30,7 @@ async function executeDev(opts) {
|
|
|
30
30
|
port
|
|
31
31
|
});
|
|
32
32
|
const url = `http://localhost:${port}`;
|
|
33
|
-
log.success(`${
|
|
33
|
+
log.success(`${styleText("bold", agentName)} running at ${fmtUrl(url)}`);
|
|
34
34
|
log.info("Press Ctrl-C to stop");
|
|
35
35
|
process.on("unhandledRejection", (err) => {
|
|
36
36
|
log.error(`Unhandled rejection: ${errorDetail(err)}`);
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { a as unwrapCancel, n as log, u as ok } from "./_ui-
|
|
2
|
+
import { a as unwrapCancel, n as log, u as ok } from "./_ui-DXZ9prrM.mjs";
|
|
3
3
|
import { i as fileExists, o as readJson, r as errorMessage, s as resolveCwd } from "./_utils-C502jKo8.mjs";
|
|
4
|
-
import { r as isDevMode, t as getMonorepoRoot } from "./_agent-
|
|
4
|
+
import { r as isDevMode, t as getMonorepoRoot } from "./_agent-D39qatZJ.mjs";
|
|
5
5
|
import path from "node:path";
|
|
6
|
+
import { styleText } from "node:util";
|
|
6
7
|
import * as p from "@clack/prompts";
|
|
7
|
-
import pc from "picocolors";
|
|
8
8
|
import { execa } from "execa";
|
|
9
9
|
//#region init.ts
|
|
10
10
|
const DEFAULT_PROJECT_NAME = "my-voice-agent";
|
|
@@ -74,7 +74,7 @@ function resolveTargetDir(dir) {
|
|
|
74
74
|
}
|
|
75
75
|
/** Run deploy after init and return deploy metadata if successful. */
|
|
76
76
|
async function tryDeploy(cwd, server) {
|
|
77
|
-
const { executeDeploy } = await import("./deploy-
|
|
77
|
+
const { executeDeploy } = await import("./deploy-D-dnt9L8.mjs");
|
|
78
78
|
try {
|
|
79
79
|
const result = await executeDeploy({
|
|
80
80
|
cwd,
|
|
@@ -92,7 +92,7 @@ async function tryDeploy(cwd, server) {
|
|
|
92
92
|
}
|
|
93
93
|
/** Scaffold the project, optionally showing a spinner. */
|
|
94
94
|
async function scaffoldProject(dir, cwd, template, silent) {
|
|
95
|
-
const { runInit } = await import("./_init-
|
|
95
|
+
const { runInit } = await import("./_init-H-lw7czL.mjs");
|
|
96
96
|
if (silent) {
|
|
97
97
|
await runInit({
|
|
98
98
|
targetDir: cwd,
|
|
@@ -116,11 +116,11 @@ function printPostInitInfo(cwd, monorepoRoot) {
|
|
|
116
116
|
}
|
|
117
117
|
async function executeInit(opts, extra) {
|
|
118
118
|
const suppressUi = extra?.silent;
|
|
119
|
-
if (!suppressUi) p.intro(
|
|
119
|
+
if (!suppressUi) p.intro(styleText("cyanBright", "Create a new voice agent"));
|
|
120
120
|
const dir = opts.dir ?? await promptProjectName(opts.yes);
|
|
121
121
|
const monorepoRoot = getMonorepoRoot();
|
|
122
122
|
const cwd = resolveTargetDir(dir);
|
|
123
|
-
if (!opts.force && await fileExists(path.join(cwd, "agent.ts"))) throw new Error(`agent.ts already exists in this directory. Use ${
|
|
123
|
+
if (!opts.force && await fileExists(path.join(cwd, "agent.ts"))) throw new Error(`agent.ts already exists in this directory. Use ${styleText("cyanBright", "--force")} to overwrite.`);
|
|
124
124
|
const template = opts.template ?? "simple";
|
|
125
125
|
await scaffoldProject(dir, cwd, template, suppressUi);
|
|
126
126
|
const installed = await installDeps(cwd, suppressUi);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { a as unwrapCancel, n as log, s as fail, u as ok } from "./_ui-
|
|
3
|
-
import { n as getServerInfo } from "./_agent-
|
|
2
|
+
import { a as unwrapCancel, n as log, s as fail, u as ok } from "./_ui-DXZ9prrM.mjs";
|
|
3
|
+
import { n as getServerInfo } from "./_agent-D39qatZJ.mjs";
|
|
4
4
|
import { t as apiRequest } from "./_api-client-a6cPMebU.mjs";
|
|
5
5
|
import * as p from "@clack/prompts";
|
|
6
6
|
import { text } from "node:stream/consumers";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { n as log, s as fail, u as ok } from "./_ui-
|
|
3
|
-
import { n as getServerInfo } from "./_agent-
|
|
2
|
+
import { n as log, s as fail, u as ok } from "./_ui-DXZ9prrM.mjs";
|
|
3
|
+
import { n as getServerInfo } from "./_agent-D39qatZJ.mjs";
|
|
4
4
|
import { t as apiRequest } from "./_api-client-a6cPMebU.mjs";
|
|
5
5
|
import * as p from "@clack/prompts";
|
|
6
6
|
//#region storage.ts
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { n as log, s as fail, u as ok } from "./_ui-
|
|
2
|
+
import { n as log, s as fail, u as ok } from "./_ui-DXZ9prrM.mjs";
|
|
3
3
|
import { r as errorMessage, t as errorCode } from "./_utils-C502jKo8.mjs";
|
|
4
4
|
import { createRequire } from "node:module";
|
|
5
5
|
import { existsSync, readFileSync } from "node:fs";
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { readFile } from "node:fs/promises";
|
|
5
|
+
import { spawn } from "node:child_process";
|
|
6
|
+
//#region typecheck.ts
|
|
7
|
+
/**
|
|
8
|
+
* Project typechecking — `tsc --noEmit` over the project's own tsconfig.
|
|
9
|
+
*
|
|
10
|
+
* Public (no `_` prefix) for the same reason as the bundlers: the guest
|
|
11
|
+
* sandbox runs the same check before `test_agent` builds, so the studio's
|
|
12
|
+
* coding agent sees type errors as build feedback instead of shipping
|
|
13
|
+
* runtime-working-but-wrong code (the bundlers strip types unchecked —
|
|
14
|
+
* excess-property bugs like `send`/`state` shipped exactly that way).
|
|
15
|
+
*
|
|
16
|
+
* Gated on `tsconfig.json`: a project that declares its type discipline is
|
|
17
|
+
* checked with it; one that doesn't isn't (scaffolded projects and studio
|
|
18
|
+
* workspaces always have one). TypeScript itself resolves from the
|
|
19
|
+
* PROJECT'S node_modules — the user's pinned compiler, not ours.
|
|
20
|
+
*/
|
|
21
|
+
/** Bound on one typecheck run — a hung compiler must not wedge a deploy. */
|
|
22
|
+
const TYPECHECK_TIMEOUT_MS = 12e4;
|
|
23
|
+
/** Diagnostics tail kept for the failure message. */
|
|
24
|
+
const OUTPUT_CAP = 16e3;
|
|
25
|
+
/**
|
|
26
|
+
* The project's own TypeScript package root — `node_modules/typescript`,
|
|
27
|
+
* walking up from `cwd` exactly as a bare import would.
|
|
28
|
+
*
|
|
29
|
+
* Deliberately NOT `require.resolve("typescript")`. Node appends
|
|
30
|
+
* `Module.globalPaths` — `NODE_PATH`, `~/.node_modules`, the install prefix —
|
|
31
|
+
* to EVERY lookup, and the `paths` option does not suppress them, so an
|
|
32
|
+
* ambient TypeScript anywhere on the host silently satisfies a project that
|
|
33
|
+
* never declared one. That breaks the promise in this module's header twice
|
|
34
|
+
* over: the gate would check a user's build with a compiler their project
|
|
35
|
+
* doesn't pin, and the "TypeScript is not installed" branch below would be
|
|
36
|
+
* unreachable on any host that sets NODE_PATH — which is how vitest runs its
|
|
37
|
+
* workers (it points NODE_PATH at pnpm's hidden store), so the test for that
|
|
38
|
+
* branch could not fail honestly either.
|
|
39
|
+
*
|
|
40
|
+
* The walk-up follows symlinks, which is what pnpm's `node_modules/typescript
|
|
41
|
+
* -> .pnpm/typescript@x/node_modules/typescript` link needs, and what lets a
|
|
42
|
+
* guest sandbox workspace reach the toolchain baked next to the harness.
|
|
43
|
+
*/
|
|
44
|
+
function findTypescriptPackage(cwd) {
|
|
45
|
+
let dir = path.resolve(cwd);
|
|
46
|
+
for (;;) {
|
|
47
|
+
const candidate = path.join(dir, "node_modules", "typescript");
|
|
48
|
+
if (existsSync(path.join(candidate, "package.json"))) return candidate;
|
|
49
|
+
const parent = path.dirname(dir);
|
|
50
|
+
if (parent === dir) return;
|
|
51
|
+
dir = parent;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/** Resolve the project's TypeScript compiler entry (its own `tsc` bin). */
|
|
55
|
+
function resolveTscEntry(cwd) {
|
|
56
|
+
const dir = findTypescriptPackage(cwd);
|
|
57
|
+
if (dir === void 0) throw new Error("no typescript package in the project's node_modules");
|
|
58
|
+
const pkg = JSON.parse(readFileSync(path.join(dir, "package.json"), "utf-8"));
|
|
59
|
+
const bin = typeof pkg.bin === "string" ? pkg.bin : pkg.bin?.tsc;
|
|
60
|
+
if (!bin) throw new Error("installed typescript package declares no tsc bin");
|
|
61
|
+
return path.join(dir, bin);
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Typecheck the project at `cwd` with its own tsconfig + compiler.
|
|
65
|
+
* Skips (ok, skipped: true) when the project has no tsconfig.json.
|
|
66
|
+
*/
|
|
67
|
+
async function typecheckProject(cwd) {
|
|
68
|
+
if (!await readFile(path.join(cwd, "tsconfig.json"), "utf-8").then(() => true, () => false)) return {
|
|
69
|
+
ok: true,
|
|
70
|
+
skipped: true
|
|
71
|
+
};
|
|
72
|
+
let tscEntry;
|
|
73
|
+
try {
|
|
74
|
+
tscEntry = resolveTscEntry(cwd);
|
|
75
|
+
} catch (err) {
|
|
76
|
+
return {
|
|
77
|
+
ok: false,
|
|
78
|
+
output: `tsconfig.json is present but TypeScript is not installed — add it (npm install -D typescript) or remove tsconfig.json: ${errMessage(err)}`
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
const result = await new Promise((resolve, reject) => {
|
|
82
|
+
const child = spawn(process.execPath, [
|
|
83
|
+
tscEntry,
|
|
84
|
+
"--noEmit",
|
|
85
|
+
"--pretty",
|
|
86
|
+
"false"
|
|
87
|
+
], {
|
|
88
|
+
cwd,
|
|
89
|
+
timeout: TYPECHECK_TIMEOUT_MS,
|
|
90
|
+
stdio: [
|
|
91
|
+
"ignore",
|
|
92
|
+
"pipe",
|
|
93
|
+
"pipe"
|
|
94
|
+
]
|
|
95
|
+
});
|
|
96
|
+
let output = "";
|
|
97
|
+
const keep = (s) => s.length > OUTPUT_CAP ? `…${s.slice(-16e3)}` : s;
|
|
98
|
+
child.stdout.on("data", (chunk) => {
|
|
99
|
+
output = keep(output + chunk.toString());
|
|
100
|
+
});
|
|
101
|
+
child.stderr.on("data", (chunk) => {
|
|
102
|
+
output = keep(output + chunk.toString());
|
|
103
|
+
});
|
|
104
|
+
child.on("error", reject);
|
|
105
|
+
child.on("close", (code, signal) => {
|
|
106
|
+
if (signal) {
|
|
107
|
+
reject(/* @__PURE__ */ new Error(`tsc killed by ${signal} after ${TYPECHECK_TIMEOUT_MS}ms`));
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
resolve({
|
|
111
|
+
code,
|
|
112
|
+
output
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
if (result.code === 0) return {
|
|
117
|
+
ok: true,
|
|
118
|
+
skipped: false
|
|
119
|
+
};
|
|
120
|
+
return {
|
|
121
|
+
ok: false,
|
|
122
|
+
output: `Type check failed:\n${result.output.trim()}`
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
function errMessage(err) {
|
|
126
|
+
return err instanceof Error ? err.message : String(err);
|
|
127
|
+
}
|
|
128
|
+
//#endregion
|
|
129
|
+
export { typecheckProject };
|
package/dist/worker-bundler.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { t as withPreservedNodeEnv } from "./_vite-env-Dg_QlVv0.mjs";
|
|
3
3
|
import path from "node:path";
|
|
4
|
+
import fs from "node:fs/promises";
|
|
4
5
|
import { build } from "vite";
|
|
5
6
|
//#region worker-bundler.ts
|
|
6
7
|
/**
|
|
@@ -12,26 +13,48 @@ import { build } from "vite";
|
|
|
12
13
|
* worker published from the browser comes out of the same Vite/Rollup pass as
|
|
13
14
|
* one from `aai deploy`.
|
|
14
15
|
*
|
|
16
|
+
* Every worker is built through a generated wrapper entry that re-exports the
|
|
17
|
+
* agent *and* its extracted config as `__aaiConfig` (via the dependency-free
|
|
18
|
+
* `@alexkroman1/aai/manifest` helpers, bundled in). The guest harness returns
|
|
19
|
+
* that export from `bundle/load`, which is how the platform obtains an
|
|
20
|
+
* agent's config without ever evaluating tenant code on the host — the
|
|
21
|
+
* `POST /deploy` route and the studio's sandbox inspection both rely on it.
|
|
22
|
+
*
|
|
23
|
+
* **The worker ships its own runtime.** Deploy builds also export
|
|
24
|
+
* `__aaiCreateRuntime` — a factory over the *user's installed* SDK's
|
|
25
|
+
* `createRuntime`, bundled in alongside the provider SDKs. The guest harness
|
|
26
|
+
* builds the session runtime through it, so a deployed agent runs exactly the
|
|
27
|
+
* runtime version it was built and tested against (identical to `aai dev`),
|
|
28
|
+
* instead of whatever SDK the platform's harness image was baked with. The
|
|
29
|
+
* harness↔bundle contract is deliberately tiny: the factory takes
|
|
30
|
+
* `{ env, db?, runCode? }` and returns `{ startSession, shutdown }`.
|
|
31
|
+
*
|
|
15
32
|
* What the studio supplies via options, because a workspace is not a project:
|
|
16
33
|
*
|
|
17
|
-
* - **`root` + `entry`.** The studio builds a generated entry that re-exports
|
|
18
|
-
* the agent *and* its extracted config, so the guest can report the config
|
|
19
|
-
* back without the host ever evaluating agent code.
|
|
20
34
|
* - **`configFile: false`.** Workspace files are untrusted and a Vite config
|
|
21
35
|
* is executable host code.
|
|
22
36
|
* - **`plugins`.** The studio adds its import allowlist; without it, Vite
|
|
23
37
|
* would happily resolve any package in the server's `node_modules`.
|
|
24
38
|
*/
|
|
25
39
|
/**
|
|
26
|
-
*
|
|
27
|
-
*
|
|
40
|
+
* Generated wrapper entry, written under `.aai/` for the duration of the
|
|
41
|
+
* build (the CLI's own scratch dir — dot-paths are ignored by the dev
|
|
42
|
+
* watcher, and the studio's workspace materialization never writes there).
|
|
28
43
|
*/
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
44
|
+
const WRAPPER_ENTRY_REL = path.join(".aai", "worker-entry.ts");
|
|
45
|
+
function wrapperEntrySource(runtime) {
|
|
46
|
+
return `import def from "../agent.ts";
|
|
47
|
+
import { agentToolsToSchemas, toAgentConfig } from "@alexkroman1/aai/manifest";
|
|
48
|
+
${runtime ? `import { createRuntime } from "@alexkroman1/aai/runtime";` : ""}
|
|
49
|
+
export default def;
|
|
50
|
+
export const __aaiConfig = {
|
|
51
|
+
...toAgentConfig(def),
|
|
52
|
+
toolSchemas: agentToolsToSchemas(def.tools ?? {}),
|
|
34
53
|
};
|
|
54
|
+
${runtime ? `export const __aaiCreateRuntime = (opts: Record<string, unknown>) =>
|
|
55
|
+
createRuntime({ ...opts, agent: def });
|
|
56
|
+
` : ""}`;
|
|
57
|
+
}
|
|
35
58
|
/**
|
|
36
59
|
* Bundle agent.ts into a single ESM string for the sandbox worker.
|
|
37
60
|
*
|
|
@@ -39,25 +62,36 @@ const rawMdPlugin = {
|
|
|
39
62
|
* and gracefully degrades in restricted environments like Deno.
|
|
40
63
|
*/
|
|
41
64
|
async function buildWorker(cwd, opts = {}) {
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
65
|
+
const wrapperPath = path.join(cwd, WRAPPER_ENTRY_REL);
|
|
66
|
+
await fs.mkdir(path.dirname(wrapperPath), { recursive: true });
|
|
67
|
+
await fs.writeFile(wrapperPath, wrapperEntrySource(opts.runtime !== false), "utf-8");
|
|
68
|
+
let result;
|
|
69
|
+
try {
|
|
70
|
+
result = await withPreservedNodeEnv(() => build({
|
|
71
|
+
root: cwd,
|
|
72
|
+
logLevel: "silent",
|
|
73
|
+
...opts.configFile === false && { configFile: false },
|
|
74
|
+
...opts.plugins && { plugins: opts.plugins },
|
|
75
|
+
ssr: { noExternal: true },
|
|
76
|
+
build: {
|
|
77
|
+
ssr: true,
|
|
78
|
+
lib: {
|
|
79
|
+
entry: wrapperPath,
|
|
80
|
+
formats: ["es"],
|
|
81
|
+
fileName: "worker"
|
|
82
|
+
},
|
|
83
|
+
target: "node20",
|
|
84
|
+
minify: opts.minify ? "oxc" : false,
|
|
85
|
+
write: false,
|
|
86
|
+
rollupOptions: { output: {
|
|
87
|
+
entryFileNames: "[name].js",
|
|
88
|
+
codeSplitting: false
|
|
89
|
+
} }
|
|
90
|
+
}
|
|
91
|
+
}));
|
|
92
|
+
} finally {
|
|
93
|
+
await fs.rm(wrapperPath, { force: true }).catch(() => void 0);
|
|
94
|
+
}
|
|
61
95
|
const output = Array.isArray(result) ? result[0] : result;
|
|
62
96
|
if (!output) throw new Error("Vite produced no output for agent.ts");
|
|
63
97
|
const chunk = output.output.find((o) => o.type === "chunk" && o.isEntry);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alexkroman1/aai-cli",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.0.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"aai": "dist/cli.mjs"
|
|
@@ -13,6 +13,10 @@
|
|
|
13
13
|
"./worker-bundler": {
|
|
14
14
|
"@dev/source": "./worker-bundler.ts",
|
|
15
15
|
"import": "./dist/worker-bundler.mjs"
|
|
16
|
+
},
|
|
17
|
+
"./typecheck": {
|
|
18
|
+
"@dev/source": "./typecheck.ts",
|
|
19
|
+
"import": "./dist/typecheck.mjs"
|
|
16
20
|
}
|
|
17
21
|
},
|
|
18
22
|
"files": [
|
|
@@ -28,12 +32,11 @@
|
|
|
28
32
|
"giget": "^3.3.0",
|
|
29
33
|
"ofetch": "^1.5.1",
|
|
30
34
|
"p-debounce": "^5.1.0",
|
|
31
|
-
"
|
|
32
|
-
"rolldown": "~1.1.4",
|
|
35
|
+
"p-timeout": "^7.0.1",
|
|
33
36
|
"vite": "^8.1.5",
|
|
34
37
|
"zod": "^4.4.3",
|
|
35
|
-
"@alexkroman1/aai": "
|
|
36
|
-
"@alexkroman1/aai
|
|
38
|
+
"@alexkroman1/aai-ui": "5.0.0",
|
|
39
|
+
"@alexkroman1/aai": "5.0.0"
|
|
37
40
|
},
|
|
38
41
|
"devDependencies": {
|
|
39
42
|
"playwright": "^1.61.1",
|
|
@@ -63,6 +66,8 @@
|
|
|
63
66
|
"build": "tsdown",
|
|
64
67
|
"typecheck": "tsc --noEmit",
|
|
65
68
|
"lint": "biome check .",
|
|
69
|
+
"check:publint": "publint",
|
|
70
|
+
"check:attw": "attw --pack --profile esm-only",
|
|
66
71
|
"test:e2e": "VITEST_PROFILE=e2e VITEST_INCLUDE=e2e*.test.ts vitest run -c ../../vitest.slow.config.ts",
|
|
67
72
|
"check:e2e": "pnpm run test:e2e"
|
|
68
73
|
}
|
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { n as log, u as ok } from "./_ui-CKEIHAtB.mjs";
|
|
3
|
-
import { c as validateAgentExport, r as errorMessage } from "./_utils-C502jKo8.mjs";
|
|
4
|
-
import { t as buildClient } from "./client-bundler-DO3GgO6p.mjs";
|
|
5
|
-
import { buildWorker } from "./worker-bundler.mjs";
|
|
6
|
-
import path from "node:path";
|
|
7
|
-
import { pathToFileURL } from "node:url";
|
|
8
|
-
import fs from "node:fs/promises";
|
|
9
|
-
import { createHash } from "node:crypto";
|
|
10
|
-
import { agentToolsToSchemas, toAgentConfig } from "@alexkroman1/aai/manifest";
|
|
11
|
-
//#region _bundler.ts
|
|
12
|
-
/**
|
|
13
|
-
* Bundle an agent directory: build agent.ts into worker ESM + extract config.
|
|
14
|
-
*
|
|
15
|
-
* - agent.ts is the single entry point: `export default agent({...})`
|
|
16
|
-
* - A single Vite build produces the worker ESM (all deps bundled in).
|
|
17
|
-
* The AgentDef is extracted from that bundle via dynamic import, avoiding a
|
|
18
|
-
* second build pass.
|
|
19
|
-
*/
|
|
20
|
-
async function buildAgentBundle(cwd, opts = {}) {
|
|
21
|
-
const [[worker, agentDef], clientFiles] = await Promise.all([buildWorker(cwd, opts).then(async (code) => [code, await evalWorkerBundle(code, cwd)]), buildClient(cwd)]);
|
|
22
|
-
log.step(`Bundling ${agentDef.name}`);
|
|
23
|
-
const config = toAgentConfig(agentDef);
|
|
24
|
-
const toolSchemas = agentToolsToSchemas(agentDef.tools ?? {});
|
|
25
|
-
return {
|
|
26
|
-
worker,
|
|
27
|
-
clientFiles,
|
|
28
|
-
agentConfig: {
|
|
29
|
-
...config,
|
|
30
|
-
toolSchemas
|
|
31
|
-
}
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
/**
|
|
35
|
-
* Write the worker ESM to a temp file and dynamic-import it, returning
|
|
36
|
-
* the AgentDef default export. All dependencies are bundled in, so the
|
|
37
|
-
* file can be evaluated from any directory.
|
|
38
|
-
*
|
|
39
|
-
* Each call imports a uniquely-named file, and Node's ESM registry never
|
|
40
|
-
* evicts — so every call retains one bundle for the process lifetime. That is
|
|
41
|
-
* fine for one-shot commands (`aai build`/`aai deploy`); long-lived callers
|
|
42
|
-
* must go through `createWorkerEvaluator` to at least dedupe identical
|
|
43
|
-
* builds. Evaluating in a discardable context is not an option: tool
|
|
44
|
-
* `execute` functions from the returned AgentDef are called in-process by the
|
|
45
|
-
* dev runtime, which rules out worker threads, and `node:vm` ESM evaluation
|
|
46
|
-
* is still flagged experimental.
|
|
47
|
-
*/
|
|
48
|
-
async function evalWorkerBundle(code, cwd) {
|
|
49
|
-
const evalDir = path.join(cwd, ".aai", "eval");
|
|
50
|
-
const tmpPath = path.join(evalDir, `agent-${Date.now()}-${Math.random().toString(36).slice(2)}.mjs`);
|
|
51
|
-
try {
|
|
52
|
-
await fs.mkdir(evalDir, { recursive: true });
|
|
53
|
-
await fs.writeFile(tmpPath, code);
|
|
54
|
-
} catch (err) {
|
|
55
|
-
await fs.rm(tmpPath, { force: true }).catch(() => void 0);
|
|
56
|
-
throw new Error(`Failed to write the eval bundle under ${evalDir} — is the project directory writable? (${errorMessage(err)})`, { cause: err });
|
|
57
|
-
}
|
|
58
|
-
try {
|
|
59
|
-
const mod = await import(pathToFileURL(tmpPath).href);
|
|
60
|
-
const agentDef = mod.default ?? mod;
|
|
61
|
-
validateAgentExport(agentDef);
|
|
62
|
-
return agentDef;
|
|
63
|
-
} finally {
|
|
64
|
-
await fs.rm(tmpPath).catch(() => {});
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
/**
|
|
68
|
-
* Memoizing wrapper around `evalWorkerBundle` for long-lived callers (the
|
|
69
|
-
* dev server): byte-identical worker code returns the previously evaluated
|
|
70
|
-
* AgentDef without touching the ESM registry. No-op saves and formatter
|
|
71
|
-
* churn are the common watcher events, so this caps the registry leak (see
|
|
72
|
-
* `evalWorkerBundle`) to genuinely-new bundles — the residual one-module-per-
|
|
73
|
-
* distinct-build leak is accepted for the reasons documented there.
|
|
74
|
-
*/
|
|
75
|
-
function createWorkerEvaluator(cwd) {
|
|
76
|
-
let lastHash;
|
|
77
|
-
let lastAgentDef;
|
|
78
|
-
return async (code) => {
|
|
79
|
-
const hash = createHash("sha256").update(code).digest("hex");
|
|
80
|
-
if (lastAgentDef && hash === lastHash) return lastAgentDef;
|
|
81
|
-
const agentDef = await evalWorkerBundle(code, cwd);
|
|
82
|
-
lastHash = hash;
|
|
83
|
-
lastAgentDef = agentDef;
|
|
84
|
-
return agentDef;
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
async function executeBuild(cwd) {
|
|
88
|
-
const bundle = await buildAgentBundle(cwd, { minify: true });
|
|
89
|
-
log.success("Build complete");
|
|
90
|
-
return ok({
|
|
91
|
-
name: bundle.agentConfig.name,
|
|
92
|
-
workerBytes: bundle.worker.length
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
//#endregion
|
|
96
|
-
export { buildAgentBundle, createWorkerEvaluator, executeBuild };
|