feat(client,spec): ai.agents.* and ai.pendingActions.* — the AI routes the SDK could not reach (#3718) - #3888
Merged
Conversation
…outes the SDK could not reach (#3718) #3718 deleted three `client.ai.*` methods whose URLs no route had ever mounted, then expressed the surface that does exist. It expressed ONE builder's worth of it. `service-ai` mounts seven; widening its ledger (objectstack-ai/cloud#903) counted ten routes the SDK cannot reach, nine of which had never been counted. This closes the six with the strongest evidence: objectui already ships product on them, over URLs it hand-builds because there is nothing to call. ai.agents — `/ai/chat` talks to the default agent; these talk to a named one. list() — agents this CALLER may chat with; the route filters by permission (ADR-0049), so empty is a legitimate answer, not an error to retry chat(name, req) — forces `stream: false`, same reason `ai.chat` does: the route streams by default chatStream(name, req) — same route, streaming mode. One route, two methods, mirroring chat/chatStream rather than inventing a third shape ai.pendingActions — the HITL approval queue an embedding app must render. list(options?) — status/conversationId/limit ONLY. The service also accepts objectName; the route never forwards it, so typing it would offer a filter that silently does nothing get(id) approve(id) — approves AND executes. `{status:'failed'}` comes back on HTTP 200: the approval succeeded, the execution did not. Reading only `res.ok` reports a failed write as a success reject(id, reason?) — executes nothing Typed from what the routes RETURN, not from what a client might like them to — the failure #3718 exists to punish. Pending actions are the persisted row, snake_case on the wire because that is what it is; agent rows require `capabilities` because that object is what tells a UI what to render. The capstone's `/api/v1/ai/` prefix exemption says the evidence lives across the repo boundary. It does, and it now reaches these: cloud's ledger drives every `ai.*` method against the tables its builders really return, and since #903 that means all seven. Comment updated there — it still described the one-builder version, under which `buildAgentRoutes()` and `buildPendingActionRoutes()` were invisible. Verification: client 184/184, spec 6836/6836 (24 in protocol.test.ts, extended with the new shapes incl. the negative cases — an agent row without capabilities, a non-enum status filter, `approve` yielding "rejected"). Mutation-checked: pointing `ai.pendingActions.get` off the AI prefix fails the capstone by name, proving the new methods are really in its sweep rather than silently absent. Generated artifacts regenerated: api-surface.json (+20 exports, 0 breaking) and json-schema.manifest.json. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WJX6GnuNix7HisBc92THMN
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 2 package(s): 108 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
… new ai methods `check:docs` regenerates `content/docs/references` from the schemas and fails when the committed copy drifts. The seven new AI schemas produce new reference entries, so that check went red on the first push — the artifact half of the same commit, not a separate defect. Also updates the hand-written SDK doc, which enumerates the whole `client.ai.*` surface and carried the #3718 history note. Leaving it at ten methods while shipping seventeen would be the exact drift this line of work keeps closing — and the docs-drift check flagged `content/docs/api/client-sdk.mdx` as affected, which on inspection it genuinely was (the other 107 files it lists are package-level fan-out, unrelated to this diff). The added block documents the two things a caller gets wrong by default: an access-filtered agent catalog is legitimately EMPTY for a seat-less user, and `pendingActions.approve` returns `{ status: 'failed' }` on HTTP 200 when the tool fails after approval — reading only `res.ok` calls that a success. check:docs / check:api-surface / check:spec-changes / check:upgrade-guide all pass; 250 generated files in sync. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WJX6GnuNix7HisBc92THMN
os-zhuang
marked this pull request as ready for review
July 28, 2026 14:46
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
#3718 deleted three
client.ai.*methods whose URLs no route had ever mounted, then expressed the surface that does exist. It expressed one builder's worth of it.service-aimounts seven. Widening its ledger (objectstack-ai/cloud#903) counted ten routes the SDK cannot reach — nine of which had simply never been counted, because nothing enumerated the other six builders.This closes the six with the strongest evidence:
objectuialready ships product on them, over URLs it builds by hand because there is nothing to call.ai.*methodsai.agents/ai/chattalks to the environment's default agent; these talk to one you name.agents.list()— the agents this caller may chat with. The route filters by the caller's permissions (ADR-0049), so an empty list is a legitimate answer for a seat-less user, not an error to retry.agents.chat(name, request)— forcesstream: false, for the same reasonai.chatdoes: the route streams by default, so leaving the flag to the caller makes the JSON path the one you have to remember.agents.chatStream(name, request)— same route, streaming mode. One route, two methods, mirroringai.chat/ai.chatStreamrather than inventing a third shape for the same endpoint.ai.pendingActionsThe human-in-the-loop approval queue. When a tool call needs a human decision the turn parks an action instead of executing it, and an app embedding the chat has to render and resolve that queue.
list(options?)—status,conversationId,limitonly.AIService.listPendingActionsalso acceptsobjectName, but the route never forwards it; typing it here would offer a filter that silently does nothing.get(id)approve(id)— approves and executes. Check the returnedstatus: a tool that fails after approval comes back{ status: 'failed', error }on HTTP 200, because the approval succeeded even though the execution did not. Code reading onlyres.okreports a failed write as a success.reject(id, reason?)— executes nothing.Reads and decisions are separately permissioned server-side (
ai:readvsai:approve), so a caller that can list the queue may still be refused on approve. One does not imply the other.Typed from what the routes return
Not from what a client might like them to — the failure #3718 exists to punish. Every shape here was read off the handler in
cloud:snake_caseon the wire because that is what it is, not a style slip.capabilities— that object is what tells a UI which affordances to render, so a row without it is not a row this route returns.Why the capstone still holds
client-url-conformance.test.tsexempts/api/v1/ai/by prefix and says the evidence lives on the other side of the repo boundary. It does — and it now reaches these: cloud's ledger drives everyai.*method against the tables its builders really return, and since #903 that means all seven.The comment there still described the one-builder version, under which
buildAgentRoutes()andbuildPendingActionRoutes()were invisible. Updated, because a comment that outlives its fact is how an exemption quietly becomes a wave-through — which is the original #3718 bug, not a hypothetical.Verification
protocol.test.tsextended with the new shapes including the negative cases: an agent row withoutcapabilities, a non-enumstatusfilter,approveyielding"rejected"(that is the other route).ai.pendingActions.getoff the AI prefix fails the capstone by name — proving the new methods are genuinely in its sweep rather than silently absent, which is the way a guard like this fails.api-surface.json(+20 exports, 0 breaking) andjson-schema.manifest.json.Follow-up, not in this PR
cloud's ledger flips those six rows
gap → sdkand drops the ratchets to 0 — it needs this merged and the pin bumped first, the same sequencing as #3840 → cloud#902.objectuithen replaces its hand-built URLs withclient.ai.*; that one can be scheduled independently.🤖 Generated with Claude Code
https://claude.ai/code/session_01WJX6GnuNix7HisBc92THMN
Generated by Claude Code