@nylorun/runtime 0.1.1-beta → 0.2.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 +13 -0
- package/README.md +20 -30
- package/dist/adapters/journal.d.ts +10 -10
- package/dist/adapters/journal.js +20 -20
- package/dist/adapters/observe.d.ts +10 -0
- package/dist/adapters/observe.js +23 -0
- package/dist/cli.js +58 -293
- package/dist/config.d.ts +9 -6
- package/dist/config.js +1 -15
- package/dist/contracts.d.ts +26 -7
- package/dist/index.d.ts +5 -5
- package/dist/index.js +3 -3
- package/dist/model/pi-model.js +64 -4
- package/dist/server/ag-ui.js +13 -4
- package/dist/server/digests.d.ts +4 -2
- package/dist/server/host.d.ts +25 -12
- package/dist/server/host.js +342 -327
- package/package.json +9 -8
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.0-beta
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 4badb5b: Move model execution to session startup, provide Runtime as a mountable Hono router, and generate Hono-first projects with supervised application and Studio development. Studio now resolves root-relative Runtime endpoints correctly for custom mount paths.
|
|
8
|
+
|
|
9
|
+
## 0.1.2-beta
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- d27242c: Show stopped guardrail and failed model requests as errors in Studio. Runtime now emits a terminal AG-UI error instead of marking failed requests successful, and Studio displays the reported message. The guardrails example also checks text content parts sent by Studio, including mixed media input, before invoking the model.
|
|
14
|
+
- d27242c: Preserve opaque provider continuation metadata through assistant conversation history. Gemini tool calls now retain thought signatures when sending tool results back to the model, including signed empty text and reasoning blocks. Only the originating provider and model receive their signatures.
|
|
15
|
+
|
|
3
16
|
## 0.1.1-beta
|
|
4
17
|
|
|
5
18
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -1,44 +1,34 @@
|
|
|
1
1
|
# @nylorun/runtime
|
|
2
2
|
|
|
3
|
-
Portable agent
|
|
3
|
+
Portable agent lifecycle, a mountable Hono protocol router, pi-ai model providers, and the `nylorun` CLI. Runtime has no Harness dependency.
|
|
4
4
|
|
|
5
5
|
```ts
|
|
6
|
-
import {
|
|
7
|
-
import { agents } from "./agent/registry.js";
|
|
8
|
-
export default defineRuntime({ agents });
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
Applications install their engine and Runtime directly, with Studio as a development dependency. An agent may bind Runtime's `piModel()` through its engine's model interface. `piModel({ media })` uses the explicitly supplied media adapter to resolve opaque image references. It never fetches arbitrary media URLs.
|
|
12
|
-
|
|
13
|
-
## Commands
|
|
14
|
-
|
|
15
|
-
Run from the directory containing `nylorun.config.ts`:
|
|
16
|
-
|
|
17
|
-
- `nylorun dev [--no-studio] [--no-open] [--port 4111] [--host 127.0.0.1] [--allowed-hosts a,b]`
|
|
18
|
-
- `nylorun studio --agent-url http://127.0.0.1:4111 [--port 3000] [--no-open]`
|
|
19
|
-
- `nylorun configure`
|
|
20
|
-
- `nylorun inspect`
|
|
21
|
-
- `nylorun build`
|
|
22
|
-
- `nylorun start [--port 4111] [--host 127.0.0.1] [--allowed-hosts a,b]`
|
|
6
|
+
import { Runtime, serveAgents } from "@nylorun/runtime";
|
|
23
7
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
8
|
+
const runtime = new Runtime();
|
|
9
|
+
app.route("/agents", serveAgents({ agents, runtime }));
|
|
10
|
+
```
|
|
27
11
|
|
|
28
|
-
|
|
12
|
+
`Runtime` is the primitive stack: model adapter (`piModel` by default), observer (`jsonlObserver` per session by default), and durability (`localJsonl` by default). Session files live together under `.data/sessions/<agent>/<session>/` as `events.jsonl` and `observe.jsonl`. Agents bind only when served.
|
|
29
13
|
|
|
30
|
-
The
|
|
14
|
+
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.
|
|
31
15
|
|
|
32
|
-
|
|
16
|
+
Runtime publishes root-relative discovery and endpoint URLs. Mounting below root requires the matching public prefix, so Studio and other clients can resolve those URLs against any configured agent-server URL:
|
|
33
17
|
|
|
34
|
-
|
|
18
|
+
```ts
|
|
19
|
+
app.route(
|
|
20
|
+
"/api/agents",
|
|
21
|
+
serveAgents({ agents, runtime, basePath: "/api/agents" })
|
|
22
|
+
);
|
|
23
|
+
```
|
|
35
24
|
|
|
36
|
-
|
|
25
|
+
`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.
|
|
37
26
|
|
|
38
|
-
|
|
27
|
+
## Commands
|
|
39
28
|
|
|
40
|
-
|
|
29
|
+
- `nylorun configure`
|
|
30
|
+
- `nylorun studio --agent-url http://localhost:3000/agents [--port 4161] [--no-open]`
|
|
41
31
|
|
|
42
|
-
|
|
32
|
+
Studio attaches to an application you run. Use your own TypeScript/build tooling and a Node adapter such as `@hono/node-server` when applicable. `projectAsset("agents/skills/catalog")` resolves bundled application assets from source or a compiled `dist/` deployment.
|
|
43
33
|
|
|
44
|
-
|
|
34
|
+
Provider credentials are stored in `.env/auth.json`, and selection in `config/model.json`. `nylorun configure` can run before an agent graph is importable.
|
|
@@ -12,24 +12,24 @@ export type SessionSummary = Readonly<{
|
|
|
12
12
|
startedAt: number;
|
|
13
13
|
endedAt?: number;
|
|
14
14
|
}>;
|
|
15
|
-
/** Explicit local-only JSONL
|
|
15
|
+
/** Explicit local-only JSONL durability. Raw provider payloads never enter this service. */
|
|
16
16
|
export declare class JsonlJournal {
|
|
17
17
|
private readonly root;
|
|
18
18
|
private readonly secrets;
|
|
19
19
|
constructor(root: string, secrets: readonly string[]);
|
|
20
|
-
append(
|
|
21
|
-
events(
|
|
22
|
-
list(
|
|
20
|
+
append(agentId: string, event: CanonicalEvent): Promise<void>;
|
|
21
|
+
events(agentId: string, sessionId: string): Promise<readonly CanonicalEvent[]>;
|
|
22
|
+
list(agentId: string): Promise<readonly SessionSummary[]>;
|
|
23
23
|
private file;
|
|
24
24
|
}
|
|
25
25
|
export declare function scrub(value: unknown, secrets: readonly string[]): unknown;
|
|
26
|
-
export interface
|
|
27
|
-
append(
|
|
28
|
-
events(
|
|
29
|
-
list(
|
|
26
|
+
export interface RuntimeDurability {
|
|
27
|
+
append(agentId: string, event: CanonicalEvent): Promise<void>;
|
|
28
|
+
events(agentId: string, sessionId: string): Promise<readonly CanonicalEvent[]>;
|
|
29
|
+
list(agentId: string): Promise<readonly SessionSummary[]>;
|
|
30
30
|
}
|
|
31
31
|
export declare function localJsonl(options?: {
|
|
32
32
|
root?: string;
|
|
33
33
|
secrets?: readonly string[];
|
|
34
|
-
}):
|
|
35
|
-
export declare function memoryHistory():
|
|
34
|
+
}): RuntimeDurability;
|
|
35
|
+
export declare function memoryHistory(): RuntimeDurability;
|
package/dist/adapters/journal.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { appendFile, mkdir, readFile, readdir } from "node:fs/promises";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
-
/** Explicit local-only JSONL
|
|
3
|
+
/** Explicit local-only JSONL durability. Raw provider payloads never enter this service. */
|
|
4
4
|
export class JsonlJournal {
|
|
5
5
|
root;
|
|
6
6
|
secrets;
|
|
@@ -8,14 +8,14 @@ export class JsonlJournal {
|
|
|
8
8
|
this.root = root;
|
|
9
9
|
this.secrets = secrets;
|
|
10
10
|
}
|
|
11
|
-
async append(
|
|
12
|
-
const file = this.file(
|
|
13
|
-
await mkdir(join(this.root,
|
|
11
|
+
async append(agentId, event) {
|
|
12
|
+
const file = this.file(agentId, event.session);
|
|
13
|
+
await mkdir(join(this.root, agentId, event.session), { recursive: true });
|
|
14
14
|
await appendFile(file, `${JSON.stringify(scrub(event, this.secrets))}\n`);
|
|
15
15
|
}
|
|
16
|
-
async events(
|
|
16
|
+
async events(agentId, sessionId) {
|
|
17
17
|
try {
|
|
18
|
-
return Object.freeze((await readFile(this.file(
|
|
18
|
+
return Object.freeze((await readFile(this.file(agentId, sessionId), "utf8"))
|
|
19
19
|
.split("\n")
|
|
20
20
|
.filter(Boolean)
|
|
21
21
|
.flatMap((line) => {
|
|
@@ -31,15 +31,15 @@ export class JsonlJournal {
|
|
|
31
31
|
return Object.freeze([]);
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
|
-
async list(
|
|
34
|
+
async list(agentId) {
|
|
35
35
|
try {
|
|
36
|
-
const ids = await readdir(join(this.root,
|
|
37
|
-
const summaries = await Promise.all(ids.map(async (
|
|
38
|
-
const events = await this.events(
|
|
36
|
+
const ids = await readdir(join(this.root, agentId));
|
|
37
|
+
const summaries = await Promise.all(ids.map(async (sessionId) => {
|
|
38
|
+
const events = await this.events(agentId, sessionId);
|
|
39
39
|
const final = events.findLast((event) => event.type === "final");
|
|
40
40
|
const title = sessionTitle(events);
|
|
41
41
|
return {
|
|
42
|
-
session,
|
|
42
|
+
session: sessionId,
|
|
43
43
|
...(title === undefined ? {} : { title }),
|
|
44
44
|
status: sessionStatus(events),
|
|
45
45
|
startedAt: events[0] ? Date.parse(events[0].ts) : 0,
|
|
@@ -52,8 +52,8 @@ export class JsonlJournal {
|
|
|
52
52
|
return Object.freeze([]);
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
|
-
file(
|
|
56
|
-
return join(this.root, safe(
|
|
55
|
+
file(agentId, sessionId) {
|
|
56
|
+
return join(this.root, safe(agentId), safe(sessionId), "events.jsonl");
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
59
|
function sessionTitle(events) {
|
|
@@ -106,18 +106,18 @@ export function localJsonl(options = {}) {
|
|
|
106
106
|
export function memoryHistory() {
|
|
107
107
|
const agents = new Map();
|
|
108
108
|
return {
|
|
109
|
-
async append(
|
|
110
|
-
const sessions = agents.get(
|
|
111
|
-
agents.set(
|
|
109
|
+
async append(agentId, event) {
|
|
110
|
+
const sessions = agents.get(agentId) ?? new Map();
|
|
111
|
+
agents.set(agentId, sessions);
|
|
112
112
|
const events = sessions.get(event.session) ?? [];
|
|
113
113
|
events.push(event);
|
|
114
114
|
sessions.set(event.session, events);
|
|
115
115
|
},
|
|
116
|
-
async events(
|
|
117
|
-
return agents.get(
|
|
116
|
+
async events(agentId, sessionId) {
|
|
117
|
+
return agents.get(agentId)?.get(sessionId) ?? [];
|
|
118
118
|
},
|
|
119
|
-
async list(
|
|
120
|
-
return [...(agents.get(
|
|
119
|
+
async list(agentId) {
|
|
120
|
+
return [...(agents.get(agentId) ?? [])]
|
|
121
121
|
.map(([session, events]) => ({
|
|
122
122
|
session,
|
|
123
123
|
title: sessionTitle(events),
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** Local JSONL observer. Writes raw engine events beside session durability files. */
|
|
2
|
+
export declare function jsonlObserver(options: {
|
|
3
|
+
readonly agentId: string;
|
|
4
|
+
readonly sessionId: string;
|
|
5
|
+
readonly root?: string;
|
|
6
|
+
}): {
|
|
7
|
+
(event: {
|
|
8
|
+
readonly type: string;
|
|
9
|
+
}): void;
|
|
10
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { appendFile, mkdir } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { scrub } from "./journal.js";
|
|
4
|
+
import { projectSecrets } from "../model/settings.js";
|
|
5
|
+
/** Local JSONL observer. Writes raw engine events beside session durability files. */
|
|
6
|
+
export function jsonlObserver(options) {
|
|
7
|
+
const root = options.root ?? join(process.cwd(), ".data", "sessions");
|
|
8
|
+
const directory = join(root, safe(options.agentId), safe(options.sessionId));
|
|
9
|
+
const file = join(directory, "observe.jsonl");
|
|
10
|
+
const secrets = projectSecrets();
|
|
11
|
+
return (event) => {
|
|
12
|
+
const line = `${JSON.stringify(scrub(event, secrets))}\n`;
|
|
13
|
+
void mkdir(directory, { recursive: true })
|
|
14
|
+
.then(() => appendFile(file, line))
|
|
15
|
+
.catch(() => { });
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
function safe(value) {
|
|
19
|
+
if (value === "." || value === ".." || !/^[a-zA-Z0-9._-]+$/u.test(value)) {
|
|
20
|
+
throw new Error("Refusing a path-shaped ID.");
|
|
21
|
+
}
|
|
22
|
+
return value;
|
|
23
|
+
}
|
package/dist/cli.js
CHANGED
|
@@ -1,319 +1,84 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { existsSync } from "node:fs";
|
|
3
3
|
import { loadEnvFile } from "node:process";
|
|
4
|
-
import {
|
|
5
|
-
import { createRequire } from "node:module";
|
|
6
|
-
import { resolve, join, relative, isAbsolute, sep } from "node:path";
|
|
4
|
+
import { join } from "node:path";
|
|
7
5
|
import { pathToFileURL } from "node:url";
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
import { modelsFor } from "./model/models.js";
|
|
15
|
-
import { ProjectCredentialStore } from "./model/auth-store.js";
|
|
16
|
-
const usage = `nylorun <dev|studio|configure|inspect|build|start>
|
|
17
|
-
dev [--no-studio] [--no-open] [--port <n>] [--host <address>] [--allowed-hosts <list>]
|
|
18
|
-
studio --agent-url <http(s)-url> [--port <n>] [--no-open]
|
|
19
|
-
start [--port <n>] [--host <address>] [--allowed-hosts <list>]
|
|
20
|
-
Run from the directory containing nylorun.config.ts.
|
|
21
|
-
PORT, HOST and ALLOWED_HOSTS environment variables supply the same settings.`;
|
|
22
|
-
async function studio(agentServerUrl, open, port) {
|
|
6
|
+
import { createRequire } from "node:module";
|
|
7
|
+
import { ConfigurationCancelled, configureProvider } from "./model/configure.js";
|
|
8
|
+
const usage = `nylorun <configure|studio>
|
|
9
|
+
configure
|
|
10
|
+
studio --agent-url <http(s)-url> [--port <n>] [--no-open]`;
|
|
11
|
+
async function startStudio(agentServerUrl, open, port) {
|
|
23
12
|
let entry;
|
|
24
13
|
try {
|
|
25
14
|
entry = createRequire(join(process.cwd(), "package.json")).resolve("@nylorun/studio");
|
|
26
15
|
}
|
|
27
16
|
catch {
|
|
28
|
-
throw new Error("Install @nylorun/studio
|
|
17
|
+
throw new Error("Install @nylorun/studio to use the Studio dashboard.");
|
|
29
18
|
}
|
|
30
|
-
const
|
|
31
|
-
return
|
|
32
|
-
agentServerUrl,
|
|
33
|
-
open,
|
|
34
|
-
...(port === undefined ? {} : { port }),
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
function port(value, fallback) {
|
|
38
|
-
const result = value === undefined ? fallback : Number(value);
|
|
39
|
-
if (!Number.isInteger(result) || result < 1 || result > 65535)
|
|
40
|
-
throw new Error("Port must be an integer between 1 and 65535.");
|
|
41
|
-
return result;
|
|
19
|
+
const studio = await import(pathToFileURL(entry).href);
|
|
20
|
+
return studio.startStudio({ agentServerUrl, open, ...(port === undefined ? {} : { port }) });
|
|
42
21
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
const loopback = ["127.0.0.1", "localhost", "::1"].includes(hostname);
|
|
51
|
-
const configured = (allowed ?? "")
|
|
52
|
-
.split(",")
|
|
53
|
-
.map((entry) => entry.trim())
|
|
54
|
-
.filter(Boolean);
|
|
55
|
-
const hosts = configured.length
|
|
56
|
-
? [...(loopback ? loopbackHosts(hostPort) : []), ...configured]
|
|
57
|
-
: loopback
|
|
58
|
-
? loopbackHosts(hostPort)
|
|
59
|
-
: ["*"];
|
|
60
|
-
const unspecified = ["0.0.0.0", "::"].includes(hostname);
|
|
61
|
-
const reachable = unspecified
|
|
62
|
-
? "127.0.0.1"
|
|
63
|
-
: hostname.includes(":")
|
|
64
|
-
? `[${hostname}]`
|
|
65
|
-
: hostname;
|
|
66
|
-
return { hostname, reachable, loopback, hosts };
|
|
67
|
-
}
|
|
68
|
-
async function sourceLoader() {
|
|
69
|
-
const vite = await createServer({
|
|
70
|
-
configFile: false,
|
|
71
|
-
appType: "custom",
|
|
72
|
-
optimizeDeps: { noDiscovery: true, include: [] },
|
|
73
|
-
server: { middlewareMode: true, hmr: false, ws: false },
|
|
74
|
-
ssr: { external: true },
|
|
75
|
-
});
|
|
76
|
-
return {
|
|
77
|
-
vite,
|
|
78
|
-
async load() {
|
|
79
|
-
const module = await vite.ssrLoadModule("/nylorun.config.ts");
|
|
80
|
-
return defineRuntime(module.default);
|
|
81
|
-
},
|
|
82
|
-
};
|
|
22
|
+
function parsePort(value) {
|
|
23
|
+
if (value === undefined)
|
|
24
|
+
return undefined;
|
|
25
|
+
const port = Number(value);
|
|
26
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535)
|
|
27
|
+
throw new Error("--port must be an integer between 1 and 65535.");
|
|
28
|
+
return port;
|
|
83
29
|
}
|
|
84
30
|
async function main() {
|
|
85
31
|
const [command, ...args] = process.argv.slice(2);
|
|
86
|
-
if (!command || command === "--help" || command === "-h")
|
|
87
|
-
console.log(usage);
|
|
88
|
-
return;
|
|
89
|
-
}
|
|
90
|
-
const allowed = {
|
|
91
|
-
dev: ["--no-studio", "--no-open", "--port", "--host", "--allowed-hosts"],
|
|
92
|
-
studio: ["--agent-url", "--port", "--no-open"],
|
|
93
|
-
start: ["--port", "--host", "--allowed-hosts"],
|
|
94
|
-
configure: [],
|
|
95
|
-
inspect: [],
|
|
96
|
-
build: [],
|
|
97
|
-
};
|
|
98
|
-
if (!(command in allowed))
|
|
99
|
-
throw new Error(usage);
|
|
100
|
-
const flags = new Map();
|
|
101
|
-
for (let i = 0; i < args.length; i++) {
|
|
102
|
-
const arg = args[i];
|
|
103
|
-
if (!allowed[command].includes(arg) || flags.has(arg))
|
|
104
|
-
throw new Error(`Invalid option ${arg}\n${usage}`);
|
|
105
|
-
if (["--port", "--agent-url", "--host", "--allowed-hosts"].includes(arg)) {
|
|
106
|
-
const value = args[++i];
|
|
107
|
-
if (!value || value.startsWith("--"))
|
|
108
|
-
throw new Error(`${arg} requires a value.`);
|
|
109
|
-
flags.set(arg, value);
|
|
110
|
-
}
|
|
111
|
-
else
|
|
112
|
-
flags.set(arg, true);
|
|
113
|
-
}
|
|
114
|
-
const requestedPort = flags.get("--port");
|
|
32
|
+
if (!command || command === "--help" || command === "-h")
|
|
33
|
+
return void console.log(usage);
|
|
115
34
|
if (command === "configure") {
|
|
35
|
+
if (args.length)
|
|
36
|
+
throw new Error(usage);
|
|
116
37
|
const controller = new AbortController();
|
|
117
|
-
const
|
|
118
|
-
|
|
119
|
-
process.
|
|
120
|
-
process.on("SIGTERM", onTerm);
|
|
121
|
-
try {
|
|
122
|
-
const integrations = join(process.cwd(), ".env", "integrations.env");
|
|
123
|
-
if (existsSync(integrations))
|
|
124
|
-
loadEnvFile(integrations);
|
|
125
|
-
await configureProvider({ signal: controller.signal });
|
|
126
|
-
}
|
|
127
|
-
finally {
|
|
128
|
-
process.removeListener("SIGINT", onInt);
|
|
129
|
-
process.removeListener("SIGTERM", onTerm);
|
|
130
|
-
}
|
|
131
|
-
return;
|
|
132
|
-
}
|
|
133
|
-
let stopping = false;
|
|
134
|
-
const shutdown = [];
|
|
135
|
-
const stop = async () => {
|
|
136
|
-
if (stopping)
|
|
137
|
-
return;
|
|
138
|
-
stopping = true;
|
|
139
|
-
const results = await Promise.allSettled(shutdown.map((close) => close()));
|
|
140
|
-
if (results.some((result) => result.status === "rejected"))
|
|
141
|
-
process.exitCode = 1;
|
|
142
|
-
};
|
|
143
|
-
process.once("SIGINT", () => void stop());
|
|
144
|
-
process.once("SIGTERM", () => void stop());
|
|
145
|
-
try {
|
|
38
|
+
const cancel = (signal) => controller.abort(new ConfigurationCancelled(signal));
|
|
39
|
+
process.once("SIGINT", () => cancel("SIGINT"));
|
|
40
|
+
process.once("SIGTERM", () => cancel("SIGTERM"));
|
|
146
41
|
const integrations = join(process.cwd(), ".env", "integrations.env");
|
|
147
42
|
if (existsSync(integrations))
|
|
148
43
|
loadEnvFile(integrations);
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
rollupOptions: { output: { entryFileNames: "nylorun.config.js" } },
|
|
166
|
-
},
|
|
167
|
-
ssr: { external: true },
|
|
168
|
-
});
|
|
169
|
-
if (existsSync("agent")) {
|
|
170
|
-
await mkdir("dist/agent", { recursive: true });
|
|
171
|
-
await cp("agent", "dist/agent", {
|
|
172
|
-
recursive: true,
|
|
173
|
-
filter: (source) => !source.split(/[\\/]/).includes("node_modules"),
|
|
174
|
-
});
|
|
175
|
-
}
|
|
176
|
-
return;
|
|
177
|
-
}
|
|
178
|
-
let loader;
|
|
179
|
-
let config;
|
|
180
|
-
if (command === "start")
|
|
181
|
-
config = defineRuntime((await import(pathToFileURL(resolve("dist/nylorun.config.js")).href))
|
|
182
|
-
.default);
|
|
183
|
-
else {
|
|
184
|
-
loader = await sourceLoader();
|
|
185
|
-
shutdown.push(() => loader.vite.close());
|
|
186
|
-
config = await loader.load();
|
|
187
|
-
}
|
|
188
|
-
if (command === "inspect") {
|
|
189
|
-
let setup = "required";
|
|
190
|
-
try {
|
|
191
|
-
const selected = modelSelection();
|
|
192
|
-
setup = (await modelsFor(selected, new ProjectCredentialStore()).checkAuth(selected.provider))
|
|
193
|
-
? "ready"
|
|
194
|
-
: "required";
|
|
195
|
-
}
|
|
196
|
-
catch {
|
|
197
|
-
/* Unconfigured is a valid inspection state. */
|
|
198
|
-
}
|
|
199
|
-
console.log(JSON.stringify({ agents: config.agents.map((agent) => agent.manifest), setup }, null, 2));
|
|
200
|
-
await stop();
|
|
201
|
-
return;
|
|
202
|
-
}
|
|
203
|
-
let current = await createRuntime(config);
|
|
204
|
-
const retained = [current];
|
|
205
|
-
shutdown.push(async () => {
|
|
206
|
-
await Promise.all(retained.map((runtime) => runtime.close()));
|
|
207
|
-
});
|
|
208
|
-
const fetch = async (request) => {
|
|
209
|
-
const path = new URL(request.url).pathname;
|
|
210
|
-
const collection = path.match(/^\/agents\/([^/]+)\/v1\/sessions$/);
|
|
211
|
-
if (request.method === "GET" && collection && retained.length > 1) {
|
|
212
|
-
const response = await current.app.fetch(request);
|
|
213
|
-
if (!response.ok)
|
|
214
|
-
return response;
|
|
215
|
-
const agentId = collection[1];
|
|
216
|
-
const document = (await response.json());
|
|
217
|
-
const summaries = new Map(document.sessions.map((item) => [item.session, item]));
|
|
218
|
-
// A journal may be shared across reloads, but only the owning runtime
|
|
219
|
-
// knows whether a retained session is still running or waiting.
|
|
220
|
-
for (const runtime of retained) {
|
|
221
|
-
if (runtime === current)
|
|
222
|
-
continue;
|
|
223
|
-
const previous = await runtime.app.fetch(request.clone());
|
|
224
|
-
if (!previous.ok)
|
|
225
|
-
continue;
|
|
226
|
-
const history = (await previous.json());
|
|
227
|
-
for (const summary of history.sessions) {
|
|
228
|
-
if (runtime.hasSession(agentId, summary.session) ||
|
|
229
|
-
!summaries.has(summary.session))
|
|
230
|
-
summaries.set(summary.session, summary);
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
return new Response(JSON.stringify({
|
|
234
|
-
sessions: [...summaries.values()].sort((a, b) => b.startedAt - a.startedAt),
|
|
235
|
-
}), { status: response.status, headers: response.headers });
|
|
236
|
-
}
|
|
237
|
-
const match = path.match(/^\/agents\/([^/]+)\/v1\/(?:sessions|ag-ui\/sessions|media)\/([^/]+)/);
|
|
238
|
-
let agentId = match?.[1];
|
|
239
|
-
let sessionId = match?.[2];
|
|
240
|
-
if (!match && request.method === "POST" && path.endsWith("/v1/ag-ui")) {
|
|
241
|
-
agentId = path.split("/")[2];
|
|
242
|
-
const body = await request
|
|
243
|
-
.clone()
|
|
244
|
-
.json()
|
|
245
|
-
.catch(() => ({}));
|
|
246
|
-
sessionId =
|
|
247
|
-
typeof body.threadId === "string" ? body.threadId : undefined;
|
|
248
|
-
}
|
|
249
|
-
const runtime = agentId && sessionId
|
|
250
|
-
? [...retained]
|
|
251
|
-
.reverse()
|
|
252
|
-
.find((item) => item.hasSession(agentId, sessionId)) ?? current
|
|
253
|
-
: current;
|
|
254
|
-
return runtime.app.fetch(request);
|
|
255
|
-
};
|
|
256
|
-
const hostPort = port(requestedPort ?? process.env.PORT, 4111);
|
|
257
|
-
const binding = bindAddress(flags.get("--host") ?? process.env.HOST, flags.get("--allowed-hosts") ??
|
|
258
|
-
process.env.ALLOWED_HOSTS, hostPort);
|
|
259
|
-
const guarded = async (request) => allowedHost(request.headers.get("host") ?? undefined, binding.hosts)
|
|
260
|
-
? fetch(request)
|
|
261
|
-
: Response.json({
|
|
262
|
-
error: "Host header is not allowed. Set --allowed-hosts or ALLOWED_HOSTS.",
|
|
263
|
-
}, { status: 421 });
|
|
264
|
-
const server = serve({
|
|
265
|
-
fetch: guarded,
|
|
266
|
-
hostname: binding.hostname,
|
|
267
|
-
port: hostPort,
|
|
268
|
-
});
|
|
269
|
-
shutdown.push(() => new Promise((resolve, reject) => server.close((error) => (error ? reject(error) : resolve()))));
|
|
270
|
-
await new Promise((resolve, reject) => {
|
|
271
|
-
server.once("listening", resolve);
|
|
272
|
-
server.once("error", reject);
|
|
273
|
-
});
|
|
274
|
-
const address = `http://${binding.reachable}:${hostPort}`;
|
|
275
|
-
console.log(`Agent runtime on ${address}`);
|
|
276
|
-
if (!binding.loopback)
|
|
277
|
-
console.log(binding.hosts.includes("*")
|
|
278
|
-
? "Serving every Host header; add TLS and access control at the network boundary."
|
|
279
|
-
: `Serving Host headers: ${binding.hosts.join(", ")}`);
|
|
280
|
-
if (command === "dev" && !flags.has("--no-studio")) {
|
|
281
|
-
const dashboard = await studio(address, !flags.has("--no-open"));
|
|
282
|
-
shutdown.push(() => dashboard.close());
|
|
283
|
-
console.log(`Studio on ${dashboard.address}`);
|
|
44
|
+
await configureProvider({ signal: controller.signal });
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
if (command !== "studio")
|
|
48
|
+
throw new Error(usage);
|
|
49
|
+
let agentUrl;
|
|
50
|
+
let port;
|
|
51
|
+
let open = true;
|
|
52
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
53
|
+
const arg = args[index];
|
|
54
|
+
if (arg === "--agent-url") {
|
|
55
|
+
if (agentUrl !== undefined)
|
|
56
|
+
throw new Error("--agent-url may only be supplied once.");
|
|
57
|
+
agentUrl = args[++index];
|
|
58
|
+
if (!agentUrl || agentUrl.startsWith("--"))
|
|
59
|
+
throw new Error("--agent-url requires a value.");
|
|
284
60
|
}
|
|
285
|
-
if (
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
if (stopping ||
|
|
290
|
-
!["add", "change", "unlink"].includes(event) ||
|
|
291
|
-
isAbsolute(path) ||
|
|
292
|
-
path.startsWith("..") ||
|
|
293
|
-
!(path === "nylorun.config.ts" || path.startsWith("agent/")))
|
|
294
|
-
return;
|
|
295
|
-
reload = reload.then(async () => {
|
|
296
|
-
try {
|
|
297
|
-
loader.vite.moduleGraph.invalidateAll();
|
|
298
|
-
const replacement = await createRuntime(await loader.load());
|
|
299
|
-
current = replacement;
|
|
300
|
-
retained.push(replacement);
|
|
301
|
-
console.log(`Reloaded ${path}`);
|
|
302
|
-
}
|
|
303
|
-
catch (error) {
|
|
304
|
-
console.error(`Reload failed; existing agents remain active: ${error instanceof Error ? error.message : String(error)}`);
|
|
305
|
-
}
|
|
306
|
-
});
|
|
307
|
-
});
|
|
61
|
+
else if (arg === "--port") {
|
|
62
|
+
if (port !== undefined)
|
|
63
|
+
throw new Error("--port may only be supplied once.");
|
|
64
|
+
port = parsePort(args[++index]);
|
|
308
65
|
}
|
|
66
|
+
else if (arg === "--no-open" && open)
|
|
67
|
+
open = false;
|
|
68
|
+
else
|
|
69
|
+
throw new Error(usage);
|
|
309
70
|
}
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
}
|
|
71
|
+
if (!agentUrl)
|
|
72
|
+
throw new Error("--agent-url is required.");
|
|
73
|
+
const dashboard = await startStudio(agentUrl, open, port);
|
|
74
|
+
console.log(`Studio on ${dashboard.address}`);
|
|
75
|
+
await new Promise((resolve, reject) => {
|
|
76
|
+
const close = () => void dashboard.close().then(resolve, reject);
|
|
77
|
+
process.once("SIGINT", close);
|
|
78
|
+
process.once("SIGTERM", close);
|
|
79
|
+
});
|
|
314
80
|
}
|
|
315
81
|
void main().catch((error) => {
|
|
316
82
|
console.error(error instanceof Error ? error.message : String(error));
|
|
317
|
-
process.exitCode =
|
|
318
|
-
error instanceof ConfigurationCancelled ? error.exitCode : 1;
|
|
83
|
+
process.exitCode = error instanceof ConfigurationCancelled ? error.exitCode : 1;
|
|
319
84
|
});
|
package/dist/config.d.ts
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type {
|
|
1
|
+
import type { RuntimeModelAdapter } from "./contracts.js";
|
|
2
|
+
import type { RuntimeDurability } from "./adapters/journal.js";
|
|
3
3
|
import type { RuntimeMedia } from "./adapters/media.js";
|
|
4
4
|
export interface RuntimeConfig {
|
|
5
|
-
readonly
|
|
6
|
-
readonly
|
|
5
|
+
readonly onModelCall?: RuntimeModelAdapter;
|
|
6
|
+
readonly observer?: {
|
|
7
|
+
(event: {
|
|
8
|
+
readonly type: string;
|
|
9
|
+
}): void | Promise<void>;
|
|
10
|
+
};
|
|
11
|
+
readonly durability?: RuntimeDurability;
|
|
7
12
|
readonly media?: RuntimeMedia;
|
|
8
|
-
readonly origins?: readonly string[];
|
|
9
13
|
}
|
|
10
|
-
export declare function defineRuntime(config: RuntimeConfig): RuntimeConfig;
|