diff --git a/packages/server/src/server/plugins/compiler.test.ts b/packages/server/src/server/plugins/compiler.test.ts index facba48fdb..fc86f85db3 100644 --- a/packages/server/src/server/plugins/compiler.test.ts +++ b/packages/server/src/server/plugins/compiler.test.ts @@ -138,6 +138,51 @@ async function createRootAlias(directory: string): Promise { } describe("plugin runtime entries", () => { + it.each([ + 'import type { AgentTimelineItem, ToolCallDetail } from "@getpaseo/protocol/agent-types"; export type Detail = ToolCallDetail; export type Item = AgentTimelineItem;', + 'import { type AgentTimelineItem } from "@getpaseo/protocol/agent-types"; export type Item = AgentTimelineItem;', + 'export type { AgentTimelineItem } from "@getpaseo/protocol/agent-types";', + 'export type Item = import("@getpaseo/protocol/agent-types").AgentTimelineItem;', + 'export type { AgentAttachment } from "@getpaseo/protocol/messages";', + ])("compiles protocol types without local dependencies: %s", async (typeSource) => { + const entries = await createSplitPlugin(); + await writeFile( + path.join(entries.directory, "shared", "labels.ts"), + `${typeSource}\nexport const clientLabel = "Client contribution"; export const serverLabel = "Server contribution";`, + ); + + const result = await compilePlugin(entries); + + expect(result.clientBundle).toContain("Client contribution"); + expect(result.serverBundle).toContain("Server contribution"); + expect(result.clientBundle).not.toContain("@getpaseo/protocol"); + expect(result.serverBundle).not.toContain("@getpaseo/protocol"); + }); + + it("still requires local dependencies for protocol runtime imports", async () => { + const entries = await createSplitPlugin(); + await writeFile( + entries.server, + 'import { TOOL_CALL_ICON_NAMES } from "@getpaseo/protocol/agent-types"; export default () => TOOL_CALL_ICON_NAMES;', + ); + + await expect(compilePlugin(entries)).rejects.toThrow( + 'Could not resolve "@getpaseo/protocol/agent-types"', + ); + }); + + it("still rejects missing third-party type dependencies", async () => { + const entries = await createSplitPlugin(); + await writeFile( + entries.client, + 'import type { Item } from "missing-types"; export default (item: Item) => item;', + ); + + await expect(compilePlugin(entries)).rejects.toThrow( + 'Could not resolve type dependency "missing-types"', + ); + }); + it.each([ "react", "react/jsx-runtime", diff --git a/packages/server/src/server/plugins/compiler.ts b/packages/server/src/server/plugins/compiler.ts index 15df395d06..b48449bb50 100644 --- a/packages/server/src/server/plugins/compiler.ts +++ b/packages/server/src/server/plugins/compiler.ts @@ -245,6 +245,10 @@ function createRuntimeBoundaryPlugin(target: PluginBuildTarget, pluginDirectory: runtimeSpecifierError(packageSpecifier, owner, file) ?? lexicalBoundaryError(specifier, path.dirname(file), pluginDirectory, owner); if (error) return error; + // Protocol types are shared host contracts, also exposed by the SDK's peer + // dependency during development. Erased imports need no plugin-local install; + // runtime imports still go through normal dependency resolution below. + if (typeOnly && specifier.startsWith("@getpaseo/protocol/")) continue; // Host modules have separately enforced SDK boundaries and need no local installation. if ( (PLUGIN_SDK_SPECIFIERS as readonly string[]).includes(specifier) ||