@nylorun/runtime 0.2.1-beta → 0.4.0-beta
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/CHANGELOG.md +12 -0
- package/README.md +38 -10
- package/dist/cli.js +25 -9
- package/dist/dev-entry.d.ts +1 -0
- package/dist/dev-entry.js +2 -0
- package/dist/dev.d.ts +2 -0
- package/dist/dev.js +126 -0
- package/dist/environment.d.ts +2 -0
- package/dist/environment.js +64 -0
- package/dist/launcher.d.ts +1 -0
- package/dist/launcher.js +28 -0
- package/dist/model/auth-store.d.ts +2 -1
- package/dist/model/auth-store.js +9 -2
- package/dist/model/configure.js +60 -20
- package/dist/model/models.d.ts +2 -2
- package/dist/model/models.js +24 -3
- package/dist/model/pi-model.js +1 -1
- package/dist/model/settings.js +46 -13
- package/dist/server/host.d.ts +1 -1
- package/dist/server/host.js +50 -4
- package/package.json +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.4.0-beta
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- fa1860a: Use standard MODEL_PROVIDER, MODEL, MODEL_PROVIDER_API_KEY, and MODEL_PROVIDER_BASE_URL environment configuration. Export starter Hono apps and provide CLI development and production Node launchers. Existing starters require manual migration. Release preparation must update the creator Runtime compatibility pin with this release.
|
|
8
|
+
|
|
9
|
+
## 0.3.0-beta
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- 9c350be: Provide `nylorun dev` with optional Studio and browser opening, automatic development loopback CORS, and inferred Hono mount paths. Move local model selection to `.env/model.json` with legacy fallback and migration. Generate starters without copied launcher scripts, a top-level config directory, or a separate TypeScript build config. Release preparation must update the creator's Runtime compatibility pin together with these changes.
|
|
14
|
+
|
|
3
15
|
## 0.2.1-beta
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ import { Runtime, serveAgents } from "@nylorun/runtime";
|
|
|
8
8
|
const runtime = new Runtime();
|
|
9
9
|
app.route(
|
|
10
10
|
"/agents",
|
|
11
|
-
serveAgents({ agents, runtime
|
|
11
|
+
serveAgents({ agents, runtime })
|
|
12
12
|
);
|
|
13
13
|
```
|
|
14
14
|
|
|
@@ -16,22 +16,50 @@ app.route(
|
|
|
16
16
|
|
|
17
17
|
The application owns Hono composition, authentication, CORS, logging, process lifecycle, and deployment. Runtime owns agent sessions, durability, media, and AG-UI/session protocol routes. Graceful shutdown is optional: if the application installs signal handlers and wants to drain live sessions, flush pending journal writes, and run optional agent cleanup, it should await `runtime.close()`. An application that does not install handlers exits normally on its host's shutdown policy; `runtime.close()` does not run on crash, OOM, or SIGKILL.
|
|
18
18
|
|
|
19
|
-
Runtime publishes root-relative discovery and endpoint URLs.
|
|
19
|
+
Runtime publishes root-relative discovery and endpoint URLs. It infers the Hono
|
|
20
|
+
mount from each request URL (so a separate consumer `hono` install still works).
|
|
21
|
+
Mount at `/agents` or `/api/agents` without repeating that path in `serveAgents`.
|
|
22
|
+
Pass explicit `basePath` when a reverse proxy rewrites the public prefix.
|
|
20
23
|
|
|
21
|
-
|
|
22
|
-
app.route(
|
|
23
|
-
"/api/agents",
|
|
24
|
-
serveAgents({ agents, runtime, basePath: "/api/agents" })
|
|
25
|
-
);
|
|
26
|
-
```
|
|
24
|
+
`nylorun dev` enables local Studio connections automatically by setting `NYLORUN_DEV=1` for its child application. This allows HTTP/HTTPS browser origins on `localhost`, `127.0.0.1`, or `[::1]`, including Studio's fallback ports. Ordinary production startup does not enable this policy; the application owns production CORS and authorization.
|
|
27
25
|
|
|
28
26
|
`getActor(context)` supplies an optional actor id and session context for newly created sessions. `getRequestMetadata(context)` supplies JSON-safe metadata for inbound messages. Application middleware remains responsible for authorizing every agent route.
|
|
29
27
|
|
|
30
28
|
## Commands
|
|
31
29
|
|
|
32
30
|
- `nylorun configure`
|
|
31
|
+
- `nylorun dev [--no-studio] [--no-open]`
|
|
32
|
+
- `nylorun start [entry]` (default: `dist/src/index.js`)
|
|
33
33
|
- `nylorun studio --agent-url http://localhost:3000/agents [--port 4161] [--no-open]`
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
Export your Hono application with `export default app`. The CLI supplies the Node server adapter. `nylorun dev` watches `src/index.ts`, waits for `/agents/v1/agents`, and starts project-local Studio. `PORT` defaults to 3000. `--no-studio` runs only the application; `--no-open` keeps the browser closed. Ctrl-C stops both processes. `nylorun start` serves the built app without Studio or development CORS.
|
|
36
|
+
|
|
37
|
+
### Environment configuration
|
|
38
|
+
|
|
39
|
+
Copy `.env.example` to `.env` and fill it in, or run `nylorun configure` before your agent graph is importable:
|
|
40
|
+
|
|
41
|
+
```dotenv
|
|
42
|
+
MODEL_PROVIDER=custom
|
|
43
|
+
MODEL=your-model-id
|
|
44
|
+
MODEL_PROVIDER_API_KEY=your-key
|
|
45
|
+
MODEL_PROVIDER_BASE_URL=https://your-provider.example/v1
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
`MODEL_PROVIDER_BASE_URL` is required only for `MODEL_PROVIDER=custom`; omit it for built-in providers. `configure`, `dev`, and `start` load `.env` before importing the app. Existing process variables win; a missing `.env` is valid. `.env.local` is ignored by Git but is not automatically loaded. Direct imports of Runtime do not load dotenv files.
|
|
49
|
+
|
|
50
|
+
Explicit `piModel({ selection })` options take precedence over environment selection. Environment selection takes precedence over legacy files; incomplete selection produces an error. `MODEL_PROVIDER_API_KEY` overrides provider-native variables such as `OPENAI_API_KEY` and `ANTHROPIC_API_KEY`, which override stored credentials. API-key deployments require no credential files. Supply the same variables through your hosting provider's environment settings.
|
|
51
|
+
|
|
52
|
+
The optional wizard writes selection and entered API keys to `.env`, preserving unrelated configuration. It reuses environment credentials without copying them into the file. OAuth is an alternative where supported; its credentials and refresh state live in ignored `.nylorun/auth.json`. Keep `.env` and `.nylorun/` private.
|
|
53
|
+
|
|
54
|
+
### Upgrading an existing starter
|
|
55
|
+
|
|
56
|
+
Migration is manual; the CLI will not replace a legacy `.env` directory.
|
|
57
|
+
|
|
58
|
+
1. Back up the existing `.env/` directory to a private location outside the project before replacing it with a file. Preserve any `config/model.json` too.
|
|
59
|
+
2. Translate `model.json` fields `provider`, `model`, and `custom.baseUrl` into `MODEL_PROVIDER`, `MODEL`, and `MODEL_PROVIDER_BASE_URL`. Copy API keys into `MODEL_PROVIDER_API_KEY` or provider-native variables. Replace the old `NYLO_CUSTOM_API_KEY` variable with `MODEL_PROVIDER_API_KEY`.
|
|
60
|
+
3. Merge `integrations.env` variables into `.env`. Move OAuth records into `.nylorun/auth.json`. Add `.env`, `.env.local`, and `.nylorun/` to `.gitignore`; remove the old `.env/` exceptions.
|
|
61
|
+
4. Replace the entrypoint's `serve(...)` call and Node adapter import with `export default app`. Set scripts to `"dev": "nylorun dev"` and `"start": "nylorun start"`. Remove the application's `@hono/node-server` dependency if unused elsewhere. Keep your build and asset-copy steps.
|
|
62
|
+
|
|
63
|
+
Runtime retains legacy `.env/model.json`, `config/model.json`, and `.env/auth.json` reads for existing applications using their own launcher. It does not automatically move or delete these files. Use the matching Runtime release pinned by the new creator; older releases cannot launch an exported app.
|
|
36
64
|
|
|
37
|
-
|
|
65
|
+
The exported app follows Hono composition conventions. This change does not establish verified serverless persistence, execution lifetime, or Workers support. `projectAsset()` resolves bundled assets from source or compiled deployments.
|
package/dist/cli.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { existsSync } from "node:fs";
|
|
3
|
-
import { loadEnvFile } from "node:process";
|
|
4
2
|
import { join } from "node:path";
|
|
5
3
|
import { pathToFileURL } from "node:url";
|
|
6
4
|
import { createRequire } from "node:module";
|
|
7
|
-
import { ConfigurationCancelled, configureProvider } from "./model/configure.js";
|
|
8
|
-
|
|
5
|
+
import { ConfigurationCancelled, configureProvider, } from "./model/configure.js";
|
|
6
|
+
import { loadProjectEnvironment } from "./environment.js";
|
|
7
|
+
import { develop } from "./dev.js";
|
|
8
|
+
const usage = `nylorun <configure|dev|start|studio>
|
|
9
|
+
dev [--no-studio] [--no-open]
|
|
10
|
+
start [entry]
|
|
9
11
|
configure
|
|
10
12
|
studio --agent-url <http(s)-url> [--port <n>] [--no-open]`;
|
|
11
13
|
async function startStudio(agentServerUrl, open, port) {
|
|
@@ -17,7 +19,11 @@ async function startStudio(agentServerUrl, open, port) {
|
|
|
17
19
|
throw new Error("Install @nylorun/studio to use the Studio dashboard.");
|
|
18
20
|
}
|
|
19
21
|
const studio = await import(pathToFileURL(entry).href);
|
|
20
|
-
return studio.startStudio({
|
|
22
|
+
return studio.startStudio({
|
|
23
|
+
agentServerUrl,
|
|
24
|
+
open,
|
|
25
|
+
...(port === undefined ? {} : { port }),
|
|
26
|
+
});
|
|
21
27
|
}
|
|
22
28
|
function parsePort(value) {
|
|
23
29
|
if (value === undefined)
|
|
@@ -31,6 +37,18 @@ async function main() {
|
|
|
31
37
|
const [command, ...args] = process.argv.slice(2);
|
|
32
38
|
if (!command || command === "--help" || command === "-h")
|
|
33
39
|
return void console.log(usage);
|
|
40
|
+
if (["configure", "dev", "start"].includes(command))
|
|
41
|
+
loadProjectEnvironment();
|
|
42
|
+
if (command === "start") {
|
|
43
|
+
if (args.length > 1 || args[0]?.startsWith("--"))
|
|
44
|
+
throw new Error(usage);
|
|
45
|
+
await (await import("./launcher.js")).start(args[0]);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
if (command === "dev") {
|
|
49
|
+
process.exitCode = await develop(args);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
34
52
|
if (command === "configure") {
|
|
35
53
|
if (args.length)
|
|
36
54
|
throw new Error(usage);
|
|
@@ -38,9 +56,6 @@ async function main() {
|
|
|
38
56
|
const cancel = (signal) => controller.abort(new ConfigurationCancelled(signal));
|
|
39
57
|
process.once("SIGINT", () => cancel("SIGINT"));
|
|
40
58
|
process.once("SIGTERM", () => cancel("SIGTERM"));
|
|
41
|
-
const integrations = join(process.cwd(), ".env", "integrations.env");
|
|
42
|
-
if (existsSync(integrations))
|
|
43
|
-
loadEnvFile(integrations);
|
|
44
59
|
await configureProvider({ signal: controller.signal });
|
|
45
60
|
return;
|
|
46
61
|
}
|
|
@@ -80,5 +95,6 @@ async function main() {
|
|
|
80
95
|
}
|
|
81
96
|
void main().catch((error) => {
|
|
82
97
|
console.error(error instanceof Error ? error.message : String(error));
|
|
83
|
-
process.exitCode =
|
|
98
|
+
process.exitCode =
|
|
99
|
+
error instanceof ConfigurationCancelled ? error.exitCode : 1;
|
|
84
100
|
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/dev.d.ts
ADDED
package/dist/dev.js
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { setTimeout as delay } from "node:timers/promises";
|
|
6
|
+
/** Runs project development tooling without copying a supervisor into each application. */
|
|
7
|
+
export async function develop(args) {
|
|
8
|
+
const usage = "Usage: nylorun dev [--no-studio] [--no-open]";
|
|
9
|
+
if (new Set(args).size !== args.length ||
|
|
10
|
+
args.some((arg) => !["--no-studio", "--no-open"].includes(arg)))
|
|
11
|
+
throw new Error(usage);
|
|
12
|
+
const port = Number(process.env.PORT ?? "3000");
|
|
13
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535)
|
|
14
|
+
throw new Error("PORT must be an integer between 1 and 65535.");
|
|
15
|
+
const require = createRequire(join(process.cwd(), "package.json"));
|
|
16
|
+
let tsx;
|
|
17
|
+
try {
|
|
18
|
+
tsx = require.resolve("tsx/cli");
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
throw new Error("Install tsx in your project to use nylorun dev.");
|
|
22
|
+
}
|
|
23
|
+
if (!args.includes("--no-studio")) {
|
|
24
|
+
try {
|
|
25
|
+
require.resolve("@nylorun/studio");
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
throw new Error("Install @nylorun/studio or use nylorun dev --no-studio.");
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
const controller = new AbortController();
|
|
32
|
+
const children = new Set();
|
|
33
|
+
const exits = [];
|
|
34
|
+
let result = 0;
|
|
35
|
+
let force;
|
|
36
|
+
const stop = (code, signal = "SIGTERM") => {
|
|
37
|
+
if (controller.signal.aborted)
|
|
38
|
+
return;
|
|
39
|
+
result = code;
|
|
40
|
+
controller.abort();
|
|
41
|
+
for (const child of children)
|
|
42
|
+
child.kill(signal);
|
|
43
|
+
force = setTimeout(() => {
|
|
44
|
+
for (const child of children)
|
|
45
|
+
child.kill("SIGKILL");
|
|
46
|
+
}, 5_000);
|
|
47
|
+
force.unref();
|
|
48
|
+
};
|
|
49
|
+
const interrupt = () => stop(130, "SIGINT");
|
|
50
|
+
const terminate = () => stop(143);
|
|
51
|
+
process.once("SIGINT", interrupt);
|
|
52
|
+
process.once("SIGTERM", terminate);
|
|
53
|
+
const launch = (argv) => {
|
|
54
|
+
const child = spawn(process.execPath, argv, {
|
|
55
|
+
stdio: "inherit",
|
|
56
|
+
env: { ...process.env, NYLORUN_DEV: "1" },
|
|
57
|
+
});
|
|
58
|
+
children.add(child);
|
|
59
|
+
exits.push(new Promise((resolve) => {
|
|
60
|
+
child.once("error", (error) => {
|
|
61
|
+
console.error(`Could not start development process: ${error.message}`);
|
|
62
|
+
stop(1);
|
|
63
|
+
});
|
|
64
|
+
child.once("close", (code, signal) => {
|
|
65
|
+
children.delete(child);
|
|
66
|
+
stop(code ?? (signal === "SIGINT" ? 130 : signal === "SIGTERM" ? 143 : 1));
|
|
67
|
+
resolve();
|
|
68
|
+
});
|
|
69
|
+
}));
|
|
70
|
+
};
|
|
71
|
+
try {
|
|
72
|
+
launch([
|
|
73
|
+
tsx,
|
|
74
|
+
"watch",
|
|
75
|
+
fileURLToPath(new URL("./dev-entry.js", import.meta.url)),
|
|
76
|
+
"src/index.ts",
|
|
77
|
+
]);
|
|
78
|
+
if (!args.includes("--no-studio")) {
|
|
79
|
+
const url = `http://127.0.0.1:${port}/agents/v1/agents`;
|
|
80
|
+
const deadline = Date.now() + 20_000;
|
|
81
|
+
let ready = false;
|
|
82
|
+
while (!controller.signal.aborted && Date.now() < deadline) {
|
|
83
|
+
try {
|
|
84
|
+
const response = await fetch(url, {
|
|
85
|
+
signal: AbortSignal.any([
|
|
86
|
+
controller.signal,
|
|
87
|
+
AbortSignal.timeout(500),
|
|
88
|
+
]),
|
|
89
|
+
});
|
|
90
|
+
ready = response.ok;
|
|
91
|
+
await response.body?.cancel();
|
|
92
|
+
if (ready)
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
/* Watch mode may still be compiling or restarting the app. */
|
|
97
|
+
}
|
|
98
|
+
await delay(50, undefined, { signal: controller.signal }).catch(() => { });
|
|
99
|
+
}
|
|
100
|
+
if (!controller.signal.aborted) {
|
|
101
|
+
if (!ready)
|
|
102
|
+
throw new Error(`Application did not become ready at ${url} within 20 seconds.`);
|
|
103
|
+
launch([
|
|
104
|
+
fileURLToPath(new URL("./cli.js", import.meta.url)),
|
|
105
|
+
"studio",
|
|
106
|
+
"--agent-url",
|
|
107
|
+
`http://localhost:${port}/agents`,
|
|
108
|
+
...(args.includes("--no-open") ? ["--no-open"] : []),
|
|
109
|
+
]);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
await Promise.all(exits);
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
stop(1);
|
|
116
|
+
await Promise.all(exits);
|
|
117
|
+
throw error;
|
|
118
|
+
}
|
|
119
|
+
finally {
|
|
120
|
+
if (force !== undefined)
|
|
121
|
+
clearTimeout(force);
|
|
122
|
+
process.removeListener("SIGINT", interrupt);
|
|
123
|
+
process.removeListener("SIGTERM", terminate);
|
|
124
|
+
}
|
|
125
|
+
return result;
|
|
126
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { readFileSync, statSync } from "node:fs";
|
|
2
|
+
import { writeFile, rename, rm } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { randomUUID } from "node:crypto";
|
|
5
|
+
import { loadEnvFile } from "node:process";
|
|
6
|
+
export function loadProjectEnvironment(root = process.cwd()) {
|
|
7
|
+
const file = join(root, ".env");
|
|
8
|
+
try {
|
|
9
|
+
if (statSync(file).isDirectory())
|
|
10
|
+
throw new Error("The .env directory must be migrated manually: back it up, create a .env file with MODEL_PROVIDER, MODEL and MODEL_PROVIDER_API_KEY, and move OAuth credentials to .nylorun/auth.json. See the Runtime migration guide.");
|
|
11
|
+
}
|
|
12
|
+
catch (error) {
|
|
13
|
+
if (error.code === "ENOENT")
|
|
14
|
+
return;
|
|
15
|
+
throw error;
|
|
16
|
+
}
|
|
17
|
+
loadEnvFile(file);
|
|
18
|
+
}
|
|
19
|
+
// Match complete dotenv assignments, including quoted multiline values.
|
|
20
|
+
const assignment = /^(?:export\s+)?([\w]+)[\t ]*=[\t ]*(?:"[^"]*"|'[^']*'|`[^`]*`|[^#\r\n]*)([^\r\n]*)(?:\r?\n|$)/gm;
|
|
21
|
+
export async function saveEnvironment(root, updates, signal) {
|
|
22
|
+
const file = join(root, ".env");
|
|
23
|
+
let contents = "";
|
|
24
|
+
try {
|
|
25
|
+
contents = readFileSync(file, "utf8");
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
if (error.code !== "ENOENT")
|
|
29
|
+
throw error;
|
|
30
|
+
}
|
|
31
|
+
const encode = (value) => {
|
|
32
|
+
// Node's dotenv parser has no general quote-escaping syntax. Select a
|
|
33
|
+
// delimiter absent from the value rather than changing the credential.
|
|
34
|
+
for (const quote of ["'", '"', "`"]) {
|
|
35
|
+
if (!value.includes(quote) && !(quote === '"' && /\\[nr]/.test(value)))
|
|
36
|
+
return quote + value + quote;
|
|
37
|
+
}
|
|
38
|
+
throw new Error("This value contains all dotenv quote delimiters; set it through your process environment instead.");
|
|
39
|
+
};
|
|
40
|
+
const remaining = new Set(Object.keys(updates));
|
|
41
|
+
contents = contents.replace(assignment, (whole, key, suffix) => {
|
|
42
|
+
if (!(key in updates))
|
|
43
|
+
return whole;
|
|
44
|
+
if (!remaining.delete(key))
|
|
45
|
+
return "";
|
|
46
|
+
return updates[key] === undefined
|
|
47
|
+
? ""
|
|
48
|
+
: `${key}=${encode(updates[key])}${suffix}\n`;
|
|
49
|
+
});
|
|
50
|
+
if (contents && !contents.endsWith("\n"))
|
|
51
|
+
contents += "\n";
|
|
52
|
+
for (const key of remaining)
|
|
53
|
+
if (updates[key] !== undefined)
|
|
54
|
+
contents += `${key}=${encode(updates[key])}\n`;
|
|
55
|
+
const temporary = join(root, `.env-${randomUUID()}.tmp`);
|
|
56
|
+
try {
|
|
57
|
+
await writeFile(temporary, contents, { mode: 0o600, signal });
|
|
58
|
+
signal?.throwIfAborted();
|
|
59
|
+
await rename(temporary, file);
|
|
60
|
+
}
|
|
61
|
+
finally {
|
|
62
|
+
await rm(temporary, { force: true });
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function start(entry?: string, development?: boolean): Promise<void>;
|
package/dist/launcher.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { serve } from "@hono/node-server";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { pathToFileURL } from "node:url";
|
|
4
|
+
import { loadProjectEnvironment } from "./environment.js";
|
|
5
|
+
export async function start(entry = "dist/src/index.js", development = false) {
|
|
6
|
+
loadProjectEnvironment();
|
|
7
|
+
if (development)
|
|
8
|
+
process.env.NYLORUN_DEV = "1";
|
|
9
|
+
else
|
|
10
|
+
delete process.env.NYLORUN_DEV;
|
|
11
|
+
const port = Number(process.env.PORT ?? "3000");
|
|
12
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535)
|
|
13
|
+
throw new Error("PORT must be an integer between 1 and 65535.");
|
|
14
|
+
const { default: app } = await import(pathToFileURL(resolve(entry)).href);
|
|
15
|
+
if (!app || typeof app.fetch !== "function")
|
|
16
|
+
throw new Error(`${entry} must export a Hono application with 'export default app' and a callable fetch.`);
|
|
17
|
+
const server = serve({ fetch: app.fetch.bind(app), port }, (info) => {
|
|
18
|
+
console.log(`Server is running on http://localhost:${info.port}`);
|
|
19
|
+
});
|
|
20
|
+
const stop = () => {
|
|
21
|
+
process.removeListener("SIGINT", stop);
|
|
22
|
+
process.removeListener("SIGTERM", stop);
|
|
23
|
+
server.close(() => process.exit(0));
|
|
24
|
+
setTimeout(() => process.exit(0), 5_000).unref();
|
|
25
|
+
};
|
|
26
|
+
process.once("SIGINT", stop);
|
|
27
|
+
process.once("SIGTERM", stop);
|
|
28
|
+
}
|
|
@@ -2,7 +2,8 @@ import type { Credential, CredentialInfo, CredentialStore } from "@earendil-work
|
|
|
2
2
|
export declare class ProjectCredentialStore implements CredentialStore {
|
|
3
3
|
#private;
|
|
4
4
|
private readonly file;
|
|
5
|
-
|
|
5
|
+
private readonly legacyFile?;
|
|
6
|
+
constructor(file?: string, legacyFile?: string | undefined);
|
|
6
7
|
read(providerId: string): Promise<Credential | undefined>;
|
|
7
8
|
list(): Promise<readonly CredentialInfo[]>;
|
|
8
9
|
modify(providerId: string, fn: (current: Credential | undefined) => Promise<Credential | undefined>): Promise<Credential | undefined>;
|
package/dist/model/auth-store.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
|
+
var _a;
|
|
1
2
|
import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
2
3
|
import { dirname, join } from "node:path";
|
|
3
4
|
export class ProjectCredentialStore {
|
|
4
5
|
file;
|
|
6
|
+
legacyFile;
|
|
5
7
|
#chain = Promise.resolve();
|
|
6
|
-
constructor(file = join(process.cwd(), ".
|
|
8
|
+
constructor(file = join(process.cwd(), ".nylorun", "auth.json"), legacyFile) {
|
|
7
9
|
this.file = file;
|
|
10
|
+
this.legacyFile = legacyFile;
|
|
8
11
|
}
|
|
9
12
|
async read(providerId) {
|
|
10
13
|
return (await this.#all())[providerId];
|
|
@@ -54,8 +57,11 @@ export class ProjectCredentialStore {
|
|
|
54
57
|
return JSON.parse(await readFile(this.file, "utf8"));
|
|
55
58
|
}
|
|
56
59
|
catch (error) {
|
|
57
|
-
if (error.code
|
|
60
|
+
if (["ENOENT", "ENOTDIR"].includes(error.code ?? "")) {
|
|
61
|
+
if (this.legacyFile)
|
|
62
|
+
return new _a(this.legacyFile).#all();
|
|
58
63
|
return {};
|
|
64
|
+
}
|
|
59
65
|
throw error;
|
|
60
66
|
}
|
|
61
67
|
}
|
|
@@ -83,3 +89,4 @@ export class ProjectCredentialStore {
|
|
|
83
89
|
}
|
|
84
90
|
}
|
|
85
91
|
}
|
|
92
|
+
_a = ProjectCredentialStore;
|
package/dist/model/configure.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { mkdir, writeFile, rename, rm } from "node:fs/promises";
|
|
2
|
-
import { randomUUID } from "node:crypto";
|
|
3
1
|
import { join } from "node:path";
|
|
4
2
|
import { createInterface } from "node:readline/promises";
|
|
3
|
+
import { saveEnvironment } from "../environment.js";
|
|
5
4
|
import { ProjectCredentialStore } from "./auth-store.js";
|
|
6
5
|
import { modelsFor } from "./models.js";
|
|
7
6
|
export class ConfigurationCancelled extends Error {
|
|
@@ -21,7 +20,25 @@ export async function configureProvider(options = {}) {
|
|
|
21
20
|
const signal = controller.signal;
|
|
22
21
|
const forwardAbort = () => controller.abort(options.signal.reason);
|
|
23
22
|
options.signal?.throwIfAborted();
|
|
24
|
-
|
|
23
|
+
let enteredKey;
|
|
24
|
+
let enteredEnvironment = {};
|
|
25
|
+
const oauthStore = new ProjectCredentialStore(join(root, ".nylorun", "auth.json"), join(root, ".env", "auth.json"));
|
|
26
|
+
const store = {
|
|
27
|
+
read: (id) => oauthStore.read(id),
|
|
28
|
+
list: () => oauthStore.list(),
|
|
29
|
+
delete: (id) => oauthStore.delete(id),
|
|
30
|
+
async modify(id, fn) {
|
|
31
|
+
const next = await fn(await oauthStore.read(id));
|
|
32
|
+
if (next?.type === "api_key") {
|
|
33
|
+
if (next.key === "")
|
|
34
|
+
throw new Error("An API key is required.");
|
|
35
|
+
enteredKey = next.key;
|
|
36
|
+
enteredEnvironment = { ...next.env };
|
|
37
|
+
return next;
|
|
38
|
+
}
|
|
39
|
+
return oauthStore.modify(id, async () => next);
|
|
40
|
+
},
|
|
41
|
+
};
|
|
25
42
|
const models = modelsFor({ provider: "", model: "" }, store);
|
|
26
43
|
const providers = models.getProviders();
|
|
27
44
|
const prompt = createInterface({
|
|
@@ -59,7 +76,8 @@ export async function configureProvider(options = {}) {
|
|
|
59
76
|
custom: { baseUrl },
|
|
60
77
|
};
|
|
61
78
|
const customModels = modelsFor(selection, store);
|
|
62
|
-
await customModels.
|
|
79
|
+
if (!(await customModels.checkAuth("custom", { signal })))
|
|
80
|
+
await customModels.login("custom", "api_key", interaction());
|
|
63
81
|
await save(selection);
|
|
64
82
|
}
|
|
65
83
|
else {
|
|
@@ -72,7 +90,17 @@ export async function configureProvider(options = {}) {
|
|
|
72
90
|
if (!model)
|
|
73
91
|
throw new Error("Choose a listed model.");
|
|
74
92
|
if (!(await models.checkAuth(chosen.id, { signal }))) {
|
|
75
|
-
|
|
93
|
+
let method = chosen.auth.apiKey
|
|
94
|
+
? "api_key"
|
|
95
|
+
: "oauth";
|
|
96
|
+
if (chosen.auth.apiKey && chosen.auth.oauth) {
|
|
97
|
+
const answer = (await question("Choose authentication: 1. API key (default), 2. OAuth: ")).trim();
|
|
98
|
+
if (answer && !["1", "2"].includes(answer))
|
|
99
|
+
throw new Error("Choose authentication 1 or 2.");
|
|
100
|
+
if (answer === "2")
|
|
101
|
+
method = "oauth";
|
|
102
|
+
}
|
|
103
|
+
await models.login(chosen.id, method, interaction());
|
|
76
104
|
}
|
|
77
105
|
await save({ provider: chosen.id, model: model.id });
|
|
78
106
|
}
|
|
@@ -92,24 +120,36 @@ export async function configureProvider(options = {}) {
|
|
|
92
120
|
function interaction() {
|
|
93
121
|
return {
|
|
94
122
|
signal,
|
|
95
|
-
prompt: async (item) =>
|
|
96
|
-
|
|
123
|
+
prompt: async (item) => {
|
|
124
|
+
if (item.type !== "select")
|
|
125
|
+
return question(item.message + ": ");
|
|
126
|
+
item.options.forEach((option, index) => output.write(`${index + 1}. ${option.label}\n`));
|
|
127
|
+
const answer = (await question(item.message + " ")).trim();
|
|
128
|
+
const option = item.options.find((option) => option.id === answer) ??
|
|
129
|
+
item.options[Number(answer) - 1];
|
|
130
|
+
if (!option)
|
|
131
|
+
throw new Error("Choose a listed authentication option.");
|
|
132
|
+
return option.id;
|
|
133
|
+
},
|
|
134
|
+
notify: (event) => {
|
|
135
|
+
output.write(("url" in event
|
|
136
|
+
? event.url
|
|
137
|
+
: "verificationUri" in event
|
|
138
|
+
? event.verificationUri
|
|
139
|
+
: event.message) + "\n");
|
|
140
|
+
},
|
|
97
141
|
};
|
|
98
142
|
}
|
|
99
143
|
async function save(selection) {
|
|
100
144
|
signal.throwIfAborted();
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
}
|
|
111
|
-
finally {
|
|
112
|
-
await rm(temporary, { force: true });
|
|
113
|
-
}
|
|
145
|
+
await saveEnvironment(root, {
|
|
146
|
+
...enteredEnvironment,
|
|
147
|
+
MODEL_PROVIDER: selection.provider,
|
|
148
|
+
MODEL: selection.model,
|
|
149
|
+
MODEL_PROVIDER_BASE_URL: selection.custom?.baseUrl,
|
|
150
|
+
...(enteredKey === undefined
|
|
151
|
+
? {}
|
|
152
|
+
: { MODEL_PROVIDER_API_KEY: enteredKey }),
|
|
153
|
+
}, signal);
|
|
114
154
|
}
|
|
115
155
|
}
|
package/dist/model/models.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type CredentialStore } from "@earendil-works/pi-ai";
|
|
2
2
|
export type Selection = Readonly<{
|
|
3
3
|
provider: string;
|
|
4
4
|
model: string;
|
|
@@ -6,4 +6,4 @@ export type Selection = Readonly<{
|
|
|
6
6
|
baseUrl: string;
|
|
7
7
|
}>;
|
|
8
8
|
}>;
|
|
9
|
-
export declare function modelsFor(selection: Selection, credentials:
|
|
9
|
+
export declare function modelsFor(selection: Selection, credentials: CredentialStore): import("@earendil-works/pi-ai").MutableModels;
|
package/dist/model/models.js
CHANGED
|
@@ -1,8 +1,29 @@
|
|
|
1
|
-
import { createProvider, envApiKeyAuth, } from "@earendil-works/pi-ai";
|
|
1
|
+
import { createProvider, defaultProviderAuthContext, envApiKeyAuth, } from "@earendil-works/pi-ai";
|
|
2
2
|
import { stream, streamSimple, } from "@earendil-works/pi-ai/api/openai-completions";
|
|
3
3
|
import { builtinModels } from "@earendil-works/pi-ai/providers/all";
|
|
4
4
|
export function modelsFor(selection, credentials) {
|
|
5
|
-
const
|
|
5
|
+
const environmentFirst = {
|
|
6
|
+
async read(providerId, options) {
|
|
7
|
+
const explicit = process.env.MODEL_PROVIDER_API_KEY;
|
|
8
|
+
if (explicit &&
|
|
9
|
+
(!selection.provider || selection.provider === providerId))
|
|
10
|
+
return { type: "api_key", key: explicit };
|
|
11
|
+
const provider = models
|
|
12
|
+
.getProviders()
|
|
13
|
+
.find((item) => item.id === providerId);
|
|
14
|
+
const ambient = await provider?.auth.apiKey?.resolve({
|
|
15
|
+
ctx: defaultProviderAuthContext(),
|
|
16
|
+
signal: options?.signal ?? new AbortController().signal,
|
|
17
|
+
});
|
|
18
|
+
if (ambient)
|
|
19
|
+
return undefined;
|
|
20
|
+
return credentials.read(providerId, options);
|
|
21
|
+
},
|
|
22
|
+
list: (options) => credentials.list(options),
|
|
23
|
+
modify: (id, fn, options) => credentials.modify(id, fn, options),
|
|
24
|
+
delete: (id, options) => credentials.delete(id, options),
|
|
25
|
+
};
|
|
26
|
+
const models = builtinModels({ credentials: environmentFirst });
|
|
6
27
|
if (!selection.custom)
|
|
7
28
|
return models;
|
|
8
29
|
const model = {
|
|
@@ -22,7 +43,7 @@ export function modelsFor(selection, credentials) {
|
|
|
22
43
|
name: "Custom OpenAI-compatible",
|
|
23
44
|
baseUrl: selection.custom.baseUrl,
|
|
24
45
|
auth: {
|
|
25
|
-
apiKey: envApiKeyAuth("Custom API key", ["
|
|
46
|
+
apiKey: envApiKeyAuth("Custom API key", ["MODEL_PROVIDER_API_KEY"]),
|
|
26
47
|
},
|
|
27
48
|
models: [model],
|
|
28
49
|
api: { stream, streamSimple },
|
package/dist/model/pi-model.js
CHANGED
|
@@ -17,7 +17,7 @@ export function piModel(options = {}) {
|
|
|
17
17
|
context.signal.throwIfAborted();
|
|
18
18
|
const root = options.root ?? process.cwd();
|
|
19
19
|
const selection = options.selection ?? modelSelection(root);
|
|
20
|
-
const registry = modelsFor(selection, new ProjectCredentialStore(join(root, ".env", "auth.json")));
|
|
20
|
+
const registry = modelsFor(selection, new ProjectCredentialStore(join(root, ".nylorun", "auth.json"), join(root, ".env", "auth.json")));
|
|
21
21
|
const selected = registry.getModel(selection.provider, selection.model);
|
|
22
22
|
if (!selected)
|
|
23
23
|
throw new Error("Unknown model. Run nylorun configure.");
|
package/dist/model/settings.js
CHANGED
|
@@ -1,8 +1,40 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
export function modelSelection(root = process.cwd()) {
|
|
4
|
+
const { MODEL_PROVIDER: provider, MODEL: model, MODEL_PROVIDER_BASE_URL: baseUrl, } = process.env;
|
|
5
|
+
if (provider !== undefined || model !== undefined || baseUrl !== undefined) {
|
|
6
|
+
if (!provider?.trim() || !model?.trim())
|
|
7
|
+
throw new Error("Set both MODEL_PROVIDER and MODEL, or run nylorun configure.");
|
|
8
|
+
if (provider === "custom" && !baseUrl?.trim())
|
|
9
|
+
throw new Error("Set MODEL_PROVIDER_BASE_URL for MODEL_PROVIDER=custom.");
|
|
10
|
+
if (baseUrl && provider !== "custom")
|
|
11
|
+
throw new Error("MODEL_PROVIDER_BASE_URL requires MODEL_PROVIDER=custom.");
|
|
12
|
+
if (baseUrl) {
|
|
13
|
+
let url;
|
|
14
|
+
try {
|
|
15
|
+
url = new URL(baseUrl);
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
throw new Error("MODEL_PROVIDER_BASE_URL must be an HTTP(S) URL.");
|
|
19
|
+
}
|
|
20
|
+
if (!["http:", "https:"].includes(url.protocol))
|
|
21
|
+
throw new Error("MODEL_PROVIDER_BASE_URL must be an HTTP(S) URL.");
|
|
22
|
+
}
|
|
23
|
+
return { provider, model, ...(baseUrl ? { custom: { baseUrl } } : {}) };
|
|
24
|
+
}
|
|
4
25
|
try {
|
|
5
|
-
|
|
26
|
+
let contents;
|
|
27
|
+
try {
|
|
28
|
+
contents = readFileSync(join(root, ".env", "model.json"), "utf8");
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
if (!(error instanceof Error) ||
|
|
32
|
+
!("code" in error) ||
|
|
33
|
+
!["ENOENT", "ENOTDIR"].includes(String(error.code)))
|
|
34
|
+
throw error;
|
|
35
|
+
contents = readFileSync(join(root, "config", "model.json"), "utf8");
|
|
36
|
+
}
|
|
37
|
+
const value = JSON.parse(contents);
|
|
6
38
|
if (typeof value.provider === "string" &&
|
|
7
39
|
value.provider &&
|
|
8
40
|
typeof value.model === "string" &&
|
|
@@ -19,17 +51,18 @@ export function projectSecrets(root = process.cwd()) {
|
|
|
19
51
|
const values = Object.entries(process.env)
|
|
20
52
|
.filter(([key]) => /key|token|secret|password|credential/i.test(key))
|
|
21
53
|
.flatMap(([, value]) => (value ? [value] : []));
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
54
|
+
for (const directory of [".nylorun", ".env"])
|
|
55
|
+
try {
|
|
56
|
+
const collect = (value) => {
|
|
57
|
+
if (typeof value === "string")
|
|
58
|
+
values.push(value);
|
|
59
|
+
else if (value && typeof value === "object")
|
|
60
|
+
Object.values(value).forEach(collect);
|
|
61
|
+
};
|
|
62
|
+
collect(JSON.parse(readFileSync(join(root, directory, "auth.json"), "utf8")));
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
/* The vault may not exist before setup. */
|
|
66
|
+
}
|
|
34
67
|
return values;
|
|
35
68
|
}
|
package/dist/server/host.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ export type RuntimeActor = Readonly<{
|
|
|
6
6
|
context?: Record<string, JsonValue>;
|
|
7
7
|
}>;
|
|
8
8
|
export type AgentRouterOptions = Readonly<{
|
|
9
|
-
/**
|
|
9
|
+
/** Override the public URL prefix; defaults to the current Hono mount path. */
|
|
10
10
|
basePath?: string;
|
|
11
11
|
getActor?: (context: Context) => RuntimeActor | undefined | Promise<RuntimeActor | undefined>;
|
|
12
12
|
getRequestMetadata?: (context: Context) => Record<string, JsonValue> | undefined | Promise<Record<string, JsonValue> | undefined>;
|
package/dist/server/host.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { Hono } from "hono";
|
|
3
|
+
import { cors } from "hono/cors";
|
|
3
4
|
import { HTTPException } from "hono/http-exception";
|
|
4
5
|
import { agUiEvents, sse } from "./ag-ui.js";
|
|
5
6
|
import { observedPayload } from "./digests.js";
|
|
@@ -38,9 +39,34 @@ export class Runtime {
|
|
|
38
39
|
const configuredObserver = this.#config.observer;
|
|
39
40
|
const redact = (value) => scrub(value, projectSecrets());
|
|
40
41
|
const live = new Map();
|
|
41
|
-
let publicPath = (path) => path;
|
|
42
42
|
const routerOptions = options;
|
|
43
43
|
const app = new Hono();
|
|
44
|
+
if (process.env.NYLORUN_DEV === "1") {
|
|
45
|
+
app.use("*", cors({
|
|
46
|
+
origin: (origin) => {
|
|
47
|
+
try {
|
|
48
|
+
const url = new URL(origin);
|
|
49
|
+
return ["http:", "https:"].includes(url.protocol) &&
|
|
50
|
+
["localhost", "127.0.0.1", "[::1]"].includes(url.hostname) &&
|
|
51
|
+
url.origin === origin
|
|
52
|
+
? origin
|
|
53
|
+
: undefined;
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
allowMethods: [
|
|
60
|
+
"GET",
|
|
61
|
+
"HEAD",
|
|
62
|
+
"POST",
|
|
63
|
+
"PUT",
|
|
64
|
+
"PATCH",
|
|
65
|
+
"DELETE",
|
|
66
|
+
"OPTIONS",
|
|
67
|
+
],
|
|
68
|
+
}));
|
|
69
|
+
}
|
|
44
70
|
app.onError((error, context) => context.json({ error: String(redact(error.message)) }, error instanceof HTTPException ? error.status : 500));
|
|
45
71
|
app.use("*", async (context, next) => {
|
|
46
72
|
await next();
|
|
@@ -52,19 +78,22 @@ export class Runtime {
|
|
|
52
78
|
});
|
|
53
79
|
}
|
|
54
80
|
});
|
|
55
|
-
|
|
81
|
+
// Infer the public mount from this request URL and the local route path.
|
|
82
|
+
// Do not use hono/route basePath: consumers often install a separate `hono`
|
|
83
|
+
// copy, and that helper's match-result Symbol then misses the parent's match.
|
|
84
|
+
const publicPath = (context, routePath, path) => `${normalizeBasePath(routerOptions.basePath ?? inferMountPath(context, routePath))}${path}`;
|
|
56
85
|
app.get("/v1/agents", (context) => context.json({
|
|
57
86
|
protocolVersion: 2,
|
|
58
87
|
agents: agents.map((agent) => ({
|
|
59
88
|
id: agent.id,
|
|
60
|
-
manifestUrl: publicPath(`/${agent.id}/manifest.json`),
|
|
89
|
+
manifestUrl: publicPath(context, "/v1/agents", `/${agent.id}/manifest.json`),
|
|
61
90
|
})),
|
|
62
91
|
}));
|
|
63
92
|
app.get("/:agentId/manifest.json", (context) => {
|
|
64
93
|
const agent = byId.get(context.req.param("agentId"));
|
|
65
94
|
return agent === undefined
|
|
66
95
|
? context.json({ error: "unknown agent" }, 404)
|
|
67
|
-
: context.json(manifest(agent, media !== undefined, publicPath));
|
|
96
|
+
: context.json(manifest(agent, media !== undefined, (path) => publicPath(context, "/:agentId/manifest.json", path)));
|
|
68
97
|
});
|
|
69
98
|
app.get("/:agentId/v1/media/:session/:assetId", async (context) => {
|
|
70
99
|
const agent = requireAgent(context.req.param("agentId"));
|
|
@@ -602,3 +631,20 @@ function normalizeBasePath(value) {
|
|
|
602
631
|
throw new Error("basePath must start with / and must not end with /.");
|
|
603
632
|
return value;
|
|
604
633
|
}
|
|
634
|
+
/** Public mount prefix for this request, derived without hono/route Symbols. */
|
|
635
|
+
function inferMountPath(context, routePath) {
|
|
636
|
+
const pathname = new URL(context.req.url).pathname;
|
|
637
|
+
const suffix = routePath.replace(/:([A-Za-z0-9_]+)/g, (_, key) => {
|
|
638
|
+
const value = context.req.param(key);
|
|
639
|
+
if (value === undefined)
|
|
640
|
+
throw new Error(`Unable to infer Runtime mount path from ${pathname}. Pass basePath matching the Hono mount.`);
|
|
641
|
+
return value;
|
|
642
|
+
});
|
|
643
|
+
if (suffix !== "/" && pathname.endsWith(suffix)) {
|
|
644
|
+
const base = pathname.slice(0, -suffix.length);
|
|
645
|
+
return base === "" ? "/" : base;
|
|
646
|
+
}
|
|
647
|
+
if (pathname === suffix || `${pathname}/` === suffix)
|
|
648
|
+
return "/";
|
|
649
|
+
throw new Error(`Unable to infer Runtime mount path from ${pathname}. Pass basePath matching the Hono mount.`);
|
|
650
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nylorun/runtime",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0-beta",
|
|
4
4
|
"description": "Portable agent runtime, Hono protocol router, model providers, and the Nylorun CLI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -39,7 +39,8 @@
|
|
|
39
39
|
"prepack": "npm run build"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@earendil-works/pi-ai": "0.85.1"
|
|
42
|
+
"@earendil-works/pi-ai": "0.85.1",
|
|
43
|
+
"@hono/node-server": "^2.1.1"
|
|
43
44
|
},
|
|
44
45
|
"peerDependencies": {
|
|
45
46
|
"hono": "^4.13.7"
|