@nylorun/runtime 0.6.0-beta → 0.8.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 (85) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/README.md +9 -5
  3. package/dist/adapters/sandbox/microsandbox.d.ts +2 -0
  4. package/dist/adapters/sandbox/microsandbox.js +238 -0
  5. package/dist/adapters/sandbox/virtual.d.ts +4 -0
  6. package/dist/adapters/sandbox/virtual.js +125 -0
  7. package/dist/configuration.d.ts +1 -1
  8. package/dist/configuration.js +1 -1
  9. package/dist/core/executors.d.ts +32 -0
  10. package/dist/core/executors.js +60 -0
  11. package/dist/core/main.js +7 -12
  12. package/dist/core/runtime.d.ts +41 -1
  13. package/dist/core/runtime.js +594 -53
  14. package/dist/core/store.d.ts +13 -0
  15. package/dist/core/store.js +95 -1
  16. package/dist/index.d.ts +0 -1
  17. package/dist/index.js +0 -1
  18. package/dist/mcp/connect.d.ts +31 -0
  19. package/dist/mcp/connect.js +190 -0
  20. package/dist/mcp/pool.d.ts +45 -0
  21. package/dist/mcp/pool.js +143 -0
  22. package/dist/mcp/snapshot.d.ts +25 -0
  23. package/dist/mcp/snapshot.js +10 -0
  24. package/dist/mcp/stdio.d.ts +15 -0
  25. package/dist/mcp/stdio.js +80 -0
  26. package/dist/model/catalog.d.ts +12 -0
  27. package/dist/model/catalog.js +29 -0
  28. package/dist/model/models.d.ts +3 -1
  29. package/dist/model/models.js +4 -2
  30. package/dist/model/pi-model.d.ts +9 -3
  31. package/dist/model/pi-model.js +47 -11
  32. package/dist/model/settings.d.ts +0 -2
  33. package/dist/model/settings.js +0 -47
  34. package/dist/node/index.d.ts +1 -1
  35. package/dist/node/index.js +1 -1
  36. package/dist/sandbox/manager.d.ts +61 -0
  37. package/dist/sandbox/manager.js +239 -0
  38. package/dist/sandbox/policy.d.ts +13 -0
  39. package/dist/sandbox/policy.js +67 -0
  40. package/dist/sandbox/select.d.ts +35 -0
  41. package/dist/sandbox/select.js +76 -0
  42. package/dist/sandbox/tools.d.ts +25 -0
  43. package/dist/sandbox/tools.js +165 -0
  44. package/dist/sandbox/types.d.ts +66 -0
  45. package/dist/vault/crypto.d.ts +12 -0
  46. package/dist/vault/crypto.js +80 -0
  47. package/dist/vault/error.d.ts +4 -0
  48. package/dist/vault/error.js +7 -0
  49. package/dist/vault/kek.d.ts +6 -0
  50. package/dist/vault/kek.js +26 -0
  51. package/dist/vault/service.d.ts +99 -0
  52. package/dist/vault/service.js +825 -0
  53. package/dist/vault/url.d.ts +3 -0
  54. package/dist/vault/url.js +27 -0
  55. package/dist/version.d.ts +1 -0
  56. package/dist/version.js +3 -0
  57. package/package.json +12 -6
  58. package/dist/config.d.ts +0 -20
  59. package/dist/model/defaults.d.ts +0 -17
  60. package/dist/model/defaults.js +0 -21
  61. package/dist/model/http-model.d.ts +0 -12
  62. package/dist/model/http-model.js +0 -299
  63. package/dist/node/local-sessions.d.ts +0 -5
  64. package/dist/node/local-sessions.js +0 -174
  65. package/dist/server/ag-ui.d.ts +0 -8
  66. package/dist/server/ag-ui.js +0 -76
  67. package/dist/server/delivery.d.ts +0 -24
  68. package/dist/server/delivery.js +0 -107
  69. package/dist/server/digests.d.ts +0 -13
  70. package/dist/server/digests.js +0 -39
  71. package/dist/server/host.d.ts +0 -70
  72. package/dist/server/host.js +0 -647
  73. package/dist/session/api.d.ts +0 -10
  74. package/dist/session/api.js +0 -15
  75. package/dist/session/default.d.ts +0 -5
  76. package/dist/session/default.js +0 -30
  77. package/dist/session/handle.d.ts +0 -27
  78. package/dist/session/handle.js +0 -199
  79. package/dist/session/index.d.ts +0 -2
  80. package/dist/session/index.js +0 -2
  81. package/dist/sessions/host.d.ts +0 -39
  82. package/dist/sessions/host.js +0 -359
  83. package/dist/sessions/store.d.ts +0 -41
  84. package/dist/sessions/store.js +0 -33
  85. /package/dist/{config.js → sandbox/types.js} +0 -0
