@nylorun/runtime 0.5.0-beta → 0.6.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.
Files changed (45) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/README.md +29 -61
  3. package/dist/config.d.ts +1 -1
  4. package/dist/configuration.d.ts +3 -0
  5. package/dist/configuration.js +3 -0
  6. package/dist/contracts.d.ts +4 -2
  7. package/dist/core/main.js +40 -0
  8. package/dist/core/provider.d.ts +12 -0
  9. package/dist/core/provider.js +60 -0
  10. package/dist/core/runtime.d.ts +50 -0
  11. package/dist/core/runtime.js +877 -0
  12. package/dist/core/store.d.ts +16 -0
  13. package/dist/core/store.js +101 -0
  14. package/dist/index.d.ts +6 -11
  15. package/dist/index.js +4 -6
  16. package/dist/model/defaults.d.ts +1 -1
  17. package/dist/model/http-model.d.ts +1 -1
  18. package/dist/node/local-sessions.js +17 -0
  19. package/dist/server/host.d.ts +42 -4
  20. package/dist/server/host.js +69 -3
  21. package/dist/session/api.d.ts +10 -0
  22. package/dist/session/api.js +15 -0
  23. package/dist/session/default.d.ts +5 -0
  24. package/dist/session/default.js +30 -0
  25. package/dist/session/handle.d.ts +27 -0
  26. package/dist/session/handle.js +199 -0
  27. package/dist/session/index.d.ts +2 -0
  28. package/dist/session/index.js +2 -0
  29. package/dist/sessions/host.d.ts +11 -1
  30. package/dist/sessions/host.js +75 -7
  31. package/dist/sessions/store.d.ts +1 -0
  32. package/dist/sessions/store.js +3 -0
  33. package/package.json +16 -11
  34. package/dist/cli.d.ts +0 -2
  35. package/dist/cli.js +0 -100
  36. package/dist/dev-entry.js +0 -2
  37. package/dist/dev.d.ts +0 -2
  38. package/dist/dev.js +0 -126
  39. package/dist/environment.d.ts +0 -2
  40. package/dist/environment.js +0 -64
  41. package/dist/launcher.d.ts +0 -1
  42. package/dist/launcher.js +0 -33
  43. package/dist/model/configure.d.ts +0 -12
  44. package/dist/model/configure.js +0 -155
  45. /package/dist/{dev-entry.d.ts → core/main.d.ts} +0 -0
