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 —

agents — packages/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 actions — packages/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.

Activity

  1. xuyushun441-sys commented on Aug 3, 2026

    @xuyushun441-sys
    Contributor

    维护者裁决(2026-08-03)

    决策记录批准生效:UI 包不引入任何 @objectstack/* 运行时依赖,保持 data-objectstack 单一桥接;URL 漂移保护以 cloud 侧账本 + conformance 为第一份真相。

    增量项不采纳 type-only devDependency——那会把「@object-ui/* 不依赖 @objectstack/*」从可机械执行的绝对规则变成带例外的条件规则,而 workspace 边界守卫(#3207 那类)恰恰依赖规则绝对。类型镜像的漂移改用边界外 parity 守卫看住,已立 #3293。

    Revisit 条件照本单记录不变。决策完成,关闭本单。


    Generated by Claude Code


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions