Skip to content

Decision: objectui keeps hand-built /api/v1/ai/* URLs rather than adopting client.ai.* (framework#3718 follow-up) #2929

Description

@os-zhuang

Recording a decision so the next person does not re-derive it — and so the reasoning is on file if the trade-off changes.

The context

objectstack-ai/objectstack#3888 added ai.agents.* and ai.pendingActions.* to @objectstack/client, closing six of the ten AI routes the SDK could not reach. objectstack-ai/cloud#909 flipped the matching ledger rows to sdk and drove two gap ratchets to zero.

The obvious next step looked like: replace objectui's hand-built AI URLs with client.ai.*. Six call sites —

agentspackages/plugin-chatbot/src/useAgents.ts, agentAliases.ts, packages/app-shell/src/hooks/useAiSurface.ts, packages/app-shell/src/console/ai/AiChatPage.tsx, plugin-chatbot's useObjectChat
pending actionspackages/plugin-chatbot/src/usePendingActions.ts, ChatbotEnhanced.tsx, apps/console/src/pages/system/AiPendingActionsPage.tsx

Why we are not doing it

Three facts, found while scoping it:

  1. No objectui package depends on @objectstack/client — or on any @objectstack/* package. plugin-chatbot's 19 dependencies are all @object-ui/* and third-party. data-objectstack is the one adapter that bridges to the SDK, and it is a separate package precisely so the UI packages do not take that dependency. Adopting client.ai.* in the hooks would make this the first framework runtime dependency in the UI layer — inverting a deliberate boundary, not tidying one.

  2. usePendingActions.ts documents the constraint in its own header: "Pure React + fetch — no extra deps so it stays inside plugin-chatbot's tiny bundle." The package ships at 179.93 KB / 42.67 KB gzipped today.

  3. The bridge route is not cheap either. plugin-chatbot's hooks receive no DataSource or adapter (no useAdapter anywhere in the package), and data-objectstack exposes no ai surface. Routing these calls through the adapter would mean adding ai there and threading a DataSource into hooks that currently take none.

What we would be buying

Mostly URL-drift protection: if objectui called client.ai.*, cloud's reachability sweep — which drives the real SDK against the routes its builders return — would transitively cover objectui's URLs too.

That is real, but it is the second copy of a guarantee. The routes themselves are already audited on the cloud side (packages/service-ai/src/ai-route-ledger.ts + its conformance test, objectstack-ai/cloud#903/#906), and the class of bug that started this work — a URL nothing mounts — is caught there for every ai.* method regardless of who calls it.

What is still worth doing, cheaply

usePendingActions.ts hand-copies two wire types (PendingActionStatus, PendingActionRow) that the framework now exports as AiPendingActionStatus / AiPendingAction. A type-only import would delete the mirror at zero bundle cost (types are erased), though it would still be the first @objectstack/* entry in the package — as a devDependency.

Not doing that here either; noting it as the cheapest available increment if the mirror ever drifts.

Revisit if

  • objectui's UI packages take an SDK dependency for some other reason — then this becomes free.
  • The pending-action or agent wire shapes change and the hand-copied types drift (the mirror above is the early-warning canary).
  • data-objectstack grows an ai surface for unrelated reasons — then the bridge route costs only the threading.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions