Skip to content

feat: client-tools — frontend-declared, frontend-executed tools (foundation)#642

Open
blove wants to merge 28 commits into
mainfrom
claude/client-tools-spec
Open

feat: client-tools — frontend-declared, frontend-executed tools (foundation)#642
blove wants to merge 28 commits into
mainfrom
claude/client-tools-spec

Conversation

@blove

@blove blove commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Client Tools — frontend-declared, frontend-executed tools

Tools declared in the Angular app (name + description + a Standard Schema) that the model calls and the browser executes — as an async function, a rendered view, or an interactive ask (HITL) component. Per the approved spec (docs/superpowers/specs/2026-06-08-client-tools-design.md).

Status: feature-complete and green. One gate before merge — see "Before merging".

What's implemented (all green; TDD + reviewed; two examples e2e-verified)

  • @threadplane/renderRenderViewEntry.schema/description, RenderResultEvent, element-scoped injectRenderHost() (set/emit/result); a2ui catalog migrated off the legacy a2ui:datamodel: string protocol (removed).
  • @threadplane/chattools() + action/view/ask; Standard-Schema→JSON-Schema (Zod via zod/v4); Agent.clientTools; function-tool executor; ClientToolsCoordinator; wired into ChatComponent ([clientTools]).
  • @threadplane/ag-uiclientTools over native RunAgentInput.tools + addMessage(ToolMessage)/re-run.
  • @threadplane/langgraphclientTools via input.client_tools + ToolMessage re-run on the same thread.
  • packages/threadplane-client-tools (Python) — LangGraph middleware (bind client stubs from state["tools"], route client calls → END). Staged workflow_dispatch-only PyPI publish (dry-run default; no live publish).
  • Examplescockpit/ag-ui/client-tools and cockpit/langgraph/client-tools, each demoing function/view/ask, with docs + registry/ports/manifest wiring.

Verification

  • Units: render 90 · chat 832 · ag-ui +21 · langgraph +25 · python middleware 35 — all green; 4 libs lint + build clean.
  • e2e: ag-ui 3/3, langgraph 3/3 (function/view/ask) via aimock replay.
  • The AG-UI example was driven live in a real browser across all three flows (this is how the executor status==='complete' bug — 7a15b15a — was caught).

Before merging (your calls)

  1. Publish threadplane-client-tools to PyPI (the staged workflow_dispatch). The example backends consume it via a local path source, which is fine for the monorepo + e2e but won't resolve in the deployed (Railway/LangGraph) builds until it's on PyPI. Merging before publishing would land example backends that can't deploy.
  2. Retarget/rebase onto main. Base is currently claude/ag-ui-example-guides (for a clean diff).

The unrelated cockpit next.config.ts turbopack change is intentionally not included.

🤖 Generated with Claude Code

@vercel

vercel Bot commented Jun 9, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
threadplane Ready Ready Preview, Comment Jun 10, 2026 3:03am

Request Review

blove and others added 23 commits June 9, 2026 14:20
Design for a new client-tools capability: tools declared in the Angular app
(name + description + Standard Schema) that the model calls, routed back to the
client to execute as an async function, a rendered view, or an interactive
(HITL) component. Covers the render-lib component contract (schema/description
metadata + typed injectRenderHost result channel), chat-lib tools()/action/
view/ask + executor + Agent.clientTools, both adapters unified on end+re-run
(no interrupt()), and a published Python LangGraph middleware. TS LangGraph.js
middleware deferred to a fast-follow.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Decompose client-tools into six dependency-ordered, independently-testable
plans. Plan 01 (render foundation) is full TDD detail: RenderViewEntry
schema/description, RenderResultEvent, and an element-scoped injectRenderHost()
(set/emit/result) added alongside the legacy emit so each step stays green;
the a2ui:datamodel: removal + catalog migration is isolated in plan 01b.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Task 1 now vendors the Standard Schema type instead of npm-installing
@standard-schema/spec, per the repo rule against regenerating package-lock.json
on macOS (drops Linux @next/swc-* bindings, breaks CI).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…space lint

The upstream Standard Schema vendoring snippet uses a TS namespace, which this
repo forbids (@typescript-eslint/no-namespace, enforced as error). Flatten the
nested types to top-level StandardSchema* aliases; the StandardSchemaV1
interface (the only consumed symbol so far) is unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…l string protocol)

- Rewrite emitBinding() to accept RenderHost and call host.set(path, value)
  with the typed value instead of emitting a2ui:datamodel: strings
- Update 5 catalog components (text-field, check-box, slider, date-time-input,
  multiple-choice): inject injectRenderHost(), remove emit input, swap call sites
- multiple-choice onCheckChange now passes the actual string[] array to host.set
  instead of JSON.stringify (no more string coercion needed)
- Update all 6 specs (emit-binding + 5 component specs) to assert against
  host.set([path, value]) pairs with typed values

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Now that all catalog components write via RenderHost.set(), the legacy
a2ui:datamodel: string intercept in emitFn is dead code. Remove:
- A2UI_DATAMODEL_PREFIX constant
- applyDatamodelWrite() method (path/value splitting + store write)
- coerceValue() helper (no longer needed; host.set receives typed values)
- The if (event.startsWith(PREFIX)) branch in emitFn

emitFn now simply delegates all events to invokeHandlers().

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace references to the old magic-string emit protocol with the new
injectRenderHost().set() description.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…N-schema derivation)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds the ClientToolsCapability interface (pending signal + setCatalog/resolve),
wires it as optional onto the Agent contract, and implements the pure execute
helpers (validateArgs / executeFunctionTool) plus startClientToolExecutor which
auto-dispatches pending function-tool calls via an Angular effect while leaving
view/ask tools for the rendering layer. Covered by TDD specs (execute.spec.ts
and client-tool-executor.spec.ts) — all pass, 0 lint errors, build green.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…essage/re-run)

Adds ClientToolsCapability to the AgUiAgent via a testable createClientToolsCapability
factory. Threads the catalog into all runAgent calls (submit, resume, regenerate).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… ToolMessage re-run)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…aged publish)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A client tool call is marked status='complete' once its args finish streaming,
but it still has no result and needs the browser to execute it. The executor's
`tc.status === 'complete'` guard wrongly skipped these, so function tools never
ran end-to-end (caught via the live ag-ui/client-tools example). Drop the guard
(pending already excludes resolved/result-present calls; inFlight prevents
double-dispatch). Regression test now uses status='complete'.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…2e + wiring

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@blove blove force-pushed the claude/client-tools-spec branch from 0b5bf50 to 5afb3c3 Compare June 9, 2026 21:25
@blove blove changed the base branch from claude/ag-ui-example-guides to main June 9, 2026 21:25
…ublish (cacheplane org) + name reservation

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…-client-tools

Switch both example backends off the local path source onto the published
PyPI package (threadplane-client-tools>=0.0.1, resolved to 0.0.1). Regenerated
uv.lock + requirements.txt. ag-ui e2e re-verified 3/3 green against the
PyPI-sourced backend; deploys now resolve the middleware.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…-ui/core peer dep

Cockpit example apps compile the lib sources with strict:false, where the
ClientToolResult / StandardSchemaResult discriminated-union narrowing didn't
narrow — failing every example's production build (and cascading to ~all
cockpit/chat e2e). Read those fields via explicit casts (runtime-identical;
public discriminated-union types unchanged, so strict consumers still narrow).
Also add @ag-ui/core to @threadplane/ag-ui peerDependencies (nx dependency-checks).

Verified: ag-ui tool-views + langgraph streaming example production builds now
green; ag-ui lint clean; chat/ag-ui/langgraph tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant