@nylorun/runtime 0.23.0-beta → 0.25.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 +252 -0
- package/README.md +87 -28
- package/dist/adapters/execution/restate.js +1 -1
- package/dist/adapters/replication/pgoutput.d.ts +2 -0
- package/dist/adapters/replication/pgoutput.js +6 -3
- package/dist/adapters/sandbox/local.d.ts +9 -1
- package/dist/adapters/sandbox/local.js +16 -4
- package/dist/adapters/sandbox/virtual.js +4 -1
- package/dist/api/a2a/card.d.ts +11 -1
- package/dist/api/a2a/card.js +26 -2
- package/dist/api/a2a/endpoint.js +9 -3
- package/dist/api/a2a/protocol.d.ts +25 -2
- package/dist/api/a2a/protocol.js +20 -3
- package/dist/api/a2a/routes.d.ts +6 -1
- package/dist/api/a2a/routes.js +67 -9
- package/dist/api/a2a/tasks.js +2 -1
- package/dist/api/ag-ui/endpoint.js +8 -3
- package/dist/api/ag-ui/history.d.ts +6 -1
- package/dist/api/ag-ui/history.js +39 -3
- package/dist/api/ag-ui/routes.d.ts +4 -1
- package/dist/api/ag-ui/routes.js +63 -20
- package/dist/api/ag-ui/translate.js +3 -2
- package/dist/api/components.d.ts +5327 -1091
- package/dist/api/components.js +40 -16
- package/dist/api/gate/keys.d.ts +2 -1
- package/dist/api/gate/keys.js +14 -1
- package/dist/api/gate/routes.d.ts +6 -0
- package/dist/api/gate/routes.js +52 -3
- package/dist/api/http/app.js +17 -6
- package/dist/api/http/body.d.ts +3 -3
- package/dist/api/http/body.js +7 -7
- package/dist/api/http/define.d.ts +6 -0
- package/dist/api/http/define.js +13 -1
- package/dist/api/http/respond.js +1 -1
- package/dist/api/http/routes/artifacts.js +35 -8
- package/dist/api/http/routes/coverage.d.ts +9 -0
- package/dist/api/http/routes/coverage.js +34 -0
- package/dist/api/http/routes/environment-manifests.d.ts +9 -0
- package/dist/api/http/routes/environment-manifests.js +118 -0
- package/dist/api/http/routes/environments.d.ts +11 -0
- package/dist/api/http/routes/environments.js +332 -0
- package/dist/api/http/routes/mcp.d.ts +1 -1
- package/dist/api/http/routes/mcp.js +2 -2
- package/dist/api/http/routes/me.js +1 -1
- package/dist/api/http/routes/reads.d.ts +94 -9
- package/dist/api/http/routes/reads.js +54 -5
- package/dist/api/http/routes/sessions.js +24 -9
- package/dist/api/http/routes/tenant.js +65 -15
- package/dist/api/inbound-files.d.ts +65 -0
- package/dist/api/inbound-files.js +115 -0
- package/dist/api/openapi.js +48 -28
- package/dist/artifacts/disk.d.ts +59 -0
- package/dist/artifacts/disk.js +569 -0
- package/dist/artifacts/export.d.ts +1 -1
- package/dist/artifacts/export.js +2 -3
- package/dist/artifacts/folders.d.ts +6 -3
- package/dist/artifacts/folders.js +17 -12
- package/dist/artifacts/media-types.d.ts +15 -0
- package/dist/artifacts/media-types.js +56 -4
- package/dist/artifacts/parts.d.ts +12 -3
- package/dist/artifacts/parts.js +15 -4
- package/dist/artifacts/service.d.ts +2 -1
- package/dist/artifacts/service.js +6 -1
- package/dist/artifacts/workspace.d.ts +5 -5
- package/dist/artifacts/workspace.js +7 -19
- package/dist/core/flow-host.d.ts +3 -4
- package/dist/core/flow-host.js +5 -2
- package/dist/core/provider.d.ts +3 -2
- package/dist/core/provider.js +5 -37
- package/dist/core/sandbox-routes.d.ts +2 -21
- package/dist/core/sandbox-routes.js +11 -44
- package/dist/core/turn-manifest.d.ts +3 -2
- package/dist/core/turn-manifest.js +29 -5
- package/dist/execution/types.d.ts +1 -1
- package/dist/gates/contract.d.ts +3 -24
- package/dist/gates/contract.js +1 -36
- package/dist/gates/egress-credentials.d.ts +9 -0
- package/dist/gates/egress-credentials.js +11 -4
- package/dist/gates/egress.d.ts +22 -1
- package/dist/gates/egress.js +116 -10
- package/dist/gates/fixture-model.d.ts +28 -0
- package/dist/gates/fixture-model.js +92 -0
- package/dist/gates/handler.js +4 -4
- package/dist/gates/http-tool.d.ts +9 -6
- package/dist/gates/http-tool.js +11 -17
- package/dist/gates/in-process.d.ts +5 -0
- package/dist/gates/in-process.js +12 -1
- package/dist/gates/journal.d.ts +30 -0
- package/dist/gates/journal.js +51 -0
- package/dist/gates/mcp-handler.d.ts +4 -0
- package/dist/gates/mcp-handler.js +7 -4
- package/dist/gates/meter.d.ts +10 -2
- package/dist/gates/meter.js +20 -5
- package/dist/gates/policy.d.ts +49 -0
- package/dist/gates/policy.js +132 -0
- package/dist/gates/tenant-vaults.d.ts +7 -7
- package/dist/gates/tenant-vaults.js +5 -5
- package/dist/gates/tool-calls.d.ts +10 -5
- package/dist/gates/tool-calls.js +28 -10
- package/dist/gates/tool-client.d.ts +8 -0
- package/dist/gates/tool-client.js +27 -11
- package/dist/gates/tool-contract.d.ts +11 -69
- package/dist/gates/tool-contract.js +1 -85
- package/dist/gates/tool-gate.d.ts +1 -1
- package/dist/harness/calls.d.ts +14 -7
- package/dist/harness/calls.js +30 -24
- package/dist/harness/executors.js +12 -9
- package/dist/harness/main.js +24 -5
- package/dist/harness/pod.d.ts +39 -0
- package/dist/harness/pod.js +46 -0
- package/dist/harness/service.d.ts +0 -1
- package/dist/harness/service.js +17 -9
- package/dist/harness/setup.d.ts +29 -0
- package/dist/harness/setup.js +174 -0
- package/dist/harness/ws-client.js +3 -2
- package/dist/harness/ws-port.d.ts +1 -5
- package/dist/harness/ws-port.js +1 -5
- package/dist/harness-api/in-process.js +3 -3
- package/dist/harness-api/record.d.ts +15 -1
- package/dist/harness-api/record.js +180 -50
- package/dist/harness-api/redact.d.ts +3 -0
- package/dist/harness-api/redact.js +45 -0
- package/dist/harness-api/server.d.ts +35 -4
- package/dist/harness-api/server.js +130 -17
- package/dist/harness-api/start.d.ts +10 -3
- package/dist/harness-api/start.js +40 -10
- package/dist/harness-api/tool-journal.d.ts +105 -0
- package/dist/harness-api/tool-journal.js +132 -0
- package/dist/harness-api/tool-status.d.ts +13 -0
- package/dist/harness-api/tool-status.js +26 -0
- package/dist/harness-api/workspace.d.ts +12 -7
- package/dist/harness-api/workspace.js +47 -25
- package/dist/harness-api/ws-server.d.ts +11 -3
- package/dist/harness-api/ws-server.js +94 -15
- package/dist/host/gates.d.ts +2 -1
- package/dist/host/gates.js +10 -2
- package/dist/host/main.js +54 -5
- package/dist/host/operate.js +67 -5
- package/dist/host/stack-config.d.ts +64 -0
- package/dist/host/stack-config.js +62 -2
- package/dist/keys/client.js +4 -1
- package/dist/keys/keys.d.ts +13 -6
- package/dist/keys/keys.js +9 -8
- package/dist/management-openapi.json +1264 -2171
- package/dist/mcp/connect.d.ts +26 -22
- package/dist/mcp/connect.js +30 -34
- package/dist/mcp/pool.d.ts +3 -6
- package/dist/mcp/pool.js +14 -28
- package/dist/mcp/preview.d.ts +3 -3
- package/dist/mcp/preview.js +10 -21
- package/dist/mcp/snapshot.d.ts +16 -24
- package/dist/mcp/snapshot.js +33 -45
- package/dist/model/capabilities.d.ts +26 -0
- package/dist/model/capabilities.js +44 -0
- package/dist/model/catalog.d.ts +34 -3
- package/dist/model/catalog.js +38 -2
- package/dist/model/models.d.ts +2 -0
- package/dist/model/models.js +1 -1
- package/dist/model/native-blocks.d.ts +35 -0
- package/dist/model/native-blocks.js +170 -0
- package/dist/model/pi-model.js +114 -94
- package/dist/openapi.json +12822 -3943
- package/dist/reads/types.d.ts +16 -3
- package/dist/record/append.d.ts +3 -1
- package/dist/record/append.js +1 -0
- package/dist/record/envelope.d.ts +5 -1
- package/dist/record/envelope.js +7 -3
- package/dist/record/environment.d.ts +25 -0
- package/dist/record/environment.js +44 -0
- package/dist/record/index.d.ts +1 -1
- package/dist/record/index.js +1 -1
- package/dist/sandbox/disk-fs.d.ts +95 -0
- package/dist/sandbox/disk-fs.js +206 -0
- package/dist/sandbox/join.d.ts +17 -18
- package/dist/sandbox/join.js +46 -15
- package/dist/sandbox/manager.d.ts +47 -21
- package/dist/sandbox/manager.js +162 -107
- package/dist/sandbox/placement.d.ts +14 -13
- package/dist/sandbox/placement.js +31 -14
- package/dist/sandbox/pods/client.d.ts +9 -2
- package/dist/sandbox/pods/lifecycle.d.ts +36 -27
- package/dist/sandbox/pods/lifecycle.js +29 -26
- package/dist/sandbox/pods/reconcile.js +18 -12
- package/dist/sandbox/pods/spec.d.ts +15 -13
- package/dist/sandbox/pods/spec.js +17 -20
- package/dist/sandbox/records.d.ts +2 -2
- package/dist/sandbox/records.js +3 -3
- package/dist/sandbox/resolve.d.ts +37 -27
- package/dist/sandbox/resolve.js +73 -41
- package/dist/sandbox/runtime-tools.d.ts +9 -0
- package/dist/sandbox/runtime-tools.js +16 -0
- package/dist/sandbox/session-sandbox.d.ts +4 -6
- package/dist/sandbox/session-sandbox.js +47 -94
- package/dist/sandbox/setup.d.ts +64 -0
- package/dist/sandbox/setup.js +160 -0
- package/dist/sandbox/share.d.ts +21 -26
- package/dist/sandbox/share.js +36 -63
- package/dist/sandbox/skills.d.ts +8 -6
- package/dist/sandbox/skills.js +24 -23
- package/dist/sandbox/tenant-config.d.ts +12 -14
- package/dist/sandbox/tenant-config.js +12 -17
- package/dist/sandbox/types.d.ts +6 -0
- package/dist/store/postgres/drizzle/0020_environments.sql +63 -0
- package/dist/store/postgres/drizzle/0021_disk_versions.sql +7 -0
- package/dist/store/postgres/drizzle/0022_model_usage_timing.sql +5 -0
- package/dist/store/postgres/drizzle/0023_artifact_pages.sql +1 -0
- package/dist/store/postgres/drizzle/0024_flat_manifest.sql +7 -0
- package/dist/store/postgres/drizzle/0025_environment_manifests.sql +14 -0
- package/dist/store/postgres/drizzle/0026_environment_forks.sql +1 -0
- package/dist/store/postgres/drizzle/0027_environment_image_digest.sql +1 -0
- package/dist/store/postgres/drizzle/meta/_journal.json +56 -0
- package/dist/store/postgres/reads.js +123 -17
- package/dist/store/postgres/record-writer.d.ts +4 -4
- package/dist/store/postgres/record-writer.js +9 -9
- package/dist/store/postgres/record.d.ts +7 -0
- package/dist/store/postgres/record.js +23 -2
- package/dist/store/postgres/schema.d.ts +1126 -736
- package/dist/store/postgres/schema.js +136 -73
- package/dist/store/postgres/store.js +195 -67
- package/dist/store/types.d.ts +165 -50
- package/dist/tenant/advance.d.ts +7 -6
- package/dist/tenant/advance.js +118 -42
- package/dist/tenant/artifact-tool.d.ts +1 -1
- package/dist/tenant/artifact-tool.js +46 -26
- package/dist/tenant/commands.d.ts +15 -2
- package/dist/tenant/commands.js +115 -14
- package/dist/tenant/context.d.ts +56 -24
- package/dist/tenant/context.js +4 -15
- package/dist/tenant/effects.d.ts +1 -1
- package/dist/tenant/effects.js +44 -29
- package/dist/tenant/environment-manifests.d.ts +57 -0
- package/dist/tenant/environment-manifests.js +200 -0
- package/dist/tenant/environments.d.ts +174 -0
- package/dist/tenant/environments.js +1044 -0
- package/dist/tenant/harness-tokens.d.ts +21 -0
- package/dist/tenant/harness-tokens.js +61 -0
- package/dist/tenant/host-token.d.ts +1 -1
- package/dist/tenant/host-token.js +2 -4
- package/dist/tenant/identity-file.d.ts +4 -4
- package/dist/tenant/identity-file.js +17 -8
- package/dist/tenant/issuers.js +3 -3
- package/dist/tenant/model-setting.d.ts +14 -0
- package/dist/tenant/model-setting.js +51 -0
- package/dist/tenant/principals.js +1 -1
- package/dist/tenant/reset.js +3 -3
- package/dist/tenant/runtime.d.ts +6 -1
- package/dist/tenant/runtime.js +7 -1
- package/dist/tenant/session.d.ts +3 -3
- package/dist/tenant/session.js +0 -14
- package/dist/tenant/sessions.d.ts +18 -14
- package/dist/tenant/sessions.js +99 -143
- package/dist/tenant/signing-keys.d.ts +5 -1
- package/dist/tenant/skill-tool.d.ts +3 -3
- package/dist/tenant/skill-tool.js +6 -11
- package/dist/tenant/status.js +3 -3
- package/dist/tenant/sweep.js +13 -7
- package/dist/tenant/tool-results.js +1 -1
- package/dist/tenant/tool-search.d.ts +7 -11
- package/dist/tenant/tool-search.js +8 -5
- package/dist/tenant/transcript.d.ts +245 -36
- package/dist/tenant/transcript.js +64 -12
- package/dist/tenant/turn-timing.d.ts +43 -0
- package/dist/tenant/turn-timing.js +39 -0
- package/dist/tenant/types.d.ts +2 -0
- package/dist/traces/ids.d.ts +41 -0
- package/dist/traces/ids.js +81 -0
- package/dist/traces/key.d.ts +6 -0
- package/dist/traces/key.js +37 -0
- package/dist/traces/map.d.ts +113 -0
- package/dist/traces/map.js +289 -0
- package/dist/traces/options.d.ts +7 -0
- package/dist/traces/options.js +1 -0
- package/dist/traces/otlp.d.ts +42 -0
- package/dist/traces/otlp.js +84 -0
- package/dist/traces/projector.d.ts +58 -0
- package/dist/traces/projector.js +200 -0
- package/dist/traces/read.d.ts +23 -0
- package/dist/traces/read.js +16 -0
- package/dist/vault/coverage.d.ts +26 -0
- package/dist/vault/coverage.js +62 -0
- package/dist/vault/error.d.ts +11 -1
- package/dist/vault/error.js +5 -1
- package/dist/vault/host-model.d.ts +4 -1
- package/dist/vault/host-model.js +10 -2
- package/dist/vault/service.d.ts +92 -48
- package/dist/vault/service.js +462 -376
- package/dist/vault/sources.d.ts +5 -16
- package/dist/vault/sources.js +6 -8
- package/dist/version.d.ts +1 -1
- package/dist/version.js +2 -2
- package/package.json +13 -13
- package/dist/api/http/routes/sandboxes.d.ts +0 -9
- package/dist/api/http/routes/sandboxes.js +0 -153
- package/dist/api/http/routes/vaults.d.ts +0 -10
- package/dist/api/http/routes/vaults.js +0 -139
- package/dist/record/sandbox.d.ts +0 -25
- package/dist/record/sandbox.js +0 -43
- package/dist/sandbox/capability.d.ts +0 -3
- package/dist/sandbox/capability.js +0 -10
- package/dist/tenant/sandboxes.d.ts +0 -123
- package/dist/tenant/sandboxes.js +0 -566
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { SandboxBackend } from "../../sandbox/types.js";
|
|
1
|
+
import type { ExecRequest, ExecResult, SandboxBackend } from "../../sandbox/types.js";
|
|
2
2
|
export interface LocalBackendOptions {
|
|
3
3
|
/** Default `/workspace`. */
|
|
4
4
|
readonly workspace?: string;
|
|
@@ -6,5 +6,13 @@ export interface LocalBackendOptions {
|
|
|
6
6
|
readonly proxyEnv?: () => Readonly<Record<string, string>>;
|
|
7
7
|
/** The base environment of commands (the engine's allowlisted one); `NYLORUN_*` is dropped. */
|
|
8
8
|
readonly env: Readonly<Record<string, string | undefined>>;
|
|
9
|
+
/** The engine's `/harness` on the same volume, a disk root of its versions (R2d step 3). */
|
|
10
|
+
readonly harnessRoot?: string;
|
|
9
11
|
}
|
|
12
|
+
/**
|
|
13
|
+
* How the pod runs a command: `/bin/sh -c` in its own process group, with the base environment,
|
|
14
|
+
* the request's variables and the proxy of the moment. The sandbox tools and the disk's setup
|
|
15
|
+
* (`harness/setup.ts`) both run commands this way.
|
|
16
|
+
*/
|
|
17
|
+
export declare function localExec(options: LocalBackendOptions): (request: ExecRequest, signal: AbortSignal) => Promise<ExecResult>;
|
|
10
18
|
export declare function localBackend(options: LocalBackendOptions): SandboxBackend;
|
|
@@ -49,10 +49,14 @@ function collector() {
|
|
|
49
49
|
},
|
|
50
50
|
};
|
|
51
51
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
52
|
+
/**
|
|
53
|
+
* How the pod runs a command: `/bin/sh -c` in its own process group, with the base environment,
|
|
54
|
+
* the request's variables and the proxy of the moment. The sandbox tools and the disk's setup
|
|
55
|
+
* (`harness/setup.ts`) both run commands this way.
|
|
56
|
+
*/
|
|
57
|
+
export function localExec(options) {
|
|
58
|
+
const env = baseEnv(options.env, options.workspace ?? SANDBOX_WORKSPACE);
|
|
59
|
+
return (request, signal) => new Promise((resolve, reject) => {
|
|
56
60
|
if (signal.aborted)
|
|
57
61
|
return reject(signal.reason ?? new Error("Turn cancelled"));
|
|
58
62
|
const child = spawn("/bin/sh", ["-c", request.command], {
|
|
@@ -102,10 +106,18 @@ export function localBackend(options) {
|
|
|
102
106
|
});
|
|
103
107
|
});
|
|
104
108
|
});
|
|
109
|
+
}
|
|
110
|
+
export function localBackend(options) {
|
|
111
|
+
const workspace = options.workspace ?? SANDBOX_WORKSPACE;
|
|
112
|
+
const exec = localExec(options);
|
|
105
113
|
const missing = (error) => error?.code === "ENOENT" || error?.code === "ENOTDIR";
|
|
106
114
|
const handle = {
|
|
107
115
|
workspace,
|
|
108
116
|
exec,
|
|
117
|
+
diskRoots: {
|
|
118
|
+
workspace,
|
|
119
|
+
...(options.harnessRoot === undefined ? {} : { harness: options.harnessRoot }),
|
|
120
|
+
},
|
|
109
121
|
async readFile(path) {
|
|
110
122
|
try {
|
|
111
123
|
return await readFile(path, "utf8");
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Virtual backend: an emulated bash with a virtual filesystem, running in the
|
|
2
|
+
* Virtual backend: an emulated bash with a virtual filesystem, running in the harness's process
|
|
3
|
+
* (the harness container, or the Runtime process with NYLORUN_HARNESS=in-process).
|
|
3
4
|
* `/workspace` is backed by a host directory so files survive stop and Runtime restarts.
|
|
4
5
|
* It is not a VM boundary; it exists so the first run and CI work on any machine.
|
|
5
6
|
*/
|
|
@@ -67,6 +68,8 @@ export function virtualBackend(options) {
|
|
|
67
68
|
...(network ? { network } : {}),
|
|
68
69
|
});
|
|
69
70
|
return {
|
|
71
|
+
// A virtual workspace's disk is its host directory: `/workspace` only.
|
|
72
|
+
diskRoots: { workspace: root },
|
|
70
73
|
async exec(request, signal) {
|
|
71
74
|
const controller = new AbortController();
|
|
72
75
|
let timedOut = false;
|
package/dist/api/a2a/card.d.ts
CHANGED
|
@@ -12,4 +12,14 @@ export interface CardDefinition {
|
|
|
12
12
|
};
|
|
13
13
|
readonly implementationVersion: string;
|
|
14
14
|
}
|
|
15
|
-
|
|
15
|
+
import type { ModelInput } from "@nylorun/core/contracts";
|
|
16
|
+
/**
|
|
17
|
+
* The input modes for a model that reads `inputs`: text and JSON, then the media types of the
|
|
18
|
+
* files it reads. No inputs known (no model in the vault): text and JSON.
|
|
19
|
+
*/
|
|
20
|
+
export declare function inputModesFor(inputs: readonly ModelInput[] | undefined): string[];
|
|
21
|
+
/**
|
|
22
|
+
* `inputModes`: the media types the agent takes, when its model reads files; default text and
|
|
23
|
+
* JSON. The output modes stay text and JSON.
|
|
24
|
+
*/
|
|
25
|
+
export declare function agentCard(agentId: string, definition: CardDefinition, inputModes?: readonly string[]): Record<string, unknown>;
|
package/dist/api/a2a/card.js
CHANGED
|
@@ -4,8 +4,32 @@
|
|
|
4
4
|
* `supportedInterfaces` is empty: whoever publishes the card (the gateway in v1) adds its own
|
|
5
5
|
* URL, provider and security schemes.
|
|
6
6
|
*/
|
|
7
|
+
import { MODEL_IMAGE_TYPES } from "../../artifacts/media-types.js";
|
|
7
8
|
const MODES = ["text/plain", "application/json"];
|
|
8
|
-
|
|
9
|
+
/** The media types a card advertises for each file input a model reads. */
|
|
10
|
+
const INPUT_MODES = {
|
|
11
|
+
image: MODEL_IMAGE_TYPES,
|
|
12
|
+
pdf: ["application/pdf"],
|
|
13
|
+
office: [
|
|
14
|
+
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
15
|
+
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
16
|
+
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
17
|
+
],
|
|
18
|
+
audio: ["audio/*"],
|
|
19
|
+
video: ["video/*"],
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* The input modes for a model that reads `inputs`: text and JSON, then the media types of the
|
|
23
|
+
* files it reads. No inputs known (no model in the vault): text and JSON.
|
|
24
|
+
*/
|
|
25
|
+
export function inputModesFor(inputs) {
|
|
26
|
+
return [...MODES, ...(inputs ?? []).flatMap((input) => (input === "text" ? [] : INPUT_MODES[input]))];
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* `inputModes`: the media types the agent takes, when its model reads files; default text and
|
|
30
|
+
* JSON. The output modes stay text and JSON.
|
|
31
|
+
*/
|
|
32
|
+
export function agentCard(agentId, definition, inputModes = MODES) {
|
|
9
33
|
const { manifest } = definition;
|
|
10
34
|
const name = typeof manifest.name === "string" && manifest.name !== "" ? manifest.name : agentId;
|
|
11
35
|
const description = typeof manifest.description === "string" && manifest.description !== ""
|
|
@@ -17,7 +41,7 @@ export function agentCard(agentId, definition) {
|
|
|
17
41
|
supportedInterfaces: [],
|
|
18
42
|
version: definition.implementationVersion,
|
|
19
43
|
capabilities: { streaming: false, pushNotifications: false, extendedAgentCard: false },
|
|
20
|
-
defaultInputModes:
|
|
44
|
+
defaultInputModes: [...inputModes],
|
|
21
45
|
defaultOutputModes: MODES,
|
|
22
46
|
skills: [{ id: agentId, name, description, tags: [] }],
|
|
23
47
|
};
|
package/dist/api/a2a/endpoint.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { readText } from "../http/body.js";
|
|
3
|
-
import {
|
|
3
|
+
import { INLINE_BODY_MAX_BYTES } from "../inbound-files.js";
|
|
4
|
+
import { TraceHeaders, tenantRoute } from "../http/define.js";
|
|
5
|
+
import { traceContextOf } from "../../traces/ids.js";
|
|
4
6
|
import { jsonResponse } from "../http/respond.js";
|
|
5
7
|
import { a2aCall, a2aCard } from "./routes.js";
|
|
6
8
|
const A2A_SPECIFICATION = {
|
|
@@ -55,7 +57,7 @@ export function a2aRoutes(api) {
|
|
|
55
57
|
path: "/v1/a2a/agents/{agentId}",
|
|
56
58
|
tags: TAGS,
|
|
57
59
|
summary: "Call an agent over A2A",
|
|
58
|
-
description: "A2A 1.0 JSON-RPC, for a subject: `SendMessage` (blocking up to 5 minutes, or `returnImmediately`), `GetTask` and `CancelTask`. A context is one session per subject, agent and `contextId`; a task is one turn. JSON-RPC errors are answered with 200; limits, scopes and unavailable streams stay HTTP errors.",
|
|
60
|
+
description: "A2A 1.0 JSON-RPC, for a subject: `SendMessage` (blocking up to 5 minutes, or `returnImmediately`), `GetTask` and `CancelTask`. A message is text, one data part, or text and `raw` files (at most 20 MiB each, stored as artifacts of the context's session); a file `url` is `ContentTypeNotSupportedError`. A context is one session per subject, agent and `contextId`; a task is one turn. JSON-RPC errors are answered with 200; limits, scopes and unavailable streams stay HTTP errors.",
|
|
59
61
|
externalDocs: A2A_SPECIFICATION,
|
|
60
62
|
request: {
|
|
61
63
|
params: agentId,
|
|
@@ -63,6 +65,7 @@ export function a2aRoutes(api) {
|
|
|
63
65
|
"A2A-Version": z.string().optional().meta({ description: "Or the `A2A-Version` header: `1.0`" }),
|
|
64
66
|
}),
|
|
65
67
|
body: { required: true, content: { "application/json": { schema: JsonRpcRequest } } },
|
|
68
|
+
headers: TraceHeaders,
|
|
66
69
|
},
|
|
67
70
|
responses: {
|
|
68
71
|
200: {
|
|
@@ -73,9 +76,12 @@ export function a2aRoutes(api) {
|
|
|
73
76
|
}, async (c) => {
|
|
74
77
|
const scope = c.get("scope");
|
|
75
78
|
const version = c.env.incoming.headers["a2a-version"];
|
|
79
|
+
const trace = traceContextOf(c.req.raw.headers);
|
|
76
80
|
return jsonResponse(200, await a2aCall(c.env.tenant, scope, c.req.param("agentId"), {
|
|
77
|
-
|
|
81
|
+
// A message may carry files inline.
|
|
82
|
+
body: () => readText(c.req.raw, INLINE_BODY_MAX_BYTES),
|
|
78
83
|
version: typeof version === "string" ? version : (c.req.query("A2A-Version") ?? null),
|
|
84
|
+
...(trace ? { trace } : {}),
|
|
79
85
|
}, c.env.outgoing));
|
|
80
86
|
});
|
|
81
87
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* A2A 1.0 over JSON-RPC: the wire types the Runtime answers with, the envelope, the error
|
|
3
3
|
* codes and the parameter checks (Host feature `a2a-endpoint`). Only what v1 serves is typed:
|
|
4
|
-
* text and
|
|
4
|
+
* text, data and file parts, tasks, the card. Field names are the spec's ProtoJSON (camelCase,
|
|
5
5
|
* enum values such as `TASK_STATE_WORKING`). No Tenant state is read here.
|
|
6
6
|
*/
|
|
7
7
|
/** The A2A version this endpoint speaks (`A2A-Version`, Major.Minor). */
|
|
@@ -16,6 +16,16 @@ export type Part = {
|
|
|
16
16
|
data: unknown;
|
|
17
17
|
mediaType?: string;
|
|
18
18
|
metadata?: Record<string, unknown>;
|
|
19
|
+
} | {
|
|
20
|
+
raw: string;
|
|
21
|
+
mediaType?: string;
|
|
22
|
+
filename?: string;
|
|
23
|
+
metadata?: Record<string, unknown>;
|
|
24
|
+
} | {
|
|
25
|
+
url: string;
|
|
26
|
+
mediaType?: string;
|
|
27
|
+
filename?: string;
|
|
28
|
+
metadata?: Record<string, unknown>;
|
|
19
29
|
};
|
|
20
30
|
export interface Message {
|
|
21
31
|
messageId: string;
|
|
@@ -120,11 +130,24 @@ export declare function parseEnvelope(text: string): {
|
|
|
120
130
|
};
|
|
121
131
|
/** `A2A-Version` as Major.Minor; empty means 0.3 (spec §3.6.2). */
|
|
122
132
|
export declare function checkVersion(value: string | null | undefined): void;
|
|
123
|
-
/** A
|
|
133
|
+
/** A file sent inline (`raw`, base64): stored as an artifact of the context's session. */
|
|
134
|
+
export interface RawFile {
|
|
135
|
+
readonly raw: string;
|
|
136
|
+
readonly mediaType?: string;
|
|
137
|
+
readonly filename?: string;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* A client's message reduced to what the Runtime runs: text, one JSON value, or text and files
|
|
141
|
+
* sent inline.
|
|
142
|
+
*/
|
|
124
143
|
export type MessageInput = {
|
|
125
144
|
content: string;
|
|
126
145
|
} | {
|
|
127
146
|
data: unknown;
|
|
147
|
+
} | {
|
|
148
|
+
files: readonly ({
|
|
149
|
+
text: string;
|
|
150
|
+
} | RawFile)[];
|
|
128
151
|
};
|
|
129
152
|
export interface SendParams {
|
|
130
153
|
messageId: string;
|
package/dist/api/a2a/protocol.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* A2A 1.0 over JSON-RPC: the wire types the Runtime answers with, the envelope, the error
|
|
3
3
|
* codes and the parameter checks (Host feature `a2a-endpoint`). Only what v1 serves is typed:
|
|
4
|
-
* text and
|
|
4
|
+
* text, data and file parts, tasks, the card. Field names are the spec's ProtoJSON (camelCase,
|
|
5
5
|
* enum values such as `TASK_STATE_WORKING`). No Tenant state is read here.
|
|
6
6
|
*/
|
|
7
7
|
/** The A2A version this endpoint speaks (`A2A-Version`, Major.Minor). */
|
|
@@ -136,7 +136,12 @@ function partKind(part, field) {
|
|
|
136
136
|
return a2aFail("invalidParams", `${field} must contain exactly one of text, raw, url, data`, {}, field);
|
|
137
137
|
if (kinds[0] === "text" && typeof part.text !== "string")
|
|
138
138
|
return a2aFail("invalidParams", `${field}.text must be a string`, {}, `${field}.text`);
|
|
139
|
-
|
|
139
|
+
if (kinds[0] === "raw" && typeof part.raw !== "string")
|
|
140
|
+
return a2aFail("invalidParams", `${field}.raw must be base64 bytes`, {}, `${field}.raw`);
|
|
141
|
+
for (const key of ["mediaType", "filename"])
|
|
142
|
+
if (part[key] !== undefined && typeof part[key] !== "string")
|
|
143
|
+
return a2aFail("invalidParams", `${field}.${key} must be a string`, {}, `${field}.${key}`);
|
|
144
|
+
return kinds[0] === "raw" ? "raw" : kinds[0] === "url" ? "url" : kinds[0];
|
|
140
145
|
}
|
|
141
146
|
/** `SendMessage` params (spec §3.2.1), checked in the order a client can fix them. */
|
|
142
147
|
export function parseSendParams(params) {
|
|
@@ -166,8 +171,20 @@ export function parseSendParams(params) {
|
|
|
166
171
|
input = { content: parts.map((part) => part.text).join("\n") };
|
|
167
172
|
else if (kinds.length === 1 && kinds[0] === "data")
|
|
168
173
|
input = { data: parts[0].data };
|
|
174
|
+
else if (kinds.includes("url"))
|
|
175
|
+
return a2aFail("contentTypeNotSupported", "File URLs are not fetched yet; send the bytes inline as raw", { accepted: "raw" });
|
|
176
|
+
else if (kinds.includes("data"))
|
|
177
|
+
return a2aFail("contentTypeNotSupported", "This agent accepts text and file parts, or a single data part", { accepted: "text/plain,application/json" });
|
|
169
178
|
else
|
|
170
|
-
|
|
179
|
+
input = {
|
|
180
|
+
files: parts.map((part, index) => kinds[index] === "text"
|
|
181
|
+
? { text: part.text }
|
|
182
|
+
: {
|
|
183
|
+
raw: part.raw,
|
|
184
|
+
...(part.mediaType !== undefined ? { mediaType: part.mediaType } : {}),
|
|
185
|
+
...(part.filename !== undefined ? { filename: part.filename } : {}),
|
|
186
|
+
}),
|
|
187
|
+
};
|
|
171
188
|
if ("content" in input && input.content.trim() === "")
|
|
172
189
|
a2aFail("invalidParams", "The message has no text", {}, "message.parts");
|
|
173
190
|
return {
|
package/dist/api/a2a/routes.d.ts
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import type { ServerResponse } from "node:http";
|
|
2
2
|
import { type AuthScope, type TenantContext } from "../../tenant/context.js";
|
|
3
|
+
import type { TraceContext } from "../../traces/ids.js";
|
|
3
4
|
/**
|
|
4
5
|
* How long a blocking `SendMessage` waits for the task to end or ask for input. After it, the
|
|
5
6
|
* task is returned `WORKING` and keeps running; the caller polls `GetTask`.
|
|
6
7
|
*/
|
|
7
8
|
export declare const A2A_BLOCKING_WAIT_MS: number;
|
|
8
|
-
/**
|
|
9
|
+
/**
|
|
10
|
+
* `GET /v1/a2a/agents/:agent/card`: the agent's card, for a caller who may use the agent. Its
|
|
11
|
+
* input modes name the files the Tenant's model reads.
|
|
12
|
+
*/
|
|
9
13
|
export declare function a2aCard(ctx: TenantContext, scope: AuthScope, agentId: string): Promise<unknown>;
|
|
10
14
|
/**
|
|
11
15
|
* `POST /v1/a2a/agents/:agent`: one A2A JSON-RPC call, for a subject. JSON-RPC answers (errors
|
|
@@ -16,4 +20,5 @@ export declare function a2aCard(ctx: TenantContext, scope: AuthScope, agentId: s
|
|
|
16
20
|
export declare function a2aCall(ctx: TenantContext, scope: AuthScope, agentId: string, input: {
|
|
17
21
|
body: () => Promise<string>;
|
|
18
22
|
version: string | null;
|
|
23
|
+
trace?: TraceContext;
|
|
19
24
|
}, response: ServerResponse): Promise<unknown>;
|
package/dist/api/a2a/routes.js
CHANGED
|
@@ -9,13 +9,20 @@
|
|
|
9
9
|
* caller's scope (limits and owner checks apply), sessions derived from the caller's own
|
|
10
10
|
* subject, history from Durable Streams. A task id from the client only selects a context and
|
|
11
11
|
* a turn within the caller's own sessions, so another subject's task is `TaskNotFoundError`.
|
|
12
|
+
*
|
|
13
|
+
* A message's `raw` file parts are stored as artifacts of the context's session once it is open,
|
|
14
|
+
* and the message goes in as `parts` (`../inbound-files.ts`): a file the model does not read is
|
|
15
|
+
* `ContentTypeNotSupportedError`, and the files of a refused message are deleted.
|
|
12
16
|
*/
|
|
13
17
|
import { randomUUID } from "node:crypto";
|
|
14
18
|
import { SessionCommandSchema, } from "@nylorun/core/contracts";
|
|
15
19
|
import { commandKey } from "../../core/flow-host.js";
|
|
16
|
-
import {
|
|
20
|
+
import { contentTypeFor } from "../../artifacts/media-types.js";
|
|
21
|
+
import { discardFiles, inlineFileName, messageParts, } from "../inbound-files.js";
|
|
22
|
+
import { agentCard, inputModesFor } from "./card.js";
|
|
17
23
|
import { A2aError, a2aFail, checkVersion, jsonRpcError, jsonRpcResult, parseEnvelope, parseSendParams, parseTaskParams, } from "./protocol.js";
|
|
18
24
|
import { SETTLED_EVENTS, buildTask, contextSessionId, newContextId, parseTaskId, pendingInteraction, taskIdOf, } from "./tasks.js";
|
|
25
|
+
import { currentModelInputs } from "../../model/catalog.js";
|
|
19
26
|
import { accessOf } from "../../tenant/auth.js";
|
|
20
27
|
import { command } from "../../tenant/commands.js";
|
|
21
28
|
import { loadSession, } from "../../tenant/context.js";
|
|
@@ -27,9 +34,14 @@ import { putSession } from "../../tenant/sessions.js";
|
|
|
27
34
|
* task is returned `WORKING` and keeps running; the caller polls `GetTask`.
|
|
28
35
|
*/
|
|
29
36
|
export const A2A_BLOCKING_WAIT_MS = 5 * 60_000;
|
|
30
|
-
/**
|
|
37
|
+
/**
|
|
38
|
+
* `GET /v1/a2a/agents/:agent/card`: the agent's card, for a caller who may use the agent. Its
|
|
39
|
+
* input modes name the files the Tenant's model reads.
|
|
40
|
+
*/
|
|
31
41
|
export async function a2aCard(ctx, scope, agentId) {
|
|
32
|
-
|
|
42
|
+
const definition = await definitionFor(ctx, agentId, accessOf(scope));
|
|
43
|
+
// The files a caller may send: those the Tenant's model reads now.
|
|
44
|
+
return agentCard(agentId, definition, inputModesFor(await currentModelInputs(ctx)));
|
|
33
45
|
}
|
|
34
46
|
/**
|
|
35
47
|
* `POST /v1/a2a/agents/:agent`: one A2A JSON-RPC call, for a subject. JSON-RPC answers (errors
|
|
@@ -50,7 +62,7 @@ export async function a2aCall(ctx, scope, agentId, input, response) {
|
|
|
50
62
|
// A blocking call stops waiting when the caller hangs up; the task keeps running.
|
|
51
63
|
const hangup = new AbortController();
|
|
52
64
|
response.on("close", () => hangup.abort());
|
|
53
|
-
const caller = { ctx, scope, access, agentId };
|
|
65
|
+
const caller = { ctx, scope, access, agentId, ...(input.trace ? { trace: input.trace } : {}) };
|
|
54
66
|
const { id } = envelope.request;
|
|
55
67
|
try {
|
|
56
68
|
checkVersion(input.version);
|
|
@@ -60,7 +72,7 @@ export async function a2aCall(ctx, scope, agentId, input, response) {
|
|
|
60
72
|
if (error instanceof A2aError)
|
|
61
73
|
return jsonRpcError(id, error);
|
|
62
74
|
// Limits, scopes and unavailable streams stay HTTP answers, as on every Tenant route.
|
|
63
|
-
if (error instanceof HttpError && [403, 429, 503].includes(error.status))
|
|
75
|
+
if (error instanceof HttpError && [403, 413, 429, 503].includes(error.status))
|
|
64
76
|
throw error;
|
|
65
77
|
ctx.config.logger.warn("a2a request failed", {
|
|
66
78
|
agentId,
|
|
@@ -137,11 +149,18 @@ async function run(caller, sessionId, input,
|
|
|
137
149
|
/** The error for a `409` conflict: the session is busy or no longer waiting. */
|
|
138
150
|
conflict) {
|
|
139
151
|
try {
|
|
140
|
-
const accepted = (await command(caller.ctx, sessionId, SessionCommandSchema.parse(input), caller.scope));
|
|
152
|
+
const accepted = (await command(caller.ctx, sessionId, SessionCommandSchema.parse(input), caller.scope, caller.trace ? { trace: caller.trace } : {}));
|
|
141
153
|
return { turnId: accepted.turnId, cursor: accepted.cursor };
|
|
142
154
|
}
|
|
143
155
|
catch (error) {
|
|
144
|
-
if (!(error instanceof HttpError)
|
|
156
|
+
if (!(error instanceof HttpError))
|
|
157
|
+
throw error;
|
|
158
|
+
// A file the agent's model does not read (`attachment_unsupported`).
|
|
159
|
+
if (error.status === 422)
|
|
160
|
+
return a2aFail("contentTypeNotSupported", error.message, {
|
|
161
|
+
reason: error.rejection.code ?? "request_rejected",
|
|
162
|
+
});
|
|
163
|
+
if (error.status !== 409)
|
|
145
164
|
throw error;
|
|
146
165
|
if (error.message.startsWith("Idempotency key"))
|
|
147
166
|
return a2aFail("invalidParams", "message.messageId was already used for a different message", {}, "message.messageId");
|
|
@@ -162,13 +181,17 @@ async function openContext(caller, sessionId) {
|
|
|
162
181
|
async function sendMessage(caller, params, signal) {
|
|
163
182
|
const send = parseSendParams(params);
|
|
164
183
|
const idempotencyKey = `a2a:${send.messageId}`;
|
|
165
|
-
const value = "content" in send.input ? send.input.content : send.input.data;
|
|
166
184
|
let contextId;
|
|
167
185
|
let sessionId;
|
|
168
186
|
let accepted;
|
|
169
187
|
if (send.taskId !== undefined) {
|
|
170
188
|
// A reply on an existing task: only a question it is waiting on can take it.
|
|
171
189
|
const ids = parseTaskId(send.taskId) ?? taskNotFound(send.taskId);
|
|
190
|
+
const value = "content" in send.input
|
|
191
|
+
? send.input.content
|
|
192
|
+
: "data" in send.input
|
|
193
|
+
? send.input.data
|
|
194
|
+
: a2aFail("contentTypeNotSupported", "A reply takes text or one data part; send files in a new message", { accepted: "text/plain,application/json" });
|
|
172
195
|
if (send.contextId !== undefined && send.contextId !== ids.contextId)
|
|
173
196
|
a2aFail("invalidParams", "message.contextId does not match the task's context", {}, "message.contextId");
|
|
174
197
|
contextId = ids.contextId;
|
|
@@ -198,17 +221,21 @@ async function sendMessage(caller, params, signal) {
|
|
|
198
221
|
sessionId = contextSessionId(caller.access.owner, caller.agentId, contextId);
|
|
199
222
|
await openContext(caller, sessionId);
|
|
200
223
|
const context = contextId;
|
|
224
|
+
const { body, stored } = await messageBody(caller, sessionId, idempotencyKey, send.input);
|
|
201
225
|
accepted = await run(caller, sessionId, {
|
|
202
226
|
type: "message",
|
|
203
227
|
requestId: randomUUID(),
|
|
204
228
|
idempotencyKey,
|
|
205
|
-
...
|
|
229
|
+
...body,
|
|
206
230
|
}, async () => {
|
|
207
231
|
const active = (await contextSession(caller, sessionId))?.activeTurnId;
|
|
208
232
|
const taskId = active ? taskIdOf(context, active) : undefined;
|
|
209
233
|
return taskId
|
|
210
234
|
? new A2aError("unsupportedOperation", `The context has a task in progress (${taskId}); reply to it or cancel it`, { taskId })
|
|
211
235
|
: new A2aError("unsupportedOperation", "The context has unresolved work; start a new context");
|
|
236
|
+
}).catch(async (error) => {
|
|
237
|
+
await discardFiles(caller.ctx, stored, caller.access);
|
|
238
|
+
throw error;
|
|
212
239
|
});
|
|
213
240
|
}
|
|
214
241
|
if (!send.returnImmediately)
|
|
@@ -216,6 +243,37 @@ async function sendMessage(caller, params, signal) {
|
|
|
216
243
|
const { task } = await ownedTask(caller, taskIdOf(contextId, accepted.turnId), send.historyLength);
|
|
217
244
|
return { task };
|
|
218
245
|
}
|
|
246
|
+
/**
|
|
247
|
+
* A new message's body: its text, its JSON value, or parts with its `raw` files stored as
|
|
248
|
+
* artifacts of the session (`stored`: the ones this call created). A file the Runtime cannot
|
|
249
|
+
* store is `InvalidParamsError`; one past a limit stays an HTTP `413`.
|
|
250
|
+
*/
|
|
251
|
+
async function messageBody(caller, sessionId, idempotencyKey, input) {
|
|
252
|
+
if (!("files" in input))
|
|
253
|
+
return { body: input, stored: [] };
|
|
254
|
+
let files = 0;
|
|
255
|
+
const inbound = input.files.map((part) => {
|
|
256
|
+
if ("text" in part)
|
|
257
|
+
return { type: "text", text: part.text };
|
|
258
|
+
const index = files++;
|
|
259
|
+
const mediaType = part.mediaType ?? (part.filename !== undefined ? contentTypeFor(part.filename) : "application/octet-stream");
|
|
260
|
+
return {
|
|
261
|
+
type: "inline",
|
|
262
|
+
name: part.filename ?? inlineFileName(mediaType, index),
|
|
263
|
+
mediaType,
|
|
264
|
+
data: part.raw,
|
|
265
|
+
};
|
|
266
|
+
});
|
|
267
|
+
try {
|
|
268
|
+
const { parts, stored } = await messageParts(caller.ctx, sessionId, idempotencyKey, inbound, caller.access);
|
|
269
|
+
return { body: { parts }, stored };
|
|
270
|
+
}
|
|
271
|
+
catch (error) {
|
|
272
|
+
if (error instanceof HttpError && error.status === 400)
|
|
273
|
+
return a2aFail("invalidParams", error.message, {}, "message.parts");
|
|
274
|
+
throw error;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
219
277
|
/** The interaction a reply answers: the task must be active and paused on a question. */
|
|
220
278
|
async function replyTarget(caller, session, sessionId, turnId, taskId) {
|
|
221
279
|
if (session.activeTurnId !== turnId) {
|
package/dist/api/a2a/tasks.js
CHANGED
|
@@ -97,7 +97,8 @@ export function historyOf(events, contextId, taskId) {
|
|
|
97
97
|
add(clientMessageId(event), "ROLE_USER", [valuePart(payload.value)]);
|
|
98
98
|
break;
|
|
99
99
|
case "message.assistant":
|
|
100
|
-
// Agents used as tools speak inside the turn; only the agent's own words are history.
|
|
100
|
+
// Agents used as tools speak inside the turn; only the agent's own words are history. A
|
|
101
|
+
// step that said nothing (`text: ""`, R7.1) is none.
|
|
101
102
|
if (!payload.agent && typeof payload.text === "string" && payload.text.trim() !== "")
|
|
102
103
|
add(event.eventId, "ROLE_AGENT", [{ text: payload.text }]);
|
|
103
104
|
break;
|
|
@@ -2,7 +2,9 @@ import { RESPONSE_ALREADY_SENT } from "@hono/node-server/utils/response";
|
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { EventSchemas, MessageSchema, RunAgentInputSchema } from "@ag-ui/core/schemas";
|
|
4
4
|
import { readJson } from "../http/body.js";
|
|
5
|
-
import {
|
|
5
|
+
import { INLINE_BODY_MAX_BYTES } from "../inbound-files.js";
|
|
6
|
+
import { TraceHeaders, tenantRoute } from "../http/define.js";
|
|
7
|
+
import { traceContextOf } from "../../traces/ids.js";
|
|
6
8
|
import { jsonResponse } from "../http/respond.js";
|
|
7
9
|
import { agUiCaller, cancelRun, reattachRun, startRun, threadMessages } from "./routes.js";
|
|
8
10
|
import { sessionIdFor } from "./session-id.js";
|
|
@@ -49,18 +51,21 @@ export function agUiRoutes(api) {
|
|
|
49
51
|
path: "/v1/ag-ui/agents/{agentId}",
|
|
50
52
|
tags: TAGS,
|
|
51
53
|
summary: "Run an agent",
|
|
52
|
-
description: "For a person, named by a trusted issuer's token or by `Nylorun-Subject`. The thread is one session per person, agent and thread, created on its first run with `forwardedProps.nylorun.session`. The trailing user message starts a turn (its id is the idempotency key); `resume` answers interrupts. A busy session is a `RUN_ERROR` in the stream.",
|
|
54
|
+
description: "For a person, named by a trusted issuer's token or by `Nylorun-Subject`. The thread is one session per person, agent and thread, created on its first run with `forwardedProps.nylorun.session`. The trailing user message starts a turn (its id is the idempotency key); its media parts are files: bytes sent inline (`data`, at most 20 MiB each) are stored as artifacts of the session, a `file` source with provider `nylorun` names an artifact, and a URL is refused. `resume` answers interrupts. A busy session, or a file the model does not read, is a `RUN_ERROR` in the stream.",
|
|
53
55
|
request: {
|
|
54
56
|
params: agentId,
|
|
55
57
|
body: {
|
|
56
58
|
required: true,
|
|
57
59
|
content: { "application/json": { schema: ref("AgUiRunAgentInput") } },
|
|
58
60
|
},
|
|
61
|
+
headers: TraceHeaders,
|
|
59
62
|
},
|
|
60
63
|
responses: { 200: events("The run's events, from RUN_STARTED to its end") },
|
|
61
64
|
}, async (c) => {
|
|
62
65
|
const { scope, subject, access } = callerOf(c);
|
|
63
|
-
await startRun(c.env.tenant, scope, c.req.param("agentId"), { subject, access },
|
|
66
|
+
await startRun(c.env.tenant, scope, c.req.param("agentId"), { subject, access },
|
|
67
|
+
// A message may carry files inline.
|
|
68
|
+
await readJson(c.req.raw, INLINE_BODY_MAX_BYTES), c.env.outgoing, traceContextOf(c.req.raw.headers));
|
|
64
69
|
return RESPONSE_ALREADY_SENT;
|
|
65
70
|
});
|
|
66
71
|
tenantRoute(api, FOR_A_PERSON, {
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* An AG-UI transcript rebuilt from a session's log, with the ids a live run gave each
|
|
3
|
-
* message (see `translate.ts`), usable as `HttpAgent`'s `initialMessages`.
|
|
3
|
+
* message (see `translate.ts`), usable as `HttpAgent`'s `initialMessages`. A user message with
|
|
4
|
+
* files has array content: its text parts, and each file as a media part whose source is the
|
|
5
|
+
* artifact (`{ type: "file", provider: "nylorun", value: <artifact id> }`), typed by the media
|
|
6
|
+
* type its `artifact.created` event recorded.
|
|
4
7
|
*/
|
|
5
8
|
import type { Message } from "@ag-ui/core";
|
|
6
9
|
import { type LiveEvent } from "@nylorun/core/contracts";
|
|
10
|
+
/** The `provider` of a file source that names one of the Tenant's artifacts. */
|
|
11
|
+
export declare const NYLORUN_FILE_PROVIDER = "nylorun";
|
|
7
12
|
export declare function messagesFromEvents(items: readonly LiveEvent[]): Message[];
|
|
@@ -1,10 +1,44 @@
|
|
|
1
|
-
import { parseTranscriptEvent } from "@nylorun/core/contracts";
|
|
1
|
+
import { isEmptyAssistantMessage, parseTranscriptEvent, } from "@nylorun/core/contracts";
|
|
2
2
|
import { outputText, toolResultOf } from "./translate.js";
|
|
3
|
+
/** The `provider` of a file source that names one of the Tenant's artifacts. */
|
|
4
|
+
export const NYLORUN_FILE_PROVIDER = "nylorun";
|
|
5
|
+
/** A file part as an AG-UI media part, its kind from its media type when the log has it. */
|
|
6
|
+
function filePart(artifactId, mediaType) {
|
|
7
|
+
const top = mediaType?.split("/")[0];
|
|
8
|
+
const type = top === "image" || top === "audio" || top === "video" ? top : "document";
|
|
9
|
+
return {
|
|
10
|
+
type,
|
|
11
|
+
source: {
|
|
12
|
+
type: "file",
|
|
13
|
+
value: artifactId,
|
|
14
|
+
provider: NYLORUN_FILE_PROVIDER,
|
|
15
|
+
...(mediaType ? { mimeType: mediaType } : {}),
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
/** A message's parts (protocol 6): its text, or text and media parts when it has files. */
|
|
20
|
+
function partsContent(parts, mediaTypes) {
|
|
21
|
+
if (!parts.some((part) => part.type === "file"))
|
|
22
|
+
return parts.flatMap((part) => (typeof part.text === "string" ? [part.text] : [])).join("\n");
|
|
23
|
+
return parts.flatMap((part) => part.type === "file" && typeof part.artifactId === "string"
|
|
24
|
+
? [filePart(part.artifactId, mediaTypes.get(part.artifactId))]
|
|
25
|
+
: typeof part.text === "string"
|
|
26
|
+
? [{ type: "text", text: part.text }]
|
|
27
|
+
: []);
|
|
28
|
+
}
|
|
3
29
|
export function messagesFromEvents(items) {
|
|
4
30
|
const messages = [];
|
|
5
31
|
const results = new Set();
|
|
32
|
+
// Each artifact's media type, from its events, for the files a message names.
|
|
33
|
+
const mediaTypes = new Map();
|
|
6
34
|
let sawAssistant = false;
|
|
7
35
|
for (const raw of items) {
|
|
36
|
+
if (raw.type === "artifact.created" || raw.type === "artifact.version.created") {
|
|
37
|
+
const p = (raw.payload ?? {});
|
|
38
|
+
if (typeof p.artifactId === "string" && typeof p.contentType === "string")
|
|
39
|
+
mediaTypes.set(p.artifactId, p.contentType);
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
8
42
|
if (raw.type === "command.message") {
|
|
9
43
|
const p = (raw.payload ?? {});
|
|
10
44
|
messages.push({
|
|
@@ -14,8 +48,7 @@ export function messagesFromEvents(items) {
|
|
|
14
48
|
content: typeof p.content === "string"
|
|
15
49
|
? p.content
|
|
16
50
|
: Array.isArray(p.parts)
|
|
17
|
-
?
|
|
18
|
-
p.parts.flatMap((part) => (typeof part.text === "string" ? [part.text] : [])).join("\n")
|
|
51
|
+
? partsContent(p.parts, mediaTypes)
|
|
19
52
|
: JSON.stringify(p.data),
|
|
20
53
|
});
|
|
21
54
|
sawAssistant = false;
|
|
@@ -37,6 +70,9 @@ export function messagesFromEvents(items) {
|
|
|
37
70
|
});
|
|
38
71
|
continue;
|
|
39
72
|
}
|
|
73
|
+
// A step that said nothing is in the log for its timing (R7.1), never a message.
|
|
74
|
+
if (event.type === "message.assistant" && isEmptyAssistantMessage(event.payload))
|
|
75
|
+
continue;
|
|
40
76
|
if (event.type === "message.assistant" && !event.payload.agent) {
|
|
41
77
|
const p = event.payload;
|
|
42
78
|
sawAssistant = true;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ServerResponse } from "node:http";
|
|
2
2
|
import { type Message } from "@ag-ui/core";
|
|
3
3
|
import { type AuthScope, type SessionAccess, type TenantContext } from "../../tenant/context.js";
|
|
4
|
+
import type { TraceContext } from "../../traces/ids.js";
|
|
4
5
|
/**
|
|
5
6
|
* Who an AG-UI request acts for, once its agent is known: a person (an issuer token's subject,
|
|
6
7
|
* or `Nylorun-Subject`), who may use the agent; else 400, or the 404 of a missing agent.
|
|
@@ -13,7 +14,9 @@ export declare function agUiCaller(scope: AuthScope, agentId: string): {
|
|
|
13
14
|
export declare function startRun(ctx: TenantContext, scope: AuthScope, agentId: string, caller: {
|
|
14
15
|
subject: string;
|
|
15
16
|
access: SessionAccess | undefined;
|
|
16
|
-
}, body: unknown, response: ServerResponse
|
|
17
|
+
}, body: unknown, response: ServerResponse,
|
|
18
|
+
/** The request's trace context (R7.4): the turn the run starts joins the caller's trace. */
|
|
19
|
+
trace?: TraceContext): Promise<void>;
|
|
17
20
|
/** `GET …/threads/:thread/messages`: the thread's messages; none before its first run. */
|
|
18
21
|
export declare function threadMessages(ctx: TenantContext, id: string, access: SessionAccess | undefined): Promise<Message[]>;
|
|
19
22
|
/**
|