@nylorun/runtime 0.13.0-beta → 0.15.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 +434 -0
- package/README.md +137 -41
- package/dist/adapters/execution/restate.d.ts +7 -2
- package/dist/adapters/execution/restate.js +40 -10
- package/dist/adapters/replication/pgoutput.d.ts +21 -0
- package/dist/adapters/replication/pgoutput.js +170 -0
- package/dist/adapters/streams/s2.js +37 -20
- package/dist/admin-openapi.json +570 -0
- package/dist/api/a2a/card.d.ts +15 -0
- package/dist/api/a2a/card.js +24 -0
- package/dist/api/a2a/endpoint.d.ts +8 -0
- package/dist/api/a2a/endpoint.js +81 -0
- package/dist/api/a2a/protocol.d.ts +144 -0
- package/dist/api/a2a/protocol.js +188 -0
- package/dist/api/a2a/routes.d.ts +19 -0
- package/dist/api/a2a/routes.js +289 -0
- package/dist/api/a2a/tasks.d.ts +50 -0
- package/dist/api/a2a/tasks.js +186 -0
- package/dist/api/ag-ui/endpoint.d.ts +8 -0
- package/dist/api/ag-ui/endpoint.js +126 -0
- package/dist/api/ag-ui/history.d.ts +7 -0
- package/dist/api/ag-ui/history.js +60 -0
- package/dist/api/ag-ui/routes.d.ts +32 -0
- package/dist/api/ag-ui/routes.js +305 -0
- package/dist/api/ag-ui/session-id.d.ts +1 -0
- package/dist/api/ag-ui/session-id.js +12 -0
- package/dist/api/ag-ui/sse.d.ts +9 -0
- package/dist/api/ag-ui/sse.js +7 -0
- package/dist/api/ag-ui/translate.d.ts +38 -0
- package/dist/api/ag-ui/translate.js +222 -0
- package/dist/api/components.d.ts +8065 -0
- package/dist/api/components.js +82 -0
- package/dist/api/gate/keys.d.ts +14 -0
- package/dist/api/gate/keys.js +59 -0
- package/dist/api/gate/routes.d.ts +34 -0
- package/dist/api/gate/routes.js +235 -0
- package/dist/api/http/app.d.ts +18 -0
- package/dist/api/http/app.js +58 -0
- package/dist/api/http/body.d.ts +4 -0
- package/dist/api/http/body.js +37 -0
- package/dist/api/http/define.d.ts +39 -0
- package/dist/api/http/define.js +122 -0
- package/dist/api/http/respond.d.ts +9 -0
- package/dist/api/http/respond.js +44 -0
- package/dist/api/http/routes/access.d.ts +24 -0
- package/dist/api/http/routes/access.js +266 -0
- package/dist/api/http/routes/actions.d.ts +8 -0
- package/dist/api/http/routes/actions.js +76 -0
- package/dist/api/http/routes/endpoints.d.ts +7 -0
- package/dist/api/http/routes/endpoints.js +100 -0
- package/dist/api/http/routes/sessions.d.ts +3 -0
- package/dist/api/http/routes/sessions.js +205 -0
- package/dist/api/http/routes/tenant.d.ts +11 -0
- package/dist/api/http/routes/tenant.js +244 -0
- package/dist/api/http/routes/vaults.d.ts +8 -0
- package/dist/api/http/routes/vaults.js +167 -0
- package/dist/api/openapi.d.ts +19 -0
- package/dist/api/openapi.js +137 -0
- package/dist/api/route.d.ts +12 -0
- package/dist/api/route.js +4 -0
- package/dist/core/bearer.d.ts +8 -0
- package/dist/core/bearer.js +11 -0
- package/dist/core/flow-host.d.ts +7 -15
- package/dist/core/flow-host.js +16 -41
- package/dist/core/sandbox-routes.d.ts +12 -7
- package/dist/core/sandbox-routes.js +24 -25
- package/dist/execution/memory.d.ts +8 -5
- package/dist/execution/memory.js +33 -13
- package/dist/execution/types.d.ts +38 -6
- package/dist/execution/types.js +12 -3
- package/dist/gates/contract.d.ts +51 -0
- package/dist/gates/contract.js +37 -0
- package/dist/gates/handler.d.ts +21 -0
- package/dist/gates/handler.js +55 -0
- package/dist/gates/http-client.d.ts +12 -0
- package/dist/gates/http-client.js +160 -0
- package/dist/gates/in-process.d.ts +32 -0
- package/dist/gates/in-process.js +42 -0
- package/dist/gates/inflight.d.ts +39 -0
- package/dist/gates/inflight.js +70 -0
- package/dist/gates/mcp-handler.d.ts +35 -0
- package/dist/gates/mcp-handler.js +142 -0
- package/dist/gates/meter.d.ts +24 -0
- package/dist/gates/meter.js +165 -0
- package/dist/gates/model-gate.d.ts +45 -0
- package/dist/gates/model-gate.js +1 -0
- package/dist/gates/tenant-vaults.d.ts +64 -0
- package/dist/gates/tenant-vaults.js +94 -0
- package/dist/gates/tool-calls.d.ts +51 -0
- package/dist/gates/tool-calls.js +64 -0
- package/dist/gates/tool-client.d.ts +12 -0
- package/dist/gates/tool-client.js +201 -0
- package/dist/gates/tool-contract.d.ts +116 -0
- package/dist/gates/tool-contract.js +64 -0
- package/dist/gates/tool-gate.d.ts +45 -0
- package/dist/gates/tool-gate.js +10 -0
- package/dist/host/admin-api.d.ts +23 -0
- package/dist/host/admin-api.js +74 -0
- package/dist/host/app.d.ts +34 -0
- package/dist/host/app.js +181 -0
- package/dist/host/config-for.d.ts +3 -1
- package/dist/host/config-for.js +3 -2
- package/dist/host/config.d.ts +11 -0
- package/dist/host/cors.d.ts +27 -0
- package/dist/host/cors.js +64 -0
- package/dist/host/create-host.d.ts +36 -8
- package/dist/host/create-host.js +161 -330
- package/dist/host/execution.d.ts +20 -18
- package/dist/host/execution.js +2 -2
- package/dist/host/gates.d.ts +58 -0
- package/dist/host/gates.js +127 -0
- package/dist/host/http.d.ts +20 -4
- package/dist/host/http.js +81 -23
- package/dist/host/index.d.ts +1 -1
- package/dist/host/main.js +189 -58
- package/dist/host/stack-config.d.ts +91 -19
- package/dist/host/stack-config.js +246 -17
- package/dist/infra/database.d.ts +2 -2
- package/dist/infra/execution.d.ts +5 -5
- package/dist/infra/execution.js +8 -6
- package/dist/infra/index.d.ts +1 -1
- package/dist/infra/workers.d.ts +17 -17
- package/dist/infra/workers.js +9 -9
- package/dist/keys/client.d.ts +9 -0
- package/dist/keys/client.js +70 -0
- package/dist/keys/contract.d.ts +24 -0
- package/dist/keys/contract.js +12 -0
- package/dist/keys/keys.d.ts +41 -0
- package/dist/keys/keys.js +50 -0
- package/dist/mcp/connect.d.ts +43 -3
- package/dist/mcp/connect.js +12 -5
- package/dist/mcp/pool.d.ts +39 -3
- package/dist/mcp/pool.js +74 -11
- package/dist/model/catalog.d.ts +2 -10
- package/dist/model/classify.d.ts +13 -0
- package/dist/model/classify.js +61 -0
- package/dist/model/models.d.ts +10 -1
- package/dist/model/models.js +14 -6
- package/dist/model/pi-model.d.ts +17 -2
- package/dist/model/pi-model.js +366 -222
- package/dist/openapi.json +25889 -0
- package/dist/record/append.d.ts +51 -0
- package/dist/record/append.js +20 -0
- package/dist/record/envelope.d.ts +19 -0
- package/dist/record/envelope.js +49 -0
- package/dist/record/index.d.ts +11 -0
- package/dist/record/index.js +11 -0
- package/dist/sandbox/manager.d.ts +13 -1
- package/dist/sandbox/manager.js +8 -11
- package/dist/sandbox/resolve.d.ts +33 -0
- package/dist/sandbox/resolve.js +81 -0
- package/dist/sandbox/session-sandbox.d.ts +11 -0
- package/dist/sandbox/session-sandbox.js +51 -0
- package/dist/sandbox/share.d.ts +14 -2
- package/dist/sandbox/share.js +33 -3
- package/dist/sandbox/tenant-config.d.ts +37 -0
- package/dist/sandbox/tenant-config.js +51 -0
- package/dist/sandbox/tools.d.ts +7 -9
- package/dist/sandbox/tools.js +11 -3
- package/dist/sandbox/types.d.ts +8 -2
- package/dist/store/postgres/connect.d.ts +25 -9
- package/dist/store/postgres/connect.js +27 -10
- package/dist/store/postgres/db.d.ts +16 -0
- package/dist/store/postgres/db.js +28 -0
- package/dist/store/postgres/drizzle/0000_foundation.sql +22 -0
- package/dist/store/postgres/drizzle/0001_baseline.sql +255 -0
- package/dist/store/postgres/drizzle/0002_stream_relay.sql +4 -0
- package/dist/store/postgres/drizzle/0003_tool_crossings.sql +9 -0
- package/dist/store/postgres/drizzle/meta/_journal.json +34 -0
- package/dist/store/postgres/migrate.d.ts +68 -0
- package/dist/store/postgres/migrate.js +183 -0
- package/dist/store/postgres/record-writer.d.ts +4 -0
- package/dist/store/postgres/record-writer.js +34 -0
- package/dist/store/postgres/record.d.ts +5 -0
- package/dist/store/postgres/record.js +60 -0
- package/dist/store/postgres/schema.d.ts +3096 -0
- package/dist/store/postgres/schema.js +363 -0
- package/dist/store/postgres/store.d.ts +2 -4
- package/dist/store/postgres/store.js +590 -434
- package/dist/store/postgres/tenant.d.ts +56 -0
- package/dist/store/postgres/tenant.js +154 -0
- package/dist/store/types.d.ts +183 -132
- package/dist/store/types.js +0 -1
- package/dist/streams/basin.d.ts +18 -1
- package/dist/streams/basin.js +34 -2
- package/dist/streams/control.d.ts +18 -0
- package/dist/streams/control.js +33 -0
- package/dist/streams/memory.d.ts +3 -1
- package/dist/streams/memory.js +14 -8
- package/dist/streams/relay/core.d.ts +35 -0
- package/dist/streams/relay/core.js +366 -0
- package/dist/streams/relay/types.d.ts +78 -0
- package/dist/streams/relay/types.js +8 -0
- package/dist/streams/types.d.ts +71 -36
- package/dist/streams/types.js +18 -53
- package/dist/tenant/access-policy.d.ts +30 -0
- package/dist/tenant/access-policy.js +52 -0
- package/dist/tenant/actions.d.ts +10 -149
- package/dist/tenant/actions.js +5 -167
- package/dist/tenant/advance.d.ts +7 -2
- package/dist/tenant/advance.js +125 -14
- package/dist/tenant/auth.d.ts +19 -17
- package/dist/tenant/auth.js +90 -93
- package/dist/tenant/browser.d.ts +15 -0
- package/dist/tenant/browser.js +39 -0
- package/dist/tenant/cause.d.ts +25 -0
- package/dist/tenant/cause.js +45 -0
- package/dist/tenant/commands.d.ts +18 -2
- package/dist/tenant/commands.js +111 -115
- package/dist/tenant/context.d.ts +101 -21
- package/dist/tenant/context.js +14 -10
- package/dist/tenant/delivery-token.d.ts +41 -0
- package/dist/tenant/delivery-token.js +104 -0
- package/dist/tenant/delivery.d.ts +62 -0
- package/dist/tenant/delivery.js +481 -0
- package/dist/tenant/effects.d.ts +20 -1
- package/dist/tenant/effects.js +159 -42
- package/dist/tenant/endpoints.d.ts +67 -0
- package/dist/tenant/endpoints.js +90 -0
- package/dist/tenant/ephemeral.d.ts +22 -7
- package/dist/tenant/ephemeral.js +83 -125
- package/dist/tenant/history.d.ts +64 -0
- package/dist/tenant/history.js +120 -0
- package/dist/tenant/http.d.ts +8 -9
- package/dist/tenant/http.js +13 -27
- package/dist/tenant/jwt.d.ts +16 -0
- package/dist/tenant/jwt.js +50 -0
- package/dist/tenant/model-setting.d.ts +2 -2
- package/dist/tenant/module.d.ts +17 -28
- package/dist/tenant/module.js +133 -279
- package/dist/tenant/outbound.d.ts +44 -0
- package/dist/tenant/outbound.js +127 -0
- package/dist/tenant/paths.d.ts +11 -16
- package/dist/tenant/paths.js +24 -68
- package/dist/tenant/pool.d.ts +1 -3
- package/dist/tenant/pool.js +1 -19
- package/dist/tenant/principals.d.ts +23 -11
- package/dist/tenant/principals.js +61 -49
- package/dist/tenant/reset.d.ts +0 -4
- package/dist/tenant/reset.js +0 -5
- package/dist/tenant/runtime.d.ts +28 -20
- package/dist/tenant/runtime.js +95 -64
- package/dist/tenant/scheduler.d.ts +10 -3
- package/dist/tenant/scheduler.js +9 -5
- package/dist/tenant/session-streams.d.ts +127 -0
- package/dist/tenant/session-streams.js +404 -0
- package/dist/tenant/sessions.d.ts +28 -4
- package/dist/tenant/sessions.js +118 -24
- package/dist/tenant/signing-keys.d.ts +55 -0
- package/dist/tenant/signing-keys.js +172 -0
- package/dist/tenant/slim.d.ts +8 -0
- package/dist/tenant/slim.js +21 -0
- package/dist/tenant/status.d.ts +3 -3
- package/dist/tenant/status.js +20 -13
- package/dist/tenant/store-pg.d.ts +9 -6
- package/dist/tenant/store-pg.js +60 -125
- package/dist/tenant/streams.d.ts +62 -85
- package/dist/tenant/streams.js +143 -226
- package/dist/tenant/subject-limits.d.ts +16 -0
- package/dist/tenant/subject-limits.js +45 -0
- package/dist/tenant/sweep.d.ts +1 -10
- package/dist/tenant/sweep.js +29 -69
- package/dist/tenant/tokens.d.ts +14 -0
- package/dist/tenant/tokens.js +180 -0
- package/dist/tenant/transcript.d.ts +48 -5
- package/dist/tenant/transcript.js +58 -1
- package/dist/tenant/types.d.ts +69 -98
- package/dist/tenant/types.js +4 -15
- package/dist/tenant/worker.d.ts +3 -1
- package/dist/tenant/worker.js +13 -7
- package/dist/vault/host-model.d.ts +31 -0
- package/dist/vault/host-model.js +57 -0
- package/dist/vault/service.d.ts +44 -17
- package/dist/vault/service.js +126 -121
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +25 -9
- package/dist/core/executors.d.ts +0 -44
- package/dist/core/executors.js +0 -95
- package/dist/store/memory.d.ts +0 -57
- package/dist/store/memory.js +0 -645
- package/dist/store/postgres/migrations/001_initial.d.ts +0 -27
- package/dist/store/postgres/migrations/001_initial.js +0 -196
- package/dist/store/postgres/migrations/002_session_owner.d.ts +0 -6
- package/dist/store/postgres/migrations/002_session_owner.js +0 -13
- package/dist/store/postgres/migrations/index.d.ts +0 -49
- package/dist/store/postgres/migrations/index.js +0 -76
- package/dist/store/postgres/names.d.ts +0 -11
- package/dist/store/postgres/names.js +0 -42
- package/dist/store/postgres/tenants.d.ts +0 -90
- package/dist/store/postgres/tenants.js +0 -214
- package/dist/streams/relay.d.ts +0 -37
- package/dist/streams/relay.js +0 -254
- package/dist/tenant/envelope.d.ts +0 -7
- package/dist/tenant/envelope.js +0 -22
- package/dist/tenant/live.d.ts +0 -92
- package/dist/tenant/live.js +0 -290
- package/dist/tenant/quarantine-error.d.ts +0 -6
- package/dist/tenant/quarantine-error.js +0 -9
- package/dist/tenant/quarantine.d.ts +0 -27
- package/dist/tenant/quarantine.js +0 -72
- package/dist/tenant/routes-tenant.d.ts +0 -12
- package/dist/tenant/routes-tenant.js +0 -137
- package/dist/tenant/routes.d.ts +0 -9
- package/dist/tenant/routes.js +0 -151
- package/dist/tenant/store-memory.d.ts +0 -16
- package/dist/tenant/store-memory.js +0 -111
- /package/dist/{store → record}/cursor.d.ts +0 -0
- /package/dist/{store → record}/cursor.js +0 -0
package/dist/model/pi-model.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
import { parseJsonWithRepair, retryAssistantCall, } from "@earendil-works/pi-ai";
|
|
1
2
|
import { scrub } from "../redact.js";
|
|
3
|
+
import { classifyAssistantError, failure } from "./classify.js";
|
|
2
4
|
import { modelsFor } from "./models.js";
|
|
3
5
|
import { projectSecrets } from "./settings.js";
|
|
6
|
+
const OPENAI_COMPATIBLE_REASONING = /^reasoning(_content|_text)?$/;
|
|
4
7
|
const emptyUsage = () => ({
|
|
5
8
|
input: 0,
|
|
6
9
|
output: 0,
|
|
@@ -9,197 +12,100 @@ const emptyUsage = () => ({
|
|
|
9
12
|
cacheWrite: 0,
|
|
10
13
|
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
|
11
14
|
});
|
|
12
|
-
/**
|
|
15
|
+
/** Provider auth never reads the process environment or files; credentials come from the vault. */
|
|
16
|
+
const noAmbientAuth = {
|
|
17
|
+
async env() {
|
|
18
|
+
return undefined;
|
|
19
|
+
},
|
|
20
|
+
async fileExists() {
|
|
21
|
+
return false;
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
class InvalidRequest extends Error {
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Node model adapter. Local provider configuration is read only when invoked.
|
|
28
|
+
* A provider failure is returned as a failure outcome; only an abort is thrown.
|
|
29
|
+
*/
|
|
13
30
|
export function piModel(options = {}) {
|
|
31
|
+
const settings = {
|
|
32
|
+
attempts: Math.max(1, options.settings?.attempts ?? 3),
|
|
33
|
+
retryBaseDelayMs: options.settings?.retryBaseDelayMs ?? 2_000,
|
|
34
|
+
idleTimeoutMs: options.settings?.idleTimeoutMs ?? 300_000,
|
|
35
|
+
requestTimeoutMs: options.settings?.requestTimeoutMs ?? 600_000,
|
|
36
|
+
};
|
|
14
37
|
return async (call, context) => {
|
|
15
38
|
context.signal.throwIfAborted();
|
|
16
39
|
const root = options.root ?? "";
|
|
17
40
|
const stored = await options.readHostModel?.();
|
|
18
41
|
if (!stored)
|
|
19
|
-
|
|
42
|
+
return failure("auth", "Model provider is not configured. Set it in Studio, or run nylo configure (npx @nylorun/cli configure).", false);
|
|
43
|
+
const secrets = [
|
|
44
|
+
...projectSecrets(root),
|
|
45
|
+
...credentialSecrets(stored.credential),
|
|
46
|
+
];
|
|
47
|
+
const redacted = (outcome) => ({
|
|
48
|
+
...outcome,
|
|
49
|
+
message: String(scrub(outcome.message, secrets)),
|
|
50
|
+
});
|
|
20
51
|
const requested = call.model?.id;
|
|
21
52
|
const selection = {
|
|
22
53
|
provider: stored.provider,
|
|
23
54
|
model: requested?.startsWith(`${stored.provider}/`)
|
|
24
55
|
? requested.slice(stored.provider.length + 1)
|
|
25
56
|
: (requested ?? stored.model),
|
|
26
|
-
...(stored.baseUrl
|
|
57
|
+
...(stored.baseUrl
|
|
58
|
+
? { custom: { baseUrl: stored.baseUrl, ...(stored.settings ?? {}) } }
|
|
59
|
+
: {}),
|
|
27
60
|
};
|
|
28
|
-
const registry = modelsFor(selection, hostCredentialStore(stored, options.writeHostCredential), { environment: false });
|
|
61
|
+
const registry = modelsFor(selection, hostCredentialStore(stored, options.writeHostCredential), { environment: false, authContext: noAmbientAuth });
|
|
29
62
|
const selected = registry.getModel(selection.provider, selection.model);
|
|
30
63
|
if (!selected)
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
!("signature" in metadata) ||
|
|
41
|
-
typeof metadata.signature !== "string")
|
|
42
|
-
return;
|
|
43
|
-
return metadata.signature;
|
|
44
|
-
};
|
|
45
|
-
const metadataFor = (signature, redacted) => signature === undefined
|
|
46
|
-
? {}
|
|
47
|
-
: {
|
|
48
|
-
providerMetadata: {
|
|
49
|
-
pi: {
|
|
50
|
-
provider: selected.provider,
|
|
51
|
-
model: selected.id,
|
|
52
|
-
signature,
|
|
53
|
-
...(redacted ? { redacted: true } : {}),
|
|
54
|
-
},
|
|
55
|
-
},
|
|
56
|
-
};
|
|
57
|
-
const messages = [];
|
|
58
|
-
const instructions = [];
|
|
59
|
-
const content = async (parts) => {
|
|
60
|
-
const result = [];
|
|
61
|
-
for (const part of parts) {
|
|
62
|
-
if (part.type === "text")
|
|
63
|
-
result.push({ type: "text", text: part.text });
|
|
64
|
-
else if (part.type === "media") {
|
|
65
|
-
if (!selected.input.includes("image"))
|
|
66
|
-
throw new Error("The configured model does not accept images.");
|
|
67
|
-
const ref = part.reference;
|
|
68
|
-
if (!ref ||
|
|
69
|
-
typeof ref !== "object" ||
|
|
70
|
-
Array.isArray(ref) ||
|
|
71
|
-
!("agentId" in ref) ||
|
|
72
|
-
!("assetId" in ref) ||
|
|
73
|
-
typeof ref.agentId !== "string" ||
|
|
74
|
-
typeof ref.assetId !== "string")
|
|
75
|
-
throw new Error("Expected a local media reference.");
|
|
76
|
-
const asset = await options.media?.dataUrl({ agentId: ref.agentId, assetId: ref.assetId }, "sessionId" in ref && typeof ref.sessionId === "string"
|
|
77
|
-
? ref.sessionId
|
|
78
|
-
: call.executionId);
|
|
79
|
-
if (!asset)
|
|
80
|
-
throw new Error("Media is unavailable; pass the shared media adapter to piModel({ media }).");
|
|
81
|
-
const comma = asset.url.indexOf(",");
|
|
82
|
-
result.push({
|
|
83
|
-
type: "image",
|
|
84
|
-
data: asset.url.slice(comma + 1),
|
|
85
|
-
mimeType: asset.asset.mediaType,
|
|
86
|
-
});
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
return result;
|
|
90
|
-
};
|
|
91
|
-
for (const item of call.prompt) {
|
|
92
|
-
if (item.kind === "instructions") {
|
|
93
|
-
instructions.push(...item.content.flatMap((part) => part.type === "text" ? [part.text] : []));
|
|
94
|
-
}
|
|
95
|
-
else if (item.kind === "tool-result") {
|
|
96
|
-
messages.push({
|
|
97
|
-
role: "toolResult",
|
|
98
|
-
toolCallId: item.toolCallId,
|
|
99
|
-
toolName: item.toolName,
|
|
100
|
-
isError: item.status !== "completed",
|
|
101
|
-
timestamp: Date.now(),
|
|
102
|
-
content: await content(item.content),
|
|
103
|
-
});
|
|
104
|
-
}
|
|
105
|
-
else if (item.role === "assistant") {
|
|
106
|
-
messages.push({
|
|
107
|
-
role: "assistant",
|
|
108
|
-
api: selected.api,
|
|
109
|
-
provider: selected.provider,
|
|
110
|
-
model: selected.id,
|
|
111
|
-
timestamp: Date.now(),
|
|
112
|
-
usage: emptyUsage(),
|
|
113
|
-
stopReason: item.content.some((p) => p.type === "tool-call")
|
|
114
|
-
? "toolUse"
|
|
115
|
-
: "stop",
|
|
116
|
-
content: item.content.flatMap((part) => part.type === "tool-call"
|
|
117
|
-
? [
|
|
118
|
-
{
|
|
119
|
-
type: "toolCall",
|
|
120
|
-
id: part.id,
|
|
121
|
-
name: part.name,
|
|
122
|
-
arguments: { ...part.args },
|
|
123
|
-
...(signatureFor(part) === undefined
|
|
124
|
-
? {}
|
|
125
|
-
: { thoughtSignature: signatureFor(part) }),
|
|
126
|
-
},
|
|
127
|
-
]
|
|
128
|
-
: part.type === "text"
|
|
129
|
-
? [
|
|
130
|
-
{
|
|
131
|
-
type: "text",
|
|
132
|
-
text: part.text,
|
|
133
|
-
...(signatureFor(part) === undefined
|
|
134
|
-
? {}
|
|
135
|
-
: { textSignature: signatureFor(part) }),
|
|
136
|
-
},
|
|
137
|
-
]
|
|
138
|
-
: part.type === "reasoning" &&
|
|
139
|
-
signatureFor(part) !== undefined
|
|
140
|
-
? [
|
|
141
|
-
{
|
|
142
|
-
type: "thinking",
|
|
143
|
-
thinking: part.text,
|
|
144
|
-
thinkingSignature: signatureFor(part),
|
|
145
|
-
...(typeof part.providerMetadata?.pi === "object" &&
|
|
146
|
-
part.providerMetadata.pi !== null &&
|
|
147
|
-
"redacted" in part.providerMetadata.pi &&
|
|
148
|
-
part.providerMetadata.pi.redacted === true
|
|
149
|
-
? { redacted: true }
|
|
150
|
-
: {}),
|
|
151
|
-
},
|
|
152
|
-
]
|
|
153
|
-
: []),
|
|
154
|
-
});
|
|
155
|
-
}
|
|
156
|
-
else
|
|
157
|
-
messages.push({
|
|
158
|
-
role: "user",
|
|
159
|
-
content: await content(item.content),
|
|
160
|
-
timestamp: Date.now(),
|
|
161
|
-
});
|
|
64
|
+
return failure("invalid_request", "Unknown model. Run nylo configure (npx @nylorun/cli configure).", false);
|
|
65
|
+
let request;
|
|
66
|
+
try {
|
|
67
|
+
request = await buildContext(call, selected, options);
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
if (error instanceof InvalidRequest)
|
|
71
|
+
return failure("invalid_request", error.message, false);
|
|
72
|
+
throw error;
|
|
162
73
|
}
|
|
163
|
-
const tools = call.tools.map((tool) => ({
|
|
164
|
-
name: tool.name,
|
|
165
|
-
description: tool.description ?? "",
|
|
166
|
-
parameters: { ...tool.inputSchema },
|
|
167
|
-
}));
|
|
168
|
-
const request = {
|
|
169
|
-
systemPrompt: [
|
|
170
|
-
...instructions,
|
|
171
|
-
...(call.outputSchema
|
|
172
|
-
? [
|
|
173
|
-
`Return the final answer as JSON matching this schema: ${JSON.stringify(call.outputSchema)}. Use tools when needed before returning the final JSON.`,
|
|
174
|
-
]
|
|
175
|
-
: []),
|
|
176
|
-
].join("\n"),
|
|
177
|
-
messages,
|
|
178
|
-
tools,
|
|
179
|
-
};
|
|
180
|
-
const secrets = [
|
|
181
|
-
...projectSecrets(root),
|
|
182
|
-
...credentialSecrets(stored.credential),
|
|
183
|
-
];
|
|
184
74
|
// Publish only portable references, never the materialized provider image bytes.
|
|
185
75
|
context.reportPreparedCall?.({
|
|
186
76
|
adapter: "runtime.pi-ai",
|
|
187
77
|
call: scrub(call, secrets),
|
|
188
78
|
});
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
79
|
+
const invocationOptions = {
|
|
80
|
+
temperature: call.model?.controls?.temperature,
|
|
81
|
+
maxTokens: call.model?.controls?.maxOutputTokens,
|
|
82
|
+
...(call.model?.config
|
|
83
|
+
? { samplingParams: { ...call.model.config } }
|
|
84
|
+
: {}),
|
|
85
|
+
// The session id keys the provider's prompt cache and session affinity.
|
|
86
|
+
sessionId: call.executionId,
|
|
87
|
+
cacheRetention: "short",
|
|
88
|
+
timeoutMs: settings.requestTimeoutMs,
|
|
89
|
+
maxRetries: 2,
|
|
90
|
+
maxRetryDelayMs: 60_000,
|
|
91
|
+
};
|
|
92
|
+
const attempt = async () => {
|
|
93
|
+
const idle = new AbortController();
|
|
94
|
+
const signal = AbortSignal.any([context.signal, idle.signal]);
|
|
95
|
+
let timer;
|
|
96
|
+
const arm = () => {
|
|
97
|
+
clearTimeout(timer);
|
|
98
|
+
timer = setTimeout(() => idle.abort(), settings.idleTimeoutMs);
|
|
197
99
|
};
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
const stream = registry.streamSimple(selected, request,
|
|
100
|
+
arm();
|
|
101
|
+
try {
|
|
102
|
+
const stream = registry.streamSimple(selected, request, {
|
|
103
|
+
...invocationOptions,
|
|
104
|
+
signal,
|
|
105
|
+
});
|
|
201
106
|
for await (const event of stream) {
|
|
202
|
-
|
|
107
|
+
arm();
|
|
108
|
+
if (event.type === "text_delta" && options.onPreview) {
|
|
203
109
|
try {
|
|
204
110
|
void Promise.resolve(options.onPreview({
|
|
205
111
|
invocationId: context.invocationId,
|
|
@@ -211,66 +117,304 @@ export function piModel(options = {}) {
|
|
|
211
117
|
}
|
|
212
118
|
}
|
|
213
119
|
}
|
|
214
|
-
response = await stream.result();
|
|
120
|
+
const response = await stream.result();
|
|
121
|
+
if (idle.signal.aborted && !context.signal.aborted)
|
|
122
|
+
return idleTimeout(selected, settings.idleTimeoutMs);
|
|
123
|
+
return response;
|
|
215
124
|
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
if (part.type === "text")
|
|
226
|
-
output.push({
|
|
227
|
-
type: "text",
|
|
228
|
-
text: part.text,
|
|
229
|
-
...metadataFor(part.textSignature),
|
|
230
|
-
});
|
|
231
|
-
else if (part.type === "thinking")
|
|
232
|
-
output.push({
|
|
233
|
-
type: "reasoning",
|
|
234
|
-
text: part.thinking,
|
|
235
|
-
...metadataFor(part.thinkingSignature, part.redacted),
|
|
236
|
-
});
|
|
237
|
-
else if (part.type === "toolCall")
|
|
238
|
-
output.push({
|
|
239
|
-
type: "tool-call",
|
|
240
|
-
id: part.id,
|
|
241
|
-
name: part.name,
|
|
242
|
-
args: part.arguments,
|
|
243
|
-
...metadataFor(part.thoughtSignature),
|
|
244
|
-
});
|
|
125
|
+
catch (error) {
|
|
126
|
+
if (context.signal.aborted)
|
|
127
|
+
throw error;
|
|
128
|
+
if (idle.signal.aborted)
|
|
129
|
+
return idleTimeout(selected, settings.idleTimeoutMs);
|
|
130
|
+
return {
|
|
131
|
+
...failedMessage(selected),
|
|
132
|
+
errorMessage: error instanceof Error ? error.message : String(error),
|
|
133
|
+
};
|
|
245
134
|
}
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
const text = output
|
|
249
|
-
.filter((part) => part.type === "text")
|
|
250
|
-
.map((part) => part.text)
|
|
251
|
-
.join("");
|
|
252
|
-
const value = JSON.parse(text);
|
|
253
|
-
output.splice(0, output.length, { type: "json", value });
|
|
135
|
+
finally {
|
|
136
|
+
clearTimeout(timer);
|
|
254
137
|
}
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
138
|
+
};
|
|
139
|
+
const response = await retryAssistantCall(attempt, {
|
|
140
|
+
enabled: settings.attempts > 1,
|
|
141
|
+
maxRetries: settings.attempts - 1,
|
|
142
|
+
baseDelayMs: settings.retryBaseDelayMs,
|
|
143
|
+
}, context.signal);
|
|
144
|
+
// An abort is never a failure outcome: the host decides what it means.
|
|
145
|
+
context.signal.throwIfAborted();
|
|
146
|
+
if (response.stopReason === "aborted")
|
|
147
|
+
return redacted(failure("transient", "The provider stream was aborted.", true));
|
|
148
|
+
if (response.stopReason === "error") {
|
|
149
|
+
const outcome = classifyAssistantError(response, selected.contextWindow);
|
|
150
|
+
return redacted(outcome.code === "auth"
|
|
151
|
+
? {
|
|
152
|
+
...outcome,
|
|
153
|
+
message: `${outcome.message} Update the provider credential in Studio's Model Settings, or run nylo configure.`,
|
|
154
|
+
}
|
|
155
|
+
: outcome);
|
|
156
|
+
}
|
|
157
|
+
if (response.stopReason === "deferred")
|
|
158
|
+
return failure("invalid_request", "The provider deferred the response, which the Runtime does not request.", false);
|
|
159
|
+
const output = [];
|
|
160
|
+
for (const part of response.content) {
|
|
161
|
+
if (part.type === "text")
|
|
162
|
+
output.push({
|
|
163
|
+
type: "text",
|
|
164
|
+
text: part.text,
|
|
165
|
+
...metadataFor(selected, part.textSignature),
|
|
166
|
+
});
|
|
167
|
+
else if (part.type === "thinking")
|
|
168
|
+
output.push({
|
|
169
|
+
type: "reasoning",
|
|
170
|
+
text: part.thinking,
|
|
171
|
+
...metadataFor(selected, part.thinkingSignature, part.redacted),
|
|
172
|
+
});
|
|
173
|
+
else if (part.type === "toolCall")
|
|
174
|
+
output.push({
|
|
175
|
+
type: "tool-call",
|
|
176
|
+
id: part.id,
|
|
177
|
+
name: part.name,
|
|
178
|
+
args: part.arguments,
|
|
179
|
+
...metadataFor(selected, part.thoughtSignature),
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
if (call.outputSchema &&
|
|
183
|
+
!output.some((part) => part.type === "tool-call")) {
|
|
184
|
+
const text = output
|
|
185
|
+
.filter((part) => part.type === "text")
|
|
186
|
+
.map((part) => part.text)
|
|
187
|
+
.join("");
|
|
188
|
+
let value;
|
|
189
|
+
try {
|
|
190
|
+
value = parseJsonWithRepair(withoutCodeFence(text));
|
|
191
|
+
}
|
|
192
|
+
catch (error) {
|
|
193
|
+
return failure("invalid_output", `The model's final answer is not valid JSON: ${error instanceof Error ? error.message : String(error)}`, false);
|
|
194
|
+
}
|
|
195
|
+
output.splice(0, output.length, { type: "json", value });
|
|
196
|
+
}
|
|
197
|
+
return {
|
|
198
|
+
output,
|
|
199
|
+
finishReason: response.stopReason === "toolUse"
|
|
200
|
+
? "tool-calls"
|
|
201
|
+
: response.stopReason === "length"
|
|
202
|
+
? "length"
|
|
203
|
+
: "stop",
|
|
204
|
+
usage: usageOf(response.usage),
|
|
205
|
+
evidence: {
|
|
206
|
+
resolvedModel: response.responseModel ?? selected.id,
|
|
207
|
+
extras: {
|
|
208
|
+
producer: {
|
|
209
|
+
provider: selected.provider,
|
|
210
|
+
api: selected.api,
|
|
211
|
+
model: selected.id,
|
|
212
|
+
},
|
|
213
|
+
// The engine keeps the next prompt inside this window (Model Calls §7).
|
|
214
|
+
contextWindow: selected.contextWindow,
|
|
215
|
+
maxOutputTokens: selected.maxTokens,
|
|
267
216
|
},
|
|
268
|
-
|
|
269
|
-
|
|
217
|
+
},
|
|
218
|
+
};
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
async function buildContext(call, selected, options) {
|
|
222
|
+
const messages = [];
|
|
223
|
+
const instructions = [];
|
|
224
|
+
const content = async (parts) => {
|
|
225
|
+
const result = [];
|
|
226
|
+
for (const part of parts) {
|
|
227
|
+
if (part.type === "text")
|
|
228
|
+
result.push({ type: "text", text: part.text });
|
|
229
|
+
else if (part.type === "media") {
|
|
230
|
+
if (!selected.input.includes("image"))
|
|
231
|
+
throw new InvalidRequest("The configured model does not accept images.");
|
|
232
|
+
const ref = part.reference;
|
|
233
|
+
if (!ref ||
|
|
234
|
+
typeof ref !== "object" ||
|
|
235
|
+
Array.isArray(ref) ||
|
|
236
|
+
!("agentId" in ref) ||
|
|
237
|
+
!("assetId" in ref) ||
|
|
238
|
+
typeof ref.agentId !== "string" ||
|
|
239
|
+
typeof ref.assetId !== "string")
|
|
240
|
+
throw new InvalidRequest("Expected a local media reference.");
|
|
241
|
+
const asset = await options.media?.dataUrl({ agentId: ref.agentId, assetId: ref.assetId }, "sessionId" in ref && typeof ref.sessionId === "string"
|
|
242
|
+
? ref.sessionId
|
|
243
|
+
: call.executionId);
|
|
244
|
+
if (!asset)
|
|
245
|
+
throw new InvalidRequest("Media is unavailable; pass the shared media adapter to piModel({ media }).");
|
|
246
|
+
const comma = asset.url.indexOf(",");
|
|
247
|
+
result.push({
|
|
248
|
+
type: "image",
|
|
249
|
+
data: asset.url.slice(comma + 1),
|
|
250
|
+
mimeType: asset.asset.mediaType,
|
|
251
|
+
});
|
|
252
|
+
}
|
|
270
253
|
}
|
|
271
|
-
|
|
272
|
-
|
|
254
|
+
return result;
|
|
255
|
+
};
|
|
256
|
+
for (const item of call.prompt) {
|
|
257
|
+
if (item.kind === "instructions") {
|
|
258
|
+
instructions.push(...item.content.flatMap((part) => part.type === "text" ? [part.text] : []));
|
|
259
|
+
}
|
|
260
|
+
else if (item.kind === "tool-result") {
|
|
261
|
+
messages.push({
|
|
262
|
+
role: "toolResult",
|
|
263
|
+
toolCallId: item.toolCallId,
|
|
264
|
+
toolName: item.toolName,
|
|
265
|
+
isError: item.status !== "completed",
|
|
266
|
+
timestamp: Date.now(),
|
|
267
|
+
content: await content(item.content),
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
else if (item.kind === "message" && item.role === "assistant") {
|
|
271
|
+
messages.push(assistantMessage(item, selected));
|
|
273
272
|
}
|
|
273
|
+
else
|
|
274
|
+
messages.push({
|
|
275
|
+
role: "user",
|
|
276
|
+
content: await content(item.content),
|
|
277
|
+
timestamp: Date.now(),
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
const tools = call.tools.map((tool) => ({
|
|
281
|
+
name: tool.name,
|
|
282
|
+
description: tool.description ?? "",
|
|
283
|
+
parameters: { ...tool.inputSchema },
|
|
284
|
+
}));
|
|
285
|
+
return {
|
|
286
|
+
systemPrompt: [
|
|
287
|
+
...instructions,
|
|
288
|
+
...(call.outputSchema
|
|
289
|
+
? [
|
|
290
|
+
`Return the final answer as JSON matching this schema: ${JSON.stringify(call.outputSchema)}. Use tools when needed before returning the final JSON.`,
|
|
291
|
+
]
|
|
292
|
+
: []),
|
|
293
|
+
].join("\n"),
|
|
294
|
+
messages,
|
|
295
|
+
tools,
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* A replayed assistant message keeps the model that produced it, so pi-ai converts
|
|
300
|
+
* history correctly after a model switch (tool-call ids, foreign reasoning). Messages
|
|
301
|
+
* recorded before producers existed are treated as the current model's, as before.
|
|
302
|
+
*/
|
|
303
|
+
function assistantMessage(item, selected) {
|
|
304
|
+
const producer = item.producer;
|
|
305
|
+
const sameModel = !producer ||
|
|
306
|
+
(producer.provider === selected.provider && producer.model === selected.id);
|
|
307
|
+
return {
|
|
308
|
+
role: "assistant",
|
|
309
|
+
api: sameModel ? selected.api : (producer.api ?? "unknown"),
|
|
310
|
+
provider: sameModel ? selected.provider : producer.provider,
|
|
311
|
+
model: sameModel ? selected.id : producer.model,
|
|
312
|
+
timestamp: Date.now(),
|
|
313
|
+
usage: emptyUsage(),
|
|
314
|
+
stopReason: item.content.some((p) => p.type === "tool-call")
|
|
315
|
+
? "toolUse"
|
|
316
|
+
: "stop",
|
|
317
|
+
content: item.content.flatMap((part) => {
|
|
318
|
+
const signature = signatureFor(selected, part);
|
|
319
|
+
if (part.type === "tool-call")
|
|
320
|
+
return [
|
|
321
|
+
{
|
|
322
|
+
type: "toolCall",
|
|
323
|
+
id: part.id,
|
|
324
|
+
name: part.name,
|
|
325
|
+
arguments: { ...part.args },
|
|
326
|
+
...(signature === undefined ? {} : { thoughtSignature: signature }),
|
|
327
|
+
},
|
|
328
|
+
];
|
|
329
|
+
if (part.type === "text")
|
|
330
|
+
return [
|
|
331
|
+
{
|
|
332
|
+
type: "text",
|
|
333
|
+
text: part.text,
|
|
334
|
+
...(signature === undefined ? {} : { textSignature: signature }),
|
|
335
|
+
},
|
|
336
|
+
];
|
|
337
|
+
if (part.type === "reasoning" && signature !== undefined)
|
|
338
|
+
return [
|
|
339
|
+
{
|
|
340
|
+
type: "thinking",
|
|
341
|
+
thinking: part.text,
|
|
342
|
+
thinkingSignature: signature,
|
|
343
|
+
...(typeof part.providerMetadata?.pi === "object" &&
|
|
344
|
+
part.providerMetadata.pi !== null &&
|
|
345
|
+
"redacted" in part.providerMetadata.pi &&
|
|
346
|
+
part.providerMetadata.pi.redacted === true
|
|
347
|
+
? { redacted: true }
|
|
348
|
+
: {}),
|
|
349
|
+
},
|
|
350
|
+
];
|
|
351
|
+
return [];
|
|
352
|
+
}),
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
/** A signature is replayed only to the provider and model that produced it. */
|
|
356
|
+
function signatureFor(selected, part) {
|
|
357
|
+
const metadata = "providerMetadata" in part ? part.providerMetadata?.pi : undefined;
|
|
358
|
+
if (!metadata || typeof metadata !== "object" || Array.isArray(metadata))
|
|
359
|
+
return;
|
|
360
|
+
if (!("provider" in metadata) ||
|
|
361
|
+
metadata.provider !== selected.provider ||
|
|
362
|
+
!("model" in metadata) ||
|
|
363
|
+
metadata.model !== selected.id ||
|
|
364
|
+
!("signature" in metadata) ||
|
|
365
|
+
typeof metadata.signature !== "string")
|
|
366
|
+
return;
|
|
367
|
+
return metadata.signature;
|
|
368
|
+
}
|
|
369
|
+
function metadataFor(selected, signature, redacted) {
|
|
370
|
+
if (signature === undefined)
|
|
371
|
+
return {};
|
|
372
|
+
return {
|
|
373
|
+
providerMetadata: {
|
|
374
|
+
pi: {
|
|
375
|
+
provider: selected.provider,
|
|
376
|
+
model: selected.id,
|
|
377
|
+
signature,
|
|
378
|
+
...(redacted ? { redacted: true } : {}),
|
|
379
|
+
},
|
|
380
|
+
// An OpenAI-compatible reasoning field is needed only inside the turn's tool loop.
|
|
381
|
+
...(OPENAI_COMPATIBLE_REASONING.test(signature) ? { replay: "turn" } : {}),
|
|
382
|
+
},
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
/** Open-weight models often wrap a JSON answer in a Markdown code fence. */
|
|
386
|
+
function withoutCodeFence(text) {
|
|
387
|
+
const fenced = /^\s*```[a-zA-Z]*\s*\n([\s\S]*?)\n?```\s*$/.exec(text);
|
|
388
|
+
return fenced ? fenced[1] : text;
|
|
389
|
+
}
|
|
390
|
+
function usageOf(usage) {
|
|
391
|
+
return {
|
|
392
|
+
inputTokens: usage.input,
|
|
393
|
+
outputTokens: usage.output,
|
|
394
|
+
totalTokens: usage.totalTokens,
|
|
395
|
+
...(usage.cacheRead ? { cachedTokens: usage.cacheRead } : {}),
|
|
396
|
+
...(usage.cacheWrite ? { cacheWriteTokens: usage.cacheWrite } : {}),
|
|
397
|
+
...(usage.reasoning ? { reasoningTokens: usage.reasoning } : {}),
|
|
398
|
+
costUsd: usage.cost.total,
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
function failedMessage(selected) {
|
|
402
|
+
return {
|
|
403
|
+
role: "assistant",
|
|
404
|
+
content: [],
|
|
405
|
+
api: selected.api,
|
|
406
|
+
provider: selected.provider,
|
|
407
|
+
model: selected.id,
|
|
408
|
+
usage: emptyUsage(),
|
|
409
|
+
stopReason: "error",
|
|
410
|
+
timestamp: Date.now(),
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
/** An idle stream is a retryable timeout, not an abort. */
|
|
414
|
+
function idleTimeout(selected, idleTimeoutMs) {
|
|
415
|
+
return {
|
|
416
|
+
...failedMessage(selected),
|
|
417
|
+
errorMessage: `The model stream produced nothing for ${Math.round(idleTimeoutMs / 1000)} s (idle timeout).`,
|
|
274
418
|
};
|
|
275
419
|
}
|
|
276
420
|
function hostCredentialStore(stored, write) {
|