Releases: DaniAkash/agent-toolkit
agent-mcp-manager-v0.0.3
Closes #61. Codex's ~/.codex/config.toml accepts streamable HTTP MCP servers directly per the official OpenAI Codex MCP docs. The v0.0.2 catalog marked codex as stdio-only, which forced every codex link through an npx -y mcp-remote <url> stdio bridge. That fails silently when Node is not on the user's machine. This release surfaces the http path so the shim is no longer needed.
What changes
| Surface | Before (0.0.2) | After (0.0.3) |
|---|---|---|
codex.supportedTransports |
['stdio'] |
['stdio', 'http'] |
link({ agent: 'codex', spec: { transport: 'http', url } }) |
throws UnsupportedTransportError |
succeeds; writes [mcp_servers.<name>] with url (and http_headers when spec.headers is set) |
link({ agent: 'codex', spec: { transport: 'sse', url } }) |
throws | still throws (codex's TOML parser has no sse schema) |
transportHint('codex', ...) for sse failures |
"Codex only accepts stdio MCP servers..." | "Codex accepts stdio and streamable-HTTP MCP servers; SSE is not parsed. If the URL actually serves streamable HTTP, re-add with transport: \"http\". If SSE-only, wrap with npx -y mcp-remote <url> as stdio." |
Existing stdio entries written by 0.0.2 keep working identically. No migration needed for callers who only use stdio specs.
On-disk shape (new http path)
[mcp_servers.figma]
url = "https://mcp.figma.com/mcp"
[mcp_servers.figma.http_headers]
"X-Figma-Region" = "us-east-1"Registering a remote MCP for Codex
Pass an http spec directly. No mcp-remote shim needed:
await mgr.add({
name: 'figma',
spec: {
transport: 'http',
url: 'https://mcp.figma.com/mcp',
headers: { 'X-Figma-Region': 'us-east-1' },
},
})
await mgr.link({ serverName: 'figma', agent: 'codex' })Out of scope for this release
bearer_token_env_varandenv_http_headers. Codex's TOML schema accepts both, butMcpHttpSpechas no env-var-name field today. Hand-edit the TOML if you need env-sourced bearer tokens.startup_timeout_sec/tool_timeout_sec. Codex defaults (10s / 60s) apply.envon codex stdio entries. Not emitted in 0.0.2 either (matches upstreamdocker/mcp-gateway'scodex_handler.go).claude-desktopstays stdio-only. Anthropic's local config parser still validates stdio-shape only for local servers.
Downstream
Consumers whose install flow gates on surface.supportedTransports.includes('http') (BrowserOS's apps/server/src/lib/mcp-manager/service.ts:planFor is the reference case) now hit the http branch for codex automatically. The npx mcp-remote runtime dependency disappears for codex users.
Links
- npm: https://www.npmjs.com/package/agent-mcp-manager
- README: https://github.com/DaniAkash/agent-toolkit/tree/main/packages/agent-mcp-manager
- Codex MCP docs: https://developers.openai.com/codex/mcp
- Issue: #61
- PR: #62
agent-mcp-manager-v0.0.2
Closes two production bug reports surfaced while installing remote MCP servers (BrowserOS) across coding agents. Both are rooted in the same gap: upstream docker/mcp-gateway writes one stdio entry per client by Go-type, so it never modelled per-agent transport capability. This library had generalised to "write any transport per caller spec" without adding the per-agent gate, which let it produce shapes some agents reject.
What changes by agent
| Agent | What changes |
|---|---|
claude-desktop |
link() with an http/sse spec now throws UnsupportedTransportError at the API boundary instead of writing a {url, headers} block Claude Desktop will silently skip on next launch with "not a valid MCP server configuration." stdio specs unchanged. |
codex |
Same typed enforcement, for parity. Codex was already stdio-only via the TOML emitter; the rejection is now a typed error you can catch in a fan-out loop. |
claude-code system (~/.claude.json) |
Unchanged. Still accepts stdio, sse, and http with no type tag. |
claude-code project (.mcp.json) |
Now stdio-only AND injects type: "stdio", matching upstream Docker's set: .mcpServers[$NAME] = $JSON+{"type":"stdio"}. Newer Claude Code releases require this tag; the library was silently producing entries those releases would skip. |
vscode |
Was hardcoding type: "stdio" on every entry (wrong for http/sse). Now writes type: matching spec.transport. Stdio callers see no change. |
cursor, gemini, zed |
Unchanged. |
What changes library-wide
UnsupportedTransportError(new typed error). Includesagent,transport,details.supported, and anmcp-remoteshim hint. Exported from the package root.McpTransport(new public type, just'stdio' | 'sse' | 'http').link({ allowOverwrite: true })(new opt-in). Bypasses the foreign-entry guard, takes ownership of an existing on-disk entry, and records it in the manifest. Defaultfalse. Does NOT bypass the transport-capability check.getCatalogEntryandresolveAgentSurfacenewly exported. UseresolveAgentSurface(id, scope).supportedTransportsto pre-filter agents by transport capability before linking; it honours scope-specific overrides (Claude Code project scope is stdio-only, system is not).
Recovering from ForeignEntryError
If link() throws Cannot replace a user-edited entry. Please remove <name> from this agent's config manually and try again, it means the on-disk config already has an entry under that name the workspace manifest did not write. Two options:
// Take ownership and rewrite with your spec:
await mgr.link({
serverName: 'BrowserOS',
agent: 'claude-code',
allowOverwrite: true,
})
// Or remove manually and retry without the flag.Registering a remote MCP server in a stdio-only agent
claude-desktop, codex, and claude-code project scope only accept stdio entries. Wrap the URL with mcp-remote:
await mgr.add({
name: 'browseros',
spec: {
transport: 'stdio',
command: 'npx',
args: [
'-y',
'mcp-remote',
'https://browseros.example.com/mcp',
'--header',
`Authorization: Bearer ${process.env.BROWSEROS_TOKEN}`,
],
},
})
for (const a of (await detectInstalledAgents()).filter((a) => a.installed)) {
await mgr.link({ serverName: 'browseros', agent: a.id })
}The library does not auto-shim through mcp-remote in this release. Auto-shim is on the v0.2 roadmap so the manifest stays a faithful record of intent.
Pre-filter pattern (scope-aware)
import { resolveAgentSurface, type AgentScope, type McpTransport } from 'agent-mcp-manager'
const scope: AgentScope = 'system'
const supports = (id, t: McpTransport) =>
resolveAgentSurface(id, scope).supportedTransports.includes(t)
for (const a of (await detectInstalledAgents()).filter((a) => a.installed)) {
if (!supports(a.id, 'http')) continue
await mgr.link({ serverName: 'github', agent: a.id })
}Upstream Docker conformance
Now matches the per-agent on-disk shape upstream docker/mcp-gateway writes for every agent in our catalog. Three known deltas remain by design or are deferred:
- VS Code: we write three transports; upstream writes one. Intentional (VS Code accepts all three when tagged correctly).
- Zed spread order (defaults vs caller-supplied): deferred to v0.2.
- Missing agents (Cline, Continue.dev, Goose, LM Studio, OpenCode, Sema4, Crush, Kiro): planned for v0.2.
Out of scope for 0.0.2 (planned)
- Auto-shim http/sse via
mcp-remotefor stdio-only agents. - Case-insensitive name matching (manifest schema change).
adopt()primitive that preserves the on-disk shape verbatim instead of rewriting.
Links
ai-sdk-microsandbox-v0.0.1
First public release of ai-sdk-microsandbox.
A Vercel AI SDK v7 HarnessV1SandboxProvider backed by microsandbox. Drop-in alternative to @ai-sdk/sandbox-vercel at the sandbox: slot of HarnessAgent, running coding agents in local microVM isolation instead of Vercel-hosted sandboxes. The bridge, the agent CLI, and any files the agent touches stay on-host. Cross-process resume works via an on-disk snapshot cache.
Install
npm install ai-sdk-microsandbox @ai-sdk/harness @ai-sdk/harness-codex ai microsandboxQuickstart
import { HarnessAgent } from '@ai-sdk/harness/agent'
import { createCodex } from '@ai-sdk/harness-codex'
import { createMicrosandbox } from 'ai-sdk-microsandbox'
const agent = new HarnessAgent({
harness: createCodex({
auth: { openai: { apiKey: process.env.OPENAI_API_KEY } },
}),
sandbox: createMicrosandbox({
image: 'node:22-bookworm-slim',
cpus: 1,
memory: 1024,
workdir: '/root',
ports: [{ host: 4000, guest: 4000 }],
bootstrapPreCommands: [
'apt-get update -qq && apt-get install -y --no-install-recommends ca-certificates >/dev/null && update-ca-certificates -f >/dev/null',
'corepack enable pnpm',
],
}),
})
const session = await agent.createSession()
try {
const result = await agent.generate({
session,
prompt: 'Use bash to create /root/hi.txt containing "hello".',
})
console.log(result.text)
} finally {
await session.destroy()
}Requirements
- Linux with KVM enabled, or macOS on Apple Silicon
- Run
microsandbox setuponce on the host
Verified end-to-end
This release was validated against real Codex turns through real OpenAI on a clean machine:
- Unit suite: 216 / 216 pass
- Integration suite (real microVM): 23 / 23 pass
- E2E suite (real Codex + real OpenAI): 15 / 15 pass
Covers: text generation, streaming, bash tool through the bridge, file I/O round-trips, multi-turn context preservation, distinct sandboxes per session, cross-process resume via session.detach() + agent.createSession({ resumeFrom }), abort cancellation, and the configuration matrix (workdir, env, cpus, memory, network policy).
What's in the box
createMicrosandbox(settings)provider with both create-mode (fresh microVM per session) and wrap-mode (caller-owned sandbox)- Filesystem-level snapshot cache keyed on a stable identity hash so the bootstrap recipe runs once per identity across processes
bootstrapPreCommandssetting for image-prep steps (e.g.corepack enable pnpm,apt-get install ca-certificates) that get captured into the snapshotresumeSessionimplementation that dispatches betweenhandle.connect()(running) andhandle.start()(stopped) so resume works for bothdetach()andstop()payloads- Network policy translation (allow-all / deny-all / custom with
allowedHosts,allowedCIDRs,deniedCIDRs) into microsandbox'sNetworkPolicyBuilderat create-time - Configurable cache root via
AI_SDK_MICROSANDBOX_CACHE_DIR; defaults to the OS-conventional cache directory
Status and limitations
- Alpha. The exported API may shift before
1.0.0. - Runtime network policy updates are unavailable; policy is sealed at create-time.
- Concurrent multi-session usage on one provider is bounded by the host-port mapping (two forks cannot bind the same host port simultaneously).
- Snapshot pruning is manual today; remove the cache directory to reset.
- The chosen
workdirmust already exist in the image, or be created viabootstrapPreCommands.
Links
acpx-ai-harness-v0.0.1
First public release of acpx-ai-harness: a Vercel AI SDK v7 HarnessV1 adapter built on the acpx runtime. Brings any ACP-protocol agent (Claude Code, Codex, Gemini, Copilot, Cursor) into the AI SDK Harnesses ecosystem alongside @ai-sdk/harness-claude-code, @ai-sdk/harness-codex, and @ai-sdk/harness-pi.
Experimental. Both this package and the upstream
@ai-sdk/harnessAPI are pre-1.0 and subject to breaking changes.
Install
npm install acpx-ai-harness @ai-sdk/harness ai acpx@ai-sdk/harness, ai, and acpx are declared as peer dependencies. A sandbox provider with port exposure is also required (see Sandbox setup below).
Quick start
import { HarnessAgent } from '@ai-sdk/harness/agent'
import { createVercelSandbox } from '@ai-sdk/sandbox-vercel'
import { createAcpxHarness } from 'acpx-ai-harness'
const harness = createAcpxHarness({
agent: 'codex',
auth: { openai_api_key: process.env.OPENAI_API_KEY! },
})
const agent = new HarnessAgent({
harness,
sandbox: createVercelSandbox({ runtime: 'node22', ports: [4001] }),
})
const session = await agent.createSession()
try {
const result = await agent.stream({ session, prompt: 'Refactor user.ts to use Result<T, E>' })
for await (const part of result.fullStream) {
if (part.type === 'text-delta') process.stdout.write(part.delta)
}
} finally {
await session.destroy()
}What's in 0.0.1
Agents
The harness's bootstrap recipe pre-warms the ACP wrapper binary acpx invokes for each known agent. Vercel sandbox keys snapshots by the recipe hash, so the install cost is paid once and amortised across sessions.
agent setting |
Bootstrap pre-warm | settings.auth key |
|---|---|---|
'codex' (default) |
npx --yes @agentclientprotocol/codex-acp --version |
openai_api_key |
'claude' |
npx --yes @agentclientprotocol/claude-agent-acp --version |
anthropic_api_key |
'gemini' |
npm install -g @google/gemini-cli |
gemini_api_key |
| any other id | (skipped, bring your own install) | (varies, see acpx config docs) |
Lifecycle
acpxHarness implements the full HarnessV1Session surface except doCompact (acpx auto-compacts internally; manual compaction has no API and the method throws HarnessCapabilityUnsupportedError). The harness picks between two recovery rungs when createSession({ resumeFrom }) or createSession({ continueFrom }) is called:
- ATTACH when the saved bridge coords are still live (same sandbox id, reachable WebSocket). The host reconnects to the running bridge and replays buffered events past the saved cursor.
- RERUN if the bridge is gone (sandbox cycled, port lost). A fresh bridge spawns and the acpx session reloads from disk via
sessionKey. ForcontinueFrom, aContinue.nudge is sent so the agent picks up where it left off.
const state = await session.detach() // park, bridge stays alive
const resumed = await agent.createSession({
sessionId: session.sessionId,
resumeFrom: state,
})
const stopState = await session.stop() // bridge exits, state captured for resumePermission modes
| Harness mode | acpx mode | Behaviour |
|---|---|---|
allow-all (default) |
approve-all |
Every tool call auto-approved. |
allow-edits |
approve-all |
acpx's approve-all already covers the edits bucket. |
allow-reads |
approve-reads |
Reads / searches auto-approved; edits and shell raise an approval request via tool-approval-request. |
The host receives tool-approval-request stream parts for any tool the agent wants to call, and responds via HarnessV1PromptControl.submitToolApproval({ approvalId, approved, reason? }).
Built-in tools
The harness advertises the seven harness common-tool entries (read, write, edit, bash, grep, glob, webSearch) so cross-harness consumers can identify them. Per-agent native tool names (Claude's Bash, Codex's shell, Gemini's run_shell_command) are normalised to the common names on the wire; the original native name flows along on tool-call.nativeName.
Sandbox setup
acpx-ai-harness is a bridge-backed adapter: it spawns a Node.js process inside the sandbox that drives the ACP agent and serves a WebSocket to the host. The sandbox provider must support port exposure for the host to reach that WebSocket.
| Provider | Works? | Notes |
|---|---|---|
@ai-sdk/sandbox-vercel |
Yes | Cloud sandbox, supports port exposure + snapshots. Requires VERCEL_TOKEN, VERCEL_TEAM_ID, VERCEL_PROJECT_ID. |
@ai-sdk/sandbox-just-bash |
No | Local sandbox, doesn't expose ports. Bridge-backed adapters reject it at start. Useful for non-bridge harnesses. |
Other providers conforming to HarnessV1SandboxProvider work as long as their network sandbox session implements getPortUrl({ port, protocol: 'ws' }).
Auth
Per acpx config docs, standard provider env vars like OPENAI_API_KEY reach child processes but do not drive acpx's auth gate. Credentials must flow through acpx's own auth channels:
settings.authwrites~/.acpx/config.jsoninside the sandbox per session.- The same map is exported as
ACPX_AUTH_<METHOD_ID>env vars on the bridge spawn so acpx's runtime sees them on firstensureSession.
Both channels are populated per session, never via the bootstrap recipe, so credentials never end up in a Vercel sandbox snapshot.
The harness defaults authPolicy: 'fail' when auth is non-empty so a missing or invalid key surfaces immediately rather than as a downstream ACP error. Override with settings.authPolicy: 'skip' for legacy "let the adapter handle it" behaviour.
Verified end-to-end
generate() and stream() were exercised live against a real Vercel sandbox + a real codex turn before the release was cut: 2 pass / 0 fail / ~73 seconds cold start. Asserts in the smoke suite cover non-empty result.text and the presence of text-delta + finish parts on fullStream.
Current limitations
- Host AI SDK tools are not yet forwarded to the agent. Tools passed via
HarnessAgent({ tools })emit anunsupported-toolCallWarningonstream-startand are otherwise ignored. acpx's runtime only accepts wire-protocol MCP servers (stdio / http / sse); a host-side MCP server inside the bridge needs to land before this works end to end. Stdio / http / sse MCP servers passed via the start frame'smcpServersfield flow through verbatim today. - No manual compaction.
doCompact()throwsHarnessCapabilityUnsupportedError. acpx delegates compaction to the underlying agent. @ai-sdk/sandbox-just-bashis rejected because just-bash cannot expose ports.
Links
- npm: https://www.npmjs.com/package/acpx-ai-harness
- README: https://github.com/DaniAkash/acpx/tree/main/packages/acpx-ai-harness
- acpx runtime: https://acpx.sh
- AI SDK Harnesses: https://ai-sdk.dev/v7/docs/ai-sdk-harnesses/overview
agent-mcp-manager-v0.0.1
First public release of agent-mcp-manager: a TypeScript library for programmatically registering Model Context Protocol servers across AI coding agents (Claude Code, Claude Desktop, Cursor, VS Code, Codex, Gemini CLI, Zed). Built for hosts (IDE plugins, internal tools, enterprise onboarding flows, custom installers) that need to wire MCP servers into a user's agent configs without shelling out to a per-agent CLI.
Alpha software. The public API may change between minor versions without notice until
1.0.0. Pin exact versions; expect rough edges.
Install
npm install agent-mcp-managerQuick start
import { createMcpManager, detectInstalledAgents } from 'agent-mcp-manager'
const mgr = createMcpManager()
await mgr.add({
name: 'filesystem',
spec: {
transport: 'stdio',
command: 'npx',
args: ['-y', '@modelcontextprotocol/server-filesystem', process.cwd()],
},
})
const agents = await detectInstalledAgents()
for (const a of agents.filter((a) => a.installed)) {
await mgr.link({ serverName: 'filesystem', agent: a.id })
}
// Later, full teardown across every linked agent:
await mgr.remove({ serverName: 'filesystem' })Mental model
Two layers, clear split of responsibility:
- Workspace (yours): a directory you own with a
manifest.jsonrecording every server you've added, its spec, when, and which agents you've linked it to.add()writes here;listServers()reads it. - Agent MCP configs (the user's):
~/.claude.json,~/.cursor/mcp.json,~/.codex/config.toml, etc.link()injects entries into them;unlink()removes them;listLinks()reports the ones the manifest knows about. Foreign keys at those paths are never touched.
The manifest is authoritative for intent and metadata; the on-disk config files are authoritative for current state. rescan() cross-checks the two and reports verified / broken / unmanaged entries.
What's in 0.0.1
Seven primitives
add({ name, spec })
link({ serverName, agent, configPath? })
unlink({ serverName, agent, configPath? })
remove({ serverName, unlinkFirst? })
listServers({ scanUnmanaged? })
listLinks({ agents?, serverNames?, scanUnmanaged? })
rescan({ mode?: 'merge' | 'replace' })Detection helpers
detectInstalledAgents()
listSupportedAgents()
isAgentSupported(agent)
resolveAgentMcpConfigPath(agent, scope?, projectRoot?)Supported agents
| Agent | System config (macOS) | Emitter | Project file |
|---|---|---|---|
claude-code |
~/.claude.json |
JSON mcpServers |
.mcp.json |
claude-desktop |
~/Library/Application Support/Claude/claude_desktop_config.json |
JSONC mcpServers |
n/a |
cursor |
~/.cursor/mcp.json |
JSON mcpServers |
.cursor/mcp.json |
vscode |
~/Library/Application Support/Code/User/mcp.json |
JSON servers (injects type: stdio) |
.vscode/mcp.json |
gemini |
~/.gemini/settings.json |
JSON mcpServers |
n/a |
codex |
~/.codex/config.toml |
TOML mcp_servers |
n/a |
zed |
~/.config/zed/settings.json |
JSON context_servers (injects source: custom, enabled: true) |
n/a |
More agents (Cline, Continue.dev, OpenCode, Goose, Crush, LMStudio, Kiro, Sema4) are planned for v0.2.
Transports
stdiois fully supported.sseandhttptypes are exported and the JSON emitters write them correctly, but v0.1 tests skip non-stdio link flows. Treat as preview. Codex stays stdio-only because its upstream TOML config is stdio-only.
How this relates to docker/mcp-gateway
agent-mcp-manager derives its agent catalog from docker/mcp-gateway's pkg/client/config.yml (MIT-licensed). The per-OS paths, install-check heuristics, and config-file shapes mirror the upstream entries. Key differences:
- TypeScript-native emitters instead of yq expressions. JSONC clients use
jsonc-parser; Codex uses@iarna/toml. JSONC comment preservation is built in. - General-purpose, not Docker-specific. Docker's CLI plugin writes a single fixed
MCP_DOCKERentry pointing at its gateway. This library writes arbitrary MCP server entries from caller-supplied specs. - Manifest-backed: a
manifest.jsonrecords which entries the library wrote sounlink()can refuse to clobber user-owned keys.
See THIRD_PARTY_NOTICES.md for upstream attribution.
Errors
Every error subclasses McpManagerError:
| Error | When |
|---|---|
AgentNotSupportedError |
Unknown agent id |
ServerNotFoundError |
Operation on a server name absent from the manifest |
ForeignEntryError |
unlink sees an entry the manifest didn't write |
InvalidServerSpecError |
Spec fails validation (e.g. http transport with no url) |
UnresolvedConfigPathError |
OS or env vars do not yield a valid config path |
Secrets
The manifest stores the server spec verbatim, including any env / headers values you pass. If your spec carries a token, the manifest holds it in plaintext at ${workspaceDir}/manifest.json. v0.2 will add optional OS-keyring indirection via keytar. For v0.1: keep your workspace dir scoped to a single user and treat manifest.json like any other secret-bearing config file.
Library, not a CLI
If you want to register a single MCP server in one of your editors interactively, use that editor's own command (claude mcp add …, the Cursor settings UI, etc.) or docker mcp client connect. This package is a programmatic API for embedders.
Links
acpx-ai-provider-v0.0.6
First combined release after v0.0.4 — four feature PRs land in one version since v0.0.5 was bumped but never tagged or published.
Features
-
onPermissionRequestcallback onAcpxProviderSettings(#17). Hosts can intercept per-call permission requests (write, shell, delete, …) with their own UI instead of relying on the up-frontpermissionMode. Returningundefinedfalls through to the existing mode resolver — zero behavior change for consumers that don't opt in. Driven by the BrowserOS chat flow that wanted inline approve/deny CTA cards.createAcpxProvider({ agent: 'codex', permissionMode: 'approve-reads', // fallback for unhandled cases onPermissionRequest: async (req) => { const decision = await myUi.prompt({ title: req.raw.toolCall.title, kind: req.inferredKind, // 'edit' | 'shell' | 'delete' | … }) return decision // undefined falls through to the mode resolver }, })
-
AcpxProvider.getModels()helper (#19). Typed access to the agent's advertised model list without reaching into the untypedruntime.getStatus().detailsbag. Returns{ currentModelId, availableModelIds }for agents that advertise models (claude, codex), orundefinedfor those that don't (gemini, custom adapters).const provider = createAcpxProvider({ agent: 'claude-code', cwd }) const models = await provider.getModels() // → { currentModelId: 'claude-opus-4-7', // availableModelIds: ['claude-haiku-4-5', 'claude-sonnet-4-6', 'claude-opus-4-7'] }
-
sessionOptionsforwarded intoruntime.ensureSession(#23). Set per-sessionsystemPrompt/model/allowedTools/maxTurnson a fresh ACP session without bypassing the runtime. System prompts are applied atsession/newtime; changing them later requires a distinctsessionKey(callingprovider.close()keeps the persistent record by default and won't help here).createAcpxProvider({ agent: 'claude-code', sessionOptions: { systemPrompt: 'You are an expert Rust reviewer. Be terse.', // or { append: 'When you finish, also propose tests.' } }, })
-
fullStreamnow emits anerrorpart on failed turns (#32, #34). When an ACP turn ends with a failed result (model rejected, agent internal error, network blip, auth expired), the stream now emits{ type: 'error', error: AcpxError }immediately before the terminalfinishpart. Previously the only signal wasfinishReason: "error"onfinishplus diagnostic data stashed onproviderMetadata.acpx— invisible to astreamTextconsumer iteratingfullStream, which saw a completely silent empty assistant turn. The new error part carries the agent's actualcode/messageand the underlyingAcpRuntimeTurnResultErrorascause.
Behavior change — doGenerate / generateText throw on failed turns
The new error stream part is treated as throwable by doGenerate's accumulator (already the contract for ACP-thrown errors). On a failed-turn result, doGenerate and AI SDK's generateText now reject with an AcpxError instead of resolving with { finishReason: "error", providerMetadata: { acpx: { errorCode, errorMessage } } }. Aligns the three failure paths (thrown ACP error, mid-stream error event, failed result) on a single contract.
- Consumers that read
providerMetadata.acpx.{errorCode,errorMessage}onfinishkeep working — that field is preserved verbatim on thefinishpart for the streaming path. streamTextcallers that iteratefullStream(or use theonErrorhook) automatically receive the new diagnostic. No code change required — they just stop seeing silent empty turns.
Compatibility
- Peer dependency
acpxis now>=0.8.0(upstream contract changes that ship the new permission callback, session-options threading, andgetStatus().modelsshape). - Public API: three new settings (
onPermissionRequest,sessionOptions) and one new method (getModels()); no removals or renames. - Re-exports added:
AcpPermissionRequest,AcpPermissionDecision,SessionAgentOptions,SystemPromptOption,AcpRuntimeSessionModels,AcpRuntimeStatus.
Internal
EventTranslator.errorPartIfFailed(result)new helper sitting alongsidefinish().createTranslatingStreamcalls it betweenflush()andfinish().- Real-codex e2e contract test for the permission callback round-trip (gated behind
SMOKE_AGENTS=codex). - Unit + integration coverage across all four PRs uses the existing
MockAcpRuntimeharness; full provider suite is 186 pass / 0 fail.
⚠️ Alpha software
Both this package and its underlying runtime (acpx) are pre-1.0. Public APIs may change in any minor release. Pin exact versions. Bug reports + design feedback welcome on DaniAkash/acpx.
Full Changelog: acpx-ai-provider-v0.0.4...acpx-ai-provider-v0.0.6
acp-probe-v0.0.2
Features
-
New
result.modelConfigfield — the setable model list (#31, #33). ACP describes an agent's models in two different protocol surfaces, and they don't always agree.session/new.models.availableModels[]is the declarative list (best for display);configOptions[id=model].options[]is the contractsetConfigOption('model', X)will accept. On codex-acp 0.12.0 the two are entirely disjoint — 24 compound<model>/<effort>ids inavailableModelsvs 6 bare model names inconfigOptions[model]— and downstream pickers built onavailableModelssilently fail whensetConfigOptionrejects the value (the next prompt finishes withfinishReason: "error"and no error frame). The new derived pointer makes the setable list a one-hop typed lookup:const result = await probeAgent({ command: 'npx @zed-industries/codex-acp@^0.12.0' }) if (result.modelConfig) { for (const id of result.modelConfig.values) { // Every id here is a valid setConfigOption('model', X) input. } }
modelConfigisnullfor agents that don't exposeconfigOptions[id=model]at all (e.g. gemini-cli, wheresetConfigOptionitself returns-32601 method not found). Mirrors the existingresult.reasoningpattern.
Documentation
- New "Picking the right model list" section in the package README with three worked code examples (display browser / mutable picker / rich mutable picker) and a quick-reference table. The "Result shape" overview also lists
modelConfigwith a one-line description.
Internal
- New
EventTranslator-stylederiveModelConfig(configOptions)helper next to the existingderiveReasoningin_internal/normalize.ts. - Unit, integration, and real-agent e2e coverage added across all three fixture agents (claude / codex / gemini). The codex e2e additionally asserts the bare-id invariant — no
/-suffixed values inmodelConfig.valuesagainst the live agent.
Compatibility
- Purely additive.
result.modelsis unchanged (still byte-faithful toavailableModels[]);result.configOptionsis unchanged. No existing consumer breaks. ModelConfigInfo.configIdis typed as the string literal'model'rather thanstring, so consumers that destructure it get type-safety matching the docs.
⚠️ Alpha software
The public API may change between minor versions until 1.0.0. Pin exact versions. Bug reports + design feedback welcome — open an issue on DaniAkash/acpx.
Full Changelog: acp-probe-v0.0.1...acp-probe-v0.0.2
acp-probe-v0.0.1
First public release. acp-probe discovers what an Agent Client Protocol agent advertises — models, modes, reasoning effort, prompt capabilities (image / audio / embedded context), MCP transports, session controls, auth methods — via a single typed probeAgent({ command }) call.
Built for hosts (IDE plugins, desktop apps, internal tools) that need to render settings UIs and pickers for any ACP-compatible agent, built-in or custom, without sending a real prompt and without consuming any LLM tokens. The probe runs the ACP initialize + session/new handshake, optionally tests session/set_config_option to detect agents like gemini-cli that don't implement it, then tears the agent down. See the package README for the full API.
⚠️ Alpha software
The public API may change between minor versions until 1.0.0. Pin exact versions. Bug reports + design feedback welcome — open an issue on DaniAkash/acpx.
agent-skills-manager-v0.0.1
First public release. agent-skills-manager is a programmatic workspace + agent-link manager for skills that follow the agentskills.io specification.
Built for hosts (IDE plugins, desktop apps, internal tools) that need to manage agent skills on a user's behalf without shelling out to a CLI. See the package README for the full API.
⚠️ Alpha software
The public API may change between minor versions until 1.0.0. Pin exact versions. Bug reports + design feedback welcome — open an issue on DaniAkash/acpx.
acpx-ai-provider-v0.0.4
Fixes
provider.prepare()no longer consumes session freshness (#25, #26). Callingprovider.prepare()before the firststreamTextused to flip the internal freshness flag, so the firstdoStreamwould run incontinuationmode and silently drop all but the latest user message from a multi-turnmessagesarray. Hosts usingprepare()as an early-failure sync point combined with seeded conversation history now get the full transcript flattened onto the wire as expected. Behavior on subsequent turns (continuation mode → only latest user message) is unchanged.
Internal
- Freshness check-and-set is now atomic inside
markSessionKeyUsed, so two concurrentdoStreamcalls on the samesessionKeycan no longer both observeisFresh: trueand duplicate seeded context to the agent. - New integration tests under
test/integration/prepare-freshness.test.tscoverprepare() + multi-turn, second-turn continuation, and the concurrent-doStreamrace.
Compatibility
- No public API additions or removals.
AcpxProvider.markSessionKeyUsed(sessionKey)exists on the class but is internal — external consumers should keep going throughprovider.languageModel(...)andstreamText/generateText.
Full Changelog: v0.0.3...acpx-ai-provider-v0.0.4