@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
package/dist/tenant/advance.js
CHANGED
|
@@ -40,10 +40,13 @@
|
|
|
40
40
|
* already recorded; an `ownership.lost` leaves the session to the advance that owns it.
|
|
41
41
|
*/
|
|
42
42
|
import { runDurable, runFlowDurable, } from "@nylorun/harness/run";
|
|
43
|
+
import { redactTokens } from "../harness-api/redact.js";
|
|
43
44
|
import { aggregateWaits, cancelSiblingWork, isWorkflowManifest, wakeLinkedWorkflow, } from "../core/flow-host.js";
|
|
44
45
|
import { isOwnershipLost, ownedTx } from "../store/ownership.js";
|
|
45
46
|
import { endAdvance } from "./scheduler.js";
|
|
46
|
-
import {
|
|
47
|
+
import { harnessOf } from "./context.js";
|
|
48
|
+
import { HARNESS_ID } from "../sandbox/placement.js";
|
|
49
|
+
import { recoversModelCalls } from "./effects.js";
|
|
47
50
|
import { slimModelEffects } from "./slim.js";
|
|
48
51
|
import { applyUpdates, leanState, transcriptShadow, withTranscript, } from "./history.js";
|
|
49
52
|
import { command } from "./commands.js";
|
|
@@ -51,7 +54,8 @@ import { exportOutputs } from "../artifacts/export.js";
|
|
|
51
54
|
import { dropRunGrant, grantRun } from "./run-grants.js";
|
|
52
55
|
import { startHeartbeat } from "../harness-api/renew.js";
|
|
53
56
|
import { buildTurnStart, startSegment } from "../harness-api/start.js";
|
|
54
|
-
import {
|
|
57
|
+
import { touchEnvironment } from "./environments.js";
|
|
58
|
+
import { endTurn, pauseTurn } from "./turn-timing.js";
|
|
55
59
|
import { AdvanceAbort, AdvanceDeadlineError, abortKind, } from "./worker.js";
|
|
56
60
|
/**
|
|
57
61
|
* A segment stopped by a `shutdown` or `ownership.lost` abort before it settled. Never
|
|
@@ -179,28 +183,27 @@ export async function advance(ctx, id, signal) {
|
|
|
179
183
|
sessionId: id,
|
|
180
184
|
message: error instanceof Error ? error.message : String(error),
|
|
181
185
|
}));
|
|
182
|
-
//
|
|
183
|
-
|
|
184
|
-
|
|
186
|
+
// The Environment was used: `deleteAfter`, and a pod's idle time (D34), count from the end
|
|
187
|
+
// of each segment.
|
|
188
|
+
if (placed.environment !== undefined)
|
|
189
|
+
await touchEnvironment(ctx, placed.environment);
|
|
185
190
|
}
|
|
186
191
|
return result;
|
|
187
192
|
}
|
|
188
|
-
/** Why a pod
|
|
193
|
+
/** Why a pod Environment cannot run a segment, or undefined when it can. */
|
|
189
194
|
function podUnusable(ctx, segment) {
|
|
190
195
|
const pod = segment.pod;
|
|
191
196
|
if (!pod)
|
|
192
197
|
return undefined;
|
|
193
198
|
if (!ctx.pods)
|
|
194
|
-
return `
|
|
199
|
+
return `Environment ${pod.id} is a pod Environment, and this Runtime has no sandbox pods`;
|
|
195
200
|
const state = pod.pod;
|
|
196
201
|
if (state.desired === "deleted")
|
|
197
|
-
return `
|
|
202
|
+
return `Environment ${pod.id} was deleted`;
|
|
198
203
|
if (state.observed === "lost")
|
|
199
|
-
return `
|
|
200
|
-
if (state.observed === "expired")
|
|
201
|
-
return `Sandbox ${pod.id} expired`;
|
|
204
|
+
return `Environment ${pod.id} was lost: ${state.reason ?? "its volume is gone"}`;
|
|
202
205
|
if (state.observed === "failed")
|
|
203
|
-
return `
|
|
206
|
+
return `Environment ${pod.id} failed to start: ${state.reason ?? "its pod was not ready"}`;
|
|
204
207
|
return undefined;
|
|
205
208
|
}
|
|
206
209
|
/**
|
|
@@ -229,7 +232,6 @@ async function takeOwnership(ctx, id) {
|
|
|
229
232
|
if (taken.takeover)
|
|
230
233
|
await takeOver(t, s, {
|
|
231
234
|
recoversModelCalls: recoversModelCalls(ctx),
|
|
232
|
-
recoversToolCalls: recoversToolCalls(ctx),
|
|
233
235
|
});
|
|
234
236
|
if (!s.checkpoint || !["running", "runnable"].includes(s.status)) {
|
|
235
237
|
await t.releaseOwnership(id, lease.owner, lease.epoch);
|
|
@@ -243,10 +245,12 @@ async function takeOwnership(ctx, id) {
|
|
|
243
245
|
/**
|
|
244
246
|
* Takeover (§10.5 step 2, §11.4), inside the transaction that took ownership: the dead
|
|
245
247
|
* owner's `invoking` effects become `uncertain` and are never invoked again, except model
|
|
246
|
-
* calls when the gate recovers them (P1.2) and
|
|
247
|
-
*
|
|
248
|
-
*
|
|
249
|
-
*
|
|
248
|
+
* calls when the gate recovers them (P1.2) and every tool call (R5.3): those stay `invoking`,
|
|
249
|
+
* and the replay decides. It re-sends a model call, and an MCP or HTTP tool call with the same
|
|
250
|
+
* key; a sandbox or core-served tool call fails to the model as `tool.interrupted`
|
|
251
|
+
* (`harness-api/tool-journal.ts`). When an effect marked `uncertain` belongs to the active
|
|
252
|
+
* turn, the session becomes `uncertain` too, with an `effect.uncertain` event. Mutates and
|
|
253
|
+
* writes `s`. Returns the effect ids it marked.
|
|
250
254
|
*/
|
|
251
255
|
export async function takeOver(t, s, options = {}) {
|
|
252
256
|
const marked = [];
|
|
@@ -254,7 +258,7 @@ export async function takeOver(t, s, options = {}) {
|
|
|
254
258
|
// Still running at the gate, or finished there: the replay re-sends it (P1.2).
|
|
255
259
|
if (options.recoversModelCalls && effect.request.kind === "model")
|
|
256
260
|
continue;
|
|
257
|
-
if (
|
|
261
|
+
if (effect.request.kind === "tool")
|
|
258
262
|
continue;
|
|
259
263
|
effect.status = "uncertain";
|
|
260
264
|
effect.error ??= "The Worker running this effect stopped before its outcome was recorded";
|
|
@@ -279,13 +283,16 @@ async function runRemoteSegment(ctx, lease, started, controller, run, onTaken, p
|
|
|
279
283
|
let end;
|
|
280
284
|
try {
|
|
281
285
|
const segment = await startSegment(ctx, lease, { harness: true });
|
|
282
|
-
// A pod
|
|
286
|
+
// A pod Environment's segment runs in its pod (F7.2), or fails when the pod cannot run it.
|
|
283
287
|
const unusable = podUnusable(ctx, segment);
|
|
284
288
|
if (unusable)
|
|
285
289
|
throw new Error(unusable);
|
|
290
|
+
// A new volume being seeded from a disk version takes no turn until it holds it (R2d step 3).
|
|
291
|
+
if (segment.pod?.seeding)
|
|
292
|
+
throw new HarnessUnavailable();
|
|
286
293
|
const pod = segment.pod?.id;
|
|
287
|
-
if (
|
|
288
|
-
placed.
|
|
294
|
+
if (started.environmentId !== undefined)
|
|
295
|
+
placed.environment = started.environmentId;
|
|
289
296
|
end = await ctx.harness.offer({
|
|
290
297
|
lease,
|
|
291
298
|
start: buildTurnStart(ctx, segment),
|
|
@@ -293,6 +300,8 @@ async function runRemoteSegment(ctx, lease, started, controller, run, onTaken, p
|
|
|
293
300
|
transcript: segment.transcript,
|
|
294
301
|
run,
|
|
295
302
|
onTaken,
|
|
303
|
+
// Only a connection of the session's harness takes its runs (R4.1).
|
|
304
|
+
harness: harnessOf(segment.current),
|
|
296
305
|
...(pod === undefined ? {} : { pod, waitMs: POD_OFFER_WAIT_MS }),
|
|
297
306
|
});
|
|
298
307
|
if (end.kind === "unavailable") {
|
|
@@ -304,13 +313,17 @@ async function runRemoteSegment(ctx, lease, started, controller, run, onTaken, p
|
|
|
304
313
|
// Aborted before a harness took it: settled as an abort of the engine would be.
|
|
305
314
|
if (end.kind === "aborted")
|
|
306
315
|
throw signal.reason;
|
|
316
|
+
// The harness's own tools take names of this turn's tools (Harness API 3): the turn fails.
|
|
317
|
+
if (end.kind === "refused")
|
|
318
|
+
throw new Error(end.message);
|
|
307
319
|
// Checked before the abort: whatever core's signal says, the run's effects are unaccounted for.
|
|
308
320
|
if (end.kind === "released" && end.reason === "connection.lost")
|
|
309
321
|
throw new RunLost();
|
|
310
322
|
stopIfLeaving(signal);
|
|
311
323
|
if (end.kind === "released")
|
|
312
324
|
throw new SegmentStopped(end.reason === "ownership.lost" ? "ownership.lost" : "shutdown");
|
|
313
|
-
|
|
325
|
+
// A harness's output never carries the Runtime's own tokens into the record (R4).
|
|
326
|
+
const output = redactTokens(end.output);
|
|
314
327
|
if (output.thrown)
|
|
315
328
|
throw thrownError(output.thrown);
|
|
316
329
|
const result = resultOf(output, segment);
|
|
@@ -320,7 +333,9 @@ async function runRemoteSegment(ctx, lease, started, controller, run, onTaken, p
|
|
|
320
333
|
await settleFailure(ctx, lease, started, signal.reason);
|
|
321
334
|
else {
|
|
322
335
|
const recorded = result.status === "yielded" || result.status === "completed" || result.status === "paused";
|
|
323
|
-
cursor = await settleTurn(ctx, lease, started, result, recorded ? output.transcript ?? [] : [], signal, segment.cursor
|
|
336
|
+
cursor = await settleTurn(ctx, lease, started, result, recorded ? output.transcript ?? [] : [], signal, segment.cursor,
|
|
337
|
+
// The turn's end and its transcript are what the harness reported (R4.9).
|
|
338
|
+
{ source: { kind: "harness", id: harnessOf(started) }, evidence: "claimed", runId: output.runId });
|
|
324
339
|
}
|
|
325
340
|
end.reply(cursor === undefined ? {} : { cursor });
|
|
326
341
|
}
|
|
@@ -361,7 +376,8 @@ function resultOf(output, segment) {
|
|
|
361
376
|
effectIds: output.effectIds ?? [],
|
|
362
377
|
};
|
|
363
378
|
let state = output.state;
|
|
364
|
-
|
|
379
|
+
// Our engine's transcript shadow (tests); another harness's state is stored as sent (R4.2).
|
|
380
|
+
if (state !== undefined && transcriptShadow() && ours(current))
|
|
365
381
|
state = withTranscript(state, applyUpdates(segment.transcript, output.transcript ?? []));
|
|
366
382
|
const checkpoint = state === undefined || isWorkflowManifest(current.manifest)
|
|
367
383
|
? current.checkpoint
|
|
@@ -408,8 +424,8 @@ async function wakeWorkflowOf(ctx, t, agent, turnId, type, payload) {
|
|
|
408
424
|
* the turn has committed and while the advance still holds the lease. The harness hears back
|
|
409
425
|
* after the export. The export never throws.
|
|
410
426
|
*/
|
|
411
|
-
async function settleTurn(ctx, lease, s, result, updates, signal, cursor) {
|
|
412
|
-
const kept = await settle(ctx, lease, s, result, updates, cursor);
|
|
427
|
+
async function settleTurn(ctx, lease, s, result, updates, signal, cursor, writer) {
|
|
428
|
+
const kept = await settle(ctx, lease, s, result, updates, cursor, writer);
|
|
413
429
|
if (result.status === "completed")
|
|
414
430
|
await exportOutputs(ctx, lease.sessionId, s.activeTurnId, signal);
|
|
415
431
|
return kept;
|
|
@@ -419,7 +435,9 @@ async function settleTurn(ctx, lease, s, result, updates, signal, cursor) {
|
|
|
419
435
|
* cursor of the transcript it recorded (`cursor` when it recorded no edit), or undefined when
|
|
420
436
|
* the transcript was not kept.
|
|
421
437
|
*/
|
|
422
|
-
async function settle(ctx, lease, s, result, updates = [], cursor
|
|
438
|
+
async function settle(ctx, lease, s, result, updates = [], cursor,
|
|
439
|
+
/** Who reported the result (R4.9): the run's harness. */
|
|
440
|
+
writer) {
|
|
423
441
|
const id = lease.sessionId;
|
|
424
442
|
let kept;
|
|
425
443
|
const siblingCancelIds = await ownedTx(ctx.store, id, lease.epoch, async (t, current) => {
|
|
@@ -430,11 +448,24 @@ async function settle(ctx, lease, s, result, updates = [], cursor) {
|
|
|
430
448
|
// A result can arrive during concurrent action persistence. Preserve the runnable marker.
|
|
431
449
|
const resumeRequested = current.status === "runnable";
|
|
432
450
|
if (result.status === "waiting" || result.status === "uncertain") {
|
|
451
|
+
// Tool calls the Tool Gate held for approval (R5 step 2): the turn pauses for a person.
|
|
452
|
+
const held = resumeRequested ? [] : await heldCalls(t, result.effectIds);
|
|
453
|
+
if (held.length > 0) {
|
|
454
|
+
current.status = "paused";
|
|
455
|
+
pauseTurn(current);
|
|
456
|
+
current.waits = held;
|
|
457
|
+
const payload = { interactions: held };
|
|
458
|
+
await t.event(id, s.activeTurnId, "turn.paused", payload);
|
|
459
|
+
await t.put("sessions", id, current);
|
|
460
|
+
await wakeWorkflowOf(ctx, t, current, s.activeTurnId, "turn.paused", payload);
|
|
461
|
+
return siblings;
|
|
462
|
+
}
|
|
433
463
|
const linkedWaits = isWorkflowManifest(current.manifest)
|
|
434
464
|
? await aggregateWaits({ t, workflowSessionId: id })
|
|
435
465
|
: [];
|
|
436
466
|
if (linkedWaits.length > 0 && !resumeRequested) {
|
|
437
467
|
current.status = "paused";
|
|
468
|
+
pauseTurn(current);
|
|
438
469
|
current.waits = linkedWaits;
|
|
439
470
|
}
|
|
440
471
|
else {
|
|
@@ -456,8 +487,8 @@ async function settle(ctx, lease, s, result, updates = [], cursor) {
|
|
|
456
487
|
if (result.status === "yielded") {
|
|
457
488
|
// The turn goes on in a new segment: same turn, next checkpoint, woken right away.
|
|
458
489
|
const finished = result.checkpoint;
|
|
459
|
-
kept = (await recordTranscript(t, id, s.activeTurnId, current, updates)) ?? cursor;
|
|
460
|
-
current.state =
|
|
490
|
+
kept = (await recordTranscript(t, id, s.activeTurnId, current, updates, writer)) ?? cursor;
|
|
491
|
+
current.state = leanOf(current, result.result.state);
|
|
461
492
|
current.checkpoint = {
|
|
462
493
|
...finished,
|
|
463
494
|
segment: finished.segment + 1,
|
|
@@ -478,16 +509,16 @@ async function settle(ctx, lease, s, result, updates = [], cursor) {
|
|
|
478
509
|
const flow = isWorkflowManifest(current.manifest);
|
|
479
510
|
if (!flow) {
|
|
480
511
|
if (result.status !== "failed") {
|
|
481
|
-
const last = await recordTranscript(t, id, s.activeTurnId, current, updates);
|
|
512
|
+
const last = await recordTranscript(t, id, s.activeTurnId, current, updates, writer);
|
|
482
513
|
if (result.status !== "cancelled")
|
|
483
514
|
kept = last ?? cursor;
|
|
484
515
|
}
|
|
485
516
|
current.state =
|
|
486
517
|
result.status === "failed"
|
|
487
518
|
? current.turnStartState
|
|
488
|
-
:
|
|
519
|
+
: leanOf(current, result.result.state);
|
|
489
520
|
}
|
|
490
|
-
current.checkpoint = leanCheckpoint(result.checkpoint);
|
|
521
|
+
current.checkpoint = leanCheckpoint(current, result.checkpoint);
|
|
491
522
|
current.status = result.status;
|
|
492
523
|
current.waits =
|
|
493
524
|
result.status === "paused" ? result.result.pending : undefined;
|
|
@@ -508,7 +539,11 @@ async function settle(ctx, lease, s, result, updates = [], cursor) {
|
|
|
508
539
|
});
|
|
509
540
|
siblings.push(...cancelResult.agentSessionIds);
|
|
510
541
|
}
|
|
511
|
-
|
|
542
|
+
// The turn's span (R7.1): a pause starts its wait; an end, its duration and totals.
|
|
543
|
+
const ended = result.status === "paused" ? undefined : await endTurn(t, current, s.activeTurnId);
|
|
544
|
+
if (result.status === "paused")
|
|
545
|
+
pauseTurn(current);
|
|
546
|
+
else {
|
|
512
547
|
current.activeTurnId = null;
|
|
513
548
|
if (s.activeTurnId)
|
|
514
549
|
current.lastTurnId = s.activeTurnId;
|
|
@@ -516,7 +551,7 @@ async function settle(ctx, lease, s, result, updates = [], cursor) {
|
|
|
516
551
|
}
|
|
517
552
|
const type = `turn.${result.status}`;
|
|
518
553
|
const payload = result.status === "completed"
|
|
519
|
-
? { output: result.result.output }
|
|
554
|
+
? { output: result.result.output, ...ended }
|
|
520
555
|
: result.status === "paused"
|
|
521
556
|
? {
|
|
522
557
|
interactions: (result.result.pending ?? []).map((call) => ({
|
|
@@ -539,9 +574,10 @@ async function settle(ctx, lease, s, result, updates = [], cursor) {
|
|
|
539
574
|
? { path: result.result.error.path }
|
|
540
575
|
: {}),
|
|
541
576
|
},
|
|
577
|
+
...ended,
|
|
542
578
|
}
|
|
543
|
-
: {};
|
|
544
|
-
await t.event(id, s.activeTurnId, type, payload);
|
|
579
|
+
: { ...ended };
|
|
580
|
+
await t.event(id, s.activeTurnId, type, payload, writer);
|
|
545
581
|
await t.put("sessions", id, current);
|
|
546
582
|
await wakeWorkflowOf(ctx, t, current, s.activeTurnId, type, payload);
|
|
547
583
|
return siblings;
|
|
@@ -564,6 +600,37 @@ async function settle(ctx, lease, s, result, updates = [], cursor) {
|
|
|
564
600
|
}
|
|
565
601
|
return kept;
|
|
566
602
|
}
|
|
603
|
+
/**
|
|
604
|
+
* The approvals a waiting segment's held tool calls ask for (R5 step 2), as `turn.paused`
|
|
605
|
+
* interactions: each one's id is the call's effect id, which `approve` answers.
|
|
606
|
+
*/
|
|
607
|
+
async function heldCalls(t, effectIds) {
|
|
608
|
+
const held = [];
|
|
609
|
+
for (const effectId of effectIds) {
|
|
610
|
+
const row = await t.get("effects", effectId);
|
|
611
|
+
if (row?.status !== "held")
|
|
612
|
+
continue;
|
|
613
|
+
const request = row.request;
|
|
614
|
+
const context = (request.context ?? {});
|
|
615
|
+
const toolName = request.toolName ?? request.key ?? effectId;
|
|
616
|
+
const reason = row.hold?.reason ?? "The Tenant's tool policy asks a person about this call";
|
|
617
|
+
held.push({
|
|
618
|
+
invocationId: typeof context.invocationId === "string" ? context.invocationId : effectId,
|
|
619
|
+
...(typeof context.callId === "string" ? { callId: context.callId } : {}),
|
|
620
|
+
interaction: {
|
|
621
|
+
id: effectId,
|
|
622
|
+
kind: "approval",
|
|
623
|
+
prompt: `Approve ${toolName}?`,
|
|
624
|
+
reason,
|
|
625
|
+
...(row.server === undefined ? {} : { server: row.server }),
|
|
626
|
+
input: request.input ?? null,
|
|
627
|
+
},
|
|
628
|
+
status: "interaction",
|
|
629
|
+
toolName,
|
|
630
|
+
});
|
|
631
|
+
}
|
|
632
|
+
return held;
|
|
633
|
+
}
|
|
567
634
|
/** A segment threw: fail the turn, restore the turn-start state, wake a linked workflow. */
|
|
568
635
|
async function settleFailure(ctx, lease, s, error) {
|
|
569
636
|
const id = lease.sessionId;
|
|
@@ -574,7 +641,7 @@ async function settleFailure(ctx, lease, s, error) {
|
|
|
574
641
|
current.status = "failed";
|
|
575
642
|
current.state = current.turnStartState;
|
|
576
643
|
current.error = error instanceof Error ? error.message : String(error);
|
|
577
|
-
const payload = { message: current.error };
|
|
644
|
+
const payload = { message: current.error, ...(await endTurn(t, current, current.activeTurnId)) };
|
|
578
645
|
await t.event(id, current.activeTurnId, "turn.failed", payload);
|
|
579
646
|
await slimModelEffects(t, id, current.activeTurnId);
|
|
580
647
|
if (current.activeTurnId)
|
|
@@ -584,21 +651,30 @@ async function settleFailure(ctx, lease, s, error) {
|
|
|
584
651
|
await wakeWorkflowOf(ctx, t, current, s.activeTurnId, "turn.failed", payload);
|
|
585
652
|
});
|
|
586
653
|
}
|
|
587
|
-
/**
|
|
588
|
-
|
|
654
|
+
/** Whether the session runs our harness, whose state core may store lean (R4.2). */
|
|
655
|
+
const ours = (session) => harnessOf(session) === HARNESS_ID;
|
|
656
|
+
/**
|
|
657
|
+
* `state` as the session row stores it: our engine's without its transcript (`history.ts`);
|
|
658
|
+
* another harness's as it sent it, never read (R4.2).
|
|
659
|
+
*/
|
|
660
|
+
function leanOf(session, state) {
|
|
661
|
+
return ours(session) ? leanState(state) : state;
|
|
662
|
+
}
|
|
663
|
+
/** `checkpoint` with its engine state stored lean (`leanOf`). */
|
|
664
|
+
function leanCheckpoint(session, checkpoint) {
|
|
589
665
|
const state = checkpoint?.state;
|
|
590
666
|
if (!checkpoint || !state)
|
|
591
667
|
return checkpoint;
|
|
592
|
-
return { ...checkpoint, state:
|
|
668
|
+
return { ...checkpoint, state: leanOf(session, state) };
|
|
593
669
|
}
|
|
594
670
|
/**
|
|
595
671
|
* Appends a segment's transcript edits; a snapshot moves `history.snapshot`. Returns the seq of
|
|
596
672
|
* the last edit, if any.
|
|
597
673
|
*/
|
|
598
|
-
async function recordTranscript(t, id, turnId, s, updates) {
|
|
674
|
+
async function recordTranscript(t, id, turnId, s, updates, writer) {
|
|
599
675
|
let last;
|
|
600
676
|
for (const update of updates) {
|
|
601
|
-
const event = await t.event(id, turnId, "transcript.updated", update);
|
|
677
|
+
const event = await t.event(id, turnId, "transcript.updated", update, writer);
|
|
602
678
|
if (update.keep === 0)
|
|
603
679
|
s.history = { from: s.history?.from ?? event.seq, snapshot: event.seq };
|
|
604
680
|
last = event.seq;
|
|
@@ -4,7 +4,7 @@ import type { HostEffect } from "@nylorun/harness/run";
|
|
|
4
4
|
import { type TenantContext } from "./context.js";
|
|
5
5
|
export { isReadArtifactCall, isSaveArtifactCall } from "../harness/calls.js";
|
|
6
6
|
export declare function callSaveArtifact(ctx: TenantContext, request: HostEffect, signal: AbortSignal): Promise<SandboxToolOutcome>;
|
|
7
|
-
/** What `read_artifact` returns: a page of a text artifact, or
|
|
7
|
+
/** What `read_artifact` returns: a page of a text artifact, or a file the model reads. */
|
|
8
8
|
interface ReadOutcome {
|
|
9
9
|
readonly kind: "completed";
|
|
10
10
|
readonly output: Record<string, unknown>;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Our engine's built-in artifact tools,
|
|
3
|
-
* (`sandbox/session-sandbox.ts`). The Runtime runs them itself, like the sandbox tools.
|
|
2
|
+
* Our engine's built-in artifact tools, which the Runtime pins in a session agent's `runtime`
|
|
3
|
+
* block (`sandbox/session-sandbox.ts`). The Runtime runs them itself, like the sandbox tools.
|
|
4
4
|
*
|
|
5
5
|
* `save_artifact` (F8.1), which comes with a session's sandbox, saves a file as an artifact of
|
|
6
6
|
* its session:
|
|
@@ -9,25 +9,28 @@
|
|
|
9
9
|
* The version commits with `artifact.created` (or `artifact.version.created`) on the session's
|
|
10
10
|
* stream, carrying the turn and the tool call.
|
|
11
11
|
*
|
|
12
|
-
* `read_artifact` (R2b C11, Q24
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
12
|
+
* `read_artifact` (R2b C11, Q24; every agent since protocol 12) reads a text artifact in pages of
|
|
13
|
+
* at most 32 KiB, from a byte `offset`: how the model reads a tool result the Runtime stored
|
|
14
|
+
* because it was too large to show (`tool-results.ts`), and a file of an earlier turn again, by
|
|
15
|
+
* its reference line. Another file is shown as a file of the result when the Tenant's model reads
|
|
16
|
+
* its type, and refused (`artifact.unsupported`) when it does not. It reaches the session's own
|
|
17
|
+
* artifacts and the Tenant-wide ones its messages named (`Session.fileArtifacts`), never another
|
|
18
|
+
* session's. `version` picks a version; the latest by default.
|
|
17
19
|
*
|
|
18
20
|
* Expected problems (no such file, too large, the Tenant's total reached, not text) are failed
|
|
19
21
|
* outcomes the model sees; only infrastructure errors throw.
|
|
20
22
|
*/
|
|
21
23
|
import { basename } from "node:path";
|
|
22
|
-
import { READ_ARTIFACT_MAX_BYTES
|
|
24
|
+
import { READ_ARTIFACT_MAX_BYTES } from "@nylorun/core/define";
|
|
23
25
|
import { isArtifactId } from "@nylorun/core/compatibility";
|
|
24
26
|
import { readArtifactLimits } from "../artifacts/config.js";
|
|
25
|
-
import {
|
|
27
|
+
import { contentTypeFor, essence, isText, normalizeContentType, unreadableReason, } from "../artifacts/media-types.js";
|
|
28
|
+
import { currentModelInputs } from "../model/catalog.js";
|
|
26
29
|
import { uploadArtifact } from "../artifacts/service.js";
|
|
27
30
|
import { DEFAULT_CONTENT_TYPE } from "../blob/index.js";
|
|
28
|
-
import {
|
|
31
|
+
import { sandboxToolSpec } from "../sandbox/manager.js";
|
|
29
32
|
import { sandboxWorkspaceOf } from "../sandbox/share.js";
|
|
30
|
-
import {
|
|
33
|
+
import { sessionOf } from "./context.js";
|
|
31
34
|
import { HttpError } from "./http.js";
|
|
32
35
|
import { manifestFor } from "./session.js";
|
|
33
36
|
import { jsonBytes, utf8Boundary } from "./tool-results.js";
|
|
@@ -47,13 +50,12 @@ export async function callSaveArtifact(ctx, request, signal) {
|
|
|
47
50
|
const artifactId = typeof input.artifactId === "string" ? input.artifactId : undefined;
|
|
48
51
|
const { s, workspace, limits, ours } = await ctx.store.tx(async (t) => {
|
|
49
52
|
const s = await sessionOf(t, request.sessionId);
|
|
50
|
-
const lookup = await sandboxLookup(t, s.id);
|
|
51
53
|
// A new version only of an artifact of this session.
|
|
52
54
|
const ours = artifactId === undefined ||
|
|
53
55
|
(isArtifactId(artifactId) && (await t.artifact(artifactId))?.sessionId === s.id);
|
|
54
56
|
return {
|
|
55
57
|
s,
|
|
56
|
-
workspace: sandboxWorkspaceOf(s
|
|
58
|
+
workspace: sandboxWorkspaceOf(s),
|
|
57
59
|
limits: await readArtifactLimits(t),
|
|
58
60
|
ours,
|
|
59
61
|
};
|
|
@@ -63,15 +65,15 @@ export async function callSaveArtifact(ctx, request, signal) {
|
|
|
63
65
|
let bytes;
|
|
64
66
|
let fileName;
|
|
65
67
|
if (path !== undefined) {
|
|
66
|
-
const
|
|
67
|
-
if (!
|
|
68
|
+
const spec = sandboxToolSpec(manifestFor(s.manifest, request.agent), "read");
|
|
69
|
+
if (!spec)
|
|
68
70
|
return failed("artifact.no_sandbox", "This session has no sandbox to read a path from.");
|
|
69
71
|
const read = await ctx.sandbox.readBytes({
|
|
70
72
|
id: workspace.ownerId,
|
|
71
73
|
activeTurnId: s.activeTurnId,
|
|
72
74
|
manifest: s.manifest,
|
|
73
|
-
...(workspace.
|
|
74
|
-
},
|
|
75
|
+
...(workspace.environmentId === undefined ? {} : { environmentId: workspace.environmentId }),
|
|
76
|
+
}, spec, path, limits.fileBytes, signal);
|
|
75
77
|
if (read.kind === "failed")
|
|
76
78
|
return read;
|
|
77
79
|
if (read.kind === "missing")
|
|
@@ -129,9 +131,12 @@ export async function callSaveArtifact(ctx, request, signal) {
|
|
|
129
131
|
export async function callReadArtifact(ctx, request, signal) {
|
|
130
132
|
const input = (request.input ?? {});
|
|
131
133
|
const artifactId = typeof input.artifactId === "string" ? input.artifactId : "";
|
|
134
|
+
const wanted = input.version;
|
|
132
135
|
const offset = input.offset ?? 0;
|
|
133
136
|
const length = input.length ?? READ_ARTIFACT_MAX_BYTES;
|
|
134
137
|
if (artifactId === "" ||
|
|
138
|
+
(wanted !== undefined &&
|
|
139
|
+
(typeof wanted !== "number" || !Number.isSafeInteger(wanted) || wanted < 1)) ||
|
|
135
140
|
typeof offset !== "number" ||
|
|
136
141
|
!Number.isSafeInteger(offset) ||
|
|
137
142
|
offset < 0 ||
|
|
@@ -139,20 +144,25 @@ export async function callReadArtifact(ctx, request, signal) {
|
|
|
139
144
|
!Number.isSafeInteger(length) ||
|
|
140
145
|
length < 1 ||
|
|
141
146
|
length > READ_ARTIFACT_MAX_BYTES)
|
|
142
|
-
return failed("artifact.invalid_input", `Give artifactId, an offset of 0 or more, and a length of 1 to ${READ_ARTIFACT_MAX_BYTES}.`);
|
|
147
|
+
return failed("artifact.invalid_input", `Give artifactId, a version of 1 or more if any, an offset of 0 or more, and a length of 1 to ${READ_ARTIFACT_MAX_BYTES}.`);
|
|
143
148
|
const found = await ctx.store.tx(async (t) => {
|
|
144
149
|
const s = await sessionOf(t, request.sessionId);
|
|
145
150
|
if (!isArtifactId(artifactId))
|
|
146
151
|
return undefined;
|
|
147
|
-
//
|
|
152
|
+
// An artifact of this session, or a Tenant-wide one its messages named; never another
|
|
153
|
+
// session's.
|
|
148
154
|
const artifact = await t.artifact(artifactId);
|
|
149
|
-
if (!artifact ||
|
|
155
|
+
if (!artifact ||
|
|
156
|
+
(artifact.sessionId !== s.id &&
|
|
157
|
+
!(artifact.sessionId === null && s.fileArtifacts?.includes(artifact.id))))
|
|
150
158
|
return undefined;
|
|
151
|
-
const version = await t.artifactVersion(artifact.id, artifact.latestVersion);
|
|
152
|
-
return
|
|
159
|
+
const version = await t.artifactVersion(artifact.id, typeof wanted === "number" ? wanted : artifact.latestVersion);
|
|
160
|
+
return { artifact, version };
|
|
153
161
|
});
|
|
154
162
|
if (!found)
|
|
155
163
|
return failed("artifact.not_found", `Artifact ${artifactId} is not an artifact of this session.`);
|
|
164
|
+
if (!found.version)
|
|
165
|
+
return failed("artifact.not_found", `Artifact ${artifactId} has no version ${String(wanted)}.`);
|
|
156
166
|
const { artifact, version } = found;
|
|
157
167
|
const about = {
|
|
158
168
|
artifactId: artifact.id,
|
|
@@ -163,19 +173,29 @@ export async function callReadArtifact(ctx, request, signal) {
|
|
|
163
173
|
};
|
|
164
174
|
if (artifact.kind !== "file")
|
|
165
175
|
return failed("artifact.not_text", `${artifact.name} is a folder; read_artifact reads a file.`);
|
|
166
|
-
if (
|
|
176
|
+
if (!isText(version.contentType)) {
|
|
177
|
+
// A file the Tenant's model reads goes to it beside the result (protocol 12). Without a
|
|
178
|
+
// model in the vault (a test's fake provider), text and images.
|
|
179
|
+
const inputs = (await currentModelInputs(ctx)) ?? ["text", "image"];
|
|
180
|
+
const reason = unreadableReason(version.contentType, inputs);
|
|
181
|
+
if (reason)
|
|
182
|
+
return failed("artifact.unsupported", `${artifact.name} is ${essence(version.contentType)}: ${reason}.`);
|
|
167
183
|
return {
|
|
168
184
|
kind: "completed",
|
|
169
185
|
output: about,
|
|
170
186
|
files: [
|
|
171
187
|
{
|
|
172
188
|
mediaType: version.contentType,
|
|
173
|
-
reference: {
|
|
189
|
+
reference: {
|
|
190
|
+
artifactId: artifact.id,
|
|
191
|
+
version: version.version,
|
|
192
|
+
name: artifact.name,
|
|
193
|
+
size: version.size,
|
|
194
|
+
},
|
|
174
195
|
},
|
|
175
196
|
],
|
|
176
197
|
};
|
|
177
|
-
|
|
178
|
-
return failed("artifact.not_text", `${artifact.name} is ${essence(version.contentType)}, not text; read_artifact reads text and shows images.`);
|
|
198
|
+
}
|
|
179
199
|
if (offset > version.size || (offset === version.size && version.size > 0))
|
|
180
200
|
return failed("artifact.invalid_input", `Offset ${offset} is past the end of ${artifact.name}, which is ${version.size} bytes.`);
|
|
181
201
|
const end = Math.min(offset + length, version.size);
|
|
@@ -1,3 +1,16 @@
|
|
|
1
|
-
import type { SessionCommand } from "@nylorun/core/contracts";
|
|
1
|
+
import type { EventWriter, SessionCommand } from "@nylorun/core/contracts";
|
|
2
2
|
import { type AuthScope, type TenantContext } from "./context.js";
|
|
3
|
-
|
|
3
|
+
import type { TraceContext } from "../traces/ids.js";
|
|
4
|
+
export declare function command(ctx: TenantContext, id: string, input: SessionCommand, scope: AuthScope,
|
|
5
|
+
/**
|
|
6
|
+
* The W3C trace context the request carried (R7.4): a `message` records it on its
|
|
7
|
+
* `command.message`, and the turn joins the caller's trace. Not part of the command's identity.
|
|
8
|
+
*/
|
|
9
|
+
options?: {
|
|
10
|
+
trace?: TraceContext;
|
|
11
|
+
}): Promise<unknown>;
|
|
12
|
+
/**
|
|
13
|
+
* Who sent a command (R4.9), from its credential and never its body: a client over the Runtime
|
|
14
|
+
* API (an application, or a person through it or a token), or core itself.
|
|
15
|
+
*/
|
|
16
|
+
export declare function commandWriter(scope: AuthScope): EventWriter;
|