@@ -0,0 +1,3 @@
1
+ export declare function normalizeVaultUrl(input: string, options?: {
2
+ httpsOnly?: boolean;
3
+ }): string;
@@ -0,0 +1,27 @@
1
+ import { VaultError } from "./error.js";
2
+ export function normalizeVaultUrl(input, options = {}) {
3
+ let url;
4
+ try {
5
+ url = new URL(input);
6
+ }
7
+ catch {
8
+ throw new VaultError(400, "Credential url is invalid");
9
+ }
10
+ if (url.username !== "" || url.password !== "")
11
+ throw new VaultError(400, "Credential url must not contain userinfo");
12
+ if (url.hash !== "")
13
+ throw new VaultError(400, "Credential url must not contain a fragment");
14
+ if (url.hostname.includes("*") || url.pathname.includes("*"))
15
+ throw new VaultError(400, "Credential url must not contain a wildcard");
16
+ if (options.httpsOnly) {
17
+ if (url.protocol !== "https:")
18
+ throw new VaultError(400, "Credential url must use https");
19
+ }
20
+ else if (url.protocol !== "https:" && url.protocol !== "http:") {
21
+ throw new VaultError(400, "Credential url must use http or https");
22
+ }
23
+ if ((url.protocol === "https:" && url.port === "443") ||
24
+ (url.protocol === "http:" && url.port === "80"))
25
+ url.port = "";
26
+ return `${url.origin}${url.pathname}${url.search}`;
27
+ }
@@ -0,0 +1 @@
1
+ export declare const RUNTIME_VERSION = "0.8.0-beta";
@@ -0,0 +1,3 @@
1
+ // Kept in step with package.json by runtime/scripts/check-package.mjs, which runs in npm run check.
2
+ // release:prepare also rewrites this when it bumps @nylorun/runtime.
3
+ export const RUNTIME_VERSION = "0.8.0-beta";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nylorun/runtime",
3
- "version": "0.6.0-beta",
3
+ "version": "0.8.0-beta",
4
4
  "description": "SQLite agent execution host and model providers.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -29,7 +29,8 @@
29
29
  "./configuration": {
30
30
  "types": "./dist/configuration.d.ts",
31
31
  "import": "./dist/configuration.js"
32
- }
32
+ },
33
+ "./package.json": "./package.json"
33
34
  },
34
35
  "files": [
35
36
  "dist",
@@ -51,16 +52,21 @@
51
52
  "start": "node dist/core/main.js"
52
53
  },
53
54
  "dependencies": {
55
+ "@modelcontextprotocol/sdk": "^1.30.0",
54
56
  "@earendil-works/pi-ai": "0.85.1",
55
57
  "@hono/node-server": "^2.1.1",
56
- "@nylorun/harness": "0.15.0-beta",
57
- "@nylorun/core": "0.1.1-beta"
58
+ "@nylorun/harness": "0.17.0-beta",
59
+ "@nylorun/core": "0.3.0-beta",
60
+ "just-bash": "3.4.2"
61
+ },
62
+ "optionalDependencies": {
63
+ "microsandbox": "0.7.2"
58
64
  },
59
65
  "devDependencies": {
60
66
  "@types/node": "^26.5.0",
61
67
  "@typescript/native": "npm:typescript@^7.0.2",
62
- "hono": "^4.13.7",
68
+ "hono": "^4.13.8",
63
69
  "typescript": "npm:@typescript/typescript6@^6.0.2",
64
- "vitest": "^5.0.0"
70
+ "vitest": "^5.0.1"
65
71
  }
