@dreb/coding-agent 2.70.1 → 2.72.0
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/README.md +4 -1
- package/dist/cli/args.d.ts +6 -0
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +17 -0
- package/dist/cli/args.js.map +1 -1
- package/dist/core/agent-session.d.ts +3 -9
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +19 -51
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/output-guard.d.ts +6 -0
- package/dist/core/output-guard.d.ts.map +1 -1
- package/dist/core/output-guard.js +86 -13
- package/dist/core/output-guard.js.map +1 -1
- package/dist/core/thinking.d.ts +2 -0
- package/dist/core/thinking.d.ts.map +1 -1
- package/dist/core/thinking.js +20 -14
- package/dist/core/thinking.js.map +1 -1
- package/dist/core/tools/subagent.d.ts +1 -0
- package/dist/core/tools/subagent.d.ts.map +1 -1
- package/dist/core/tools/subagent.js +13 -0
- package/dist/core/tools/subagent.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +13 -10
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +9 -5
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/rpc/index.d.ts +2 -2
- package/dist/modes/rpc/index.d.ts.map +1 -1
- package/dist/modes/rpc/index.js +1 -1
- package/dist/modes/rpc/index.js.map +1 -1
- package/dist/modes/rpc/rpc-event-projection.d.ts +14 -9
- package/dist/modes/rpc/rpc-event-projection.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-event-projection.js +16 -11
- package/dist/modes/rpc/rpc-event-projection.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts +10 -1
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +29 -15
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-types.d.ts +28 -2
- package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-types.js.map +1 -1
- package/docs/agent-models.md +1 -1
- package/docs/models.md +1 -1
- package/docs/providers.md +26 -0
- package/docs/rpc.md +25 -13
- package/package.json +1 -1
|
@@ -6,6 +6,6 @@
|
|
|
6
6
|
*/
|
|
7
7
|
export type { ModelInfo, RpcClientOptions, RpcEventListener, RpcExitInfo, RpcExitListener } from "./rpc-client.js";
|
|
8
8
|
export { RpcClient } from "./rpc-client.js";
|
|
9
|
-
export { createDashboardRpcEventProjector, projectDashboardRpcEvent, } from "./rpc-event-projection.js";
|
|
10
|
-
export type { RpcAgentTypeInfo, RpcBackgroundAgentInfo, RpcCommand, RpcCommandType, RpcContextTrustEvaluation, RpcContextTrustMutationResult, RpcDashboardSnapshot, RpcDashboardSnapshotBarrierEvent, RpcEvent, RpcExtensionUIRequest, RpcExtensionUIResponse, RpcPendingMessages, RpcPerformanceStats, RpcQueuedMessage, RpcResources, RpcResponse, RpcScopedModel, RpcSessionInfo, RpcSessionState, RpcSessionTask, RpcSettingsSetResult, RpcSettingsSnapshot, RpcSettingsUpdate, RpcSlashCommand, RpcTreeNode, RpcTrustedFolderRemovalResult, } from "./rpc-types.js";
|
|
9
|
+
export { createDashboardRpcEventProjector, projectDashboardRpcEvent, projectRpcMessageUpdates, } from "./rpc-event-projection.js";
|
|
10
|
+
export type { RpcAgentTypeInfo, RpcAssistantMessageEvent, RpcBackgroundAgentInfo, RpcCommand, RpcCommandType, RpcContextTrustEvaluation, RpcContextTrustMutationResult, RpcDashboardSnapshot, RpcDashboardSnapshotBarrierEvent, RpcEvent, RpcExtensionUIRequest, RpcExtensionUIResponse, RpcMessageUpdateEvent, RpcPendingMessages, RpcPerformanceStats, RpcQueuedMessage, RpcResources, RpcResponse, RpcScopedModel, RpcSessionEvent, RpcSessionInfo, RpcSessionState, RpcSessionTask, RpcSettingsSetResult, RpcSettingsSnapshot, RpcSettingsUpdate, RpcSlashCommand, RpcTreeNode, RpcTrustedFolderRemovalResult, } from "./rpc-types.js";
|
|
11
11
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/modes/rpc/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,YAAY,EAAE,SAAS,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AACnH,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EACN,gCAAgC,EAChC,wBAAwB,GACxB,MAAM,2BAA2B,CAAC;AACnC,YAAY,EACX,gBAAgB,EAChB,sBAAsB,EACtB,UAAU,EACV,cAAc,EACd,yBAAyB,EACzB,6BAA6B,EAC7B,oBAAoB,EACpB,gCAAgC,EAChC,QAAQ,EACR,qBAAqB,EACrB,sBAAsB,EACtB,kBAAkB,EAClB,mBAAmB,EACnB,gBAAgB,EAChB,YAAY,EACZ,WAAW,EACX,cAAc,EACd,cAAc,EACd,eAAe,EACf,cAAc,EACd,oBAAoB,EACpB,mBAAmB,EACnB,iBAAiB,EACjB,eAAe,EACf,WAAW,EACX,6BAA6B,GAC7B,MAAM,gBAAgB,CAAC","sourcesContent":["/**\n * RPC client and types for programmatic access to the coding agent.\n *\n * Usage:\n * import { RpcClient } from \"@dreb/coding-agent/rpc\";\n */\n\nexport type { ModelInfo, RpcClientOptions, RpcEventListener, RpcExitInfo, RpcExitListener } from \"./rpc-client.js\";\nexport { RpcClient } from \"./rpc-client.js\";\nexport {\n\tcreateDashboardRpcEventProjector,\n\tprojectDashboardRpcEvent,\n} from \"./rpc-event-projection.js\";\nexport type {\n\tRpcAgentTypeInfo,\n\tRpcBackgroundAgentInfo,\n\tRpcCommand,\n\tRpcCommandType,\n\tRpcContextTrustEvaluation,\n\tRpcContextTrustMutationResult,\n\tRpcDashboardSnapshot,\n\tRpcDashboardSnapshotBarrierEvent,\n\tRpcEvent,\n\tRpcExtensionUIRequest,\n\tRpcExtensionUIResponse,\n\tRpcPendingMessages,\n\tRpcPerformanceStats,\n\tRpcQueuedMessage,\n\tRpcResources,\n\tRpcResponse,\n\tRpcScopedModel,\n\tRpcSessionInfo,\n\tRpcSessionState,\n\tRpcSessionTask,\n\tRpcSettingsSetResult,\n\tRpcSettingsSnapshot,\n\tRpcSettingsUpdate,\n\tRpcSlashCommand,\n\tRpcTreeNode,\n\tRpcTrustedFolderRemovalResult,\n} from \"./rpc-types.js\";\n"]}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/modes/rpc/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,YAAY,EAAE,SAAS,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AACnH,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EACN,gCAAgC,EAChC,wBAAwB,EACxB,wBAAwB,GACxB,MAAM,2BAA2B,CAAC;AACnC,YAAY,EACX,gBAAgB,EAChB,wBAAwB,EACxB,sBAAsB,EACtB,UAAU,EACV,cAAc,EACd,yBAAyB,EACzB,6BAA6B,EAC7B,oBAAoB,EACpB,gCAAgC,EAChC,QAAQ,EACR,qBAAqB,EACrB,sBAAsB,EACtB,qBAAqB,EACrB,kBAAkB,EAClB,mBAAmB,EACnB,gBAAgB,EAChB,YAAY,EACZ,WAAW,EACX,cAAc,EACd,eAAe,EACf,cAAc,EACd,eAAe,EACf,cAAc,EACd,oBAAoB,EACpB,mBAAmB,EACnB,iBAAiB,EACjB,eAAe,EACf,WAAW,EACX,6BAA6B,GAC7B,MAAM,gBAAgB,CAAC","sourcesContent":["/**\n * RPC client and types for programmatic access to the coding agent.\n *\n * Usage:\n * import { RpcClient } from \"@dreb/coding-agent/rpc\";\n */\n\nexport type { ModelInfo, RpcClientOptions, RpcEventListener, RpcExitInfo, RpcExitListener } from \"./rpc-client.js\";\nexport { RpcClient } from \"./rpc-client.js\";\nexport {\n\tcreateDashboardRpcEventProjector,\n\tprojectDashboardRpcEvent,\n\tprojectRpcMessageUpdates,\n} from \"./rpc-event-projection.js\";\nexport type {\n\tRpcAgentTypeInfo,\n\tRpcAssistantMessageEvent,\n\tRpcBackgroundAgentInfo,\n\tRpcCommand,\n\tRpcCommandType,\n\tRpcContextTrustEvaluation,\n\tRpcContextTrustMutationResult,\n\tRpcDashboardSnapshot,\n\tRpcDashboardSnapshotBarrierEvent,\n\tRpcEvent,\n\tRpcExtensionUIRequest,\n\tRpcExtensionUIResponse,\n\tRpcMessageUpdateEvent,\n\tRpcPendingMessages,\n\tRpcPerformanceStats,\n\tRpcQueuedMessage,\n\tRpcResources,\n\tRpcResponse,\n\tRpcScopedModel,\n\tRpcSessionEvent,\n\tRpcSessionInfo,\n\tRpcSessionState,\n\tRpcSessionTask,\n\tRpcSettingsSetResult,\n\tRpcSettingsSnapshot,\n\tRpcSettingsUpdate,\n\tRpcSlashCommand,\n\tRpcTreeNode,\n\tRpcTrustedFolderRemovalResult,\n} from \"./rpc-types.js\";\n"]}
|
package/dist/modes/rpc/index.js
CHANGED
|
@@ -5,5 +5,5 @@
|
|
|
5
5
|
* import { RpcClient } from "@dreb/coding-agent/rpc";
|
|
6
6
|
*/
|
|
7
7
|
export { RpcClient } from "./rpc-client.js";
|
|
8
|
-
export { createDashboardRpcEventProjector, projectDashboardRpcEvent, } from "./rpc-event-projection.js";
|
|
8
|
+
export { createDashboardRpcEventProjector, projectDashboardRpcEvent, projectRpcMessageUpdates, } from "./rpc-event-projection.js";
|
|
9
9
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/modes/rpc/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EACN,gCAAgC,EAChC,wBAAwB,GACxB,MAAM,2BAA2B,CAAC","sourcesContent":["/**\n * RPC client and types for programmatic access to the coding agent.\n *\n * Usage:\n * import { RpcClient } from \"@dreb/coding-agent/rpc\";\n */\n\nexport type { ModelInfo, RpcClientOptions, RpcEventListener, RpcExitInfo, RpcExitListener } from \"./rpc-client.js\";\nexport { RpcClient } from \"./rpc-client.js\";\nexport {\n\tcreateDashboardRpcEventProjector,\n\tprojectDashboardRpcEvent,\n} from \"./rpc-event-projection.js\";\nexport type {\n\tRpcAgentTypeInfo,\n\tRpcBackgroundAgentInfo,\n\tRpcCommand,\n\tRpcCommandType,\n\tRpcContextTrustEvaluation,\n\tRpcContextTrustMutationResult,\n\tRpcDashboardSnapshot,\n\tRpcDashboardSnapshotBarrierEvent,\n\tRpcEvent,\n\tRpcExtensionUIRequest,\n\tRpcExtensionUIResponse,\n\tRpcPendingMessages,\n\tRpcPerformanceStats,\n\tRpcQueuedMessage,\n\tRpcResources,\n\tRpcResponse,\n\tRpcScopedModel,\n\tRpcSessionInfo,\n\tRpcSessionState,\n\tRpcSessionTask,\n\tRpcSettingsSetResult,\n\tRpcSettingsSnapshot,\n\tRpcSettingsUpdate,\n\tRpcSlashCommand,\n\tRpcTreeNode,\n\tRpcTrustedFolderRemovalResult,\n} from \"./rpc-types.js\";\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/modes/rpc/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EACN,gCAAgC,EAChC,wBAAwB,EACxB,wBAAwB,GACxB,MAAM,2BAA2B,CAAC","sourcesContent":["/**\n * RPC client and types for programmatic access to the coding agent.\n *\n * Usage:\n * import { RpcClient } from \"@dreb/coding-agent/rpc\";\n */\n\nexport type { ModelInfo, RpcClientOptions, RpcEventListener, RpcExitInfo, RpcExitListener } from \"./rpc-client.js\";\nexport { RpcClient } from \"./rpc-client.js\";\nexport {\n\tcreateDashboardRpcEventProjector,\n\tprojectDashboardRpcEvent,\n\tprojectRpcMessageUpdates,\n} from \"./rpc-event-projection.js\";\nexport type {\n\tRpcAgentTypeInfo,\n\tRpcAssistantMessageEvent,\n\tRpcBackgroundAgentInfo,\n\tRpcCommand,\n\tRpcCommandType,\n\tRpcContextTrustEvaluation,\n\tRpcContextTrustMutationResult,\n\tRpcDashboardSnapshot,\n\tRpcDashboardSnapshotBarrierEvent,\n\tRpcEvent,\n\tRpcExtensionUIRequest,\n\tRpcExtensionUIResponse,\n\tRpcMessageUpdateEvent,\n\tRpcPendingMessages,\n\tRpcPerformanceStats,\n\tRpcQueuedMessage,\n\tRpcResources,\n\tRpcResponse,\n\tRpcScopedModel,\n\tRpcSessionEvent,\n\tRpcSessionInfo,\n\tRpcSessionState,\n\tRpcSessionTask,\n\tRpcSettingsSetResult,\n\tRpcSettingsSnapshot,\n\tRpcSettingsUpdate,\n\tRpcSlashCommand,\n\tRpcTreeNode,\n\tRpcTrustedFolderRemovalResult,\n} from \"./rpc-types.js\";\n"]}
|
|
@@ -1,31 +1,36 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* RPC event projection.
|
|
3
3
|
*
|
|
4
4
|
* `message_update` events carry two cumulative copies of the growing assistant
|
|
5
5
|
* message: the top-level `message` field and `assistantMessageEvent.partial`.
|
|
6
6
|
* Serializing both for every token makes the child->parent JSONL pipe quadratic
|
|
7
7
|
* in response length and floods the stdout queue (see issue 448).
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
* the delta fields (`delta`, `content`, `toolCall`, `contentIndex`)
|
|
11
|
-
* authoritative
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
9
|
+
* Every RPC runtime projects those fields out by default (issue 535): consumers
|
|
10
|
+
* read the delta fields (`delta`, `content`, `toolCall`, `contentIndex`) and get
|
|
11
|
+
* the authoritative final message from `message_end` (and, for the dashboard,
|
|
12
|
+
* `get_dashboard_snapshot` responses). Clients that still need the legacy full
|
|
13
|
+
* frames opt back in with `--rpc-full-message-updates`.
|
|
14
|
+
*
|
|
15
|
+
* Dashboard runtimes additionally dedupe inline images (see below), which
|
|
16
|
+
* requires a consumer that resolves `image_reference` blocks.
|
|
15
17
|
*
|
|
16
18
|
* Only the quadratic `message_update` fields are removed here. Broader bounding
|
|
17
19
|
* (agent_end messages, tool_execution_update args, retry discardedPartial,
|
|
18
20
|
* images) remains the EventHub's browser-facing concern.
|
|
19
21
|
*/
|
|
20
22
|
/**
|
|
21
|
-
* Project a single agent session event for
|
|
23
|
+
* Project a single agent session event for RPC transport: strip the cumulative
|
|
24
|
+
* `message` / `assistantMessageEvent.partial` fields from `message_update`.
|
|
22
25
|
*
|
|
23
26
|
* Unknown event types are returned exactly as received (same reference) so
|
|
24
27
|
* extensions and future event types remain forward-safe. Projected events are
|
|
25
28
|
* shallow copies — the input event is never mutated, because other session
|
|
26
29
|
* subscribers (and the session's own state) share the same object.
|
|
27
30
|
*/
|
|
28
|
-
export declare function
|
|
31
|
+
export declare function projectRpcMessageUpdates(event: Record<string, unknown>): Record<string, unknown>;
|
|
32
|
+
/** Dashboard projection of a single event (message_update bounding only, no image dedupe). */
|
|
33
|
+
export declare const projectDashboardRpcEvent: typeof projectRpcMessageUpdates;
|
|
29
34
|
/**
|
|
30
35
|
* Create the dashboard-mode RPC event projector for one runRpcMode process.
|
|
31
36
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rpc-event-projection.d.ts","sourceRoot":"","sources":["../../../src/modes/rpc/rpc-event-projection.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAcH;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAgBhG;AA8JD;;;;;GAKG;AACH,wBAAgB,gCAAgC,IAAI,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAM9G","sourcesContent":["/**\n * Dashboard-mode RPC event projection.\n *\n * `message_update` events carry two cumulative copies of the growing assistant\n * message: the top-level `message` field and `assistantMessageEvent.partial`.\n * Serializing both for every token makes the child->parent JSONL pipe quadratic\n * in response length and floods the stdout queue (see issue 448).\n *\n * The dashboard never reads those fields: its browser reducer consumes only\n * the delta fields (`delta`, `content`, `toolCall`, `contentIndex`), and its\n * authoritative transcript comes from `message_end` plus `get_dashboard_snapshot`\n * RPC responses. The dashboard's own EventHub already strips the same fields at\n * the browser SSE boundary (projectDashboardEvent); this module applies the same\n * removal one boundary earlier, before JSONL serialization in runRpcMode.\n *\n * Only the quadratic `message_update` fields are removed here. Broader bounding\n * (agent_end messages, tool_execution_update args, retry discardedPartial,\n * images) remains the EventHub's browser-facing concern.\n */\n\nimport { createHash } from \"node:crypto\";\n\nfunction omit(event: Record<string, unknown>, ...keys: string[]): Record<string, unknown> {\n\tconst copy = { ...event };\n\tfor (const key of keys) delete copy[key];\n\treturn copy;\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n/**\n * Project a single agent session event for dashboard-mode RPC transport.\n *\n * Unknown event types are returned exactly as received (same reference) so\n * extensions and future event types remain forward-safe. Projected events are\n * shallow copies — the input event is never mutated, because other session\n * subscribers (and the session's own state) share the same object.\n */\nexport function projectDashboardRpcEvent(event: Record<string, unknown>): Record<string, unknown> {\n\tswitch (event.type) {\n\t\tcase \"message_update\": {\n\t\t\tconst projected = omit(event, \"message\");\n\t\t\tconst streamEvent = event.assistantMessageEvent;\n\t\t\treturn isPlainObject(streamEvent)\n\t\t\t\t? { ...projected, assistantMessageEvent: omit(streamEvent, \"partial\") }\n\t\t\t\t: projected;\n\t\t}\n\t\tcase \"background_agent_event\": {\n\t\t\tconst child = event.event;\n\t\t\treturn isPlainObject(child) ? { ...event, event: projectDashboardRpcEvent(child) } : event;\n\t\t}\n\t\tdefault:\n\t\t\treturn event;\n\t}\n}\n\n// ---------------------------------------------------------------------------\n// Image dedupe (dashboard mode)\n//\n// Inline image blocks cross the JSONL pipe multiple times per turn: prompt\n// re-emission (message_start + message_end), each tool result\n// (tool_execution_end + message_start + message_end), and the agent_end\n// transcript. A turn with several multi-MiB images easily fills the child\n// stdout queue while the dashboard is busy decoding, and the stdout guard\n// kills the session (issue 495).\n//\n// The dashboard already treats images content-globally: its server projection\n// replaces every inline image with an image_reference whose id is\n// sha256(mimeType + 0x00 + decodedBytes), caches the binary on first sight,\n// and serves later fetches from that cache (recovering from the authoritative\n// transcript when evicted). This projector applies the same reduction one\n// boundary earlier: each unique image crosses stdout at most once per process\n// lifetime; every later occurrence becomes an image_reference the dashboard\n// resolves from its cache. Live events only — command responses\n// (get_messages, get_dashboard_snapshot) always keep full payloads, because\n// they are the authoritative source the dashboard's image recovery reads.\n// ---------------------------------------------------------------------------\n\nconst DASHBOARD_IMAGE_MIME_TYPES = new Set([\"image/png\", \"image/jpeg\", \"image/gif\", \"image/webp\"]);\n\nconst BASE64_PATTERN = /^[A-Za-z0-9+/]+={0,2}$/;\n\nconst JPEG_EOI = Buffer.from([0xff, 0xd9]);\n\ntype ImageReference = { type: \"image_reference\"; id: string; mimeType: string; size: number };\n\n/**\n * Raster-signature check mirroring the dashboard server's hasRasterSignature\n * (packages/dashboard/src/server/dashboard-images.ts). Keep in sync: the child\n * may only turn a block into a reference when the dashboard's strict decode\n * (decodeDashboardImage) will accept it, or the reference dangles (the cache\n * holds nothing and the authoritative recovery decodes the same bytes and\n * rejects them again).\n */\nfunction hasRasterSignature(mimeType: string, bytes: Uint8Array): boolean {\n\tconst buffer = Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength);\n\tswitch (mimeType) {\n\t\tcase \"image/png\": {\n\t\t\tconst signature =\n\t\t\t\tbytes.length >= 24 &&\n\t\t\t\t[0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a].every((byte, i) => bytes[i] === byte);\n\t\t\treturn (\n\t\t\t\tsignature &&\n\t\t\t\tbuffer.subarray(12, 16).toString(\"ascii\") === \"IHDR\" &&\n\t\t\t\tbuffer.readUInt32BE(16) > 0 &&\n\t\t\t\tbuffer.readUInt32BE(20) > 0 &&\n\t\t\t\tbuffer.indexOf(Buffer.from(\"IEND\"), 24) >= 0\n\t\t\t);\n\t\t}\n\t\tcase \"image/jpeg\": {\n\t\t\tif (bytes.length < 4 || bytes[0] !== 0xff || bytes[1] !== 0xd8 || bytes[2] !== 0xff) return false;\n\t\t\t// EOI does not have to be the final byte. Phone JPEGs commonly append\n\t\t\t// auxiliary gain-map, motion-photo, or vendor metadata after the\n\t\t\t// primary image. Browsers and Photon decode the JPEG through EOI and\n\t\t\t// preserve the trailing bytes when the exact original is requested.\n\t\t\treturn buffer.lastIndexOf(JPEG_EOI) >= 2;\n\t\t}\n\t\tcase \"image/gif\": {\n\t\t\tconst header = buffer.subarray(0, 6).toString(\"ascii\");\n\t\t\treturn (\n\t\t\t\tbytes.length >= 14 &&\n\t\t\t\t(header === \"GIF87a\" || header === \"GIF89a\") &&\n\t\t\t\tbuffer.readUInt16LE(6) > 0 &&\n\t\t\t\tbuffer.readUInt16LE(8) > 0 &&\n\t\t\t\tbytes[bytes.length - 1] === 0x3b\n\t\t\t);\n\t\t}\n\t\tcase \"image/webp\": {\n\t\t\tif (\n\t\t\t\tbytes.length < 20 ||\n\t\t\t\tbuffer.subarray(0, 4).toString(\"ascii\") !== \"RIFF\" ||\n\t\t\t\tbuffer.subarray(8, 12).toString(\"ascii\") !== \"WEBP\"\n\t\t\t) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tconst chunk = buffer.subarray(12, 16).toString(\"ascii\");\n\t\t\treturn (\n\t\t\t\tbuffer.readUInt32LE(4) + 8 <= bytes.length && (chunk === \"VP8 \" || chunk === \"VP8L\" || chunk === \"VP8X\")\n\t\t\t);\n\t\t}\n\t}\n\treturn false;\n}\n\n/**\n * Content identity of an inline image block, mirroring the dashboard server's\n * dashboardImageId (sha256 of mimeType + 0x00 + decoded bytes). The gate is as\n * strict as the dashboard's decodeDashboardImage — allowlisted mimeType,\n * canonical base64 (round trip), and a matching raster signature — so a block\n * the dashboard would reject never becomes a reference: it stays inline at\n * every occurrence and is dropped by the dashboard's strict decode, exactly as\n * before this projection existed (no dangling image_references).\n */\nfunction imageBlockIdentity(mimeType: string, data: string): { id: string; size: number } | undefined {\n\tif (!DASHBOARD_IMAGE_MIME_TYPES.has(mimeType)) return undefined;\n\tif (data.length === 0 || data.length % 4 !== 0 || !BASE64_PATTERN.test(data)) return undefined;\n\tconst bytes = Buffer.from(data, \"base64\");\n\tif (bytes.byteLength === 0) return undefined;\n\tif (bytes.toString(\"base64\") !== data) return undefined; // non-canonical encoding the dashboard rejects\n\tif (!hasRasterSignature(mimeType, bytes)) return undefined; // signature mismatch the dashboard rejects\n\tconst id = createHash(\"sha256\").update(mimeType).update(Uint8Array.of(0)).update(bytes).digest(\"hex\");\n\treturn { id, size: bytes.byteLength };\n}\n\n/**\n * Replace occurrences of already-seen image blocks with their stable\n * reference, keeping the first occurrence of each unique image inline.\n * Returns the input by reference when nothing changed, so events without\n * images keep sharing objects with other session subscribers.\n */\nfunction dedupeImages(node: unknown, seen: Map<string, ImageReference>): unknown {\n\tif (Array.isArray(node)) {\n\t\tlet changed = false;\n\t\tconst result: unknown[] = new Array(node.length);\n\t\tfor (let i = 0; i < node.length; i++) {\n\t\t\tconst original = node[i];\n\t\t\tconst next = dedupeImages(original, seen);\n\t\t\tif (next !== original) changed = true;\n\t\t\tresult[i] = next;\n\t\t}\n\t\treturn changed ? result : node;\n\t}\n\tif (isPlainObject(node)) {\n\t\tif (node.type === \"image\" && typeof node.mimeType === \"string\" && typeof node.data === \"string\") {\n\t\t\tconst identity = imageBlockIdentity(node.mimeType, node.data);\n\t\t\tif (identity) {\n\t\t\t\tconst reference = seen.get(identity.id);\n\t\t\t\tif (reference) return reference;\n\t\t\t\tconst created: ImageReference = {\n\t\t\t\t\ttype: \"image_reference\",\n\t\t\t\t\tid: identity.id,\n\t\t\t\t\tmimeType: node.mimeType,\n\t\t\t\t\tsize: identity.size,\n\t\t\t\t};\n\t\t\t\tseen.set(identity.id, created);\n\t\t\t\treturn node;\n\t\t\t}\n\t\t\treturn node;\n\t\t}\n\t\tlet changed = false;\n\t\tconst result: Record<string, unknown> = {};\n\t\tfor (const key of Object.keys(node)) {\n\t\t\tconst original = node[key];\n\t\t\tconst next = dedupeImages(original, seen);\n\t\t\tif (next !== original) changed = true;\n\t\t\tresult[key] = next;\n\t\t}\n\t\treturn changed ? result : node;\n\t}\n\treturn node;\n}\n\n/**\n * Create the dashboard-mode RPC event projector for one runRpcMode process.\n *\n * Composes the per-event field projection with process-lifetime image dedupe.\n * Dashboard-mode sessions only — generic RPC consumers keep the full protocol.\n */\nexport function createDashboardRpcEventProjector(): (event: Record<string, unknown>) => Record<string, unknown> {\n\tconst seenImageIds = new Map<string, ImageReference>();\n\treturn (event: Record<string, unknown>): Record<string, unknown> => {\n\t\tconst projected = projectDashboardRpcEvent(event);\n\t\treturn dedupeImages(projected, seenImageIds) as Record<string, unknown>;\n\t};\n}\n"]}
|
|
1
|
+
{"version":3,"file":"rpc-event-projection.d.ts","sourceRoot":"","sources":["../../../src/modes/rpc/rpc-event-projection.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAcH;;;;;;;;GAQG;AACH,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAgBhG;AAED,8FAA8F;AAC9F,eAAO,MAAM,wBAAwB,iCAA2B,CAAC;AA8JjE;;;;;GAKG;AACH,wBAAgB,gCAAgC,IAAI,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAM9G","sourcesContent":["/**\n * RPC event projection.\n *\n * `message_update` events carry two cumulative copies of the growing assistant\n * message: the top-level `message` field and `assistantMessageEvent.partial`.\n * Serializing both for every token makes the child->parent JSONL pipe quadratic\n * in response length and floods the stdout queue (see issue 448).\n *\n * Every RPC runtime projects those fields out by default (issue 535): consumers\n * read the delta fields (`delta`, `content`, `toolCall`, `contentIndex`) and get\n * the authoritative final message from `message_end` (and, for the dashboard,\n * `get_dashboard_snapshot` responses). Clients that still need the legacy full\n * frames opt back in with `--rpc-full-message-updates`.\n *\n * Dashboard runtimes additionally dedupe inline images (see below), which\n * requires a consumer that resolves `image_reference` blocks.\n *\n * Only the quadratic `message_update` fields are removed here. Broader bounding\n * (agent_end messages, tool_execution_update args, retry discardedPartial,\n * images) remains the EventHub's browser-facing concern.\n */\n\nimport { createHash } from \"node:crypto\";\n\nfunction omit(event: Record<string, unknown>, ...keys: string[]): Record<string, unknown> {\n\tconst copy = { ...event };\n\tfor (const key of keys) delete copy[key];\n\treturn copy;\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n/**\n * Project a single agent session event for RPC transport: strip the cumulative\n * `message` / `assistantMessageEvent.partial` fields from `message_update`.\n *\n * Unknown event types are returned exactly as received (same reference) so\n * extensions and future event types remain forward-safe. Projected events are\n * shallow copies — the input event is never mutated, because other session\n * subscribers (and the session's own state) share the same object.\n */\nexport function projectRpcMessageUpdates(event: Record<string, unknown>): Record<string, unknown> {\n\tswitch (event.type) {\n\t\tcase \"message_update\": {\n\t\t\tconst projected = omit(event, \"message\");\n\t\t\tconst streamEvent = event.assistantMessageEvent;\n\t\t\treturn isPlainObject(streamEvent)\n\t\t\t\t? { ...projected, assistantMessageEvent: omit(streamEvent, \"partial\") }\n\t\t\t\t: projected;\n\t\t}\n\t\tcase \"background_agent_event\": {\n\t\t\tconst child = event.event;\n\t\t\treturn isPlainObject(child) ? { ...event, event: projectRpcMessageUpdates(child) } : event;\n\t\t}\n\t\tdefault:\n\t\t\treturn event;\n\t}\n}\n\n/** Dashboard projection of a single event (message_update bounding only, no image dedupe). */\nexport const projectDashboardRpcEvent = projectRpcMessageUpdates;\n\n// ---------------------------------------------------------------------------\n// Image dedupe (dashboard mode)\n//\n// Inline image blocks cross the JSONL pipe multiple times per turn: prompt\n// re-emission (message_start + message_end), each tool result\n// (tool_execution_end + message_start + message_end), and the agent_end\n// transcript. A turn with several multi-MiB images easily fills the child\n// stdout queue while the dashboard is busy decoding, and the stdout guard\n// kills the session (issue 495).\n//\n// The dashboard already treats images content-globally: its server projection\n// replaces every inline image with an image_reference whose id is\n// sha256(mimeType + 0x00 + decodedBytes), caches the binary on first sight,\n// and serves later fetches from that cache (recovering from the authoritative\n// transcript when evicted). This projector applies the same reduction one\n// boundary earlier: each unique image crosses stdout at most once per process\n// lifetime; every later occurrence becomes an image_reference the dashboard\n// resolves from its cache. Live events only — command responses\n// (get_messages, get_dashboard_snapshot) always keep full payloads, because\n// they are the authoritative source the dashboard's image recovery reads.\n// ---------------------------------------------------------------------------\n\nconst DASHBOARD_IMAGE_MIME_TYPES = new Set([\"image/png\", \"image/jpeg\", \"image/gif\", \"image/webp\"]);\n\nconst BASE64_PATTERN = /^[A-Za-z0-9+/]+={0,2}$/;\n\nconst JPEG_EOI = Buffer.from([0xff, 0xd9]);\n\ntype ImageReference = { type: \"image_reference\"; id: string; mimeType: string; size: number };\n\n/**\n * Raster-signature check mirroring the dashboard server's hasRasterSignature\n * (packages/dashboard/src/server/dashboard-images.ts). Keep in sync: the child\n * may only turn a block into a reference when the dashboard's strict decode\n * (decodeDashboardImage) will accept it, or the reference dangles (the cache\n * holds nothing and the authoritative recovery decodes the same bytes and\n * rejects them again).\n */\nfunction hasRasterSignature(mimeType: string, bytes: Uint8Array): boolean {\n\tconst buffer = Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength);\n\tswitch (mimeType) {\n\t\tcase \"image/png\": {\n\t\t\tconst signature =\n\t\t\t\tbytes.length >= 24 &&\n\t\t\t\t[0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a].every((byte, i) => bytes[i] === byte);\n\t\t\treturn (\n\t\t\t\tsignature &&\n\t\t\t\tbuffer.subarray(12, 16).toString(\"ascii\") === \"IHDR\" &&\n\t\t\t\tbuffer.readUInt32BE(16) > 0 &&\n\t\t\t\tbuffer.readUInt32BE(20) > 0 &&\n\t\t\t\tbuffer.indexOf(Buffer.from(\"IEND\"), 24) >= 0\n\t\t\t);\n\t\t}\n\t\tcase \"image/jpeg\": {\n\t\t\tif (bytes.length < 4 || bytes[0] !== 0xff || bytes[1] !== 0xd8 || bytes[2] !== 0xff) return false;\n\t\t\t// EOI does not have to be the final byte. Phone JPEGs commonly append\n\t\t\t// auxiliary gain-map, motion-photo, or vendor metadata after the\n\t\t\t// primary image. Browsers and Photon decode the JPEG through EOI and\n\t\t\t// preserve the trailing bytes when the exact original is requested.\n\t\t\treturn buffer.lastIndexOf(JPEG_EOI) >= 2;\n\t\t}\n\t\tcase \"image/gif\": {\n\t\t\tconst header = buffer.subarray(0, 6).toString(\"ascii\");\n\t\t\treturn (\n\t\t\t\tbytes.length >= 14 &&\n\t\t\t\t(header === \"GIF87a\" || header === \"GIF89a\") &&\n\t\t\t\tbuffer.readUInt16LE(6) > 0 &&\n\t\t\t\tbuffer.readUInt16LE(8) > 0 &&\n\t\t\t\tbytes[bytes.length - 1] === 0x3b\n\t\t\t);\n\t\t}\n\t\tcase \"image/webp\": {\n\t\t\tif (\n\t\t\t\tbytes.length < 20 ||\n\t\t\t\tbuffer.subarray(0, 4).toString(\"ascii\") !== \"RIFF\" ||\n\t\t\t\tbuffer.subarray(8, 12).toString(\"ascii\") !== \"WEBP\"\n\t\t\t) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tconst chunk = buffer.subarray(12, 16).toString(\"ascii\");\n\t\t\treturn (\n\t\t\t\tbuffer.readUInt32LE(4) + 8 <= bytes.length && (chunk === \"VP8 \" || chunk === \"VP8L\" || chunk === \"VP8X\")\n\t\t\t);\n\t\t}\n\t}\n\treturn false;\n}\n\n/**\n * Content identity of an inline image block, mirroring the dashboard server's\n * dashboardImageId (sha256 of mimeType + 0x00 + decoded bytes). The gate is as\n * strict as the dashboard's decodeDashboardImage — allowlisted mimeType,\n * canonical base64 (round trip), and a matching raster signature — so a block\n * the dashboard would reject never becomes a reference: it stays inline at\n * every occurrence and is dropped by the dashboard's strict decode, exactly as\n * before this projection existed (no dangling image_references).\n */\nfunction imageBlockIdentity(mimeType: string, data: string): { id: string; size: number } | undefined {\n\tif (!DASHBOARD_IMAGE_MIME_TYPES.has(mimeType)) return undefined;\n\tif (data.length === 0 || data.length % 4 !== 0 || !BASE64_PATTERN.test(data)) return undefined;\n\tconst bytes = Buffer.from(data, \"base64\");\n\tif (bytes.byteLength === 0) return undefined;\n\tif (bytes.toString(\"base64\") !== data) return undefined; // non-canonical encoding the dashboard rejects\n\tif (!hasRasterSignature(mimeType, bytes)) return undefined; // signature mismatch the dashboard rejects\n\tconst id = createHash(\"sha256\").update(mimeType).update(Uint8Array.of(0)).update(bytes).digest(\"hex\");\n\treturn { id, size: bytes.byteLength };\n}\n\n/**\n * Replace occurrences of already-seen image blocks with their stable\n * reference, keeping the first occurrence of each unique image inline.\n * Returns the input by reference when nothing changed, so events without\n * images keep sharing objects with other session subscribers.\n */\nfunction dedupeImages(node: unknown, seen: Map<string, ImageReference>): unknown {\n\tif (Array.isArray(node)) {\n\t\tlet changed = false;\n\t\tconst result: unknown[] = new Array(node.length);\n\t\tfor (let i = 0; i < node.length; i++) {\n\t\t\tconst original = node[i];\n\t\t\tconst next = dedupeImages(original, seen);\n\t\t\tif (next !== original) changed = true;\n\t\t\tresult[i] = next;\n\t\t}\n\t\treturn changed ? result : node;\n\t}\n\tif (isPlainObject(node)) {\n\t\tif (node.type === \"image\" && typeof node.mimeType === \"string\" && typeof node.data === \"string\") {\n\t\t\tconst identity = imageBlockIdentity(node.mimeType, node.data);\n\t\t\tif (identity) {\n\t\t\t\tconst reference = seen.get(identity.id);\n\t\t\t\tif (reference) return reference;\n\t\t\t\tconst created: ImageReference = {\n\t\t\t\t\ttype: \"image_reference\",\n\t\t\t\t\tid: identity.id,\n\t\t\t\t\tmimeType: node.mimeType,\n\t\t\t\t\tsize: identity.size,\n\t\t\t\t};\n\t\t\t\tseen.set(identity.id, created);\n\t\t\t\treturn node;\n\t\t\t}\n\t\t\treturn node;\n\t\t}\n\t\tlet changed = false;\n\t\tconst result: Record<string, unknown> = {};\n\t\tfor (const key of Object.keys(node)) {\n\t\t\tconst original = node[key];\n\t\t\tconst next = dedupeImages(original, seen);\n\t\t\tif (next !== original) changed = true;\n\t\t\tresult[key] = next;\n\t\t}\n\t\treturn changed ? result : node;\n\t}\n\treturn node;\n}\n\n/**\n * Create the dashboard-mode RPC event projector for one runRpcMode process.\n *\n * Composes the per-event field projection with process-lifetime image dedupe.\n * Dashboard-mode sessions only — generic RPC consumers keep the full protocol.\n */\nexport function createDashboardRpcEventProjector(): (event: Record<string, unknown>) => Record<string, unknown> {\n\tconst seenImageIds = new Map<string, ImageReference>();\n\treturn (event: Record<string, unknown>): Record<string, unknown> => {\n\t\tconst projected = projectRpcMessageUpdates(event);\n\t\treturn dedupeImages(projected, seenImageIds) as Record<string, unknown>;\n\t};\n}\n"]}
|
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* RPC event projection.
|
|
3
3
|
*
|
|
4
4
|
* `message_update` events carry two cumulative copies of the growing assistant
|
|
5
5
|
* message: the top-level `message` field and `assistantMessageEvent.partial`.
|
|
6
6
|
* Serializing both for every token makes the child->parent JSONL pipe quadratic
|
|
7
7
|
* in response length and floods the stdout queue (see issue 448).
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
* the delta fields (`delta`, `content`, `toolCall`, `contentIndex`)
|
|
11
|
-
* authoritative
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
9
|
+
* Every RPC runtime projects those fields out by default (issue 535): consumers
|
|
10
|
+
* read the delta fields (`delta`, `content`, `toolCall`, `contentIndex`) and get
|
|
11
|
+
* the authoritative final message from `message_end` (and, for the dashboard,
|
|
12
|
+
* `get_dashboard_snapshot` responses). Clients that still need the legacy full
|
|
13
|
+
* frames opt back in with `--rpc-full-message-updates`.
|
|
14
|
+
*
|
|
15
|
+
* Dashboard runtimes additionally dedupe inline images (see below), which
|
|
16
|
+
* requires a consumer that resolves `image_reference` blocks.
|
|
15
17
|
*
|
|
16
18
|
* Only the quadratic `message_update` fields are removed here. Broader bounding
|
|
17
19
|
* (agent_end messages, tool_execution_update args, retry discardedPartial,
|
|
@@ -28,14 +30,15 @@ function isPlainObject(value) {
|
|
|
28
30
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
29
31
|
}
|
|
30
32
|
/**
|
|
31
|
-
* Project a single agent session event for
|
|
33
|
+
* Project a single agent session event for RPC transport: strip the cumulative
|
|
34
|
+
* `message` / `assistantMessageEvent.partial` fields from `message_update`.
|
|
32
35
|
*
|
|
33
36
|
* Unknown event types are returned exactly as received (same reference) so
|
|
34
37
|
* extensions and future event types remain forward-safe. Projected events are
|
|
35
38
|
* shallow copies — the input event is never mutated, because other session
|
|
36
39
|
* subscribers (and the session's own state) share the same object.
|
|
37
40
|
*/
|
|
38
|
-
export function
|
|
41
|
+
export function projectRpcMessageUpdates(event) {
|
|
39
42
|
switch (event.type) {
|
|
40
43
|
case "message_update": {
|
|
41
44
|
const projected = omit(event, "message");
|
|
@@ -46,12 +49,14 @@ export function projectDashboardRpcEvent(event) {
|
|
|
46
49
|
}
|
|
47
50
|
case "background_agent_event": {
|
|
48
51
|
const child = event.event;
|
|
49
|
-
return isPlainObject(child) ? { ...event, event:
|
|
52
|
+
return isPlainObject(child) ? { ...event, event: projectRpcMessageUpdates(child) } : event;
|
|
50
53
|
}
|
|
51
54
|
default:
|
|
52
55
|
return event;
|
|
53
56
|
}
|
|
54
57
|
}
|
|
58
|
+
/** Dashboard projection of a single event (message_update bounding only, no image dedupe). */
|
|
59
|
+
export const projectDashboardRpcEvent = projectRpcMessageUpdates;
|
|
55
60
|
// ---------------------------------------------------------------------------
|
|
56
61
|
// Image dedupe (dashboard mode)
|
|
57
62
|
//
|
|
@@ -208,7 +213,7 @@ function dedupeImages(node, seen) {
|
|
|
208
213
|
export function createDashboardRpcEventProjector() {
|
|
209
214
|
const seenImageIds = new Map();
|
|
210
215
|
return (event) => {
|
|
211
|
-
const projected =
|
|
216
|
+
const projected = projectRpcMessageUpdates(event);
|
|
212
217
|
return dedupeImages(projected, seenImageIds);
|
|
213
218
|
};
|
|
214
219
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rpc-event-projection.js","sourceRoot":"","sources":["../../../src/modes/rpc/rpc-event-projection.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Dashboard-mode RPC event projection.\n *\n * `message_update` events carry two cumulative copies of the growing assistant\n * message: the top-level `message` field and `assistantMessageEvent.partial`.\n * Serializing both for every token makes the child->parent JSONL pipe quadratic\n * in response length and floods the stdout queue (see issue 448).\n *\n * The dashboard never reads those fields: its browser reducer consumes only\n * the delta fields (`delta`, `content`, `toolCall`, `contentIndex`), and its\n * authoritative transcript comes from `message_end` plus `get_dashboard_snapshot`\n * RPC responses. The dashboard's own EventHub already strips the same fields at\n * the browser SSE boundary (projectDashboardEvent); this module applies the same\n * removal one boundary earlier, before JSONL serialization in runRpcMode.\n *\n * Only the quadratic `message_update` fields are removed here. Broader bounding\n * (agent_end messages, tool_execution_update args, retry discardedPartial,\n * images) remains the EventHub's browser-facing concern.\n */\n\nimport { createHash } from \"node:crypto\";\n\nfunction omit(event: Record<string, unknown>, ...keys: string[]): Record<string, unknown> {\n\tconst copy = { ...event };\n\tfor (const key of keys) delete copy[key];\n\treturn copy;\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n/**\n * Project a single agent session event for dashboard-mode RPC transport.\n *\n * Unknown event types are returned exactly as received (same reference) so\n * extensions and future event types remain forward-safe. Projected events are\n * shallow copies — the input event is never mutated, because other session\n * subscribers (and the session's own state) share the same object.\n */\nexport function projectDashboardRpcEvent(event: Record<string, unknown>): Record<string, unknown> {\n\tswitch (event.type) {\n\t\tcase \"message_update\": {\n\t\t\tconst projected = omit(event, \"message\");\n\t\t\tconst streamEvent = event.assistantMessageEvent;\n\t\t\treturn isPlainObject(streamEvent)\n\t\t\t\t? { ...projected, assistantMessageEvent: omit(streamEvent, \"partial\") }\n\t\t\t\t: projected;\n\t\t}\n\t\tcase \"background_agent_event\": {\n\t\t\tconst child = event.event;\n\t\t\treturn isPlainObject(child) ? { ...event, event: projectDashboardRpcEvent(child) } : event;\n\t\t}\n\t\tdefault:\n\t\t\treturn event;\n\t}\n}\n\n// ---------------------------------------------------------------------------\n// Image dedupe (dashboard mode)\n//\n// Inline image blocks cross the JSONL pipe multiple times per turn: prompt\n// re-emission (message_start + message_end), each tool result\n// (tool_execution_end + message_start + message_end), and the agent_end\n// transcript. A turn with several multi-MiB images easily fills the child\n// stdout queue while the dashboard is busy decoding, and the stdout guard\n// kills the session (issue 495).\n//\n// The dashboard already treats images content-globally: its server projection\n// replaces every inline image with an image_reference whose id is\n// sha256(mimeType + 0x00 + decodedBytes), caches the binary on first sight,\n// and serves later fetches from that cache (recovering from the authoritative\n// transcript when evicted). This projector applies the same reduction one\n// boundary earlier: each unique image crosses stdout at most once per process\n// lifetime; every later occurrence becomes an image_reference the dashboard\n// resolves from its cache. Live events only — command responses\n// (get_messages, get_dashboard_snapshot) always keep full payloads, because\n// they are the authoritative source the dashboard's image recovery reads.\n// ---------------------------------------------------------------------------\n\nconst DASHBOARD_IMAGE_MIME_TYPES = new Set([\"image/png\", \"image/jpeg\", \"image/gif\", \"image/webp\"]);\n\nconst BASE64_PATTERN = /^[A-Za-z0-9+/]+={0,2}$/;\n\nconst JPEG_EOI = Buffer.from([0xff, 0xd9]);\n\ntype ImageReference = { type: \"image_reference\"; id: string; mimeType: string; size: number };\n\n/**\n * Raster-signature check mirroring the dashboard server's hasRasterSignature\n * (packages/dashboard/src/server/dashboard-images.ts). Keep in sync: the child\n * may only turn a block into a reference when the dashboard's strict decode\n * (decodeDashboardImage) will accept it, or the reference dangles (the cache\n * holds nothing and the authoritative recovery decodes the same bytes and\n * rejects them again).\n */\nfunction hasRasterSignature(mimeType: string, bytes: Uint8Array): boolean {\n\tconst buffer = Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength);\n\tswitch (mimeType) {\n\t\tcase \"image/png\": {\n\t\t\tconst signature =\n\t\t\t\tbytes.length >= 24 &&\n\t\t\t\t[0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a].every((byte, i) => bytes[i] === byte);\n\t\t\treturn (\n\t\t\t\tsignature &&\n\t\t\t\tbuffer.subarray(12, 16).toString(\"ascii\") === \"IHDR\" &&\n\t\t\t\tbuffer.readUInt32BE(16) > 0 &&\n\t\t\t\tbuffer.readUInt32BE(20) > 0 &&\n\t\t\t\tbuffer.indexOf(Buffer.from(\"IEND\"), 24) >= 0\n\t\t\t);\n\t\t}\n\t\tcase \"image/jpeg\": {\n\t\t\tif (bytes.length < 4 || bytes[0] !== 0xff || bytes[1] !== 0xd8 || bytes[2] !== 0xff) return false;\n\t\t\t// EOI does not have to be the final byte. Phone JPEGs commonly append\n\t\t\t// auxiliary gain-map, motion-photo, or vendor metadata after the\n\t\t\t// primary image. Browsers and Photon decode the JPEG through EOI and\n\t\t\t// preserve the trailing bytes when the exact original is requested.\n\t\t\treturn buffer.lastIndexOf(JPEG_EOI) >= 2;\n\t\t}\n\t\tcase \"image/gif\": {\n\t\t\tconst header = buffer.subarray(0, 6).toString(\"ascii\");\n\t\t\treturn (\n\t\t\t\tbytes.length >= 14 &&\n\t\t\t\t(header === \"GIF87a\" || header === \"GIF89a\") &&\n\t\t\t\tbuffer.readUInt16LE(6) > 0 &&\n\t\t\t\tbuffer.readUInt16LE(8) > 0 &&\n\t\t\t\tbytes[bytes.length - 1] === 0x3b\n\t\t\t);\n\t\t}\n\t\tcase \"image/webp\": {\n\t\t\tif (\n\t\t\t\tbytes.length < 20 ||\n\t\t\t\tbuffer.subarray(0, 4).toString(\"ascii\") !== \"RIFF\" ||\n\t\t\t\tbuffer.subarray(8, 12).toString(\"ascii\") !== \"WEBP\"\n\t\t\t) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tconst chunk = buffer.subarray(12, 16).toString(\"ascii\");\n\t\t\treturn (\n\t\t\t\tbuffer.readUInt32LE(4) + 8 <= bytes.length && (chunk === \"VP8 \" || chunk === \"VP8L\" || chunk === \"VP8X\")\n\t\t\t);\n\t\t}\n\t}\n\treturn false;\n}\n\n/**\n * Content identity of an inline image block, mirroring the dashboard server's\n * dashboardImageId (sha256 of mimeType + 0x00 + decoded bytes). The gate is as\n * strict as the dashboard's decodeDashboardImage — allowlisted mimeType,\n * canonical base64 (round trip), and a matching raster signature — so a block\n * the dashboard would reject never becomes a reference: it stays inline at\n * every occurrence and is dropped by the dashboard's strict decode, exactly as\n * before this projection existed (no dangling image_references).\n */\nfunction imageBlockIdentity(mimeType: string, data: string): { id: string; size: number } | undefined {\n\tif (!DASHBOARD_IMAGE_MIME_TYPES.has(mimeType)) return undefined;\n\tif (data.length === 0 || data.length % 4 !== 0 || !BASE64_PATTERN.test(data)) return undefined;\n\tconst bytes = Buffer.from(data, \"base64\");\n\tif (bytes.byteLength === 0) return undefined;\n\tif (bytes.toString(\"base64\") !== data) return undefined; // non-canonical encoding the dashboard rejects\n\tif (!hasRasterSignature(mimeType, bytes)) return undefined; // signature mismatch the dashboard rejects\n\tconst id = createHash(\"sha256\").update(mimeType).update(Uint8Array.of(0)).update(bytes).digest(\"hex\");\n\treturn { id, size: bytes.byteLength };\n}\n\n/**\n * Replace occurrences of already-seen image blocks with their stable\n * reference, keeping the first occurrence of each unique image inline.\n * Returns the input by reference when nothing changed, so events without\n * images keep sharing objects with other session subscribers.\n */\nfunction dedupeImages(node: unknown, seen: Map<string, ImageReference>): unknown {\n\tif (Array.isArray(node)) {\n\t\tlet changed = false;\n\t\tconst result: unknown[] = new Array(node.length);\n\t\tfor (let i = 0; i < node.length; i++) {\n\t\t\tconst original = node[i];\n\t\t\tconst next = dedupeImages(original, seen);\n\t\t\tif (next !== original) changed = true;\n\t\t\tresult[i] = next;\n\t\t}\n\t\treturn changed ? result : node;\n\t}\n\tif (isPlainObject(node)) {\n\t\tif (node.type === \"image\" && typeof node.mimeType === \"string\" && typeof node.data === \"string\") {\n\t\t\tconst identity = imageBlockIdentity(node.mimeType, node.data);\n\t\t\tif (identity) {\n\t\t\t\tconst reference = seen.get(identity.id);\n\t\t\t\tif (reference) return reference;\n\t\t\t\tconst created: ImageReference = {\n\t\t\t\t\ttype: \"image_reference\",\n\t\t\t\t\tid: identity.id,\n\t\t\t\t\tmimeType: node.mimeType,\n\t\t\t\t\tsize: identity.size,\n\t\t\t\t};\n\t\t\t\tseen.set(identity.id, created);\n\t\t\t\treturn node;\n\t\t\t}\n\t\t\treturn node;\n\t\t}\n\t\tlet changed = false;\n\t\tconst result: Record<string, unknown> = {};\n\t\tfor (const key of Object.keys(node)) {\n\t\t\tconst original = node[key];\n\t\t\tconst next = dedupeImages(original, seen);\n\t\t\tif (next !== original) changed = true;\n\t\t\tresult[key] = next;\n\t\t}\n\t\treturn changed ? result : node;\n\t}\n\treturn node;\n}\n\n/**\n * Create the dashboard-mode RPC event projector for one runRpcMode process.\n *\n * Composes the per-event field projection with process-lifetime image dedupe.\n * Dashboard-mode sessions only — generic RPC consumers keep the full protocol.\n */\nexport function createDashboardRpcEventProjector(): (event: Record<string, unknown>) => Record<string, unknown> {\n\tconst seenImageIds = new Map<string, ImageReference>();\n\treturn (event: Record<string, unknown>): Record<string, unknown> => {\n\t\tconst projected = projectDashboardRpcEvent(event);\n\t\treturn dedupeImages(projected, seenImageIds) as Record<string, unknown>;\n\t};\n}\n"]}
|
|
1
|
+
{"version":3,"file":"rpc-event-projection.js","sourceRoot":"","sources":["../../../src/modes/rpc/rpc-event-projection.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * RPC event projection.\n *\n * `message_update` events carry two cumulative copies of the growing assistant\n * message: the top-level `message` field and `assistantMessageEvent.partial`.\n * Serializing both for every token makes the child->parent JSONL pipe quadratic\n * in response length and floods the stdout queue (see issue 448).\n *\n * Every RPC runtime projects those fields out by default (issue 535): consumers\n * read the delta fields (`delta`, `content`, `toolCall`, `contentIndex`) and get\n * the authoritative final message from `message_end` (and, for the dashboard,\n * `get_dashboard_snapshot` responses). Clients that still need the legacy full\n * frames opt back in with `--rpc-full-message-updates`.\n *\n * Dashboard runtimes additionally dedupe inline images (see below), which\n * requires a consumer that resolves `image_reference` blocks.\n *\n * Only the quadratic `message_update` fields are removed here. Broader bounding\n * (agent_end messages, tool_execution_update args, retry discardedPartial,\n * images) remains the EventHub's browser-facing concern.\n */\n\nimport { createHash } from \"node:crypto\";\n\nfunction omit(event: Record<string, unknown>, ...keys: string[]): Record<string, unknown> {\n\tconst copy = { ...event };\n\tfor (const key of keys) delete copy[key];\n\treturn copy;\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n/**\n * Project a single agent session event for RPC transport: strip the cumulative\n * `message` / `assistantMessageEvent.partial` fields from `message_update`.\n *\n * Unknown event types are returned exactly as received (same reference) so\n * extensions and future event types remain forward-safe. Projected events are\n * shallow copies — the input event is never mutated, because other session\n * subscribers (and the session's own state) share the same object.\n */\nexport function projectRpcMessageUpdates(event: Record<string, unknown>): Record<string, unknown> {\n\tswitch (event.type) {\n\t\tcase \"message_update\": {\n\t\t\tconst projected = omit(event, \"message\");\n\t\t\tconst streamEvent = event.assistantMessageEvent;\n\t\t\treturn isPlainObject(streamEvent)\n\t\t\t\t? { ...projected, assistantMessageEvent: omit(streamEvent, \"partial\") }\n\t\t\t\t: projected;\n\t\t}\n\t\tcase \"background_agent_event\": {\n\t\t\tconst child = event.event;\n\t\t\treturn isPlainObject(child) ? { ...event, event: projectRpcMessageUpdates(child) } : event;\n\t\t}\n\t\tdefault:\n\t\t\treturn event;\n\t}\n}\n\n/** Dashboard projection of a single event (message_update bounding only, no image dedupe). */\nexport const projectDashboardRpcEvent = projectRpcMessageUpdates;\n\n// ---------------------------------------------------------------------------\n// Image dedupe (dashboard mode)\n//\n// Inline image blocks cross the JSONL pipe multiple times per turn: prompt\n// re-emission (message_start + message_end), each tool result\n// (tool_execution_end + message_start + message_end), and the agent_end\n// transcript. A turn with several multi-MiB images easily fills the child\n// stdout queue while the dashboard is busy decoding, and the stdout guard\n// kills the session (issue 495).\n//\n// The dashboard already treats images content-globally: its server projection\n// replaces every inline image with an image_reference whose id is\n// sha256(mimeType + 0x00 + decodedBytes), caches the binary on first sight,\n// and serves later fetches from that cache (recovering from the authoritative\n// transcript when evicted). This projector applies the same reduction one\n// boundary earlier: each unique image crosses stdout at most once per process\n// lifetime; every later occurrence becomes an image_reference the dashboard\n// resolves from its cache. Live events only — command responses\n// (get_messages, get_dashboard_snapshot) always keep full payloads, because\n// they are the authoritative source the dashboard's image recovery reads.\n// ---------------------------------------------------------------------------\n\nconst DASHBOARD_IMAGE_MIME_TYPES = new Set([\"image/png\", \"image/jpeg\", \"image/gif\", \"image/webp\"]);\n\nconst BASE64_PATTERN = /^[A-Za-z0-9+/]+={0,2}$/;\n\nconst JPEG_EOI = Buffer.from([0xff, 0xd9]);\n\ntype ImageReference = { type: \"image_reference\"; id: string; mimeType: string; size: number };\n\n/**\n * Raster-signature check mirroring the dashboard server's hasRasterSignature\n * (packages/dashboard/src/server/dashboard-images.ts). Keep in sync: the child\n * may only turn a block into a reference when the dashboard's strict decode\n * (decodeDashboardImage) will accept it, or the reference dangles (the cache\n * holds nothing and the authoritative recovery decodes the same bytes and\n * rejects them again).\n */\nfunction hasRasterSignature(mimeType: string, bytes: Uint8Array): boolean {\n\tconst buffer = Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength);\n\tswitch (mimeType) {\n\t\tcase \"image/png\": {\n\t\t\tconst signature =\n\t\t\t\tbytes.length >= 24 &&\n\t\t\t\t[0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a].every((byte, i) => bytes[i] === byte);\n\t\t\treturn (\n\t\t\t\tsignature &&\n\t\t\t\tbuffer.subarray(12, 16).toString(\"ascii\") === \"IHDR\" &&\n\t\t\t\tbuffer.readUInt32BE(16) > 0 &&\n\t\t\t\tbuffer.readUInt32BE(20) > 0 &&\n\t\t\t\tbuffer.indexOf(Buffer.from(\"IEND\"), 24) >= 0\n\t\t\t);\n\t\t}\n\t\tcase \"image/jpeg\": {\n\t\t\tif (bytes.length < 4 || bytes[0] !== 0xff || bytes[1] !== 0xd8 || bytes[2] !== 0xff) return false;\n\t\t\t// EOI does not have to be the final byte. Phone JPEGs commonly append\n\t\t\t// auxiliary gain-map, motion-photo, or vendor metadata after the\n\t\t\t// primary image. Browsers and Photon decode the JPEG through EOI and\n\t\t\t// preserve the trailing bytes when the exact original is requested.\n\t\t\treturn buffer.lastIndexOf(JPEG_EOI) >= 2;\n\t\t}\n\t\tcase \"image/gif\": {\n\t\t\tconst header = buffer.subarray(0, 6).toString(\"ascii\");\n\t\t\treturn (\n\t\t\t\tbytes.length >= 14 &&\n\t\t\t\t(header === \"GIF87a\" || header === \"GIF89a\") &&\n\t\t\t\tbuffer.readUInt16LE(6) > 0 &&\n\t\t\t\tbuffer.readUInt16LE(8) > 0 &&\n\t\t\t\tbytes[bytes.length - 1] === 0x3b\n\t\t\t);\n\t\t}\n\t\tcase \"image/webp\": {\n\t\t\tif (\n\t\t\t\tbytes.length < 20 ||\n\t\t\t\tbuffer.subarray(0, 4).toString(\"ascii\") !== \"RIFF\" ||\n\t\t\t\tbuffer.subarray(8, 12).toString(\"ascii\") !== \"WEBP\"\n\t\t\t) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tconst chunk = buffer.subarray(12, 16).toString(\"ascii\");\n\t\t\treturn (\n\t\t\t\tbuffer.readUInt32LE(4) + 8 <= bytes.length && (chunk === \"VP8 \" || chunk === \"VP8L\" || chunk === \"VP8X\")\n\t\t\t);\n\t\t}\n\t}\n\treturn false;\n}\n\n/**\n * Content identity of an inline image block, mirroring the dashboard server's\n * dashboardImageId (sha256 of mimeType + 0x00 + decoded bytes). The gate is as\n * strict as the dashboard's decodeDashboardImage — allowlisted mimeType,\n * canonical base64 (round trip), and a matching raster signature — so a block\n * the dashboard would reject never becomes a reference: it stays inline at\n * every occurrence and is dropped by the dashboard's strict decode, exactly as\n * before this projection existed (no dangling image_references).\n */\nfunction imageBlockIdentity(mimeType: string, data: string): { id: string; size: number } | undefined {\n\tif (!DASHBOARD_IMAGE_MIME_TYPES.has(mimeType)) return undefined;\n\tif (data.length === 0 || data.length % 4 !== 0 || !BASE64_PATTERN.test(data)) return undefined;\n\tconst bytes = Buffer.from(data, \"base64\");\n\tif (bytes.byteLength === 0) return undefined;\n\tif (bytes.toString(\"base64\") !== data) return undefined; // non-canonical encoding the dashboard rejects\n\tif (!hasRasterSignature(mimeType, bytes)) return undefined; // signature mismatch the dashboard rejects\n\tconst id = createHash(\"sha256\").update(mimeType).update(Uint8Array.of(0)).update(bytes).digest(\"hex\");\n\treturn { id, size: bytes.byteLength };\n}\n\n/**\n * Replace occurrences of already-seen image blocks with their stable\n * reference, keeping the first occurrence of each unique image inline.\n * Returns the input by reference when nothing changed, so events without\n * images keep sharing objects with other session subscribers.\n */\nfunction dedupeImages(node: unknown, seen: Map<string, ImageReference>): unknown {\n\tif (Array.isArray(node)) {\n\t\tlet changed = false;\n\t\tconst result: unknown[] = new Array(node.length);\n\t\tfor (let i = 0; i < node.length; i++) {\n\t\t\tconst original = node[i];\n\t\t\tconst next = dedupeImages(original, seen);\n\t\t\tif (next !== original) changed = true;\n\t\t\tresult[i] = next;\n\t\t}\n\t\treturn changed ? result : node;\n\t}\n\tif (isPlainObject(node)) {\n\t\tif (node.type === \"image\" && typeof node.mimeType === \"string\" && typeof node.data === \"string\") {\n\t\t\tconst identity = imageBlockIdentity(node.mimeType, node.data);\n\t\t\tif (identity) {\n\t\t\t\tconst reference = seen.get(identity.id);\n\t\t\t\tif (reference) return reference;\n\t\t\t\tconst created: ImageReference = {\n\t\t\t\t\ttype: \"image_reference\",\n\t\t\t\t\tid: identity.id,\n\t\t\t\t\tmimeType: node.mimeType,\n\t\t\t\t\tsize: identity.size,\n\t\t\t\t};\n\t\t\t\tseen.set(identity.id, created);\n\t\t\t\treturn node;\n\t\t\t}\n\t\t\treturn node;\n\t\t}\n\t\tlet changed = false;\n\t\tconst result: Record<string, unknown> = {};\n\t\tfor (const key of Object.keys(node)) {\n\t\t\tconst original = node[key];\n\t\t\tconst next = dedupeImages(original, seen);\n\t\t\tif (next !== original) changed = true;\n\t\t\tresult[key] = next;\n\t\t}\n\t\treturn changed ? result : node;\n\t}\n\treturn node;\n}\n\n/**\n * Create the dashboard-mode RPC event projector for one runRpcMode process.\n *\n * Composes the per-event field projection with process-lifetime image dedupe.\n * Dashboard-mode sessions only — generic RPC consumers keep the full protocol.\n */\nexport function createDashboardRpcEventProjector(): (event: Record<string, unknown>) => Record<string, unknown> {\n\tconst seenImageIds = new Map<string, ImageReference>();\n\treturn (event: Record<string, unknown>): Record<string, unknown> => {\n\t\tconst projected = projectRpcMessageUpdates(event);\n\t\treturn dedupeImages(projected, seenImageIds) as Record<string, unknown>;\n\t};\n}\n"]}
|
|
@@ -189,5 +189,14 @@ export interface PendingRpcExtensionRequest {
|
|
|
189
189
|
export declare function createRpcExtensionUIContext(output: (obj: RpcResponse | RpcExtensionUIRequest | object) => void, pendingExtensionRequests: Map<string, PendingRpcExtensionRequest>, onAskStop?: () => void): ExtensionUIContext;
|
|
190
190
|
/** Cancel every pending dialog during RPC host teardown. */
|
|
191
191
|
export declare function cancelPendingRpcExtensionRequests(pendingExtensionRequests: Map<string, PendingRpcExtensionRequest>): void;
|
|
192
|
-
export
|
|
192
|
+
export interface RpcModeOptions {
|
|
193
|
+
/**
|
|
194
|
+
* Emit legacy full `message_update` frames (cumulative `message` and
|
|
195
|
+
* `assistantMessageEvent.partial`). Off by default: those fields make the
|
|
196
|
+
* stream quadratic in response length (issue 535). Ignored for dashboard
|
|
197
|
+
* runtimes, which are always projected.
|
|
198
|
+
*/
|
|
199
|
+
fullMessageUpdates?: boolean;
|
|
200
|
+
}
|
|
201
|
+
export declare function runRpcMode(session: AgentSession, modelFallbackMessage?: string, options?: RpcModeOptions): Promise<never>;
|
|
193
202
|
//# sourceMappingURL=rpc-mode.d.ts.map
|