Skip to content

Repository files navigation

agent-kit

A small family of MIT packages for driving a local agent (Codex App Server now; Kimi/Qwen/Gemini/Grok over ACP next) and presenting a consistent chat/tool experience behind one neutral schema. Extracted from PwrSnap and PwrAgnt so downstream apps consume it instead of re-implementing the transport, discovery, turn loop, and chat surface.

Packages

Package Role
@pwrdrvr/agent-core Neutral message/thread/tool-call schema + injected interfaces (Logger, ThreadStore, Clock, OpenExternal). The dependency hub. Zero runtime deps.
@pwrdrvr/agent-transport JSON-RPC 2.0 core + stdio transport. (planned)
@pwrdrvr/codex-discovery Codex binary discovery, version compare, auth profiles, login/relogin. (planned)
@pwrdrvr/agent-client Codex App Server adapter: thread client + chat controller + defineTool, normalizing into agent-core. (planned)
@pwrdrvr/agent-acp ACP adapter for Kimi/Qwen/Gemini/Grok, normalizing into agent-core. (planned)
@pwrdrvr/agent-chat-react Presentational chat UI (React peer dep). (planned)

The Codex protocol types live in a separate repo, @pwrdrvr/codex-app-server-protocol, consumed as a normal versioned dependency.

Codex dynamic tools

Codex App Server protocol 0.144.0 represents dynamic tools as a discriminated union. Use buildToolCatalog() for the dynamicTools/tools value sent when a thread starts; it emits unnamespaced tools as top-level { type: "function" } objects and groups tools with the same ToolSpec.namespace into one { type: "namespace", tools: [...] } object.

import {
  buildToolCatalog,
  defineTool,
  toDynamicToolFunctionSpec
} from "@pwrdrvr/agent-client";

const tools = [
  defineTool({
    namespace: "capture_library",
    name: "list",
    description: "List captures.",
    argsSchema,
    deferLoading: true,
    dispatch
  })
];

const codexDynamicTools = buildToolCatalog(tools);

// Flat consumers such as an MCP bridge can retain the owner separately.
const mcpTools = tools.map((tool) => ({
  namespace: tool.namespace,
  ...toDynamicToolFunctionSpec(tool)
}));

Do not build the Codex catalog with tools.map(toDynamicToolSpec): that compatibility helper can only produce a one-tool namespace at a time and cannot group a namespaced catalog.

ACP MCP servers

@pwrdrvr/agent-acp accepts the ACP v1 stdio, streamable HTTP, and HTTP+SSE MCP server shapes. Stdio callers remain source-compatible: args may be omitted and env may remain a Record<string, string>. HTTP/SSE headers and stdio env may also be supplied as ACP-native { name, value }[] collections; the package always serializes the exact arrays required by the pinned protocol library.

import type { AcpMcpServerConfig } from "@pwrdrvr/agent-acp";

const threadMcpServers: AcpMcpServerConfig[] = [
  {
    name: "local-tools",
    command: "/opt/local-tools",
    env: { SOCKET_PATH: "/tmp/local-tools.sock" }
  },
  {
    name: "remote-tools",
    type: "http",
    url: "https://mcp.example.com/rpc",
    headers: [{ name: "Authorization", value: `Bearer ${token}` }]
  }
];

await client.connect();
if (client.supportsHttpMcp()) {
  await client.reopenThread({ threadId, mcpServers: threadMcpServers });
}

Optional HTTP and SSE transports are rejected unless the agent explicitly advertises them in initialize.agentCapabilities.mcpCapabilities. Hosts can gate UI/configuration with supportsHttpMcp() / supportsSseMcp() or the exported acpRuntimeSupportsHttpMcp() / acpRuntimeSupportsSseMcp() helpers. Per-thread lists are accepted by startThreadNative, reopenThread, and loadThreadNative; a pooled client keeps the wire payload and permission-policy server names isolated to that session. ACP v1 in the pinned library has no separate session/resume method, so the package does not invent one.

PwrAgent and PwrSnap consumers can replace local stdio/HTTP/SSE unions with AcpMcpServerConfig, keep their existing { name, value }[] headers unchanged, and pass the selected list at the per-thread lifecycle call. Credentials belong only in env/headers; lifecycle errors redact those values and remote URLs.

The design and sequencing live in docs/plans.

Develop

nvm use            # v24.14.1
pnpm install
pnpm build         # tsup per package
pnpm typecheck
pnpm test          # vitest
pnpm lint:licenses # MIT-only gate
pnpm lint:deps     # single-version guard (zod)

zod: keep it to one copy

@pwrdrvr/agent-acp speaks ACP through @agentclientprotocol/sdk, which declares zod@^3.25 || ^4 as a peer from SDK 1.3 onward. First-party packages here (@pwrdrvr/agent-client, and consumers like PwrAgent / PwrSnap) use zod@^4. A single resolved copy avoids unnecessary bundle weight and the cross-instance footgun where a schema built by one copy fails instanceof against the other.

agent-acp imports nothing from zod directly. This repo resolves the SDK peer and the first-party packages to one zod 4 copy with a root override:

// package.json
"pnpm": { "overrides": { "zod": "^4.0.0" } }

This repo ships that override and a CI guard (pnpm lint:deps, scripts/check-single-version-deps.mjs) that fails if a second zod ever creeps back in. The SDK peer works with a consumer's existing zod 4 installation; add an install-root override only when your package manager resolves more than one compatible copy.

Release

Versioning is per-package via changesets:

pnpm changeset            # record a bump

On merge to main, the Release workflow opens a "Version Packages" PR; merging that publishes the changed packages to npm via OIDC trusted publishing (tokenless, with provenance, gated behind the npm-publish environment). Each package needs a one-time bootstrap publish + trusted-publisher config the first time — same flow as the protocol repo.

License

MIT © PwrDrvr LLC.

About

No description, website, or topics provided.

Resources

Security policy

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages