Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
0d66145
fix(runtime): use upstream WHATWG URL parser
NathanFlurry Jul 24, 2026
1a7f7f0
refactor(runtime): share stdlib modules across ESM and CommonJS
NathanFlurry Jul 24, 2026
fd31a50
refactor(runtime): use upstream EventEmitter
NathanFlurry Jul 24, 2026
493151a
refactor(runtime-browser): use upstream Node polyfills
NathanFlurry Jul 24, 2026
8f8eb4e
refactor(runtime): use upstream text encoding
NathanFlurry Jul 25, 2026
2dab72b
refactor(runtime): use upstream Web Streams
NathanFlurry Jul 25, 2026
8a75e83
fix(runtime): align DOM event fallback
NathanFlurry Jul 25, 2026
316009b
fix(runtime): align AbortSignal fallback
NathanFlurry Jul 25, 2026
dc431f3
refactor(runtime): use upstream Blob and File
NathanFlurry Jul 25, 2026
a722295
refactor(runtime): use Undici FormData
NathanFlurry Jul 25, 2026
b3beae9
refactor(runtime): bootstrap final URL constructors
NathanFlurry Jul 25, 2026
fcd4081
refactor(runtime): bootstrap final event and abort globals
NathanFlurry Jul 25, 2026
e905fde
refactor(runtime): remove bootstrap compatibility stubs
NathanFlurry Jul 25, 2026
41c0d8f
fix(runtime): use native RGI emoji regexp support
NathanFlurry Jul 25, 2026
23ab5d0
refactor(runtime): remove dead builtin wrappers
NathanFlurry Jul 25, 2026
abc7533
refactor(runtime): use upstream file URL helpers
NathanFlurry Jul 25, 2026
0a1efa5
fix(runtime): address polyfill review edge cases
NathanFlurry Jul 25, 2026
1822d05
fix(codex): reject unsupported prompt media
NathanFlurry Jul 25, 2026
9953769
test(runtime): cover rich media across agent adapters
NathanFlurry Jul 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 52 additions & 16 deletions crates/client/tests/session_e2e.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ process.stdin.on("data", (chunk) => {
writeResponse(msg.id, {
protocolVersion: 1,
agentInfo: { name: "mock-agent", version: "1.0.0" },
agentCapabilities: { plan_mode: false, tool_calls: false, promptCapabilities: {} },
agentCapabilities: { plan_mode: false, tool_calls: false, promptCapabilities: { image: true, audio: true, embeddedContext: true } },
modes: { currentModeId: "default", availableModes: [{ id: "default", label: "Default" }] },
configOptions: [],
});
Expand All @@ -55,9 +55,11 @@ process.stdin.on("data", (chunk) => {
});
break;
case "session/prompt":
writeMessage({ jsonrpc: "2.0", method: "session/update", params: {
sessionId: "__MOCK_SESSION_ID__",
update: { sessionUpdate: "agent_message_chunk", content: { type: "text", text: "__MOCK_PROMPT_TEXT__" } } } });
for (const content of msg.params.prompt) {
writeMessage({ jsonrpc: "2.0", method: "session/update", params: {
sessionId: "__MOCK_SESSION_ID__",
update: { sessionUpdate: "agent_message_chunk", content } } });
}
writeResponse(msg.id, { stopReason: "end_turn" });
break;
case "session/cancel":
Expand Down Expand Up @@ -148,23 +150,50 @@ async fn durable_session_surface_persists_native_acp_history() {
);

let (mut events, _subscription) = os.on_session_event(Some("main"));
let content = serde_json::from_value(serde_json::json!({
"type": "text",
"text": "Say PONG",
}))
.expect("content block");
let rich_content = serde_json::json!([
{ "type": "text", "text": MOCK_PROMPT_TEXT },
{
"type": "image",
"data": "iVBORw0KGgo=",
"mimeType": "image/png",
"uri": "file:///workspace/pixel.png"
},
{
"type": "resource_link",
"uri": "https://example.test/reference.txt",
"name": "reference.txt",
"mimeType": "text/plain"
},
{
"type": "resource",
"resource": {
"uri": "file:///workspace/context.txt",
"mimeType": "text/plain",
"text": "context"
}
},
{
"type": "resource",
"resource": {
"uri": "file:///workspace/pixel-copy.png",
"mimeType": "image/png",
"blob": "iVBORw0KGgo="
}
},
{ "type": "audio", "data": "UklGRg==", "mimeType": "audio/wav" }
]);
let content = serde_json::from_value(rich_content.clone()).expect("rich content blocks");
let result = os
.prompt(PromptInput {
session_id: None,
idempotency_key: Some(String::from("prompt-1")),
content: vec![content],
content,
})
.await
.expect("durable prompt");
assert_eq!(result.session_id, "main");
assert!(serde_json::to_string(&result.message)
.expect("serialize prompt message")
.contains(MOCK_PROMPT_TEXT));
let result_message = serde_json::to_value(&result.message).expect("serialize prompt message");
assert_eq!(result_message["content"], rich_content);

let live = tokio::time::timeout(std::time::Duration::from_secs(5), events.next())
.await
Expand All @@ -177,17 +206,24 @@ async fn durable_session_surface_persists_native_acp_history() {
.read_history(ReadHistoryInput::default())
.await
.expect("history");
assert_eq!(history.events.len(), 2, "user and completed agent messages");
assert_eq!(
history.events.len(),
12,
"six user and six completed agent message blocks"
);
assert_eq!(history.events[0].sequence, 1);
assert_eq!(history.events[1].sequence, 2);
assert_eq!(history.events[11].sequence, 12);
assert!(serde_json::to_string(&history.events)
.expect("serialize rich history")
.contains("iVBORw0KGgo="));

os.unload_session(None).await.expect("unload");
assert_eq!(
os.get_session(None)
.await
.expect("SQLite get")
.latest_sequence,
2
12
);
os.delete_session(None).await.expect("delete");
assert!(os.get_session(None).await.is_err());
Expand Down
39 changes: 39 additions & 0 deletions crates/client/tests/session_event_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,45 @@ fn session_events_use_the_flat_public_json_shape() {
"outcome": { "outcome": "selected", "optionId": "allow" },
"status": "accepted"
}),
json!({
"durability": "durable",
"sessionId": "main",
"sequence": 4,
"timestamp": "2026-07-18T00:00:03.000Z",
"type": "tool_call",
"toolCallId": "tool-media-1",
"title": "Read media",
"status": "completed",
"content": [
{
"type": "content",
"content": {
"type": "image",
"data": "iVBORw0KGgo=",
"mimeType": "image/png"
}
},
{
"type": "content",
"content": {
"type": "audio",
"data": "UklGRg==",
"mimeType": "audio/wav"
}
},
{
"type": "content",
"content": {
"type": "resource",
"resource": {
"uri": "file:///workspace/context.txt",
"mimeType": "text/plain",
"text": "context"
}
}
}
]
}),
];

for expected in cases {
Expand Down
40 changes: 40 additions & 0 deletions crates/execution/assets/undici-shims/crypto.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
"use strict";

const crypto = require("agentos-legacy-crypto-polyfill");

const RANDOM_INT_LIMIT = 2 ** 48;

function randomInt(min, max, callback) {
if (typeof max === "function") {
callback = max;
max = min;
min = 0;
} else if (max === undefined) {
max = min;
min = 0;
}
if (
!Number.isSafeInteger(min) ||
!Number.isSafeInteger(max) ||
min < 0 ||
max <= min ||
max - min > RANDOM_INT_LIMIT
) {
throw new RangeError("The value of max - min is out of range");
}
const range = max - min;
const unbiasedLimit = RANDOM_INT_LIMIT - (RANDOM_INT_LIMIT % range);
let value;
do {
value = crypto.randomBytes(6).readUIntBE(0, 6);
} while (value >= unbiasedLimit);
const result = min + (value % range);
if (typeof callback === "function") {
process.nextTick(callback, null, result);
return;
}
return result;
}

crypto.randomInt = randomInt;
module.exports = crypto;
84 changes: 15 additions & 69 deletions crates/execution/assets/undici-shims/web-streams-global.js
Original file line number Diff line number Diff line change
@@ -1,77 +1,23 @@
"use strict";

import {
ReadableStream as WebReadableStream,
WritableStream as WebWritableStream,
TransformStream as WebTransformStream,
} from "web-streams-polyfill/ponyfill/es2018";

class FallbackTextEncoderStream {
constructor() {
const encoder = new globalThis.TextEncoder();
const stream = new WebTransformStream({
transform(chunk, controller) {
controller.enqueue(encoder.encode(chunk));
},
});

this.encoding = "utf-8";
this.readable = stream.readable;
this.writable = stream.writable;
const WebReadableStream = globalThis.ReadableStream;
const WebWritableStream = globalThis.WritableStream;
const WebTransformStream = globalThis.TransformStream;
const WebTextEncoderStream = globalThis.TextEncoderStream;
const WebTextDecoderStream = globalThis.TextDecoderStream;

for (const [name, constructor] of Object.entries({
ReadableStream: WebReadableStream,
WritableStream: WebWritableStream,
TransformStream: WebTransformStream,
TextEncoderStream: WebTextEncoderStream,
TextDecoderStream: WebTextDecoderStream,
})) {
if (typeof constructor !== "function") {
throw new Error(`${name} was not installed by the web-platform bootstrap`);
}
}

class FallbackTextDecoderStream {
constructor(label = "utf-8", options = undefined) {
const decoder = new globalThis.TextDecoder(label, options);
const stream = new WebTransformStream({
transform(chunk, controller) {
const text = decoder.decode(chunk, { stream: true });
if (text.length > 0) {
controller.enqueue(text);
}
},
flush(controller) {
const text = decoder.decode();
if (text.length > 0) {
controller.enqueue(text);
}
},
});

this.encoding = decoder.encoding;
this.fatal = decoder.fatal;
this.ignoreBOM = decoder.ignoreBOM;
this.readable = stream.readable;
this.writable = stream.writable;
}
}

const WebTextEncoderStream =
typeof globalThis.TextEncoderStream === "function"
? globalThis.TextEncoderStream
: FallbackTextEncoderStream;
const WebTextDecoderStream =
typeof globalThis.TextDecoderStream === "function"
? globalThis.TextDecoderStream
: FallbackTextDecoderStream;

if (typeof globalThis.ReadableStream === "undefined") {
globalThis.ReadableStream = WebReadableStream;
}
if (typeof globalThis.WritableStream === "undefined") {
globalThis.WritableStream = WebWritableStream;
}
if (typeof globalThis.TransformStream === "undefined") {
globalThis.TransformStream = WebTransformStream;
}
if (typeof globalThis.TextEncoderStream === "undefined") {
globalThis.TextEncoderStream = WebTextEncoderStream;
}
if (typeof globalThis.TextDecoderStream === "undefined") {
globalThis.TextDecoderStream = WebTextDecoderStream;
}

export {
WebReadableStream,
WebWritableStream,
Expand Down
Loading
Loading