66
72
  }
package/dist/config.d.ts DELETED
@@ -1,20 +0,0 @@
1
- import type { ModelAdapter } from "@nylorun/core/define";
2
- import type { SessionStore } from "./sessions/store.js";
3
- import type { RuntimeMedia } from "./media.js";
4
- import type { ModelEnvironment } from "./model/http-model.js";
5
- export interface RuntimeConfig {
6
- readonly onModelCall?: ModelAdapter;
7
- readonly createModel?: (options: import("./model/defaults.js").ModelFactoryOptions) => ModelAdapter;
8
- readonly observer?: (event: {
9
- readonly type: string;
10
- }) => void | Promise<void>;
11
- readonly sessions?: SessionStore;
12
- readonly media?: RuntimeMedia;
13
- readonly environment?: ModelEnvironment;
14
- readonly tokens?: boolean;
15
- readonly delivery?: {
16
- readonly previewBytes?: number;
17
- readonly eventBytes?: number;
18
- readonly eventCount?: number;
19
- };
20
- }
@@ -1,17 +0,0 @@
1
- import type { ModelAdapter } from "@nylorun/core/define";
2
- import { type ModelEnvironment } from "./http-model.js";
3
- export interface ModelPreview {
4
- readonly invocationId: string;
5
- readonly text: string;
6
- }
7
- export interface ModelFactoryOptions {
8
- readonly environment?: ModelEnvironment;
9
- readonly media?: import("../media.js").RuntimeMedia;
10
- readonly onPreview?: (preview: ModelPreview) => void;
11
- }
12
- /** Installed only by the Node launcher before application imports. */
13
- export declare function installNodeModelFactory(factory: (options: ModelFactoryOptions) => ModelAdapter): void;
14
- export declare function defaultModel(options: ModelFactoryOptions): ModelAdapter;
15
- export declare function processEnvironment(): ModelEnvironment;
16
- export declare function installRuntimeLifecycle(register: (close: () => Promise<void>) => void): void;
17
- export declare function registerRuntimeLifecycle(close: () => Promise<void>): void;
@@ -1,21 +0,0 @@
1
- import { httpModel } from "./http-model.js";
2
- let nodeFactory;
3
- /** Installed only by the Node launcher before application imports. */
4
- export function installNodeModelFactory(factory) {
5
- nodeFactory = factory;
6
- }
7
- export function defaultModel(options) {
8
- return options.environment === undefined && nodeFactory
9
- ? nodeFactory(options)
10
- : httpModel(options);
11
- }
12
- export function processEnvironment() {
13
- return typeof process === "undefined" ? {} : process.env;
14
- }
15
- let runtimeLifecycle;
16
- export function installRuntimeLifecycle(register) {
17
- runtimeLifecycle = register;
18
- }
19
- export function registerRuntimeLifecycle(close) {
20
- runtimeLifecycle?.(close);
21
- }
@@ -1,12 +0,0 @@
1
- import type { ModelAdapter } from "@nylorun/core/define";
2
- import type { ModelFactoryOptions } from "./defaults.js";
3
- export type ModelEnvironment = Readonly<Record<string, string | undefined>>;
4
- export interface HttpModelOptions extends ModelFactoryOptions {
5
- readonly provider?: "openai" | "custom" | "anthropic";
6
- readonly model?: string;
7
- readonly apiKey?: string;
8
- readonly baseUrl?: string;
9
- readonly fetch?: typeof fetch;
10
- }
11
- /** Fetch-only adapter for OpenAI-compatible and Anthropic HTTP endpoints. */
12
- export declare function httpModel(options?: HttpModelOptions): ModelAdapter;
@@ -1,299 +0,0 @@
1
- /** Fetch-only adapter for OpenAI-compatible and Anthropic HTTP endpoints. */
2
- export function httpModel(options = {}) {
3
- return async (call, context) => {
4
- const env = options.environment ??
5
- (typeof process === "undefined" ? {} : process.env);
6
- const provider = options.provider ?? env.MODEL_PROVIDER;
7
- const model = call.model?.id ?? options.model ?? env.MODEL;
8
- const baseUrl = options.baseUrl ?? env.MODEL_PROVIDER_BASE_URL;
9
- if (!provider || !model)
10
- throw new Error("Set both MODEL_PROVIDER and MODEL, or supply a model adapter.");
11
- if (!["openai", "custom", "anthropic"].includes(provider))
12
- throw new Error(`The portable HTTP adapter does not support '${provider}'. Supply onModelCall or use the Node piModel adapter.`);
13
- if (provider === "custom" && !baseUrl)
14
- throw new Error("MODEL_PROVIDER=custom requires MODEL_PROVIDER_BASE_URL.");
15
- const apiKey = options.apiKey ??
16
- env.MODEL_PROVIDER_API_KEY ??
17
- (provider === "anthropic" ? env.ANTHROPIC_API_KEY : env.OPENAI_API_KEY);
18
- if (!apiKey)
19
- throw new Error("Set MODEL_PROVIDER_API_KEY or the provider-native API key variable.");
20
- const text = (parts) => parts
21
- .flatMap((part) => (part.type === "text" ? [part.text] : []))
22
- .join("\n");
23
- if (call.prompt.some((item) => item.content.some((part) => part.type === "media")))
24
- throw new Error("Supply a media-aware model adapter for media inputs.");
25
- context.signal.throwIfAborted();
26
- let body;
27
- let url;
28
- let headers;
29
- const streaming = provider !== "anthropic" && options.onPreview !== undefined;
30
- if (provider === "anthropic") {
31
- const messages = call.prompt
32
- .filter((item) => item.kind !== "instructions")
33
- .map((item) => {
34
- if (item.kind === "tool-result")
35
- return {
36
- role: "user",
37
- content: [
38
- {
39
- type: "tool_result",
40
- tool_use_id: item.toolCallId,
41
- content: text(item.content),
42
- is_error: item.status !== "completed",
43
- },
44
- ],
45
- };
46
- return {
47
- role: item.role === "assistant" ? "assistant" : "user",
48
- content: item.content.flatMap((part) => part.type === "tool-call"
49
- ? [
50
- {
51
- type: "tool_use",
52
- id: part.id,
53
- name: part.name,
54
- input: part.args,
55
- },
56
- ]
57
- : part.type === "text"
58
- ? [{ type: "text", text: part.text }]
59
- : []),
60
- };
61
- });
62
- body = {
63
- model,
64
- messages,
65
- max_tokens: call.model?.controls?.maxOutputTokens ?? 4096,
66
- system: call.prompt
67
- .filter((item) => item.kind === "instructions")
68
- .map((item) => text(item.content))
69
- .join("\n"),
70
- ...(call.tools.length
71
- ? {
72
- tools: call.tools.map((tool) => ({
73
- name: tool.name,
74
- description: tool.description ?? "",
75
- input_schema: tool.inputSchema,
76
- })),
77
- }
78
- : {}),
79
- ...(call.outputSchema
80
- ? {
81
- output_config: {
82
- format: { type: "json_schema", schema: call.outputSchema },
83
- },
84
- }
85
- : {}),
86
- ...(call.model?.controls?.temperature === undefined
87
- ? {}
88
- : { temperature: call.model.controls.temperature }),
89
- };
90
- url = `${(baseUrl ?? "https://api.anthropic.com").replace(/\/$/, "")}/v1/messages`;
91
- headers = {
92
- "content-type": "application/json",
93
- "x-api-key": apiKey,
94
- "anthropic-version": "2023-06-01",
95
- };
96
- }
97
- else {
98
- const messages = call.prompt.map((item) => {
99
- if (item.kind === "tool-result")
100
- return {
101
- role: "tool",
102
- tool_call_id: item.toolCallId,
103
- content: text(item.content),
104
- };
105
- const calls = item.content
106
- .filter((part) => part.type === "tool-call")
107
- .map((part) => ({
108
- id: part.id,
109
- type: "function",
110
- function: { name: part.name, arguments: JSON.stringify(part.args) },
111
- }));
112
- return {
113
- role: item.kind === "instructions" ? "system" : item.role,
114
- content: text(item.content),
115
- ...(calls.length ? { tool_calls: calls } : {}),
116
- };
117
- });
118
- body = {
119
- model,
120
- messages,
121
- stream: streaming,
122
- ...(call.tools.length
123
- ? {
124
- tools: call.tools.map((tool) => ({
125
- type: "function",
126
- function: {
127
- name: tool.name,
128
- description: tool.description,
129
- parameters: tool.inputSchema,
130
- },
131
- })),
132
- }
133
- : {}),
134
- ...(call.outputSchema
135
- ? {
136
- response_format: {
137
- type: "json_schema",
138
- json_schema: {
139
- name: "agent_output",
140
- strict: true,
141
- schema: call.outputSchema,
142
- },
143
- },
144
- }
145
- : {}),
146
- ...(call.model?.controls?.temperature === undefined
147
- ? {}
148
- : { temperature: call.model.controls.temperature }),
149
- ...(call.model?.controls?.maxOutputTokens === undefined
150
- ? {}
151
- : { max_completion_tokens: call.model.controls.maxOutputTokens }),
152
- };
153
- url = `${(baseUrl ?? "https://api.openai.com/v1").replace(/\/$/, "")}/chat/completions`;
154
- headers = {
155
- "content-type": "application/json",
156
- authorization: `Bearer ${apiKey}`,
157
- };
158
- }
159
- if (!["https:", "http:"].includes(new URL(url).protocol))
160
- throw new Error("Model endpoint must use HTTP(S).");
161
- context.reportPreparedCall?.({
162
- adapter: "runtime.http",
163
- call: JSON.parse(JSON.stringify(body)),
164
- });
165
- const response = await (options.fetch ?? fetch)(url, {
166
- method: "POST",
167
- headers,
168
- body: JSON.stringify(body),
169
- signal: context.signal,
170
- });
171
- if (!response.ok)
172
- throw new Error(`Model provider returned HTTP ${response.status}. Check the selected model, credentials, and endpoint.`);
173
- let result;
174
- if (streaming)
175
- result = await readOpenAIStream(response, (value) => {
176
- try {
177
- void Promise.resolve(options.onPreview?.({
178
- invocationId: context.invocationId,
179
- text: value,
180
- })).catch(() => { });
181
- }
182
- catch {
183
- /* Preview delivery cannot affect generation. */
184
- }
185
- });
186
- else
187
- result = await response.json();
188
- context.signal.throwIfAborted();
189
- const output = [];
190
- if (provider === "anthropic") {
191
- for (const part of result.content ?? []) {
192
- if (part.type === "text")
193
- output.push(call.outputSchema &&
194
- !(result.content ?? []).some((item) => item.type === "tool_use")
195
- ? { type: "json", value: JSON.parse(part.text) }
196
- : { type: "text", text: part.text });
197
- if (part.type === "tool_use")
198
- output.push({
199
- type: "tool-call",
200
- id: part.id,
201
- name: part.name,
202
- args: part.input,
203
- });
204
- }
205
- }
206
- else {
207
- const message = result.choices?.[0]?.message;
208
- if (!message)
209
- throw new Error("Model provider returned no message.");
210
- if (message.content)
211
- output.push(call.outputSchema && !message.tool_calls?.length
212
- ? { type: "json", value: JSON.parse(message.content) }
213
- : { type: "text", text: message.content });
214
- for (const tool of message.tool_calls ?? [])
215
- output.push({
216
- type: "tool-call",
217
- id: tool.id,
218
- name: tool.function.name,
219
- args: JSON.parse(tool.function.arguments),
220
- });
221
- }
222
- return { output, evidence: { resolvedModel: result.model ?? model } };
223
- };
224
- }
225
- async function readOpenAIStream(response, preview) {
226
- if (!response.body)
227
- throw new Error("Model provider returned no stream.");
228
- const reader = response.body.getReader();
229
- const decoder = new TextDecoder();
230
- let buffer = "", content = "", complete = false;
231
- const calls = new Map();
232
- const line = (value) => {
233
- if (!value.startsWith("data:"))
234
- return;
235
- const data = value.slice(5).trim();
236
- if (data === "[DONE]") {
237
- complete = true;
238
- return;
239
- }
240
- if (!data)
241
- return;
242
- const chunk = JSON.parse(data);
243
- if (chunk.error)
244
- throw new Error("Model provider reported a streaming error.");
245
- const delta = chunk.choices?.[0]?.delta;
246
- if (typeof delta?.content === "string") {
247
- content += delta.content;
248
- preview(delta.content);
249
- }
250
- for (const tool of delta?.tool_calls ?? []) {
251
- const target = calls.get(tool.index) ?? {
252
- id: "",
253
- type: "function",
254
- function: { name: "", arguments: "" },
255
- };
256
- if (tool.id)
257
- target.id = tool.id;
258
- if (tool.function?.name)
259
- target.function.name += tool.function.name;
260
- if (tool.function?.arguments)
261
- target.function.arguments += tool.function.arguments;
262
- calls.set(tool.index, target);
263
- }
264
- };
265
- try {
266
- while (!complete) {
267
- const next = await reader.read();
268
- buffer += decoder.decode(next.value, { stream: !next.done });
269
- let end;
270
- while ((end = buffer.indexOf("\n")) >= 0) {
271
- line(buffer.slice(0, end).replace(/\r$/, ""));
272
- buffer = buffer.slice(end + 1);
273
- }
274
- if (next.done) {
275
- if (buffer)
276
- line(buffer);
277
- break;
278
- }
279
- }
280
- if (!complete)
281
- throw new Error("Model stream ended before completion.");
282
- return {
283
- choices: [
284
- {
285
- message: {
286
- content,
287
- tool_calls: [...calls]
288
- .sort(([a], [b]) => a - b)
289
- .map(([, value]) => value),
290
- },
291
- },
292
- ],
293
- };
294
- }
295
- finally {
296
- await reader.cancel().catch(() => { });
297
- reader.releaseLock();
298
- }
299
- }
@@ -1,5 +0,0 @@
1
- import { type ManagedSessionStore } from "../sessions/store.js";
2
- /** Single-owner local disk store. No lease stealing, replay, or shared-filesystem guarantee. */
3
- export declare function localSessions(options: {
4
- root: string;
5
- }): ManagedSessionStore;
@@ -1,174 +0,0 @@
1
- import { mkdir, open, readFile, readdir, rename, rm, realpath, } from "node:fs/promises";
2
- import { resolve, join } from "node:path";
3
- import { randomUUID } from "node:crypto";
4
- import { sessionSummary, } from "../sessions/store.js";
5
- /** Single-owner local disk store. No lease stealing, replay, or shared-filesystem guarantee. */
6
- export function localSessions(options) {
7
- let closed = false;
8
- let directory;
9
- const token = randomUUID();
10
- const acquired = (async () => {
11
- await mkdir(resolve(options.root), { recursive: true, mode: 0o700 });
12
- directory = await realpath(resolve(options.root));
13
- const path = join(directory, ".owner.lock");
14
- let handle;
15
- try {
16
- handle = await open(path, "wx", 0o600);
17
- }
18
- catch (cause) {
19
- throw new Error(`Session store ${directory} already has an owner. Stop the owner before opening it again. After a crash, confirm it has stopped and manually remove .owner.lock.`, { cause });
20
- }
21
- try {
22
- await handle.writeFile(JSON.stringify({ token, pid: process.pid }));
23
- await handle.sync();
24
- }
25
- finally {
26
- await handle.close();
27
- }
28
- })();
29
- void acquired.catch(() => { });
30
- let operations = Promise.resolve();
31
- const check = async () => {
32
- await acquired;
33
- if (closed)
34
- throw new Error("Session store is closed");
35
- const owner = JSON.parse(await readFile(join(directory, ".owner.lock"), "utf8"));
36
- if (owner.token !== token)
37
- throw new Error("Session store ownership was lost; refusing further writes");
38
- };
39
- const pathFor = (agentId, sessionId) => join(directory, safe(agentId), `${safe(sessionId)}.json`);
40
- const get = async (agentId, sessionId) => {
41
- await check();
42
- try {
43
- const value = JSON.parse(await readFile(pathFor(agentId, sessionId), "utf8"));
44
- if (value.version !== 1 ||
45
- value.id !== sessionId ||
46
- value.agentId !== agentId ||
47
- !Array.isArray(value.events))
48
- throw new Error("Invalid stored session document");
49
- return value;
50
- }
51
- catch (error) {
52
- if (!isMissing(error))
53
- throw error;
54
- try {
55
- const contents = await readFile(join(directory, safe(agentId), safe(sessionId), "events.jsonl"), "utf8");
56
- const events = contents
57
- .split("\n")
58
- .filter(Boolean)
59
- .map((line) => JSON.parse(line));
60
- return {
61
- version: 1,
62
- id: sessionId,
63
- agentId,
64
- status: "archived",
65
- startedAt: events[0] ? Date.parse(events[0].ts) : 0,
66
- updatedAt: 0,
67
- events,
68
- };
69
- }
70
- catch (legacyError) {
71
- if (isMissing(legacyError))
72
- return undefined;
73
- throw legacyError;
74
- }
75
- }
76
- };
77
- return {
78
- get,
79
- put(agentId, sessionId, session) {
80
- const contents = JSON.stringify(session);
81
- const operation = operations.then(async () => {
82
- await check();
83
- if (session.id !== sessionId || session.agentId !== agentId)
84
- throw new Error("Session identity mismatch");
85
- const target = pathFor(agentId, sessionId);
86
- await mkdir(join(directory, safe(agentId)), {
87
- recursive: true,
88
- mode: 0o700,
89
- });
90
- const temporary = `${target}.${randomUUID()}.tmp`;
91
- try {
92
- const file = await open(temporary, "wx", 0o600);
93
- try {
94
- await file.writeFile(contents);
95
- await file.sync();
96
- }
97
- finally {
98
- await file.close();
99
- }
100
- await check();
101
- await rename(temporary, target);
102
- const parent = await open(join(directory, safe(agentId)), "r");
103
- try {
104
- await parent.sync();
105
- }
106
- finally {
107
- await parent.close();
108
- }
109
- }
110
- finally {
111
- await rm(temporary, { force: true });
112
- }
113
- });
114
- operations = operation.catch(() => { });
115
- return operation;
116
- },
117
- async list(agentId) {
118
- await check();
119
- let names;
120
- try {
121
- names = await readdir(join(directory, safe(agentId)), {
122
- withFileTypes: true,
123
- });
124
- }
125
- catch (error) {
126
- if (isMissing(error))
127
- return [];
128
- throw error;
129
- }
130
- const sessions = await Promise.all(names
131
- .filter((item) => item.isDirectory() || item.name.endsWith(".json"))
132
- .map((item) => get(agentId, item.isDirectory() ? item.name : item.name.slice(0, -5))));
133
- return sessions
134
- .flatMap((session) => (session ? [sessionSummary(session)] : []))
135
- .sort((a, b) => b.startedAt - a.startedAt);
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
- },
154
- async close() {
155
- await operations;
156
- if (closed)
157
- return;
158
- await check();
159
- closed = true;
160
- await rm(join(directory, ".owner.lock"));
161
- },
162
- };
163
- }
164
- function safe(value) {
165
- if (value === "." || value === ".." || !/^[a-zA-Z0-9._-]+$/.test(value))
166
- throw new Error("Invalid session path identifier");
167
- return value;
168
- }
169
- function isMissing(error) {
170
- return (!!error &&
171
- typeof error === "object" &&
172
- "code" in error &&
173
- error.code === "ENOENT");
174
- }
@@ -1,8 +0,0 @@
1
- import type { CanonicalEvent } from "../sessions/store.js";
2
- /** Minimal truthful AG-UI projection from the same canonical events Studio displays. */
3
- export declare function agUiEvents(events: readonly CanonicalEvent[], threadId: string, runId: string): readonly Record<string, unknown>[];
4
- /**
5
- * Preserve headers installed by the Hono host (notably loopback CORS) when a
6
- * route returns a raw streaming Response instead of `context.json()`.
7
- */
8
- export declare function sse(events: readonly Record<string, unknown>[], inherited?: HeadersInit): Response;