@nylorun/runtime 0.1.1-beta → 0.1.2-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 CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.2-beta
4
+
5
+ ### Patch Changes
6
+
7
+ - d27242c: Show stopped guardrail and failed model requests as errors in Studio. Runtime now emits a terminal AG-UI error instead of marking failed requests successful, and Studio displays the reported message. The guardrails example also checks text content parts sent by Studio, including mixed media input, before invoking the model.
8
+ - d27242c: Preserve opaque provider continuation metadata through assistant conversation history. Gemini tool calls now retain thought signatures when sending tool results back to the model, including signed empty text and reasoning blocks. Only the originating provider and model receive their signatures.
9
+
3
10
  ## 0.1.1-beta
4
11
 
5
12
  ### Patch Changes
package/README.md CHANGED
@@ -35,10 +35,18 @@ The creator runs `nylorun configure` after installation and before development u
35
35
 
36
36
  ## Portable contracts
37
37
 
38
- `RuntimeAgent` supplies `id`, `name`, a matching `manifest`, and `run({ id })`. Its session supplies `input`, `stream`, `observe`, and `stop`. An input handle's `completed` promise settles after that input's execution or pause, with ordered lifecycle events and a status. Runtime uses completion events for terminal output and interactions, and observation events for optional diagnostics. Engines need not emit model/tool diagnostics or middleware metadata. Runtime records input before execution and flushes persistence before returning an HTTP reply. `stop()` releases session resources; optional agent `close()` runs after session shutdown. Shutdown is idempotent.
38
+ `RuntimeAgent` supplies `id`, `name`, a matching `manifest`, and `run({ id })`. Its session supplies `input`, `stream`, `observe`, and `stop`. An input handle's `completed` promise settles after that input's execution or pause, with ordered lifecycle events and a status. Runtime uses completion events for terminal output, interactions, and failures, and observation events for optional diagnostics. Error or tripwire completions and rejected, cancelled, or stopped statuses produce a terminal AG-UI error; repeated diagnostic and completion reports do not duplicate the failure. Engines need not emit model/tool diagnostics or middleware metadata. Runtime records input before execution and flushes persistence before returning an HTTP reply. `stop()` releases session resources; optional agent `close()` runs after session shutdown. Shutdown is idempotent.
39
39
 
40
40
  `RuntimeModelAdapter` accepts ordered prompt items, tools, model controls, opaque media references, and an abort signal, returning portable text/reasoning/tool-call outputs. It has no engine import. Integration tests in the creator check assignment in both directions with Harness without casts. Runtime's own tests use an independent engine.
41
41
 
42
42
  Discovery and manifest documents use protocol version 2. The agent's manifest is under `manifest`; no engine-branded envelope is required. Existing agent-scoped HTTP endpoint paths remain unchanged. Studio also understands legacy version-1 `harness.manifest` documents.
43
43
 
44
44
  Repository development: [contributing](../CONTRIBUTING.md). Package publication: [releasing](../RELEASING.md).
45
+
46
+ Provider continuation state (for example Gemini thought signatures) travels as
47
+ opaque JSON `providerMetadata` on assistant text, reasoning, and tool-call
48
+ blocks. Engines integrating `piModel()` must preserve it on the corresponding
49
+ assistant prompt parts, including empty signed blocks and their order. Runtime
50
+ only reuses signatures with the originating provider and model. Middleware that
51
+ rewrites a signed block must remove its metadata; signatures describe the
52
+ original provider output. Unsigned reasoning remains diagnostic output.
@@ -40,6 +40,10 @@ export interface RuntimeEvent {
40
40
  readonly event?: RuntimeInputEvent;
41
41
  readonly interaction?: unknown;
42
42
  readonly attributes?: unknown;
43
+ readonly tripwire?: {
44
+ readonly code: string;
45
+ readonly message?: string;
46
+ };
43
47
  }
44
48
  export interface RuntimeCompletion {
45
49
  readonly status: "completed" | "waiting" | "rejected" | "cancelled" | "stopped";
@@ -70,8 +74,15 @@ export interface RuntimeAgent {
70
74
  }): RuntimeSession;
71
75
  close?(): Promise<void>;
72
76
  }
