Uh oh!
There was an error while loading. Please reload this page.
feat(client,spec)!: express the AI surface that exists, retire the declarations that never did (#3718) - #3840
Merged
Conversation
…clarations that never did (#3718) `client.ai` and the real AI service were disjoint sets. The namespace held `nlq` / `suggest` / `insights`, whose URLs no repo has ever mounted (deleted in v17), while `service-ai` mounted 12 routes the SDK could not reach at all. v17 closed the first half. This closes the second. The SDK now expresses every AI route meant to be tenant surface: ai.chat / ai.chatStream POST /api/v1/ai/chat (JSON | UI Message Stream) ai.complete POST /api/v1/ai/complete ai.models GET /api/v1/ai/models ai.conversations.{create,list,get,update,delete,addMessage} `chat` forces `stream: false` — the endpoint streams by default, so the JSON method has to say so or `res.json()` throws on the first frame. `chatStream` returns a promise for an async iterable rather than being an async generator, so the request is issued (and an HTTP error thrown) on call, not on first iteration. Its SSE parser reads line-by-line, not frame-by-frame: the encoder also emits single-`\n` `g:` reasoning lines that a `\n\n` split would glue onto the next event. The spec's dead AI declarations go with them — none had an implementation anywhere, and none had a runtime consumer: - Ai{Nlq,Suggest,Insights}{Request,Response}[Schema] → the wire shapes of the routes that exist (AiChat*, AiStreamChunk, AiCompleteRequest, AiModelsResponse, AiConversation, AiMessage, {Create,List,Update}Ai*). The six retired JSON Schemas leave json-schema.manifest.json deliberately (#2978). - DEFAULT_AI_ROUTES → deleted; getDefaultRouteRegistrations() returns 8 groups. Re-declaring the real routes there would recreate the same illusion: they are mounted from another repo, and this table has no runtime consumer. - AiProtocol (aiNlq? / aiSuggest? / aiInsights?) → deleted. Nothing implemented it, nothing dispatched through it. The real server contract is IAIService + IAIConversationService in @objectstack/spec/contracts. Guard: /api/v1/ai/ becomes a bounded prefix exemption in the capstone (#3642) alongside the control plane — only `ai.*` may use it, and the namespace must still be reaching it. The reachability check lives where the routes are, in cloud's ai-route-ledger.conformance.test.ts, which reads buildAIRoutes() and drives this SDK against it. The wildcard-only bound stays 0: these URLs never touch the `* /ai/**` row that certified three dead methods for years. The four replaced client tests mocked fetch and asserted the URL the client BUILT, never that anything answered it. The new ones assert only what this repo can honestly know — verb, path, and the body decisions the SDK makes for the caller — and leave "does it resolve" to the ledger next to the routes. Refs #3718, #3708, #3642, #3563 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 3 package(s): 114 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
…e wire field name (#3718) `check:role-word` (ADR-0090 D3) flagged 2 new occurrences in content/docs/api/client-sdk.mdx: the `{ role: 'user', content: … }` messages in the new `ai.chat` / `ai.conversations.addMessage` examples. Baselined rather than reworded, for the reason the check itself names as legitimate: this is a genuine external boundary. `role` is the field name in the Vercel AI SDK `ModelMessage` shape the AI routes accept — the same class as the better-auth and ARIA occurrences already in the baseline. Renaming it in the example would document a payload the server rejects. Nothing else moved: the baseline gains exactly one line. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WJX6GnuNix7HisBc92THMN
…tirement (#3718) `check:api-surface` is the ratchet that makes a removed export impossible to ship by accident: 14 breaking (removed/narrowed), 22 added. Both halves are this change and nothing else — removed Ai{Nlq,Suggest,Insights}{Request,Response}[Schema] (12), DEFAULT_AI_ROUTES, AiProtocol added AiChat{Request,Response}[Schema], AiStreamChunk[Schema], AiCompleteRequest[Schema], AiModelsResponse[Schema], AiConversation[Schema], AiMessage[Schema], {Create,List,Update}AiConversation*[Schema] (22) The removals are already declared as a `major` on @objectstack/spec in the changeset, which is what the gate asks for. 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 09:43
Uh oh!
There was an error while loading. Please reload this page.
This was referenced Jul 28, 2026
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 freeto 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.
Closes the second half of #3718. Recommendation there was 2 + 3; v17 (#3736) did option 2's client half, this does the rest of 2 and all of 3.
The finding, restated
client.aimethodsnlq,suggest,insights)The two sets were disjoint. v17 deleted the dead methods; this expresses the surface
service-aireally mounts.What the SDK gains
ai.chat(request)POST /api/v1/ai/chat— sendsstream: false, so the JSON mode is what you getai.chatStream(request)POST /api/v1/ai/chat—AsyncIterableof UI Message Stream framesai.complete(request)POST /api/v1/ai/completeai.models()GET /api/v1/ai/models(ADR-0028 plan-filtered picker list)ai.conversations.create/list/get/update/delete/addMessage/api/v1/ai/conversationsroutesTwo details worth review:
chatforcesstream: false. The endpoint streams unless told otherwise, so a "JSON" method that stayed silent would handres.json()an SSE body.chatStreamreturnsPromise<AsyncIterable<…>>, not an async generator. The request is issued — and an HTTP error thrown — when you call it, not when you first iterate. Its parser reads the body line by line, not frame by frame: the encoder also emits single-\ng:reasoning lines that a\n\nsplit would glue onto the next event.The two routes left unexpressed are deliberate and noted in the ledger:
GET /statusandGET /effective-modelare operator diagnostics (server-only), andPOST /chat/streamis the generic-SSE twin of/chatwithout the tool loop or persistence — a second near-identical streaming method would buy a consumer nothing but a wrong default.Breaking: the spec's dead AI declarations are retired
None had an implementation anywhere, and none had a runtime consumer.
Ai{Nlq,Suggest,Insights}{Request,Response}[Schema]→ replaced by the wire shapes of the real routes (AiChat*,AiStreamChunk,AiCompleteRequest,AiModelsResponse,AiConversation,AiMessage,{Create,List,Update}AiConversation*). The six retired JSON Schemas leavejson-schema.manifest.jsondeliberately (gen:schema silently drops PageTabsProps since #2967 — references regen would delete real docs #2978).DEFAULT_AI_ROUTES→ deleted;getDefaultRouteRegistrations()returns 8 groups. Re-declaring the real routes there would recreate the same illusion — they are mounted from another repo, and this table has no runtime consumer at all.AiProtocol(aiNlq?/aiSuggest?/aiInsights?) → deleted. Nothing implemented it, nothing dispatched through it. The real server contract isIAIService+IAIConversationServicein@objectstack/spec/contracts.Also corrected: a
manifest.zod.tsJSDoc example that used['aiNlq', 'aiChat']as its illustration of protocol method names.The guard — why a prefix exemption is not a wave-through
/api/v1/ai/becomes a bounded prefix exemption in the capstone (#3642), alongside the control plane, and bounded from both ends: onlyai.*may use it, and the namespace must still be reaching it. The reachability check lives where the routes are — cloud'spackages/service-ai/src/ai-route-ledger.conformance.test.tsreads the tablebuildAIRoutes()returns and drives this SDK against it, so anai.*URL that stops resolving fails a test in the repo that mounts it (objectstack-ai/cloud#…, branchclaude/ai-namespace-unreachable-0qbyx2).The wildcard-only bound stays 0: these URLs are checked before
matches(), so they never touch the* /ai/**row that certified three dead methods for years.Tests
The four replaced client tests mocked
fetchand asserted the URL the client built, never that anything answered it — they passed for years against endpoints that did not exist. The new ones assert only what this repo can honestly know (verb, path, and the body decisions the SDK makes for the caller:stream: false, the 204 ondelete, SSE frame parsing) and leave "does it resolve" to the ledger next to the routes.Verified locally:
spec6745/6745,client178/178,runtime665/665, fullpnpm buildgreen. The cloud half was run against this branch's build — 18/18 in the ledger conformance file, 581/581 inservice-ai— and mutation-checked: repointingai.modelsat/modelzfails exactly two tests there, naming the method and the URL it built.Cross-repo ordering
Land this first, then bump
.objectstack-shain the cloud PR. Cloud's pin drives staging deploys, so it must move to amaincommit, never a feature branch.Not in this PR
The console's developer API-discovery panel (
objectui apps/console/src/pages/developer/hooks/useApiDiscovery.ts) still lists/nlq,/suggest,/insightswith "try it" bodies — three endpoints that always 404.objectuiis outside this session's repo scope; it needs the same trim plus the real routes, and is the last piece of #3718.🤖 Generated with Claude Code
https://claude.ai/code/session_01WJX6GnuNix7HisBc92THMN
Generated by Claude Code