Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions .changeset/main-ci-declaration-fix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"agent-bundle": patch
---

Keep the optional `@agent-bundle/runtime` peer out of the dev server's emitted declarations: the Agent Document route now consumes the peer through an opaque structural loader, so consumers without the optional peer compile against the packed root types again.
27 changes: 14 additions & 13 deletions packages/agent-bundle/src/dev/runtime-routes.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import type { IncomingMessage, ServerResponse } from 'node:http';

import type { AgentRenderEvent, AgentRenderLimits } from '@agent-bundle/runtime';

import {
DevRuntimeGenerationConflictError,
DevRuntimeUnavailableError,
Expand Down Expand Up @@ -33,12 +31,18 @@ type Route =
| Readonly<{ readonly id: string; readonly kind: 'run' | 'document' | 'flight' | 'replay' }>
| Readonly<{ readonly generation: string; readonly kind: 'asset'; readonly path: readonly string[]; readonly surfaceId: string }>;

/**
* Structural view of the optional `@agent-bundle/runtime` peer. The peer's own
* types must never appear here: this interface reaches the emitted root
* declarations, and consumers without the optional peer installed could no
* longer compile against them. Events stay opaque — this route only
* re-serializes them.
*/
export interface AgentDocumentRuntimeModule {
readonly DEFAULT_AGENT_RENDER_LIMITS: AgentRenderLimits;
readonly decodeAgentFlightStream: (
readonly decodeAgentFlight: (
flight: ReadableStream<Uint8Array>,
options?: Readonly<{ readonly limits?: Partial<AgentRenderLimits>; readonly signal?: AbortSignal }>,
) => ReadableStream<AgentRenderEvent>;
signal: AbortSignal,
) => AsyncIterable<unknown>;
}

export interface RuntimeRoutesOptions {
Expand All @@ -56,8 +60,8 @@ let agentDocumentRuntimePromise: Promise<AgentDocumentRuntimeModule> | undefined
const loadAgentDocumentRuntime = async (): Promise<AgentDocumentRuntimeModule> => {
agentDocumentRuntimePromise ??= import('@agent-bundle/runtime')
.then((runtime) => Object.freeze({
DEFAULT_AGENT_RENDER_LIMITS: runtime.DEFAULT_AGENT_RENDER_LIMITS,
decodeAgentFlightStream: runtime.decodeAgentFlightStream,
decodeAgentFlight: (flight: ReadableStream<Uint8Array>, signal: AbortSignal) =>
runtime.decodeAgentFlightStream(flight, { limits: runtime.DEFAULT_AGENT_RENDER_LIMITS, signal }),
}))
.catch((error: unknown) => {
agentDocumentRuntimePromise = undefined;
Expand Down Expand Up @@ -427,12 +431,9 @@ export class RuntimeRoutes {
controller.close();
},
});
const events: AgentRenderEvent[] = [];
const events: unknown[] = [];
let responseBytes = Buffer.byteLength('{"events":[]}');
for await (const event of runtime.decodeAgentFlightStream(flight, {
limits: runtime.DEFAULT_AGENT_RENDER_LIMITS,
signal: abortController.signal,
})) {
for await (const event of runtime.decodeAgentFlight(flight, abortController.signal)) {
const eventBytes = Buffer.byteLength(JSON.stringify(event));
const separatorBytes = events.length === 0 ? 0 : 1;
if (responseBytes + separatorBytes + eventBytes > agentDocumentResponseLimit) {
Expand Down
15 changes: 6 additions & 9 deletions packages/agent-bundle/tests/runtime-routes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -202,13 +202,12 @@ const renderReadyFlight = async (): Promise<Uint8Array> => new Promise((resolve,
});

const realAgentDocumentRuntime = async () => ({
DEFAULT_AGENT_RENDER_LIMITS,
decodeAgentFlightStream,
decodeAgentFlight: (flight: ReadableStream<Uint8Array>, signal: AbortSignal) =>
decodeAgentFlightStream(flight, { limits: DEFAULT_AGENT_RENDER_LIMITS, signal }),
});

const emptyAgentDocumentRuntime = async () => ({
DEFAULT_AGENT_RENDER_LIMITS,
decodeAgentFlightStream: () => new ReadableStream({
decodeAgentFlight: () => new ReadableStream({
start(controller) {
controller.close();
},
Expand Down Expand Up @@ -327,12 +326,11 @@ it('aborts decoding when Agent Document events exceed the aggregate response bud
const largeText = 'x'.repeat(512 * 1024);
const server = await start(new MemoryRuntime(), {
loadAgentDocumentRuntime: async () => ({
DEFAULT_AGENT_RENDER_LIMITS,
decodeAgentFlightStream: (_flight, options) => {
decodeAgentFlight: (_flight: ReadableStream<Uint8Array>, signal: AbortSignal) => {
let sequence = 0;
return new ReadableStream({
start(controller) {
options?.signal?.addEventListener('abort', () => {
signal.addEventListener('abort', () => {
aborted = true;
controller.error(new DOMException('Agent render was aborted', 'AbortError'));
}, { once: true });
Expand Down Expand Up @@ -393,8 +391,7 @@ it('returns honest diagnostics when the Agent runtime is absent or stored Flight

const invalid = await start(new MemoryRuntime(), {
loadAgentDocumentRuntime: async () => ({
DEFAULT_AGENT_RENDER_LIMITS,
decodeAgentFlightStream: () => {
decodeAgentFlight: () => {
throw new Error('invalid Flight');
},
}),
Expand Down
4 changes: 2 additions & 2 deletions packages/workbench/tests/runtime-inspector.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,8 @@ describe('Runtime inspector', () => {

await page.getByRole('tab', { name: 'Document' }).click();
await page.getByRole('heading', { name: 'Customer document' }).waitFor({ timeout: 5_000 });
expect(await page.getByLabel('Agent Document').textContent()).toContain('Version 1 · success');
expect(await page.getByLabel('Agent Document').textContent()).toContain('Loaded · 1 / 1');
expect(await page.getByLabel('Agent Document', { exact: true }).textContent()).toContain('Version 1 · success');
expect(await page.getByLabel('Agent Document', { exact: true }).textContent()).toContain('Loaded · 1 / 1');

await page.getByRole('tab', { name: 'Protocol' }).click();
await page.getByText('Provider MCP protocol', { exact: true }).waitFor({ timeout: 5_000 });
Expand Down
Loading