@@ -0,0 +1,16 @@
1
+ import { DatabaseSync } from "node:sqlite";
2
+ import type { LiveEvent } from "@nylorun/core/contracts";
3
+ export declare function canonical(value: unknown): string;
4
+ export declare class Store {
5
+ readonly db: DatabaseSync;
6
+ constructor(path: string);
7
+ tx<T>(fn: () => T): T;
8
+ get<T = any>(table: string, id: string): T | undefined;
9
+ all<T = any>(table: string): T[];
10
+ put(table: string, id: string, body: unknown): void;
11
+ event(sessionId: string, turnId: string | null, type: string, payload: unknown): LiveEvent;
12
+ history(sessionId: string, cursor?: string): {
13
+ items: LiveEvent[];
14
+ cursor: string | null;
15
+ };
16
+ }
@@ -0,0 +1,101 @@
1
+ import { DatabaseSync } from "node:sqlite";
2
+ import { randomUUID } from "node:crypto";
3
+ export function canonical(value) {
4
+ if (Array.isArray(value))
5
+ return "[" + value.map(canonical).join(",") + "]";
6
+ if (value && typeof value === "object")
7
+ return ("{" +
8
+ Object.keys(value)
9
+ .sort()
10
+ .filter((k) => value[k] !== undefined)
11
+ .map((k) => JSON.stringify(k) + ":" + canonical(value[k]))
12
+ .join(",") +
13
+ "}");
14
+ return JSON.stringify(value) ?? "null";
15
+ }
16
+ export class Store {
17
+ db;
18
+ constructor(path) {
19
+ this.db = new DatabaseSync(path);
20
+ this.db
21
+ .exec(`PRAGMA journal_mode=WAL; PRAGMA foreign_keys=ON; PRAGMA busy_timeout=5000;
22
+ CREATE TABLE IF NOT EXISTS definitions(id TEXT PRIMARY KEY, body TEXT NOT NULL);
23
+ CREATE TABLE IF NOT EXISTS sessions(id TEXT PRIMARY KEY, body TEXT NOT NULL);
24
+ CREATE TABLE IF NOT EXISTS commands(id TEXT PRIMARY KEY, body TEXT NOT NULL);
25
+ CREATE TABLE IF NOT EXISTS checkpoints(id TEXT PRIMARY KEY, body TEXT NOT NULL);
26
+ CREATE TABLE IF NOT EXISTS effects(id TEXT PRIMARY KEY, body TEXT NOT NULL);
27
+ CREATE TABLE IF NOT EXISTS actions(id TEXT PRIMARY KEY, body TEXT NOT NULL);
28
+ CREATE TABLE IF NOT EXISTS events(sequence INTEGER PRIMARY KEY AUTOINCREMENT, session_id TEXT NOT NULL, body TEXT NOT NULL);
29
+ CREATE INDEX IF NOT EXISTS events_session ON events(session_id, sequence);`);
30
+ }
31
+ tx(fn) {
32
+ this.db.exec("BEGIN IMMEDIATE");
33
+ try {
34
+ const result = fn();
35
+ this.db.exec("COMMIT");
36
+ return result;
37
+ }
38
+ catch (e) {
39
+ this.db.exec("ROLLBACK");
40
+ throw e;
41
+ }
42
+ }
43
+ get(table, id) {
44
+ const row = this.db.prepare(`SELECT body FROM ${table} WHERE id=?`).get(id);
45
+ return row ? JSON.parse(String(row.body)) : undefined;
46
+ }
47
+ all(table) {
48
+ return this.db
49
+ .prepare(`SELECT body FROM ${table}`)
50
+ .all()
51
+ .map((r) => JSON.parse(String(r.body)));
52
+ }
53
+ put(table, id, body) {
54
+ this.db
55
+ .prepare(`INSERT INTO ${table}(id,body) VALUES(?,?) ON CONFLICT(id) DO UPDATE SET body=excluded.body`)
56
+ .run(id, JSON.stringify(body));
57
+ }
58
+ event(sessionId, turnId, type, payload) {
59
+ const base = {
60
+ eventId: randomUUID(),
61
+ sessionId,
62
+ turnId,
63
+ createdAt: new Date().toISOString(),
64
+ type,
65
+ payload,
66
+ };
67
+ const result = this.db
68
+ .prepare("INSERT INTO events(session_id,body) VALUES(?,?)")
69
+ .run(sessionId, "{}");
70
+ const event = {
71
+ ...base,
72
+ cursor: Buffer.from(`${sessionId}:${result.lastInsertRowid}`).toString("base64url"),
73
+ };
74
+ this.db
75
+ .prepare("UPDATE events SET body=? WHERE sequence=?")
76
+ .run(JSON.stringify(event), result.lastInsertRowid);
77
+ return event;
78
+ }
79
+ history(sessionId, cursor) {
80
+ let sequence = 0;
81
+ if (cursor) {
82
+ const decoded = Buffer.from(cursor, "base64url").toString();
83
+ const prefix = `${sessionId}:`;
84
+ if (!decoded.startsWith(prefix) ||
85
+ !/^\d+$/.test(decoded.slice(prefix.length)))
86
+ throw new Error("Invalid cursor");
87
+ sequence = Number(decoded.slice(prefix.length));
88
+ }
89
+ const items = this.db
90
+ .prepare("SELECT body FROM events WHERE session_id=? AND sequence>? ORDER BY sequence")
91
+ .all(sessionId, sequence)
92
+ .map((r) => JSON.parse(String(r.body)));
93
+ const last = this.db
94
+ .prepare("SELECT body FROM events WHERE session_id=? ORDER BY sequence DESC LIMIT 1")
95
+ .get(sessionId);
96
+ return {
97
+ items,
98
+ cursor: last ? JSON.parse(String(last.body)).cursor : null,
99
+ };
100
+ }
101
+ }
package/dist/index.d.ts CHANGED
@@ -1,11 +1,6 @@
1
- export type { RuntimeConfig } from "./config.js";
2
- export type * from "./contracts.js";
3
- export { Runtime, serveAgents, type AgentRouterOptions, type RuntimeActor, type ServeAgentsOptions, } from "./server/host.js";
4
- export { SessionHost, type SubmitOptions } from "./sessions/host.js";
5
- export { memorySessions } from "./sessions/store.js";
6
- export type { SessionStore, ManagedSessionStore, StoredSession, SessionSummary, CanonicalEvent, } from "./sessions/store.js";
7
- export { IMAGE_MEDIA_TYPES, MAX_IMAGE_BYTES, decodeImageBase64, validateImageBytes, } from "./media.js";
8
- export type { RuntimeMedia, MediaAsset, MediaReference } from "./media.js";
9
- export { httpModel, type HttpModelOptions, type ModelEnvironment, } from "./model/http-model.js";
10
- export { agUiEvents } from "./server/ag-ui.js";
11
- export type { ModelFactoryOptions, ModelPreview } from "./model/defaults.js";
1
+ export { CoreRuntime, createRuntime, startRuntime, type RuntimeOptions } from "./core/runtime.js";
2
+ export { scriptedModel, gatewayModel, type ModelProvider } from "./core/provider.js";
3
+ export { httpModel, type HttpModelOptions, type ModelEnvironment } from "./model/http-model.js";
4
+ export type { RuntimeAgent } from "./contracts.js";
5
+ export { IMAGE_MEDIA_TYPES, MAX_IMAGE_BYTES, decodeImageBase64, validateImageBytes } from "./media.js";
6
+ export type { RuntimeModelAdapter } from "./contracts.js";
package/dist/index.js CHANGED
@@ -1,6 +1,4 @@
1
- export { Runtime, serveAgents, } from "./server/host.js";
2
- export { SessionHost } from "./sessions/host.js";
3
- export { memorySessions } from "./sessions/store.js";
4
- export { IMAGE_MEDIA_TYPES, MAX_IMAGE_BYTES, decodeImageBase64, validateImageBytes, } from "./media.js";
5
- export { httpModel, } from "./model/http-model.js";
6
- export { agUiEvents } from "./server/ag-ui.js";
1
+ export { CoreRuntime, createRuntime, startRuntime } from "./core/runtime.js";
2
+ export { scriptedModel, gatewayModel } from "./core/provider.js";
3
+ export { httpModel } from "./model/http-model.js";
4
+ export { IMAGE_MEDIA_TYPES, MAX_IMAGE_BYTES, decodeImageBase64, validateImageBytes } from "./media.js";
@@ -1,4 +1,4 @@
1
- import type { ModelAdapter } from "@nylorun/harness";
1
+ import type { ModelAdapter } from "@nylorun/core/define";
2
2
  import { type ModelEnvironment } from "./http-model.js";
