refactor!: export only lib.ts, with serveAcp and the authStatus wire names (#1566)

Stacked on #1564.

The package's `exports` map listed `"./*": "./*"`, so every file under
`dist/` was public, and any file move could break a deep import. It now
lists only `.` (`dist/lib.js`) and `./package.json`.

**`lib.ts` exports:**
- `serveAcp`, which serves ACP v1 and v2 on stdio through the SDK's
protocol router. This is what `index.ts` runs.
- The `AcpProtocols` and `Logger` types.
- `AUTH_STATUS_UPDATE_METHOD` and the `authStatus` payload types, which
a client needs to read the extension's traffic.

**No longer exported:**
- `ClaudeAcpAgent`
- `runAcp`: v1 only and unused, replaced by `serveAcp`, and now deleted.
- `toAcpNotifications`, `streamEventToAcpNotifications`,
`isLocalCommandMetadata`, `stripLocalCommandMetadata`
- `toolInfoFromToolUse`, `toolUpdateFromToolResult`, `toDisplayPath`,
`planEntries`
- `SettingsManager`
- `nodeToWebReadable`, `nodeToWebWritable`, `Pushable`, `unreachable`
- The types `NewSessionMeta`, `SDKMessageFilter`, `ToolUpdateMeta` and
`ClaudePlanEntry`.

`src/acp-agent.ts`, which re-exported `src/v1/acp-agent.ts` for the
`dist/acp-agent.js` path, is gone. So are the re-exports in
`src/v1/acp-agent.ts` that kept moved names on that path. Tests now
import those names from their own modules. The ESLint boundary rules and
`AGENTS.md` drop their `acp-agent.ts` exemptions.

`release-please-config.json` sets `bump-minor-pre-major`, so the `!`
bumps the minor version, the same as a `feat:`.

BREAKING CHANGE: deep imports of `dist/` files fail with
`ERR_PACKAGE_PATH_NOT_EXPORTED`, and `lib.ts` exports only `serveAcp`,
`AUTH_STATUS_UPDATE_METHOD` and their types.

Test results:
- `npx tsc --noEmit`, `npm run check`, `npm run build`: pass
- `npx vitest run`: 3939 passed / 33 skipped. `module-exports.test.ts`
pins the `lib.ts` names and the `exports` map.
- `node scripts/smoke-binary.mjs -- node dist/index.js`: pass
- Resolving the package from its root:
`import("@agentclientprotocol/claude-agent-acp")` gives `serveAcp` and
`AUTH_STATUS_UPDATE_METHOD`, `…/dist/acp-agent.js` throws
`ERR_PACKAGE_PATH_NOT_EXPORTED`, and `…/package.json` resolves.
This commit is contained in:
Ben Brandt
2026-11-10 08:56:24 +01:00
committed by GitHub
parent 0e7e74d424
commit 566e2652a9
12 changed files with 34 additions and 170 deletions

View File

@@ -3,10 +3,9 @@
An ACP-compatible coding agent powered by the Claude Agent SDK. Source in `src/`,
TypeScript, ESM, Node >= 22. `src/v1/` holds the v1 agent (`ClaudeAcpAgent` in
`src/v1/acp-agent.ts`), `src/v2/` the v2 agent, and the rest of `src/` the modules
both share. `src/acp-agent.ts` only re-exports `src/v1/acp-agent.ts` for the
published `dist/acp-agent.js` path. ESLint rejects imports between `src/v1/` and
both share. The package exports only `src/lib.ts`. ESLint rejects imports between `src/v1/` and
`src/v2/`, and from shared modules into either agent. The entry points (`index.ts`, `lib.ts`,
`serve.ts`, `binary-entry.ts`, `acp-agent.ts`) are exempt.
`serve.ts`, `binary-entry.ts`) are exempt.
## Commands

View File

@@ -61,11 +61,11 @@ export default [
curly: ["error", "all"],
},
},
// `src/v2/` must not import the v1 agent (`src/v1/`, or `src/acp-agent.ts`, which re-exports it).
// `src/v2/` must not import the v1 agent.
{
files: ["src/v2/**/*.ts"],
rules: protocolBoundary(
"^(\\.\\./)+(v1/|acp-agent\\.js$)",
"^(\\.\\./)+v1/",
"src/v2/ must not import the v1 agent. Move what both versions need into a shared module in src/.",
),
},
@@ -88,11 +88,10 @@ export default [
"src/lib.ts",
"src/serve.ts",
"src/binary-entry.ts",
"src/acp-agent.ts",
],
rules: protocolBoundary(
"^(\\./|(\\.\\./)+)(v1/|v2/|acp-agent\\.js$)",
"Shared modules must not import src/v1/, src/v2/ or src/acp-agent.ts. Move the code into the shared module, or pass it in from the agent.",
"^(\\./|(\\.\\./)+)(v1|v2)/",
"Shared modules must not import src/v1/ or src/v2/. Move the code into the shared module, or pass it in from the agent.",
),
},
prettierConfig,

