@nylorun/runtime 0.29.0-beta → 0.31.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 +36 -0
- package/dist/api/components.d.ts +240 -2
- package/dist/api/components.js +3 -1
- package/dist/api/http/routes/agent-manifests.d.ts +2 -1
- package/dist/api/http/routes/agent-manifests.js +62 -10
- package/dist/api/openapi.js +2 -0
- package/dist/gates/egress-credentials.js +2 -13
- package/dist/gates/handler.js +2 -1
- package/dist/gates/in-process.d.ts +9 -0
- package/dist/gates/in-process.js +21 -0
- package/dist/host/http.js +3 -1
- package/dist/host/main.js +2 -0
- package/dist/host/stack-config.d.ts +10 -0
- package/dist/host/stack-config.js +36 -0
- package/dist/management-openapi.json +361 -5
- package/dist/mcp/connect.d.ts +34 -5
- package/dist/mcp/connect.js +75 -14
- package/dist/mcp/gateway-outcomes.d.ts +14 -0
- package/dist/mcp/gateway-outcomes.js +46 -0
- package/dist/mcp/pool.js +5 -0
- package/dist/model/call-headers.d.ts +15 -0
- package/dist/model/call-headers.js +9 -0
- package/dist/model/models.d.ts +7 -0
- package/dist/model/models.js +3 -0
- package/dist/model/pi-model.d.ts +6 -0
- package/dist/model/pi-model.js +29 -3
- package/dist/openapi.json +654 -30
- package/dist/store/postgres/drizzle/0038_t6_agent_manifest_source.sql +1 -0
- package/dist/store/postgres/drizzle/meta/_journal.json +7 -0
- package/dist/store/postgres/schema.d.ts +21 -2
- package/dist/store/postgres/schema.js +7 -1
- package/dist/store/postgres/store.js +1 -0
- package/dist/store/types.d.ts +14 -0
- package/dist/tenant/agent-manifests.d.ts +18 -25
- package/dist/tenant/agent-manifests.js +136 -4
- package/dist/tenant/agents.js +7 -3
- package/dist/tenant/transcript.d.ts +8 -0
- package/dist/tenant/transcript.js +2 -0
- package/dist/vault/headers.d.ts +8 -0
- package/dist/vault/headers.js +25 -0
- package/dist/vault/host-model.js +8 -5
- package/dist/vault/service.d.ts +12 -5
- package/dist/vault/service.js +43 -6
- package/dist/vault/sources.js +22 -1
- package/dist/vault/workload-token.d.ts +16 -0
- package/dist/vault/workload-token.js +58 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +3 -3
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ALTER TABLE "nylorun"."agent_manifests" ADD COLUMN "source" json;
|
|
@@ -267,6 +267,13 @@
|
|
|
267
267
|
"when": 1792238141315,
|
|
268
268
|
"tag": "0037_model_usage_cost_split",
|
|
269
269
|
"breakpoints": true
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
"idx": 38,
|
|
273
|
+
"version": "7",
|
|
274
|
+
"when": 1792484344984,
|
|
275
|
+
"tag": "0038_t6_agent_manifest_source",
|
|
276
|
+
"breakpoints": true
|
|
270
277
|
}
|
|
271
278
|
]
|
|
272
279
|
}
|
|
@@ -2569,6 +2569,25 @@ export declare const agentManifests: import("drizzle-orm/pg-core").PgTableWithCo
|
|
|
2569
2569
|
}, {}, {
|
|
2570
2570
|
pgColumnBuilderBrand: 'PgCustomColumnBuilderBrand';
|
|
2571
2571
|
}>;
|
|
2572
|
+
source: import("drizzle-orm/pg-core").PgColumn<{
|
|
2573
|
+
name: "source";
|
|
2574
|
+
tableName: "agent_manifests";
|
|
2575
|
+
dataType: "custom";
|
|
2576
|
+
columnType: "PgCustomColumn";
|
|
2577
|
+
data: unknown;
|
|
2578
|
+
driverParam: unknown;
|
|
2579
|
+
notNull: false;
|
|
2580
|
+
hasDefault: false;
|
|
2581
|
+
isPrimaryKey: false;
|
|
2582
|
+
isAutoincrement: false;
|
|
2583
|
+
hasRuntimeDefault: false;
|
|
2584
|
+
enumValues: undefined;
|
|
2585
|
+
baseColumn: never;
|
|
2586
|
+
identity: undefined;
|
|
2587
|
+
generated: undefined;
|
|
2588
|
+
}, {}, {
|
|
2589
|
+
pgColumnBuilderBrand: 'PgCustomColumnBuilderBrand';
|
|
2590
|
+
}>;
|
|
2572
2591
|
};
|
|
2573
2592
|
dialect: 'pg';
|
|
2574
2593
|
}>;
|
|
@@ -2737,14 +2756,14 @@ export declare const vaultCredentials: import("drizzle-orm/pg-core").PgTableWith
|
|
|
2737
2756
|
tableName: "vault_credentials";
|
|
2738
2757
|
dataType: "string";
|
|
2739
2758
|
columnType: "PgText";
|
|
2740
|
-
data: "bearer" | "environment_secret" | "environment_variable" | "headers" | "model";
|
|
2759
|
+
data: "bearer" | "environment_secret" | "environment_variable" | "headers" | "model" | "workload";
|
|
2741
2760
|
driverParam: string;
|
|
2742
2761
|
notNull: true;
|
|
2743
2762
|
hasDefault: false;
|
|
2744
2763
|
isPrimaryKey: false;
|
|
2745
2764
|
isAutoincrement: false;
|
|
2746
2765
|
hasRuntimeDefault: false;
|
|
2747
|
-
enumValues: ["bearer", "headers", "environment_secret", "environment_variable", "model"];
|
|
2766
|
+
enumValues: ["bearer", "headers", "workload", "environment_secret", "environment_variable", "model"];
|
|
2748
2767
|
baseColumn: never;
|
|
2749
2768
|
identity: undefined;
|
|
2750
2769
|
generated: undefined;
|
|
@@ -326,6 +326,12 @@ export const agentManifests = nylorun.table("agent_manifests", {
|
|
|
326
326
|
/** `{ actor }`: the subject the save acted for, else `application`. */
|
|
327
327
|
savedBy: jsonText().notNull(),
|
|
328
328
|
createdAt: textC().notNull(),
|
|
329
|
+
/**
|
|
330
|
+
* The agent folder the version was compiled from (track T6): `{ compiler, files, bytes,
|
|
331
|
+
* entries }`, each entry `{ path, size, sha256 }` of a definition file. Null for a version
|
|
332
|
+
* saved as JSON.
|
|
333
|
+
*/
|
|
334
|
+
source: jsonText(),
|
|
329
335
|
}, (t) => [primaryKey({ name: "agent_manifests_pkey", columns: [t.name, t.version] })]);
|
|
330
336
|
/**
|
|
331
337
|
* A vault (D56): the host model's (`id = 'host'`), or one Environment's, created and deleted with
|
|
@@ -353,7 +359,7 @@ export const vaultCredentials = nylorun.table("vault_credentials", {
|
|
|
353
359
|
id: textC().primaryKey(),
|
|
354
360
|
vaultId: text().notNull(),
|
|
355
361
|
name: text().notNull(),
|
|
356
|
-
type: text({ enum: ["bearer", "headers", "environment_secret", "environment_variable", "model"] }).notNull(),
|
|
362
|
+
type: text({ enum: ["bearer", "headers", "workload", "environment_secret", "environment_variable", "model"] }).notNull(),
|
|
357
363
|
/** JSON text of the non-secret binding: url (and via, identity, header names), or provider and model. */
|
|
358
364
|
bindingJson: text().notNull(),
|
|
359
365
|
expiresAt: text(),
|
|
@@ -295,6 +295,7 @@ function agentManifestOf(row) {
|
|
|
295
295
|
hash: row.hash,
|
|
296
296
|
savedBy: row.savedBy,
|
|
297
297
|
createdAt: row.createdAt,
|
|
298
|
+
...(row.source === null || row.source === undefined ? {} : { source: row.source }),
|
|
298
299
|
};
|
|
299
300
|
}
|
|
300
301
|
/** A pod patch as column values: ISO times become dates, `null` clears. */
|
package/dist/store/types.d.ts
CHANGED
|
@@ -204,6 +204,20 @@ export interface AgentManifestRow {
|
|
|
204
204
|
actor: string;
|
|
205
205
|
};
|
|
206
206
|
createdAt: string;
|
|
207
|
+
/** The agent folder the version was compiled from (track T6); none for a version saved as JSON. */
|
|
208
|
+
source?: AgentManifestSourceRecord;
|
|
209
|
+
}
|
|
210
|
+
/** An agent folder as a version keeps it: its compiler, size and files, each a definition file. */
|
|
211
|
+
export interface AgentManifestSourceRecord {
|
|
212
|
+
compiler: string;
|
|
213
|
+
files: number;
|
|
214
|
+
bytes: number;
|
|
215
|
+
/** Sorted by path; `sha256` is lowercase hex, the file `sha256:<hex>` in the definition file store. */
|
|
216
|
+
entries: {
|
|
217
|
+
path: string;
|
|
218
|
+
size: number;
|
|
219
|
+
sha256: string;
|
|
220
|
+
}[];
|
|
207
221
|
}
|
|
208
222
|
/** A disk lease (R2d step 3): what holds the disk, since when, and until when unless renewed. */
|
|
209
223
|
export interface DiskOperation {
|
|
@@ -1,28 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
* Agent manifests (protocol 16, track T2): `/v1/agent-manifests/{name}`, named and versioned
|
|
3
|
-
* documents with what runs inside an agent's loop (`harness`) and what the Runtime provides
|
|
4
|
-
* around it (`runtime`). They replace agent definitions (`PUT /v1/agents/:id` before protocol
|
|
5
|
-
* 16) and environment manifests.
|
|
6
|
-
*
|
|
7
|
-
* - **Versions.** A save whose manifest hashes the same as the latest answers it unchanged; any
|
|
8
|
-
* other writes the next version. A version is never changed or deleted alone.
|
|
9
|
-
* - **Tracking Agents move.** Saving a version moves every Agent that tracks the manifest to it
|
|
10
|
-
* (track T3, `moveAgent`), in the save's transaction: at once, at its pod's next start, or once
|
|
11
|
-
* its required secrets are set. A pinned Agent stays, and so does one the version cannot move
|
|
12
|
-
* (a fixed field) or whose start failed on it. The plan counts both.
|
|
13
|
-
* - **Both sections at once.** The Runtime sees both sections, so it checks them against each
|
|
14
|
-
* other (`sectionGaps`): every gap is named in one `422 manifest_inconsistent`, which is what
|
|
15
|
-
* `requires` checked when a session opened before protocol 16. Each section's own checks run
|
|
16
|
-
* too: the harness's (no code tools, no approvals, HTTP tool timeouts, the skill files) and the
|
|
17
|
-
* runtime's (the Tenant's limits, its files).
|
|
18
|
-
* - **Plan and drift.** `?dryRun=true` answers what a save would do and saves nothing: the
|
|
19
|
-
* changes from the latest by section, every reason the Runtime would refuse it, warnings, the
|
|
20
|
-
* Agents it moves and how many stay on older versions. `If-Match: <version>` refuses
|
|
21
|
-
* (`412 manifest_changed`) a save whose latest version moved since the client read it; `0` asks
|
|
22
|
-
* for a manifest that does not exist yet.
|
|
23
|
-
* - **Every client is equal** (Q12): a version records who saved it (`savedBy`).
|
|
24
|
-
*/
|
|
25
|
-
import { type AgentManifestChange, type AgentManifestPlan, type AgentManifestV8, type AgentManifestVersions, type AgentManifestView, type PutAgentManifestRequest, type SaveAgentManifestResponse } from "@nylorun/core/contracts";
|
|
1
|
+
import { type AgentManifestChange, type AgentManifestPlan, type AgentManifestSource, type AgentManifestV8, type AgentManifestVersions, type AgentManifestView, type PutAgentFolderRequest, type PutAgentManifestRequest, type SaveAgentManifestResponse } from "@nylorun/core/contracts";
|
|
26
2
|
import { type RecipeRequest } from "../sandbox/resolve.js";
|
|
27
3
|
import type { AuthScope, TenantContext } from "./context.js";
|
|
28
4
|
/** What a manifest's runtime section asks of an Agent's recipe, with the harness its loop names. */
|
|
@@ -54,6 +30,23 @@ export declare function saveAgentManifest(ctx: TenantContext, name: string, body
|
|
|
54
30
|
dryRun?: false;
|
|
55
31
|
ifMatch?: number;
|
|
56
32
|
}): Promise<SaveAgentManifestResponse>;
|
|
33
|
+
/**
|
|
34
|
+
* `PUT /v1/agent-manifests/{name}` with an agent folder by hash (track T6): reads the files the
|
|
35
|
+
* compiler needs from the definition file store, compiles the folder (`422 folder_invalid` when
|
|
36
|
+
* it does not), then saves or plans the manifest as a JSON save does, keeping the folder as the
|
|
37
|
+
* version's source.
|
|
38
|
+
*/
|
|
39
|
+
export declare function saveAgentFolder(ctx: TenantContext, name: string, body: PutAgentFolderRequest, scope: AuthScope, options?: {
|
|
40
|
+
dryRun?: boolean;
|
|
41
|
+
ifMatch?: number;
|
|
42
|
+
}): Promise<AgentManifestPlan | SaveAgentManifestResponse>;
|
|
43
|
+
/** `GET /v1/agent-manifests/{name}/versions/{v}/source`: the folder a version was compiled from. */
|
|
44
|
+
export declare function getAgentManifestSource(ctx: TenantContext, name: string, version: number): Promise<AgentManifestSource>;
|
|
45
|
+
/** `GET .../versions/{v}/source/files/{path}`: one file of a version's source, streamed. */
|
|
46
|
+
export declare function readAgentManifestSourceFile(ctx: TenantContext, name: string, version: number, path: string): Promise<{
|
|
47
|
+
body: ReadableStream<Uint8Array>;
|
|
48
|
+
size: number;
|
|
49
|
+
}>;
|
|
57
50
|
/** `GET /v1/agent-manifests/{name}` (the latest) and `.../versions/{n}`. */
|
|
58
51
|
export declare function getAgentManifest(ctx: TenantContext, name: string, version?: number): Promise<AgentManifestView>;
|
|
59
52
|
/** `GET /v1/agent-manifests`: each manifest's latest version. */
|
|
@@ -21,7 +21,15 @@
|
|
|
21
21
|
* (`412 manifest_changed`) a save whose latest version moved since the client read it; `0` asks
|
|
22
22
|
* for a manifest that does not exist yet.
|
|
23
23
|
* - **Every client is equal** (Q12): a version records who saved it (`savedBy`).
|
|
24
|
+
* - **Agent folders** (track T6, D79): the same route takes a folder by hash
|
|
25
|
+
* (`application/vnd.nylorun.folder+json`). The Runtime reads the files the compiler needs from
|
|
26
|
+
* the definition file store, compiles them with `@nylorun/core/folder` (a folder that does not
|
|
27
|
+
* compile is `422 folder_invalid`, every diagnostic in `details.diagnostics`), and saves the
|
|
28
|
+
* manifest as above, with the folder kept as the version's source (`source`, its files recorded
|
|
29
|
+
* as the version's definition files). A folder whose manifest is unchanged saves no new
|
|
30
|
+
* version, so its source is not saved either.
|
|
24
31
|
*/
|
|
32
|
+
import { compileAgentFolder, folderTextPaths, AGENT_FOLDER_LIMITS, } from "@nylorun/core/folder";
|
|
25
33
|
import { definitionFilesOf, } from "@nylorun/core/contracts";
|
|
26
34
|
import { hashManifest } from "@nylorun/core/compatibility";
|
|
27
35
|
import { approvalRefusal, codeToolRefusal, harnessIdOf, loopManifestOf, runtimeOf, sectionGaps, } from "@nylorun/core/define";
|
|
@@ -35,7 +43,7 @@ import { readRuntimeLimits } from "./runtime-limits.js";
|
|
|
35
43
|
import { effectiveSandboxConfig, readSandboxConfig } from "../sandbox/tenant-config.js";
|
|
36
44
|
import { actorOf, fileErrors } from "./agents.js";
|
|
37
45
|
import { moveAgent, planMove } from "./agent-versions.js";
|
|
38
|
-
import { definitionFileIssues } from "./definition-files.js";
|
|
46
|
+
import { definitionFileIssues, openDefinitionFile, readDefinitionFile } from "./definition-files.js";
|
|
39
47
|
import { fail } from "./http.js";
|
|
40
48
|
/** What a manifest's runtime section asks of an Agent's recipe, with the harness its loop names. */
|
|
41
49
|
export function manifestRecipeRequest(manifest) {
|
|
@@ -73,6 +81,9 @@ function viewOf(row, counts) {
|
|
|
73
81
|
savedBy: row.savedBy,
|
|
74
82
|
createdAt: row.createdAt,
|
|
75
83
|
agents: counts.get(row.version) ?? 0,
|
|
84
|
+
...(row.source === undefined
|
|
85
|
+
? {}
|
|
86
|
+
: { source: { compiler: row.source.compiler, files: row.source.files, bytes: row.source.bytes } }),
|
|
76
87
|
};
|
|
77
88
|
}
|
|
78
89
|
/** Each field that differs between `before` and `after`, by path; arrays compare whole. */
|
|
@@ -237,6 +248,9 @@ export async function saveAgentManifest(ctx, name, body, scope, options = {}) {
|
|
|
237
248
|
const { requestId: _, ...manifest } = body;
|
|
238
249
|
if (manifest.name !== name)
|
|
239
250
|
fail(400, `The manifest is named ${manifest.name}, and the path names ${name}: they must match.`);
|
|
251
|
+
return saveManifest(ctx, name, manifest, scope, options);
|
|
252
|
+
}
|
|
253
|
+
async function saveManifest(ctx, name, manifest, scope, options, folder) {
|
|
240
254
|
const hash = agentManifestHash(manifest);
|
|
241
255
|
const gaps = sectionGaps(manifest);
|
|
242
256
|
return ctx.store.tx(async (t) => {
|
|
@@ -248,12 +262,33 @@ export async function saveAgentManifest(ctx, name, body, scope, options = {}) {
|
|
|
248
262
|
: `Agent manifest ${name} is at version ${latest.version}, not ${options.ifMatch}: someone saved it since you read it. Read it again, or save without If-Match to replace it.`, { code: "manifest_changed", details: { latest: latest?.version ?? 0 } });
|
|
249
263
|
const unchanged = latest?.hash === hash;
|
|
250
264
|
const to = unchanged ? latest.version : (latest?.version ?? 0) + 1;
|
|
265
|
+
// A folder whose manifest is unchanged keeps the latest version, and with it that version's source.
|
|
266
|
+
const notes = folder === undefined
|
|
267
|
+
? undefined
|
|
268
|
+
: unchanged && canonical(latest?.source?.entries ?? null) !== canonical(folder.source.entries)
|
|
269
|
+
? [
|
|
270
|
+
...folder.diagnostics,
|
|
271
|
+
{
|
|
272
|
+
severity: "note",
|
|
273
|
+
code: "source_not_saved",
|
|
274
|
+
message: "Source files changed that are not part of the agent; the manifest is unchanged, so no version is saved",
|
|
275
|
+
},
|
|
276
|
+
]
|
|
277
|
+
: folder.diagnostics;
|
|
251
278
|
const counts = await t.agentsOfManifest(name);
|
|
252
279
|
const checked = await errorsOf(t, manifest);
|
|
253
280
|
// The version as it would be saved, for the plan of the Agents it moves.
|
|
254
281
|
const row = unchanged
|
|
255
282
|
? latest
|
|
256
|
-
: {
|
|
283
|
+
: {
|
|
284
|
+
name,
|
|
285
|
+
version: to,
|
|
286
|
+
body: manifest,
|
|
287
|
+
hash,
|
|
288
|
+
savedBy: { actor: actorOf(scope) },
|
|
289
|
+
createdAt: new Date().toISOString(),
|
|
290
|
+
...(folder === undefined ? {} : { source: folder.source }),
|
|
291
|
+
};
|
|
257
292
|
if (options.dryRun) {
|
|
258
293
|
const errors = [...gaps, ...checked.errors];
|
|
259
294
|
// A save with errors moves nothing; one that changes nothing moves no Agent either.
|
|
@@ -268,6 +303,7 @@ export async function saveAgentManifest(ctx, name, body, scope, options = {}) {
|
|
|
268
303
|
errors,
|
|
269
304
|
behind: moves?.behind ?? (await agentsBehind(t, name, to)).length,
|
|
270
305
|
moves: { now: moves?.now ?? 0, nextStart: moves?.nextStart ?? 0, secrets: moves?.secrets ?? 0 },
|
|
306
|
+
...(folder === undefined ? {} : { manifest, diagnostics: [...notes], compiler: folder.compiler }),
|
|
271
307
|
};
|
|
272
308
|
}
|
|
273
309
|
if (gaps.length > 0)
|
|
@@ -287,8 +323,9 @@ export async function saveAgentManifest(ctx, name, body, scope, options = {}) {
|
|
|
287
323
|
});
|
|
288
324
|
if (checked.errors.length > 0)
|
|
289
325
|
fail(400, checked.errors.join(" "));
|
|
326
|
+
const folderNotes = notes === undefined ? {} : { diagnostics: [...notes] };
|
|
290
327
|
if (unchanged)
|
|
291
|
-
return { manifest: viewOf(latest, counts), unchanged: true, warnings: warningsOf(ctx, manifest, to, undefined) };
|
|
328
|
+
return { manifest: viewOf(latest, counts), unchanged: true, warnings: warningsOf(ctx, manifest, to, undefined), ...folderNotes };
|
|
292
329
|
await t.insertAgentManifest(row);
|
|
293
330
|
// Its files by hash, skills' and runtime files' alike, for the sweep that deletes unused ones.
|
|
294
331
|
const shas = new Set(definitionFilesOf(loopManifestOf(manifest)));
|
|
@@ -296,13 +333,108 @@ export async function saveAgentManifest(ctx, name, body, scope, options = {}) {
|
|
|
296
333
|
if ("files" in file)
|
|
297
334
|
for (const sha of Object.values(file.files))
|
|
298
335
|
shas.add(sha);
|
|
336
|
+
// A folder's every file too: the version keeps its source.
|
|
337
|
+
for (const entry of folder?.source.entries ?? [])
|
|
338
|
+
shas.add(`sha256:${entry.sha256}`);
|
|
299
339
|
if (shas.size > 0)
|
|
300
340
|
await t.insertDefinitionFileUses(manifestFileOwner(name), hash, [...shas]);
|
|
301
341
|
// Every Agent that tracks the manifest moves to the new version (track T3).
|
|
302
342
|
const moves = await moveTracking(t, row, true);
|
|
303
|
-
return {
|
|
343
|
+
return {
|
|
344
|
+
manifest: viewOf(row, await t.agentsOfManifest(name)),
|
|
345
|
+
unchanged: false,
|
|
346
|
+
warnings: warningsOf(ctx, manifest, to, moves),
|
|
347
|
+
...folderNotes,
|
|
348
|
+
};
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* `PUT /v1/agent-manifests/{name}` with an agent folder by hash (track T6): reads the files the
|
|
353
|
+
* compiler needs from the definition file store, compiles the folder (`422 folder_invalid` when
|
|
354
|
+
* it does not), then saves or plans the manifest as a JSON save does, keeping the folder as the
|
|
355
|
+
* version's source.
|
|
356
|
+
*/
|
|
357
|
+
export async function saveAgentFolder(ctx, name, body, scope, options = {}) {
|
|
358
|
+
const entries = [...body.entries].sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));
|
|
359
|
+
if (entries.length > AGENT_FOLDER_LIMITS.files)
|
|
360
|
+
fail(413, `An agent folder holds at most ${AGENT_FOLDER_LIMITS.files} files; this one holds ${entries.length}`, {
|
|
361
|
+
code: "limit_exceeded",
|
|
362
|
+
details: { limit: AGENT_FOLDER_LIMITS.files },
|
|
363
|
+
});
|
|
364
|
+
// Every file must be held: the version keeps them all as its source.
|
|
365
|
+
const shas = [...new Set(entries.map((entry) => `sha256:${entry.sha256}`))].sort();
|
|
366
|
+
const held = await ctx.store.tx((t) => t.heldDefinitionFiles(shas));
|
|
367
|
+
const missing = entries.filter((entry) => !held.has(`sha256:${entry.sha256}`));
|
|
368
|
+
if (missing.length > 0)
|
|
369
|
+
fail(400, `The folder names ${missing.length} file(s) the Runtime does not hold; upload each with PUT /v1/files/sha256:<hex> first: ${missing
|
|
370
|
+
.slice(0, 20)
|
|
371
|
+
.map((entry) => entry.path)
|
|
372
|
+
.join(", ")}`, { code: "definition_files_missing", details: { missing: missing.map((entry) => `sha256:${entry.sha256}`) } });
|
|
373
|
+
for (const entry of entries)
|
|
374
|
+
if (held.get(`sha256:${entry.sha256}`) !== entry.size)
|
|
375
|
+
fail(400, `${entry.path} is listed with ${entry.size} bytes, and the Runtime holds ${held.get(`sha256:${entry.sha256}`)} for its hash`);
|
|
376
|
+
// The files the compiler reads, each at most the text file limit (it reports a larger one).
|
|
377
|
+
const files = new Map();
|
|
378
|
+
for (const path of folderTextPaths(entries)) {
|
|
379
|
+
const entry = entries.find((item) => item.path === path);
|
|
380
|
+
if (entry.size > AGENT_FOLDER_LIMITS.textFileBytes)
|
|
381
|
+
continue;
|
|
382
|
+
const bytes = await readDefinitionFile(ctx.blobs, `sha256:${entry.sha256}`);
|
|
383
|
+
if (bytes)
|
|
384
|
+
files.set(path, bytes);
|
|
385
|
+
}
|
|
386
|
+
const compiled = compileAgentFolder({ entries, files, name });
|
|
387
|
+
if (!compiled.manifest) {
|
|
388
|
+
const errors = compiled.diagnostics.filter((item) => item.severity === "error");
|
|
389
|
+
fail(422, `Agent folder ${name} does not compile: ${errors.length} error(s), the first: ${located(errors[0])}`, {
|
|
390
|
+
code: "folder_invalid",
|
|
391
|
+
details: { diagnostics: compiled.diagnostics },
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
const source = {
|
|
395
|
+
compiler: compiled.compiler,
|
|
396
|
+
files: entries.length,
|
|
397
|
+
bytes: entries.reduce((sum, entry) => sum + entry.size, 0),
|
|
398
|
+
entries: entries.map((entry) => ({ path: entry.path, size: entry.size, sha256: entry.sha256 })),
|
|
399
|
+
};
|
|
400
|
+
return saveManifest(ctx, name, compiled.manifest, scope, options, {
|
|
401
|
+
source,
|
|
402
|
+
diagnostics: compiled.diagnostics,
|
|
403
|
+
compiler: compiled.compiler,
|
|
304
404
|
});
|
|
305
405
|
}
|
|
406
|
+
/** A diagnostic as a person reads it: `file:line:column message`. */
|
|
407
|
+
function located(diagnostic) {
|
|
408
|
+
if (!diagnostic)
|
|
409
|
+
return "unknown";
|
|
410
|
+
const where = diagnostic.file
|
|
411
|
+
? `${diagnostic.file}${diagnostic.line ? `:${diagnostic.line}${diagnostic.column ? `:${diagnostic.column}` : ""}` : ""} `
|
|
412
|
+
: "";
|
|
413
|
+
return `${where}${diagnostic.message}`;
|
|
414
|
+
}
|
|
415
|
+
/** The source of version `version` of manifest `name`, or a `404` for one saved as JSON. */
|
|
416
|
+
async function sourceOf(ctx, name, version) {
|
|
417
|
+
const row = (await ctx.store.tx((t) => t.agentManifest(name, version))) ?? notFound(name, version);
|
|
418
|
+
if (!row.source)
|
|
419
|
+
fail(404, `Version ${version} of agent manifest ${name} was saved as JSON: it has no source.`, { code: "not_found" });
|
|
420
|
+
return row.source;
|
|
421
|
+
}
|
|
422
|
+
/** `GET /v1/agent-manifests/{name}/versions/{v}/source`: the folder a version was compiled from. */
|
|
423
|
+
export async function getAgentManifestSource(ctx, name, version) {
|
|
424
|
+
const source = await sourceOf(ctx, name, version);
|
|
425
|
+
return { name, version, ...source };
|
|
426
|
+
}
|
|
427
|
+
/** `GET .../versions/{v}/source/files/{path}`: one file of a version's source, streamed. */
|
|
428
|
+
export async function readAgentManifestSourceFile(ctx, name, version, path) {
|
|
429
|
+
const source = await sourceOf(ctx, name, version);
|
|
430
|
+
const entry = source.entries.find((item) => item.path === path);
|
|
431
|
+
if (!entry)
|
|
432
|
+
fail(404, `Version ${version} of agent manifest ${name} has no file ${path}.`, { code: "not_found" });
|
|
433
|
+
const body = await openDefinitionFile(ctx.blobs, `sha256:${entry.sha256}`);
|
|
434
|
+
if (!body)
|
|
435
|
+
fail(404, `The Runtime no longer holds ${path} of version ${version} of agent manifest ${name}.`, { code: "not_found" });
|
|
436
|
+
return { body: body, size: entry.size };
|
|
437
|
+
}
|
|
306
438
|
/** `GET /v1/agent-manifests/{name}` (the latest) and `.../versions/{n}`. */
|
|
307
439
|
export async function getAgentManifest(ctx, name, version) {
|
|
308
440
|
return ctx.store.tx(async (t) => {
|
package/dist/tenant/agents.js
CHANGED
|
@@ -562,15 +562,19 @@ async function parseSecretRequest(ctx, id, name, raw) {
|
|
|
562
562
|
const full = slotRequest(slot);
|
|
563
563
|
if (raw !== null && typeof raw === "object" && "type" in raw) {
|
|
564
564
|
const body = PutAgentSecretRequestSchema.parse(raw);
|
|
565
|
-
|
|
565
|
+
// The gateway's tool prefix is this installation's to add (`gateway-credentials`).
|
|
566
|
+
const { requestId: _, value: _v, headers: _h, toolPrefix: _p, ...binding } = body;
|
|
566
567
|
if (canonical(binding) !== canonical(full))
|
|
567
568
|
fail(409, `Secret ${name}'s type and binding come from Agent ${id}'s manifest: send only its value ({ "value": "…" }).`, {
|
|
568
569
|
code: "secret_not_in_manifest",
|
|
569
570
|
});
|
|
570
571
|
return body;
|
|
571
572
|
}
|
|
572
|
-
const { requestId, value, headers } = PutAgentSecretValueRequestSchema.parse(raw);
|
|
573
|
-
const withId =
|
|
573
|
+
const { requestId, value, headers, toolPrefix } = PutAgentSecretValueRequestSchema.parse(raw);
|
|
574
|
+
const withId = {
|
|
575
|
+
...(requestId === undefined ? {} : { requestId }),
|
|
576
|
+
...(toolPrefix === undefined ? {} : { toolPrefix }),
|
|
577
|
+
};
|
|
574
578
|
if (slot.type === "headers") {
|
|
575
579
|
if (headers === undefined)
|
|
576
580
|
fail(400, `Secret ${name} is a headers credential: send { "headers": { … } }.`);
|
|
@@ -186,6 +186,7 @@ export declare function toolCompleted(request: HostEffect, value: unknown, facts
|
|
|
186
186
|
code: string;
|
|
187
187
|
message: string;
|
|
188
188
|
server?: string | undefined;
|
|
189
|
+
gateway?: string | undefined;
|
|
189
190
|
retryable?: boolean | undefined;
|
|
190
191
|
};
|
|
191
192
|
output?: undefined;
|
|
@@ -208,6 +209,7 @@ export declare function toolCompleted(request: HostEffect, value: unknown, facts
|
|
|
208
209
|
code: string;
|
|
209
210
|
message: string;
|
|
210
211
|
server?: string | undefined;
|
|
212
|
+
gateway?: string | undefined;
|
|
211
213
|
retryable?: boolean | undefined;
|
|
212
214
|
};
|
|
213
215
|
output?: undefined;
|
|
@@ -230,6 +232,7 @@ export declare function toolCompleted(request: HostEffect, value: unknown, facts
|
|
|
230
232
|
code: string;
|
|
231
233
|
message: string;
|
|
232
234
|
server?: string | undefined;
|
|
235
|
+
gateway?: string | undefined;
|
|
233
236
|
retryable?: boolean | undefined;
|
|
234
237
|
};
|
|
235
238
|
output?: undefined;
|
|
@@ -252,6 +255,7 @@ export declare function toolCompleted(request: HostEffect, value: unknown, facts
|
|
|
252
255
|
code: string;
|
|
253
256
|
message: string;
|
|
254
257
|
server?: string | undefined;
|
|
258
|
+
gateway?: string | undefined;
|
|
255
259
|
retryable?: boolean | undefined;
|
|
256
260
|
};
|
|
257
261
|
output?: undefined;
|
|
@@ -344,6 +348,7 @@ export declare function toolCompleted(request: HostEffect, value: unknown, facts
|
|
|
344
348
|
code: string;
|
|
345
349
|
message: string;
|
|
346
350
|
server?: string | undefined;
|
|
351
|
+
gateway?: string | undefined;
|
|
347
352
|
retryable?: boolean | undefined;
|
|
348
353
|
};
|
|
349
354
|
output?: undefined;
|
|
@@ -368,6 +373,7 @@ export declare function toolCompleted(request: HostEffect, value: unknown, facts
|
|
|
368
373
|
code: string;
|
|
369
374
|
message: string;
|
|
370
375
|
server?: string | undefined;
|
|
376
|
+
gateway?: string | undefined;
|
|
371
377
|
retryable?: boolean | undefined;
|
|
372
378
|
};
|
|
373
379
|
output?: undefined;
|
|
@@ -392,6 +398,7 @@ export declare function toolCompleted(request: HostEffect, value: unknown, facts
|
|
|
392
398
|
code: string;
|
|
393
399
|
message: string;
|
|
394
400
|
server?: string | undefined;
|
|
401
|
+
gateway?: string | undefined;
|
|
395
402
|
retryable?: boolean | undefined;
|
|
396
403
|
};
|
|
397
404
|
output?: undefined;
|
|
@@ -416,6 +423,7 @@ export declare function toolCompleted(request: HostEffect, value: unknown, facts
|
|
|
416
423
|
code: string;
|
|
417
424
|
message: string;
|
|
418
425
|
server?: string | undefined;
|
|
426
|
+
gateway?: string | undefined;
|
|
419
427
|
retryable?: boolean | undefined;
|
|
420
428
|
};
|
|
421
429
|
output?: undefined;
|
|
@@ -151,6 +151,8 @@ export function toolCompleted(request, value, facts) {
|
|
|
151
151
|
message: String(outcome.message ?? ""),
|
|
152
152
|
// `credential_rejected` (R2b C1): which server.
|
|
153
153
|
...(typeof outcome.server === "string" ? { server: outcome.server } : {}),
|
|
154
|
+
// And the gateway that refused the Runtime's workload token (#496).
|
|
155
|
+
...(typeof outcome.gateway === "string" ? { gateway: outcome.gateway } : {}),
|
|
154
156
|
...(typeof outcome.retryable === "boolean" ? { retryable: outcome.retryable } : {}),
|
|
155
157
|
},
|
|
156
158
|
}
|
package/dist/vault/headers.d.ts
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
/** Headers that describe one hop and are never forwarded or set by a credential or a setting. */
|
|
2
|
+
export declare const HOP_BY_HOP_HEADERS: ReadonlySet<string>;
|
|
3
|
+
/**
|
|
4
|
+
* Why a custom model endpoint's header (`settings.headers`, `settings.callHeaders`, #493) may not
|
|
5
|
+
* be `name`, or undefined when it may: the API key's `authorization`, a hop-by-hop header, a
|
|
6
|
+
* transport header or `Nylorun-*`.
|
|
7
|
+
*/
|
|
8
|
+
export declare function modelHeaderError(name: string): string | undefined;
|
|
1
9
|
/**
|
|
2
10
|
* The secret values of a credential's `headers`, as a server could echo them (R2b C8, Q16): each
|
|
3
11
|
* value of at least 8 characters, and the token of a `Bearer …` value. The identity header names
|
package/dist/vault/headers.js
CHANGED
|
@@ -18,6 +18,31 @@ const RESERVED_HEADERS = new Set([
|
|
|
18
18
|
"last-event-id",
|
|
19
19
|
"idempotency-key",
|
|
20
20
|
]);
|
|
21
|
+
/** Headers that describe one hop and are never forwarded or set by a credential or a setting. */
|
|
22
|
+
export const HOP_BY_HOP_HEADERS = new Set([
|
|
23
|
+
"connection",
|
|
24
|
+
"keep-alive",
|
|
25
|
+
"proxy-authenticate",
|
|
26
|
+
"proxy-authorization",
|
|
27
|
+
"proxy-connection",
|
|
28
|
+
"te",
|
|
29
|
+
"trailer",
|
|
30
|
+
"transfer-encoding",
|
|
31
|
+
"upgrade",
|
|
32
|
+
]);
|
|
33
|
+
/**
|
|
34
|
+
* Why a custom model endpoint's header (`settings.headers`, `settings.callHeaders`, #493) may not
|
|
35
|
+
* be `name`, or undefined when it may: the API key's `authorization`, a hop-by-hop header, a
|
|
36
|
+
* transport header or `Nylorun-*`.
|
|
37
|
+
*/
|
|
38
|
+
export function modelHeaderError(name) {
|
|
39
|
+
const lower = name.toLowerCase();
|
|
40
|
+
if (lower === "authorization")
|
|
41
|
+
return "A model header may not set authorization: the model's auth sends it";
|
|
42
|
+
if (HOP_BY_HOP_HEADERS.has(lower) || isReservedHeader(lower))
|
|
43
|
+
return `A model header may not set ${lower}`;
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
21
46
|
/**
|
|
22
47
|
* The secret values of a credential's `headers`, as a server could echo them (R2b C8, Q16): each
|
|
23
48
|
* value of at least 8 characters, and the token of a `Bearer …` value. The identity header names
|
package/dist/vault/host-model.js
CHANGED
|
@@ -24,11 +24,14 @@ export class HostModelVault {
|
|
|
24
24
|
const payload = readHostPayload(this.kek(), row, binding);
|
|
25
25
|
const credential = binding.authType === "oauth"
|
|
26
26
|
? { type: "oauth", ...payload.oauth }
|
|
27
|
-
:
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
27
|
+
: binding.authType === "workload"
|
|
28
|
+
? // The workload token is read from its file at each call (#496), never stored.
|
|
29
|
+
{ type: "workload" }
|
|
30
|
+
: {
|
|
31
|
+
type: "api_key",
|
|
32
|
+
key: payload.apiKey,
|
|
33
|
+
...(payload.env ? { env: payload.env } : {}),
|
|
34
|
+
};
|
|
32
35
|
return {
|
|
33
36
|
provider: binding.provider,
|
|
34
37
|
model: binding.model,
|
package/dist/vault/service.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { CredentialCoverage, CredentialCoverageRequest, CredentialIdentity, EnvironmentSecretSlot, AgentSecretView, HostModelProviderInfo, HostModelView, PutAgentSecretRequest, PutHostModelRequest, CustomModelSettings, ModelInput, SelectHostModelRequest } from "@nylorun/core/contracts";
|
|
1
|
+
import type { CredentialCoverage, CredentialCoverageRequest, CredentialIdentity, EnvironmentSecretSlot, AgentSecretView, HostModelAuthType, HostModelProviderInfo, HostModelView, PutAgentSecretRequest, PutHostModelRequest, CustomModelSettings, ModelInput, SelectHostModelRequest } from "@nylorun/core/contracts";
|
|
2
2
|
import type { AgentManifest, WorkflowManifest } from "@nylorun/core/define";
|
|
3
3
|
import type { SessionStore, Tx, VaultCredentialRow } from "../store/types.js";
|
|
4
4
|
export declare const HOST_VAULT_ID = "host";
|
|
@@ -10,9 +10,9 @@ export type HostModelSecret = {
|
|
|
10
10
|
settings?: CustomModelSettings;
|
|
11
11
|
/** The inputs the operator added to catalog models, by `<provider>/<model>` (protocol 12). */
|
|
12
12
|
addedInputs?: Readonly<Record<string, readonly ModelInput[]>>;
|
|
13
|
-
authType:
|
|
13
|
+
authType: HostModelAuthType;
|
|
14
14
|
credential: {
|
|
15
|
-
type:
|
|
15
|
+
type: HostModelAuthType;
|
|
16
16
|
key?: string;
|
|
17
17
|
env?: Record<string, string>;
|
|
18
18
|
refresh?: string;
|
|
@@ -22,7 +22,7 @@ export type HostModelSecret = {
|
|
|
22
22
|
};
|
|
23
23
|
};
|
|
24
24
|
type UserCredentialRow = VaultCredentialRow & {
|
|
25
|
-
type: "bearer" | "headers" | "environment_secret" | "environment_variable";
|
|
25
|
+
type: "bearer" | "headers" | "workload" | "environment_secret" | "environment_variable";
|
|
26
26
|
};
|
|
27
27
|
/**
|
|
28
28
|
* A fork's secrets sealed for its new vault (`sealEnvironmentSecretCopies`): each copy's
|
|
@@ -54,6 +54,13 @@ export type AuthorizeResult = {
|
|
|
54
54
|
headers: Record<string, string>;
|
|
55
55
|
/** Where the request goes instead of `url` (a gateway), when the credential has one. */
|
|
56
56
|
via?: string;
|
|
57
|
+
/** How the gateway at `via` prefixes the server's tool names, when it does. */
|
|
58
|
+
toolPrefix?: string;
|
|
59
|
+
/**
|
|
60
|
+
* A `workload` credential (#496): `sessionCredentials` adds the Runtime's workload token as
|
|
61
|
+
* the bearer, on the gate side, where `via`'s origin is one the operator lists.
|
|
62
|
+
*/
|
|
63
|
+
workload?: true;
|
|
57
64
|
/** The header that names the session owner; `sessionCredentials` adds it. */
|
|
58
65
|
identity?: CredentialIdentity;
|
|
59
66
|
} | {
|
|
@@ -195,7 +202,7 @@ type ModelBinding = {
|
|
|
195
202
|
model: string;
|
|
196
203
|
baseUrl?: string;
|
|
197
204
|
settings?: CustomModelSettings;
|
|
198
|
-
authType:
|
|
205
|
+
authType: HostModelAuthType;
|
|
199
206
|
};
|
|
200
207
|
type HostSecretPayload = {
|
|
201
208
|
apiKey?: string;
|