@rynx-ai/runtime 0.1.11-beta.48 → 0.1.11-beta.49

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.
@@ -32,19 +32,24 @@ function strippedImagePlaceholder(source) {
32
32
  * history. A Read image result can contain a full-resolution base64 payload;
33
33
  * replaying it as text only bloats the transcript, while the model cannot use
34
34
  * those encoded bytes as text. Keep a small, human-readable marker instead. */
35
- function stripInlineImageData(value) {
35
+ function stripInlineImageData(value, preview) {
36
36
  if (Array.isArray(value))
37
- return value.map(stripInlineImageData);
37
+ return value.map((part) => stripInlineImageData(part, preview));
38
38
  if (!isObject(value))
39
39
  return value;
40
40
  const source = isObject(value.source) ? value.source : undefined;
41
41
  if (value.type === "image" && source) {
42
- return { type: "text", text: strippedImagePlaceholder(source) };
42
+ return {
43
+ type: "text",
44
+ text: value === preview
45
+ ? `[${source.media_type} image attached]`
46
+ : strippedImagePlaceholder(source),
47
+ };
43
48
  }
44
- return Object.fromEntries(Object.entries(value).map(([key, nested]) => [key, stripInlineImageData(nested)]));
49
+ return Object.fromEntries(Object.entries(value).map(([key, nested]) => [key, stripInlineImageData(nested, preview)]));
45
50
  }
46
- function stringifyToolContent(content) {
47
- const stripped = stripInlineImageData(content);
51
+ function stringifyToolContent(content, preview) {
52
+ const stripped = stripInlineImageData(content, preview);
48
53
  if (typeof stripped === "string")
49
54
  return stripped;
50
55
  if (Array.isArray(stripped)) {
@@ -58,6 +63,25 @@ function stringifyToolContent(content) {
58
63
  }
59
64
  return stripped == null ? "" : JSON.stringify(stripped);
60
65
  }
66
+ /** Native Read returns inline image bytes, sometimes transcoded from the file.
67
+ * Reuse the tool-image resource pipeline so the preview preserves those bytes
68
+ * while the canonical output text stays small. Remaining images keep markers
69
+ * until the shared tool-output contract supports more than one preview. */
70
+ function inlineImagePreview(content) {
71
+ if (!Array.isArray(content))
72
+ return undefined;
73
+ for (const block of content) {
74
+ if (!isObject(block) || block.type !== "image" || !isObject(block.source))
75
+ continue;
76
+ const { type, media_type: mediaType, data } = block.source;
77
+ if (type === "base64" &&
78
+ typeof mediaType === "string" && /^image\/[a-z0-9+.-]+$/i.test(mediaType) &&
79
+ typeof data === "string" && /^[A-Za-z0-9+/]+={0,2}$/.test(data)) {
80
+ return { block, result: `data:${mediaType};base64,${data}` };
81
+ }
82
+ }
83
+ return undefined;
84
+ }
61
85
  const BASH_INPUT_RE = /<bash-input>([\s\S]*?)<\/bash-input>/;
62
86
  const BASH_STDOUT_RE = /<bash-stdout>([\s\S]*?)<\/bash-stdout>/;
63
87
  const BASH_STDERR_RE = /<bash-stderr>([\s\S]*?)<\/bash-stderr>/;
@@ -188,14 +212,16 @@ export function parseTranscriptRecord(record, opts) {
188
212
  return [];
189
213
  for (const block of message.content) {
190
214
  if (block.type === "tool_result") {
215
+ const preview = block.is_error ? undefined : inlineImagePreview(block.content);
191
216
  out.push({
192
217
  type: "tool",
193
218
  event: "on_tool_end",
194
219
  output: {
195
220
  id: block.tool_use_id,
196
221
  status: block.is_error ? "failed" : "completed",
197
- aggregatedOutput: stringifyToolContent(block.content),
222
+ aggregatedOutput: stringifyToolContent(block.content, preview?.block),
198
223
  exitCode: null,
224
+ ...(preview ? { generatedImage: { result: preview.result } } : {}),
199
225
  },
200
226
  ...(block.is_error ? { error: "tool_error" } : {}),
201
227
  data: { tool_use_id: block.tool_use_id, itemStatus: "completed" },
@@ -249,6 +249,28 @@ export function mapCodexItem(method, item) {
249
249
  });
250
250
  return { events };
251
251
  }
252
+ case "imageView": {
253
+ const image = item;
254
+ if (typeof image.id !== "string" || !image.id ||
255
+ typeof image.path !== "string" || !image.path)
256
+ return { events };
257
+ events.push({
258
+ type: "tool",
259
+ event: isStart ? "on_tool_start" : "on_tool_end",
260
+ name: "view_image",
261
+ // Image views may arrive only as completed items, including backfill.
262
+ input: isStart || isEnd ? { id: image.id, path: image.path } : undefined,
263
+ output: isEnd
264
+ ? {
265
+ id: image.id,
266
+ aggregatedOutput: `Viewed image at ${image.path}`,
267
+ generatedImage: { savedPath: image.path },
268
+ }
269
+ : undefined,
270
+ data: { method, item },
271
+ });
272
+ return { events };
273
+ }
252
274
  case "imageGeneration": {
253
275
  const image = item;
254
276
  events.push({
@@ -298,7 +298,11 @@ export interface ImageGenerationItem extends ThreadItemBase {
298
298
  /** Codex App Server guarantees this is absolute when present. */
299
299
  savedPath?: string;
300
300
  }
301
- export type ThreadItem = UserMessageItem | AgentMessageItem | ReasoningItem | PlanItem | CommandExecutionItem | FileChangeItem | McpToolCallItem | DynamicToolCallItem | WebSearchItem | ImageGenerationItem | (ThreadItemBase & Record<string, unknown>);
301
+ export interface ImageViewItem extends ThreadItemBase {
302
+ type: "imageView";
303
+ path: string;
304
+ }
305
+ export type ThreadItem = UserMessageItem | AgentMessageItem | ReasoningItem | PlanItem | CommandExecutionItem | FileChangeItem | McpToolCallItem | DynamicToolCallItem | WebSearchItem | ImageGenerationItem | ImageViewItem | (ThreadItemBase & Record<string, unknown>);
302
306
  export interface ThreadSummary {
303
307
  id?: string;
304
308
  threadId?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rynx-ai/runtime",
3
- "version": "0.1.11-beta.48",
3
+ "version": "0.1.11-beta.49",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/rynx-ai/rynx.git",
@@ -26,7 +26,7 @@
26
26
  "dependencies": {
27
27
  "smol-toml": "1.7.1",
28
28
  "ws": "^8.21.0",
29
- "@rynx-ai/core": "0.1.11-beta.48"
29
+ "@rynx-ai/core": "0.1.11-beta.49"
30
30
  },
31
31
  "devDependencies": {
32
32
  "@types/ws": "^8.18.1"