View File

@@ -16,7 +16,7 @@
"types": "./dist/lib.d.ts",
"import": "./dist/lib.js"
},
"./*": "./*"
"./package.json": "./package.json"
},
"files": [
"dist/",

View File

@@ -1,3 +0,0 @@
// The v1 agent lives in `v1/acp-agent.ts`. This module keeps the published
// `dist/acp-agent.js` path that consumers deep-import.
export * from "./v1/acp-agent.js";

View File

@@ -1,19 +1,7 @@
// Export the main agent class and utilities for library usage
export { ClaudeAcpAgent, runAcp } from "./v1/acp-agent.js";
export { isLocalCommandMetadata, stripLocalCommandMetadata } from "./message-filters.js";
export { toAcpNotifications, streamEventToAcpNotifications } from "./sdk-to-acp.js";
export type { NewSessionMeta, SDKMessageFilter } from "./new-session-meta.js";
export type { ToolUpdateMeta } from "./tool-calls/renderer.js";
export { nodeToWebReadable, nodeToWebWritable, Pushable, unreachable } from "./utils.js";
export {
toolInfoFromToolUse,
toDisplayPath,
planEntries,
toolUpdateFromToolResult,
} from "./tools.js";
export { SettingsManager, type SettingsManagerOptions } from "./settings.js";
// The `authStatus` extension's wire surface: the notification's method name and
// the payload types needed to read the traffic. The mappers stay internal.
// The package's public API: serve ACP (v1 and v2) on stdio, and the wire
// names of the `authStatus` extension that a client needs to read its traffic.
export { serveAcp, type AcpProtocols } from "./serve.js";
export type { Logger } from "./logger.js";
export {
AUTH_STATUS_UPDATE_METHOD,
type AuthStatus,
@@ -21,6 +9,3 @@ export {
type AuthStatusKind,
type AuthStatusUpdateNotification,
} from "./auth-status.js";
// Export types
export type { ClaudePlanEntry } from "./tools.js";

View File

@@ -37,12 +37,11 @@ import { ToolCallFieldTracker } from "../tool-calls/field-tracker.js";
import {
ClaudeAcpAgent,
computeSessionFingerprint,
buildConfigOptions,
createFastModeConfigOption,
discoverCustomAgents,
runPromptWithCancellation,
type SteerRequest,
} from "../v1/acp-agent.js";
import { buildConfigOptions, discoverCustomAgents } from "../session-config-options.js";
import { createFastModeConfigOption } from "../session-fast-mode.js";
import {
toAcpNotifications,
streamEventToAcpNotifications,

View File

@@ -3,7 +3,7 @@ import { randomUUID } from "crypto";
import { query } from "@anthropic-ai/claude-agent-sdk";
import type { SDKUserMessage } from "@anthropic-ai/claude-agent-sdk";
import { Pushable } from "../utils.js";
import { discoverCustomAgents, BUILTIN_AGENT_NAMES } from "../v1/acp-agent.js";
import { discoverCustomAgents, BUILTIN_AGENT_NAMES } from "../session-config-options.js";
// `discoverCustomAgents` distinguishes user/plugin-configured agents from
// Claude Code's built-in subagents by name (BUILTIN_AGENT_NAMES), since the

View File

@@ -1,9 +1,12 @@
import { describe, it, expect, vi } from "vitest";
import type { ClientCapabilities, SessionNotification } from "@agentclientprotocol/sdk/unstable";
import type { FastModeDisabledReason, ModelInfo } from "@anthropic-ai/claude-agent-sdk";
import { ClaudeAcpAgent } from "../v1/acp-agent.js";
import {
buildConfigOptions,
clientSupportsBooleanConfigOptions,
} from "../session-config-options.js";
import {
createFastModeConfigOption,
fastModeStateEnabled,
normalizeFastModeDisabledReason,
@@ -11,8 +14,7 @@ import {
FAST_MODE_CONFIG_ID,
FAST_MODE_ON,
FAST_MODE_OFF,
ClaudeAcpAgent,
} from "../v1/acp-agent.js";
} from "../session-fast-mode.js";
import type { AcpClient } from "../acp-client.js";
const MODES = {

View File

@@ -1,74 +1,22 @@
import { describe, expect, it } from "vitest";
import packageJson from "../../package.json" with { type: "json" };
describe("module runtime exports", () => {
/**
* The public runtime exports of lib.ts must change only deliberately; consumers import these names.
* The public runtime exports of lib.ts, the package's only entry point, must change only deliberately; consumers import these names.
*/
it("lib.ts exports the expected names", async () => {
expect(Object.keys(await import("../lib.js")).sort()).toEqual([
"AUTH_STATUS_UPDATE_METHOD",
"ClaudeAcpAgent",
"Pushable",
"SettingsManager",
"isLocalCommandMetadata",
"nodeToWebReadable",
"nodeToWebWritable",
"planEntries",
"runAcp",
"streamEventToAcpNotifications",
"stripLocalCommandMetadata",
"toAcpNotifications",
"toDisplayPath",
"toolInfoFromToolUse",
"toolUpdateFromToolResult",
"unreachable",
"serveAcp",
]);
});
/**
* Moving code out of acp-agent.ts must keep every name re-exported, since consumers deep-import
* dist/acp-agent.js; change this list only deliberately.
*/
it("acp-agent.ts exports the expected names", async () => {
expect(Object.keys(await import("../acp-agent.js")).sort()).toEqual([
"AGENT_CONFIG_ID",
"AUTONOMOUS_RESULT_ORIGINS",
"BUILTIN_AGENT_NAMES",
"ClaudeAcpAgent",
"DEFAULT_AGENT_ID",
"EFFORT_CONFIG_ID",
"FAST_MODE_CONFIG_ID",
"FAST_MODE_OFF",
"FAST_MODE_ON",
"MODEL_CONFIG_ID",
"MODE_CONFIG_ID",
"applyAvailableModelsAllowlist",
"buildConfigOptions",
"clientSupportsBooleanConfigOptions",
"clientSupportsRecommendedConfigValue",
"computeSessionFingerprint",
"createFastModeConfigOption",
"discoverCustomAgents",
"fastModeStateEnabled",
"getAvailableSlashCommands",
"isAbortedResult",
"isLocalCommandMetadata",
"isLoginRequiredText",
"isReplayHiddenMetaMessage",
"isSyntheticLoginMessage",
"matchResumedModel",
"messageIdForGrouping",
"normalizeFastModeDisabledReason",
"promptToClaude",
"resolveFastModeEnabled",
"resolveModelPreference",
"runAcp",
"runPromptWithCancellation",
"settingsEffortForModel",
"streamEventToAcpNotifications",
"stripLocalCommandMetadata",
"toAcpNotifications",
"v1AgentApp",
]);
it("package.json exposes lib.ts as the only module entry point", () => {
expect(Object.keys(packageJson.exports)).toEqual([".", "./package.json"]);
expect(packageJson.exports["."]).toEqual({
types: "./dist/lib.d.ts",
import: "./dist/lib.js",
});
});
});

View File

@@ -3,7 +3,8 @@ import { tmpdir } from "node:os";
import path from "node:path";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import type { ModelInfo, Options } from "@anthropic-ai/claude-agent-sdk";
import { ClaudeAcpAgent, FAST_MODE_CONFIG_ID } from "../v1/acp-agent.js";
import { ClaudeAcpAgent } from "../v1/acp-agent.js";
import { FAST_MODE_CONFIG_ID } from "../session-fast-mode.js";
import type { AcpClient } from "../acp-client.js";
import { AIR_RECOMMENDED_CONFIG_VALUE_CAPABILITY } from "../air-extension.js";
import { initializeClient, makeMockQuery } from "./helpers.js";

View File

@@ -1,7 +1,10 @@
import { describe, expect, it } from "vitest";
import type { ClientCapabilities } from "@agentclientprotocol/sdk/unstable";
import type { ModelInfo } from "@anthropic-ai/claude-agent-sdk";
import { buildConfigOptions, clientSupportsRecommendedConfigValue } from "../v1/acp-agent.js";
import {
buildConfigOptions,
clientSupportsRecommendedConfigValue,
} from "../session-config-options.js";
import { AIR_RECOMMENDED_CONFIG_VALUE_CAPABILITY } from "../air-extension.js";
const MODES = {

View File

@@ -25,7 +25,6 @@ import {
LoadSessionResponse,
LogoutRequest,
methods,
ndJsonStream,
NewSessionRequest,
NewSessionResponse,
PromptRequest,
@@ -312,14 +311,7 @@ import {
isFinalToolCallStatus,
type ToolCallUpdate,
} from "../tool-calls/renderer.js";
import {
messageLoopYielder,
nodeToWebReadable,
nodeToWebWritable,
Pushable,
raceTimeoutAndAbort,
unreachable,
} from "../utils.js";
import { messageLoopYielder, Pushable, raceTimeoutAndAbort, unreachable } from "../utils.js";
import {
acceptedPlanToolResult,
ExitPlanCoordinator,
@@ -330,7 +322,6 @@ import { AGENT_CONFIG_ID, DEFAULT_AGENT_ID } from "../session-config-ids.js";
import { type PersistedTaskNotification, taskNotificationsOf } from "../task-notifications.js";
import { formatUsageResponse, isUsageCommandText, parseUsageResponse } from "../usage-markdown.js";
export { DEFAULT_AGENT_ID } from "../session-config-ids.js";
import { parseMcpCommand, runMcpCommand } from "../mcp-command.js";
import { configuredSettings, resolveThinkingConfig } from "../session-env.js";
import {
@@ -408,33 +399,6 @@ import { STARTUP_FAILURE_RESULTS_ENV, startupFailureError } from "../startup-fai
import { recordStreamedDelta, unstreamedRemainder, type StreamedBlock } from "../streamed-text.js";
import { subagentHookOptions } from "../subagent-configuration.js";
export {
AGENT_CONFIG_ID,
BUILTIN_AGENT_NAMES,
buildConfigOptions,
clientSupportsBooleanConfigOptions,
clientSupportsRecommendedConfigValue,
type ConfigOptionPresentation,
discoverCustomAgents,
MODE_CONFIG_ID,
} from "../session-config-options.js";
export {
createFastModeConfigOption,
FAST_MODE_CONFIG_ID,
FAST_MODE_OFF,
FAST_MODE_ON,
fastModeStateEnabled,
type FastModeOptionState,
normalizeFastModeDisabledReason,
resolveFastModeEnabled,
} from "../session-fast-mode.js";
export { EFFORT_CONFIG_ID, settingsEffortForModel } from "../session-effort.js";
export {
applyAvailableModelsAllowlist,
matchResumedModel,
MODEL_CONFIG_ID,
resolveModelPreference,
} from "../session-model.js";
import type { AcpClient } from "../acp-client.js";
import type { Logger } from "../logger.js";
import {
@@ -456,28 +420,6 @@ import {
} from "../sdk-to-acp.js";
import { getAvailableSlashCommands } from "../slash-commands.js";
// Moved to their own modules; kept as exports for consumers that deep-import dist/acp-agent.js
// (re-exported by `../acp-agent.ts`).
export { AUTONOMOUS_RESULT_ORIGINS, isAbortedResult, isLoginRequiredText } from "../result-end.js";
export type { AcpClient } from "../acp-client.js";
export type { Logger } from "../logger.js";
export {
isLocalCommandMetadata,
isReplayHiddenMetaMessage,
isSyntheticLoginMessage,
stripLocalCommandMetadata,
} from "../message-filters.js";
export type { NewSessionMeta, SDKMessageFilter } from "../new-session-meta.js";
export { promptToClaude } from "../prompt-to-claude.js";
export {
messageIdForGrouping,
streamEventToAcpNotifications,
toAcpNotifications,
type StreamedToolInputCache,
type ToolUseCache,
} from "../sdk-to-acp.js";
export { getAvailableSlashCommands } from "../slash-commands.js";
/** Claude CLI emits this synthetic result when an interrupted cycle ends on
* queued user input before producing any assistant content. It is a hand-off
* marker, not the outcome of the replacement prompt that may already be
@@ -1746,8 +1688,6 @@ export function computeSessionFingerprint(params: {
* cannot drift from the errors this agent throws. */
const AUTH_REQUIRED_CODE = RequestError.authRequired().code;
export type { ToolUpdateMeta } from "../tool-calls/renderer.js";
const SUBAGENT_TRANSCRIPT_CAPABILITY = "subagent-transcript";
function supportsSubagentTranscript(capabilities?: ClientCapabilities | null): boolean {
@@ -11319,15 +11259,6 @@ export function v1AgentApp(
return sessionIndex.onSessionIndexRequests(app, () => agent);
}
/** Serves ACP v1 on stdio. */
export function runAcp(logger?: Logger) {
const stream = ndJsonStream(nodeToWebWritable(process.stdout), nodeToWebReadable(process.stdin));
let agent!: ClaudeAcpAgent;
// `connect` runs the connect handlers before it returns, so `agent` is set.
const connection = v1AgentApp(logger, (created) => (agent = created)).connect(stream);
return { connection, agent };
}
/**
* What a `command_lifecycle` "started" frame says started the command: a
* ScheduleWakeup (dynamic `/loop`) wakeup, with the time it was set for in