3
3
  export interface ModelPreview {
4
4
  readonly invocationId: string;
@@ -1,4 +1,4 @@
1
- import type { ModelAdapter } from "@nylorun/harness";
1
+ import type { ModelAdapter } from "@nylorun/core/define";
2
2
  import type { ModelFactoryOptions } from "./defaults.js";
3
3
  export type ModelEnvironment = Readonly<Record<string, string | undefined>>;
4
4
  export interface HttpModelOptions extends ModelFactoryOptions {
@@ -134,6 +134,23 @@ export function localSessions(options) {
134
134
  .flatMap((session) => (session ? [sessionSummary(session)] : []))
135
135
  .sort((a, b) => b.startedAt - a.startedAt);
136
136
  },
137
+ async delete(agentId, sessionId) {
138
+ const operation = operations.then(async () => {
139
+ await check();
140
+ const target = pathFor(agentId, sessionId);
141
+ try {
142
+ await rm(target);
143
+ return true;
144
+ }
145
+ catch (error) {
146
+ if (isMissing(error))
147
+ return false;
148
+ throw error;
149
+ }
150
+ });
151
+ operations = operation.then(() => { }, () => { });
152
+ return operation;
153
+ },
137
154
  async close() {
138
155
  await operations;
139
156
  if (closed)
@@ -1,8 +1,10 @@
1
1
  import { Hono, type Context } from "hono";
2
2
  import type { JsonValue, RuntimeAgent } from "../contracts.js";
3
+ import type { BuiltAgent, ModelAdapter } from "@nylorun/core/define";
3
4
  import { SessionHost } from "../sessions/host.js";
4
5
  import type { RuntimeConfig } from "../config.js";
5
6
  import type { ModelEnvironment } from "../model/http-model.js";
7
+ import { type OpenSessionOptions, type SessionHandle } from "../session/handle.js";
6
8
  export type RuntimeActor = Readonly<{
7
9
  id: string;
8
10
  context?: Record<string, JsonValue>;
@@ -14,19 +16,55 @@ export type AgentRouterOptions = Readonly<{
14
16
  getEnvironment?: (context: Context) => ModelEnvironment | Promise<ModelEnvironment>;
15
17
  getRequestMetadata?: (context: Context) => Record<string, JsonValue> | undefined | Promise<Record<string, JsonValue> | undefined>;
16
18
  }>;
17
- export type ServeAgentsOptions = AgentRouterOptions & {
19
+ /** 1.0 compatibility: requires an explicit Runtime and returns a Hono app. */
20
+ export type ServeAgentsCompatOptions = AgentRouterOptions & {
18
21
  readonly agents: readonly RuntimeAgent[];
19
22
  readonly runtime: Runtime;
20
23
  };
24
+ /** DX v5.6 local: optional Runtime; returns `{ fetch }`. */
25
+ export type ServeAgentsFetchOptions = AgentRouterOptions & {
26
+ readonly agents?: readonly RuntimeAgent[];
27
+ readonly runtime?: Runtime;
28
+ /** Alias for `getInfo` — keep the name `info` (never `user`). */
29
+ readonly info?: AgentRouterOptions["getInfo"];
30
+ readonly on?: {
31
+ readonly session?: {
32
+ readonly created?: (session: {
33
+ readonly id: string;
34
+ readonly agentId: string;
35
+ }) => void;
36
+ readonly idle?: (session: {
37
+ readonly id: string;
38
+ readonly agentId: string;
39
+ }) => void;
40
+ readonly failed?: (session: {
41
+ readonly id: string;
42
+ readonly agentId: string;
43
+ }) => void;
44
+ };
45
+ };
46
+ };
47
+ export type ServeAgentsOptions = ServeAgentsCompatOptions;
48
+ export type ServeAgentsFetch = {
49
+ readonly fetch: (request: Request) => Response | Promise<Response>;
50
+ };
21
51
  declare const kServe: unique symbol;
22
52
  export declare class Runtime {
23
- private readonly config;
53
+ readonly config: RuntimeConfig;
24
54
  readonly host: SessionHost;
25
55
  private served;
26
56
  private closing?;
27
57
  constructor(config?: RuntimeConfig);
28
58
  close: () => Promise<void>;
29
- [kServe](options: Omit<ServeAgentsOptions, "runtime">): Hono<any>;
59
+ resolveModel(onPreview?: (preview: import("../model/defaults.js").ModelPreview) => void): ModelAdapter;
60
+ openSession(agent: BuiltAgent<any, any>, options?: OpenSessionOptions): SessionHandle;
61
+ listSessions(agentId: string): Promise<readonly import("../sessions/store.js").SessionSummary[]>;
62
+ getSession(agentId: string, sessionId: string): Promise<import("../sessions/store.js").StoredSession | undefined>;
63
+ deleteSession(agentId: string, sessionId: string): Promise<boolean>;
64
+ [kServe](options: Omit<ServeAgentsCompatOptions, "runtime"> & {
65
+ readonly agents: readonly RuntimeAgent[];
66
+ }): Hono<any>;
30
67
  }
31
- export declare function serveAgents(options: ServeAgentsOptions): Hono<any>;
68
+ export declare function serveAgents(options: ServeAgentsCompatOptions): Hono<any>;
69
+ export declare function serveAgents(options: ServeAgentsFetchOptions): ServeAgentsFetch;
32
70
  export {};
@@ -8,6 +8,8 @@ import { memorySessions } from "../sessions/store.js";
8
8
  import { SessionHost } from "../sessions/host.js";
9
9
  import { IMAGE_MEDIA_TYPES, MAX_IMAGE_BYTES, decodeImageBase64, } from "../media.js";
10
10
  import { defaultModel, processEnvironment, registerRuntimeLifecycle, } from "../model/defaults.js";
11
+ import { createSessionHandle, } from "../session/handle.js";
12
+ import { installDefaultRuntimeFactory } from "../session/default.js";
11
13
  const randomUUID = () => crypto.randomUUID();
12
14
  const kServe = Symbol("serve");
13
15
  export class Runtime {
@@ -21,6 +23,26 @@ export class Runtime {
21
23
  registerRuntimeLifecycle(this.close);
22
24
  }
23
25
  close = () => (this.closing ??= this.host.close());
26
+ resolveModel(onPreview) {
27
+ return (this.config.onModelCall ??
28
+ (this.config.createModel ?? defaultModel)({
29
+ environment: this.config.environment ?? processEnvironment(),
30
+ onPreview,
31
+ media: this.config.media,
32
+ }));
33
+ }
34
+ openSession(agent, options) {
35
+ return createSessionHandle(this, agent, options);
36
+ }
37
+ async listSessions(agentId) {
38
+ return this.host.list(agentId);
39
+ }
40
+ async getSession(agentId, sessionId) {
41
+ return this.host.read(agentId, sessionId);
42
+ }
43
+ async deleteSession(agentId, sessionId) {
44
+ return this.host.delete(agentId, sessionId);
45
+ }
24
46
  [kServe](options) {
25
47
  if (this.served)
26
48
  throw new Error("A Runtime may only be served once.");
@@ -141,8 +163,20 @@ export class Runtime {
141
163
  id: document.id,
142
164
  state: document.status,
143
165
  pending_interaction: document.state?.plan?.calls.find((call) => call.status === "interaction")?.interaction,
166
+ pending_waits: document.state?.plan?.calls
167
+ .filter((call) => (call.status === "interaction" || call.status === "deferred") &&
168
+ call.wait)
169
+ .map((call) => call.wait),
144
170
  });
145
171
  });
172
+ app.delete("/:agentId/v1/sessions/:session", async (context) => {
173
+ const agent = agentFor(context);
174
+ const sessionId = context.req.param("session");
175
+ const deleted = await this.host.delete(agent.id, sessionId);
176
+ if (!deleted)
177
+ return context.json({ error: "unknown session" }, 404);
178
+ return context.json({ session_id: sessionId, deleted: true });
179
+ });
146
180
  app.post("/:agentId/v1/sessions/:session", async (context) => {
147
181
  const agent = agentFor(context), sessionId = context.req.param("session");
148
182
  const payload = await context.req.json();
@@ -161,6 +195,20 @@ export class Runtime {
161
195
  }
162
196
  if (payload.settlement)
163
197
  input = { kind: "settle", ...payload.settlement };
198
+ else if (payload.action === "wait-resolve" ||
199
+ payload.waitResolve ||
200
+ (interaction?.kind === "wait-resolve" &&
201
+ typeof interaction.waitId === "string")) {
202
+ const wait = payload.waitResolve ??
203
+ (interaction?.kind === "wait-resolve" ? interaction : payload);
204
+ if (typeof wait.waitId !== "string")
205
+ return context.json({ error: "wait-resolve requires waitId" }, 400);
206
+ input = {
207
+ kind: "wait-resolve",
208
+ waitId: wait.waitId,
209
+ ...("value" in wait ? { value: wait.value } : {}),
210
+ };
211
+ }
164
212
  else if (interaction?.kind === "approval" &&
165
213
  typeof interaction.id === "string" &&
166
214
  typeof interaction.approved === "boolean")
@@ -178,7 +226,9 @@ export class Runtime {
178
226
  value: interaction.value,
179
227
  };
180
228
  else
181
- return context.json({ error: "expected a correlated interaction or settlement" }, 400);
229
+ return context.json({
230
+ error: "expected a correlated interaction, settlement, or wait-resolve",
231
+ }, 400);
182
232
  const document = await this.host.read(agent.id, sessionId);
183
233
  if (!document || document.status !== "waiting")
184
234
  return context.json({ error: "interaction is no longer pending" }, 409);
@@ -280,9 +330,25 @@ export class Runtime {
280
330
  }
281
331
  }
282
332
  export function serveAgents(options) {
283
- const { runtime, ...rest } = options;
284
- return runtime[kServe](rest);
333
+ if ("runtime" in options && options.runtime) {
334
+ const { runtime, ...rest } = options;
335
+ return runtime[kServe]({
336
+ ...rest,
337
+ agents: options.agents ?? [],
338
+ });
339
+ }
340
+ const runtime = new Runtime({});
341
+ const getInfo = options.getInfo ?? ("info" in options ? options.info : undefined);
342
+ const app = runtime[kServe]({
343
+ ...options,
344
+ agents: options.agents ?? [],
345
+ ...(getInfo ? { getInfo } : {}),
346
+ });
347
+ return {
348
+ fetch: (request) => app.fetch(request),
349
+ };
285
350
  }
351
+ installDefaultRuntimeFactory(() => new Runtime({}));
286
352
  /** Workers-style provider bindings on context.env; ignore Hono Node stream slots. */
287
353
  function bindingsEnvironment(context) {
288
354
  const env = context.env;
@@ -0,0 +1,10 @@
1
+ import type { BuiltAgent } from "@nylorun/core/define";
2
+ import { setDefaultRuntime } from "./default.js";
3
+ import type { OpenSessionOptions, SessionHandle } from "./handle.js";
4
+ export { setDefaultRuntime };
5
+ export type { OpenSessionOptions, SessionHandle };
6
+ /** Open-or-create a local session with an agent (bound to the default Runtime). */
7
+ export declare function openSession(agent: BuiltAgent<any, any>, options?: OpenSessionOptions): SessionHandle;
8
+ export declare function listSessions(agentId: string): Promise<readonly import("../sessions/store.js").SessionSummary[]>;
9
+ export declare function getSession(agentId: string, sessionId: string): Promise<import("../sessions/store.js").StoredSession | undefined>;
10
+ export declare function deleteSession(agentId: string, sessionId: string): Promise<boolean>;
@@ -0,0 +1,15 @@
1
+ import { getDefaultRuntime, setDefaultRuntime, } from "./default.js";
2
+ export { setDefaultRuntime };
3
+ /** Open-or-create a local session with an agent (bound to the default Runtime). */
4
+ export function openSession(agent, options) {
5
+ return getDefaultRuntime().openSession(agent, options);
6
+ }
7
+ export async function listSessions(agentId) {
8
+ return getDefaultRuntime().listSessions(agentId);
9
+ }
10
+ export async function getSession(agentId, sessionId) {
11
+ return getDefaultRuntime().getSession(agentId, sessionId);
12
+ }
13
+ export async function deleteSession(agentId, sessionId) {
14
+ return getDefaultRuntime().deleteSession(agentId, sessionId);
15
+ }
@@ -0,0 +1,5 @@
1
+ import type { Runtime } from "../server/host.js";
2
+ /** Wired from `server/host.ts` after `Runtime` is defined (avoids init cycles). */
3
+ export declare function installDefaultRuntimeFactory(factory: () => Runtime): void;
4
+ export declare function setDefaultRuntime(runtime: Runtime | undefined): void;
5
+ export declare function getDefaultRuntime(): Runtime;
@@ -0,0 +1,30 @@
1
+ let defaultRuntime;
2
+ let bannerShown = false;
3
+ let createRuntime;
4
+ /** Wired from `server/host.ts` after `Runtime` is defined (avoids init cycles). */
5
+ export function installDefaultRuntimeFactory(factory) {
6
+ createRuntime = factory;
7
+ }
8
+ export function setDefaultRuntime(runtime) {
9
+ defaultRuntime = runtime;
10
+ }
11
+ export function getDefaultRuntime() {
12
+ if (defaultRuntime)
13
+ return defaultRuntime;
14
+ if (!createRuntime)
15
+ throw new Error("Default Runtime factory is not installed");
16
+ defaultRuntime = createRuntime();
17
+ maybeBanner();
18
+ return defaultRuntime;
19
+ }
20
+ function maybeBanner() {
21
+ if (bannerShown || typeof process === "undefined")
22
+ return;
23
+ if (process.env.NYLORUN_QUIET === "1")
24
+ return;
25
+ bannerShown = true;
26
+ const model = process.env.MODEL_PROVIDER && process.env.MODEL
27
+ ? `${process.env.MODEL_PROVIDER}/${process.env.MODEL}`
28
+ : "unset";
29
+ console.info(`nylorun ▸ local runtime · loop runs in this process · sessions in memory · model ${model} (your key)`);
30
+ }
@@ -0,0 +1,27 @@
1
+ import type { BuiltAgent, JsonObject, JsonValue, ModelAdapter } from "@nylorun/core/define";
2
+ import type { Session } from "@nylorun/harness";
3
+ import type { RuntimeConfig } from "../config.js";
4
+ import type { SessionHost } from "../sessions/host.js";
5
+ export type OpenSessionOptions = {
6
+ readonly id?: string;
7
+ /** Trusted host bag — keep the name `info` (never `user`). */
8
+ readonly info?: unknown;
9
+ /** Initial durable session memory (`ExecutionState.state`). */
10
+ readonly state?: JsonObject;
11
+ };
12
+ export type SessionHandle<Output = unknown> = Session<{
13
+ readonly id: string;
14
+ }, Output> & {
15
+ readonly agentId: string;
16
+ };
17
+ export type SessionRuntime = {
18
+ readonly host: SessionHost;
19
+ readonly config: RuntimeConfig;
20
+ resolveModel(): ModelAdapter;
21
+ };
22
+ export declare function createSessionHandle(runtime: SessionRuntime, agent: BuiltAgent<any, any>, options?: OpenSessionOptions): SessionHandle;
23
+ /** Resolve a wait by harness waitId (H6). */
24
+ export declare function resolveWait(runtime: SessionRuntime, agent: BuiltAgent<any, any>, sessionId: string, waitId: string, value?: JsonValue, options?: {
25
+ readonly info?: unknown;
26
+ readonly onModelCall?: ModelAdapter;
27
+ }): Promise<void>;
@@ -0,0 +1,199 @@
1
+ export function createSessionHandle(runtime, agent, options = {}) {
2
+ const id = options.id ?? crypto.randomUUID();
3
+ let seedState = options.state;
4
+ const info = options.info;
5
+ const submitOptions = () => {
6
+ const sessionState = seedState;
7
+ seedState = undefined;
8
+ return {
9
+ info: info === undefined
10
+ ? { sessionId: id }
11
+ : { ...info, sessionId: id },
12
+ onModelCall: runtime.resolveModel(),
13
+ ...(sessionState ? { sessionState } : {}),
14
+ };
15
+ };
16
+ const handle = {
17
+ id,
18
+ agentId: agent.id,
19
+ async input(message) {
20
+ try {
21
+ const result = await runtime.host.submit(agent, id, message, submitOptions());
22
+ const cursor = String((await runtime.host.read(agent.id, id))?.events.at(-1)?.seq ?? 0);
23
+ return {
24
+ status: "accepted",
25
+ turnId: result.state.plan?.turnId ?? result.state.executionId,
26
+ cursor,
27
+ };
28
+ }
29
+ catch (error) {
30
+ return {
31
+ status: "rejected",
32
+ error: {
33
+ code: error && typeof error === "object" && "code" in error
34
+ ? String(error.code)
35
+ : "runtime.input-rejected",
36
+ message: error instanceof Error ? error.message : String(error),
37
+ },
38
+ };
39
+ }
40
+ },
41
+ async *stream(opts) {
42
+ let after = opts?.after ? Number(opts.after) : 0;
43
+ if (!Number.isFinite(after) || after < 0)
44
+ after = 0;
45
+ const queue = [];
46
+ let notify;
47
+ const wake = () => {
48
+ notify?.();
49
+ notify = undefined;
50
+ };
51
+ const unsubscribe = runtime.host.subscribe(agent.id, id, (event) => {
52
+ queue.push(event);
53
+ wake();
54
+ });
55
+ try {
56
+ const existing = await runtime.host.read(agent.id, id);
57
+ for (const event of existing?.events ?? [])
58
+ if (event.seq > after)
59
+ queue.push(event);
60
+ for (;;) {
61
+ while (queue.length) {
62
+ const event = queue.shift();
63
+ if (event.seq <= after)
64
+ continue;
65
+ after = event.seq;
66
+ const mapped = mapEvent(event, id);
67
+ if (mapped)
68
+ yield mapped;
69
+ }
70
+ await new Promise((resolve) => {
71
+ notify = resolve;
72
+ });
73
+ }
74
+ }
75
+ finally {
76
+ unsubscribe();
77
+ }
78
+ },
79
+ async history() {
80
+ const document = await runtime.host.read(agent.id, id);
81
+ return (document?.events ?? [])
82
+ .filter((event) => ["final", "interaction.required", "error", "cancelled"].includes(event.type))
83
+ .map((event) => event.payload);
84
+ },
85
+ async approve(interactionId, approved) {
86
+ await runtime.host.submit(agent, id, { kind: "approve", interactionId, approved }, submitOptions());
87
+ },
88
+ async respond(interactionId, value) {
89
+ await runtime.host.submit(agent, id, { kind: "respond", interactionId, value }, submitOptions());
90
+ },
91
+ async cancel() {
92
+ await runtime.host.cancel(agent, id);
93
+ },
94
+ };
95
+ return handle;
96
+ }
97
+ /** Resolve a wait by harness waitId (H6). */
98
+ export async function resolveWait(runtime, agent, sessionId, waitId, value, options) {
99
+ const input = {
100
+ kind: "wait-resolve",
101
+ waitId,
102
+ ...(value === undefined ? {} : { value }),
103
+ };
104
+ await runtime.host.submit(agent, sessionId, input, {
105
+ info: options?.info,
106
+ onModelCall: options?.onModelCall ?? runtime.resolveModel(),
107
+ });
108
+ }
109
+ function mapEvent(event, sessionId) {
110
+ const turnId = typeof event.payload.turnId === "string"
111
+ ? event.payload.turnId
112
+ : typeof event.payload.executionId === "string"
113
+ ? event.payload.executionId
114
+ : sessionId;
115
+ const cursor = String(event.seq);
116
+ const at = event.ts;
117
+ switch (event.type) {
118
+ case "session.run.started":
119
+ return { type: "turn.started", cursor, at, sessionId, turnId };
120
+ case "tool.started":
121
+ return {
122
+ type: "tool.started",
123
+ tool: String(event.payload.toolName ?? "tool"),
124
+ sessionId,
125
+ turnId,
126
+ };
127
+ case "tool.progress":
128
+ return {
129
+ type: "tool.progress",
130
+ tool: String(event.payload.toolName ?? "tool"),
131
+ sessionId,
132
+ turnId,
133
+ };
134
+ case "tool.completed":
135
+ return {
136
+ type: event.payload.outcome === "failed" ? "tool.failed" : "tool.completed",
137
+ tool: String(event.payload.toolName ?? "tool"),
138
+ sessionId,
139
+ turnId,
140
+ };
141
+ case "interaction.required": {
142
+ const interaction = event.payload.interaction;
143
+ return {
144
+ type: "interaction.required",
145
+ id: String(interaction?.id ?? ""),
146
+ prompt: String(interaction?.prompt ?? ""),
147
+ ...(interaction?.metadata ? { options: interaction.metadata } : {}),
148
+ sessionId,
149
+ turnId,
150
+ };
151
+ }
152
+ case "final":
153
+ return {
154
+ type: "turn.settled",
155
+ result: {
156
+ status: "completed",
157
+ output: event.payload.output,
158
+ },
159
+ sessionId,
160
+ turnId,
161
+ };
162
+ case "error":
163
+ return {
164
+ type: "turn.settled",
165
+ result: {
166
+ status: "failed",
167
+ error: {
168
+ code: String(event.payload.code ?? "execution.failed"),
169
+ message: String(event.payload.message ?? "failed"),
170
+ },
171
+ },
172
+ sessionId,
173
+ turnId,
174
+ };
175
+ case "cancelled":
176
+ return {
177
+ type: "turn.settled",
178
+ result: { status: "cancelled" },
179
+ sessionId,
180
+ turnId,
181
+ };
182
+ default:
183
+ if (event.type === "model.requested" || event.type === "model.completed")
184
+ return {
185
+ type: "status",
186
+ status: "thinking",
187
+ sessionId,
188
+ turnId,
189
+ };
190
+ if (event.type.startsWith("tool."))
191
+ return {
192
+ type: "status",
193
+ status: "calling_tool",
194
+ sessionId,
195
+ turnId,
196
+ };
197
+ return undefined;
198
+ }
199
+ }