73
- export type PromptContentPart = UserContentPart | {
77
+ export type PromptContentPart = Exclude<UserContentPart, {
78
+ readonly type: "text";
79
+ }> | {
80
+ readonly type: "text" | "reasoning";
81
+ readonly text: string;
82
+ readonly providerMetadata?: JsonObject;
83
+ } | {
74
84
  readonly type: "tool-call";
85
+ readonly providerMetadata?: JsonObject;
75
86
  readonly id: string;
76
87
  readonly name: string;
77
88
  readonly args: JsonObject;
@@ -117,11 +128,13 @@ export interface RuntimeModelCandidate {
117
128
  readonly output: readonly ({
118
129
  readonly type: "text" | "reasoning";
119
130
  readonly text: string;
131
+ readonly providerMetadata?: JsonObject;
120
132
  } | {
121
133
  readonly type: "json";
122
134
  readonly value: JsonValue;
123
135
  } | {
124
136
  readonly type: "tool-call";
137
+ readonly providerMetadata?: JsonObject;
125
138
  readonly id: string;
126
139
  readonly name: string;
127
140
  readonly args: JsonObject;
@@ -21,6 +21,31 @@ export function piModel(options = {}) {
21
21
  const selected = registry.getModel(selection.provider, selection.model);
22
22
  if (!selected)
23
23
  throw new Error("Unknown model. Run nylorun configure.");
24
+ const signatureFor = (part) => {
25
+ const metadata = "providerMetadata" in part ? part.providerMetadata?.pi : undefined;
26
+ if (!metadata || typeof metadata !== "object" || Array.isArray(metadata))
27
+ return;
28
+ if (!("provider" in metadata) ||
29
+ metadata.provider !== selected.provider ||
30
+ !("model" in metadata) ||
31
+ metadata.model !== selected.id ||
32
+ !("signature" in metadata) ||
33
+ typeof metadata.signature !== "string")
34
+ return;
35
+ return metadata.signature;
36
+ };
37
+ const metadataFor = (signature, redacted) => signature === undefined
38
+ ? {}
39
+ : {
40
+ providerMetadata: {
41
+ pi: {
42
+ provider: selected.provider,
43
+ model: selected.id,
44
+ signature,
45
+ ...(redacted ? { redacted: true } : {}),
46
+ },
47
+ },
48
+ };
24
49
  const messages = [];
25
50
  const instructions = [];
26
51
  const content = async (parts) => {
@@ -85,11 +110,37 @@ export function piModel(options = {}) {
85
110
  id: part.id,
86
111
  name: part.name,
87
112
  arguments: { ...part.args },
113
+ ...(signatureFor(part) === undefined
114
+ ? {}
115
+ : { thoughtSignature: signatureFor(part) }),
88
116
  },
89
117
  ]
90
118
  : part.type === "text"
91
- ? [{ type: "text", text: part.text }]
92
- : []),
119
+ ? [
120
+ {
121
+ type: "text",
122
+ text: part.text,
123
+ ...(signatureFor(part) === undefined
124
+ ? {}
125
+ : { textSignature: signatureFor(part) }),
126
+ },
127
+ ]
128
+ : part.type === "reasoning" &&
129
+ signatureFor(part) !== undefined
130
+ ? [
131
+ {
132
+ type: "thinking",
133
+ thinking: part.text,
134
+ thinkingSignature: signatureFor(part),
135
+ ...(typeof part.providerMetadata?.pi === "object" &&
136
+ part.providerMetadata.pi !== null &&
137
+ "redacted" in part.providerMetadata.pi &&
138
+ part.providerMetadata.pi.redacted === true
139
+ ? { redacted: true }
140
+ : {}),
141
+ },
142
+ ]
143
+ : []),
93
144
  });
94
145
  }
95
146
  else
@@ -132,15 +183,24 @@ export function piModel(options = {}) {
132
183
  const output = [];
133
184
  for (const part of response.content) {
134
185
  if (part.type === "text")
135
- output.push({ type: "text", text: part.text });
186
+ output.push({
187
+ type: "text",
188
+ text: part.text,
189
+ ...metadataFor(part.textSignature),
190
+ });
136
191
  else if (part.type === "thinking")
137
- output.push({ type: "reasoning", text: part.thinking });
192
+ output.push({
193
+ type: "reasoning",
194
+ text: part.thinking,
195
+ ...metadataFor(part.thinkingSignature, part.redacted),
196
+ });
138
197
  else if (part.type === "toolCall")
139
198
  output.push({
140
199
  type: "tool-call",
141
200
  id: part.id,
142
201
  name: part.name,
143
202
  args: part.arguments,
203
+ ...metadataFor(part.thoughtSignature),
144
204
  });
145
205
  }
146
206
  return {
@@ -40,13 +40,22 @@ export function agUiEvents(events, threadId, runId) {
40
40
  : JSON.stringify(event.payload.output);
41
41
  output.push({ type: "TEXT_MESSAGE_START", messageId, role: "assistant" }, { type: "TEXT_MESSAGE_CONTENT", messageId, delta: text }, { type: "TEXT_MESSAGE_END", messageId });
42
42
  }
43
- else if (event.type === "error") {
43
+ else if (event.type === "error" || event.type === "tripwire") {
44
+ const tripwire = event.payload.tripwire;
45
+ const payload = tripwire && typeof tripwire === "object"
46
+ ? { ...event.payload, ...tripwire }
47
+ : event.payload;
48
+ const attributes = payload.attributes;
49
+ const message = payload.message ??
50
+ (attributes && typeof attributes === "object" && "message" in attributes
51
+ ? attributes.message
52
+ : undefined);
44
53
  output.push({
45
54
  type: "RUN_ERROR",
46
- message: typeof event.payload.message === "string"
47
- ? event.payload.message
48
- : "Agent run failed.",
55
+ message: typeof message === "string" ? message : "Agent run failed.",
56
+ ...(typeof payload.code === "string" ? { code: payload.code } : {}),
49
57
  });
58
+ return Object.freeze(output);
50
59
  }
51
60
  }
52
61
  output.push({ type: "RUN_FINISHED", threadId, runId });
@@ -176,7 +176,9 @@ export async function createRuntime(options) {
176
176
  ? payload.threadId
177
177
  : randomUUID();
178
178
  if (!isSessionId(threadId))
179
- return context.json({ error: "threadId may only contain letters, digits, '.', '_' and '-'" }, 400);
179
+ return context.json({
180
+ error: "threadId may only contain letters, digits, '.', '_' and '-'",
181
+ }, 400);
180
182
  const runId = typeof payload?.runId === "string" && payload.runId
181
183
  ? payload.runId
182
184
  : randomUUID();
@@ -266,6 +268,7 @@ export async function createRuntime(options) {
266
268
  ...("approved" in inputEvent ? { approved: inputEvent.approved } : {}),
267
269
  ...("value" in inputEvent ? { value: inputEvent.value } : {}),
268
270
  });
271
+ const start = entry.events.length;
269
272
  try {
270
273
  const result = await entry.session.input(input).completed;
271
274
  for (const event of result.events) {
@@ -281,12 +284,26 @@ export async function createRuntime(options) {
281
284
  add(entry, agentId, event.type, { interaction: event.interaction });
282
285
  }
283
286
  }
284
- entry.status =
285
- result.status === "waiting"
287
+ const observedFailure = entry.events
288
+ .slice(start)
289
+ .some((event) => event.type === "error" || event.type === "tripwire");
290
+ const completionFailure = result.events.find((event) => event.type === "error" || event.type === "tripwire");
291
+ const failed = observedFailure ||
292
+ completionFailure !== undefined ||
293
+ (result.status !== "completed" && result.status !== "waiting");
294
+ if (!observedFailure && completionFailure) {
295
+ add(entry, agentId, completionFailure.type, observedPayload(completionFailure));
296
+ }
297
+ else if (!observedFailure && failed) {
298
+ add(entry, agentId, "error", {
299
+ message: `Agent run ${result.status}.`,
300
+ });
301
+ }
302
+ entry.status = failed
303
+ ? "failed"
304
+ : result.status === "waiting"
286
305
  ? "waiting"
287
- : result.status === "completed"
288
- ? "completed"
289
- : "failed";
306
+ : "completed";
290
307
  await entry.writes;
291
308
  }
292
309
  catch (error) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nylorun/runtime",
3
- "version": "0.1.1-beta",
3
+ "version": "0.1.2-beta",
4
4
  "description": "Portable agent hosting, model providers, and the Nylorun CLI.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",