Skip to content

feat: @catalyst/cloud-ai — multi-provider AI integration with useAI hook#288

Open
mayankmahavar1mg wants to merge 17 commits into
mainfrom
feature/WebAi
Open

feat: @catalyst/cloud-ai — multi-provider AI integration with useAI hook#288
mayankmahavar1mg wants to merge 17 commits into
mainfrom
feature/WebAi

Conversation

@mayankmahavar1mg

@mayankmahavar1mg mayankmahavar1mg commented Jul 1, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • @catalyst/cloud-ai package — consolidated AI package exporting useCloudAI, useWebAI, useNativeAI, and buildAttachmentSystemPrompt. Multi-provider server route (POST /ai/:provider/stream SSE + POST /ai/:provider/generate JSON) supporting OpenAI and Gemini. All routes gated behind AI_CONFIG.enabled.
  • useAI hook (catalyst-core/hooks) — orchestrator that routes to the right sub-hook based on provider option: openai/geminiuseCloudAI, transformersuseWebAI, nativeuseNativeAI (falls back to useCloudAI if NativeBridge.isAIAvailable() is false). Stateful sessions supported via sessionMode='stateful'.
  • Gemini stateful sessions — uses Interactions API (v1beta/interactions), correct SSE event fields (event_type, parsed.interaction.id), stateful flag derived from key presence in request body.
  • Android native AIandroid/ module ships inside @catalyst/cloud-ai with AIBridge/AIBridgeCallbacks interface. NativeBridge.kt uses reflection (Class.forName + Proxy.newProxyInstance) — zero static imports, compiles without :catalyst-cloud-ai on classpath. build.gradle.kts conditionally bundles LiteRT. buildAndroid/index.js syncs the package before Gradle runs.
  • examples/useai — full test app with Cloud/Local/Native panels, streaming toggle, provider switcher, stateful session toggle, attachment component rendering, generation settings, and system prompt editor.
  • MCP knowledge base — 5 new useAI integration entries + mcp.js robustness fixes (empty DB, Node version mismatch errors).

Test plan

  • Run npm run lint — passes clean
  • Start examples/useai dev server, test cloud panel with OpenAI (stream + non-stream)
  • Test provider switch to Gemini (stream + non-stream) once quota resets
  • Toggle sessionMode=stateful — verify turn 1 returns conversationId, turn 2 maintains context
  • Test local panel with transformers provider — model download progress + inference
  • Test native panel on Android — isAIAvailable() check, fallback to cloud when bridge absent
  • Verify AI_CONFIG.enabled=false returns 403 on all /ai/* routes
  • Copy config/config.template.jsonconfig/config.json, fill API keys, confirm no keys in git

🤖 Generated with Claude Code

https://claude.ai/code/session_01A1tWZLoJqQQ7Y7sRDXUL9r


Update: OpenCL fix, native/web metrics parity, docs, CodeRabbit fixes

Root cause fix — native AI crash (Can not find OpenCL library on this device)
litertlm-android was pinned to latest.release, which silently resolved to 0.14.0 mid-session and introduced an OpenCL sampler regression (fails at first generate() call, even on Backend.CPU(), since the sampler probes OpenCL regardless of the configured backend). Confirmed via local Gradle cache timestamps (0.13.1 cached weeks earlier, 0.14.0 pulled the same day the crash appeared) and matches google-ai-edge/LiteRT#6999 and google-ai-edge/LiteRT-LM#1860. Pinned to 0.13.1 (last known-good) in packages/catalyst-cloud-ai/android/build.gradle.kts.

Native (useNativeAI) — non-streaming generate + real session metrics

  • New POST /framework-{sessionId}/ai/generate Ktor route (drains the token flow, returns one JSON response) alongside the existing SSE /ai/stream route.
  • useNativeAI.js gains a non-streaming branch, a historyRef accumulator, and real getSessionMetrics/resetSessionMetrics backed by a new aggregateNativeSessionMetrics() — replacing stubs. Native's aggregate shape has no cost/cache fields (no billing, one local model), unlike cloud.
  • Two runtime bugs fixed before this went live: the new Kotlin route wasn't installing Ktor ContentNegotiation (would've produced invalid JSON), and totalTokens was computed inconsistently between streaming (token-frame count) and non-streaming (char count) branches, which would've corrupted session aggregation across mixed-mode sessions.

Web (useWebAI) — session metrics + stateful chat (marked experimental)

  • New aggregateWebSessionMetrics() (device/dtype instead of cost, same no-billing shape reasoning as native).
  • Client-side stateful mode: since Transformers.js pipelines have no server-side session, "stateful" means the hook replays accumulated {role, content} history into each generate() call, with a locally-generated conversationId for the UI to key off.
  • Marked experimental throughout (code comments, KB, docs, UI label) — in-browser inference quality (esp. instruction-following on larger models like Llama 3) and WebGPU/WASM backend selection aren't reliable yet. This is plumbing parity, not a claim that local inference is production-ready.

examples/useai UI

  • Panel.js's Session Stats modal now has a third isLocal branch (was only isNative ? native : cloud, which would've rendered bogus $0.00000 cost tiles for web).
  • AITest.js gains a Local Session stateful toggle alongside the existing Cloud/Native ones (previously the Local panel had no sessionMode wiring at all).

Docs

  • Added a full useAI reference section to docs/content/11-API Reference/01-Hooks.md, matching the file's existing per-hook pattern (Import/Options/Returns/Usage), with dedicated subsections for what actually differs per provider: genConfig/route behavior, stateful-session mechanics, and metrics shape.
  • Corrected the MCP knowledge base's ai_integration entries, which previously described stateful sessions and metrics as if all three providers worked identically — they don't (see above).

CodeRabbit review fixes (coderabbit review --agent --base-commit e356eb9, scoped to this branch's own commits — 8 findings total, 1 in our code, 3 more picked up and fixed since they were straightforward)

  • FrameworkServerUtils.kt: /ai/generate's tokenFlow.collect had no timeout — a stalled native supplier would hang the request forever. Wrapped in a 120s withTimeout, responds 504 on timeout.
  • scripts/test-catalyst-core.sh: guarded against a silent fallback to the published catalyst-core package if npm pack doesn't produce a tarball.
  • buildAndroid/assets.js: scoped the notification meta-data removal regex to a single tag (was using a dot-all wildcard that could span tag boundaries and delete unrelated <meta-data> entries).
  • buildIos/assets.js: removed an unnecessary async from validateNotificationAsset (no awaits inside) so sync errors surface to the caller's try/catch instead of becoming unhandled promise rejections.

Security checks (semgrep, trivy vuln/secret/misconfig, npm audit) all came back clean on the files touched this session; the 7 moderate npm audit findings in examples/useai are pre-existing transitive deps (file-type, http-proxy-middleware, imagemin, sockjs, uuid, webpack-dev-server) requiring a catalyst-core major bump, unrelated to this PR's scope.

mayankmahavar1mg and others added 3 commits June 30, 2026 14:34
Consolidates AI hooks (useCloudAI, useWebAI, useNativeAI) into a single
@catalyst/cloud-ai package with a multi-provider Express router supporting
OpenAI and Gemini via streaming (SSE) and non-streaming (JSON) paths.

- Adds @catalyst/cloud-ai with route.js (CJS, server-only), useCloudAI,
  useWebAI, useNativeAI, and buildAttachmentSystemPrompt
- Adds @catalyst/ai, @catalyst/web-ai-local, @catalyst/native-ai-local stubs
- Wires useAI.js orchestrator into catalyst-core hooks with SSR guard,
  webpackIgnore, and __CATALYST_PACKAGES__.cloudAI graceful degradation
- Updates expressServer.js to mount AI routes via absolute path (bypasses
  module-alias), with graceful fallback when package is absent
- Updates base.babel.js with resolve.alias and DefinePlugin for cloud-ai
- Adds examples/useai test app and sync-packages.js dev helper
- Updates test-all.sh and test-web.sh for AI route coverage

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A1tWZLoJqQQ7Y7sRDXUL9r
…I lint fix, KB entries

- route.js: fix Gemini stateful session (Interactions API v1beta, correct event fields,
  conversationId from parsed.interaction.id, stateful flag from key presence in req.body),
  add isAIEnabled() gate on all routes, extract getAIConfig() helper, pass stateful flag
  to adapters
- NativeBridge.kt: reflection-based AIBridge loading (Class.forName + Proxy),
  no static imports of io.catalyst.nativeai.* — compiles with zero classpath dep
- android/ module: AIBridge.kt + AIBridgeCallbacks.kt interface in catalyst-cloud-ai,
  CatalystAIBridge decoupled from :app, NativeBridgeAI.kt, ServiceLoader registration
- build.gradle.kts: compileOnly always, implementation gated on ai.enabled
- settings.gradle.kts: conditionally include :catalyst-cloud-ai when android/ folder present
- buildAndroid/index.js: syncCloudAIIfEnabled copies package before Gradle runs
- useAI.js: fix lint errors — remove unused useState/useEffect imports, add
  __CATALYST_PACKAGES__ global comment, restructure to call all hooks unconditionally
  (react-compiler compliance), module-level require guarded by _pkg
- mcp_v2/mcp.js: handle empty DB file, improve error messages for version mismatch
- knowledge-base.json: add 5 useAI integration entries covering basic usage, genConfig,
  stateful sessions, attachmentComponents, and loading/progress states

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A1tWZLoJqQQ7Y7sRDXUL9r
…xample

config/config.json contains API keys and local IPs — excluded from tracking.
config.template.json provides a blank starting point for new contributors.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A1tWZLoJqQQ7Y7sRDXUL9r
@deputydev-agent

Copy link
Copy Markdown

DeputyDev will no longer review pull requests automatically.To request a review, simply comment #review on your pull request—this will trigger an on-demand review whenever you need it.

Comment thread packages/catalyst-cloud-ai/src/route.js Fixed
Comment thread packages/catalyst-cloud-ai/src/route.js Fixed
Comment thread examples/sync-packages.js Fixed
Comment thread examples/sync-packages.js Fixed
Comment thread packages/catalyst-cloud-ai/src/route.js Fixed
Comment thread packages/catalyst-cloud-ai/src/route.js Fixed
mayankmahavar1mg and others added 9 commits July 3, 2026 14:22
Normalize per-provider usage payloads (OpenAI chat + responses, Gemini
generateContent + Interactions) into a common shape so useCloudAI can
compute cost/tps/cache-savings metrics uniformly, with a token-count
fallback when a provider doesn't emit a usage frame. Stub
getSessionMetrics/resetSessionMetrics on useNativeAI/useWebAI so
callers can invoke them unconditionally across all three AI modes.

Also adds Chess and Tic-Tac-Toe demos to the useai example app and
drafts RFC/discussion docs for the agentic framework work.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…crash

latest.release silently resolved to 0.14.0 mid-session, which throws
"Can not find OpenCL library on this device" from litertlm.cc's OnError
callback on first generate() call, regardless of Backend.GPU()/CPU()
selection. 0.13.1 is the last confirmed-working version on-device.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… useCloudAI

Adds POST /ai/generate Ktor route (drains the token flow, returns one
JSON response) alongside the existing SSE /ai/stream route. useNativeAI.js
gains a non-streaming branch, a historyRef accumulator, and real
aggregateNativeSessionMetrics()-backed getSessionMetrics/resetSessionMetrics,
replacing stubs. Panel.js and TicTacToe.js updated for the native provider's
distinct session-stats shape (no cost/cache fields, since native has no
billing and one local model).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…useNativeAI

Adds aggregateWebSessionMetrics() (device/dtype instead of cost, mirroring
aggregateNativeSessionMetrics's no-billing shape), a client-side stateful
mode for useWebAI (message-history replay, since Transformers.js pipelines
have no server-side session), and a matching isLocal branch in Panel.js's
Session Stats modal. AITest.js gains a Local Session toggle alongside the
existing Cloud/Native ones.

Marked experimental throughout: in-browser inference quality and backend
selection (WebGPU/WASM) aren't reliable yet on larger models (e.g. Llama 3)
compared to the cloud/native providers — this plumbing is ready for when
that's revisited, not a claim that local inference itself is production-ready.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
# Conflicts:
#	packages/catalyst-core/mcp_v2/knowledge-base.json
#	packages/catalyst-core/src/server/expressServer.js
The ai_integration entries described stateful sessions and metrics as if
cloud/native/web all worked the same way. They don't:
- stateful sessions: cloud translates conversationId to previous_response_id
  (OpenAI) / previous_interaction_id (Gemini); native reuses a LiteRT-LM
  Conversation object keyed by conversationId; web (experimental) has no
  real session at all — it's client-side message-history replay with a
  locally-generated UUID.
- genConfig.stream / routes: cloud and native both have real stream/generate
  HTTP routes (different servers — Node vs embedded Ktor); web ignores
  genConfig.stream entirely and always streams via postMessage.
- metrics: only cloud has cost/cachedTokens/cacheSavings; native and web
  have their own leaner shapes and their own aggregate*SessionMetrics()
  functions in metrics.js.

Also added the getSessionMetrics/resetSessionMetrics/isWeb fields to the
Basic Integration entry's return-value list, which were missing entirely.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Follows the existing per-hook doc pattern (Import/Options/Returns/Usage)
used throughout this file. Documents all three providers (cloud/native/web),
the per-provider genConfig routing and stateful-session mechanics, the
differing metrics shapes, and attachment components — matching what's
actually implemented in useCloudAI/useNativeAI/useWebAI, not a generic
unified description. Notes the web/transformers provider as experimental.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… manifest regex, async cleanup)

- FrameworkServerUtils.kt: wrap /ai/generate's tokenFlow.collect in a
  120s withTimeout, responding 504 instead of hanging forever if the
  native supplier stalls.
- test-catalyst-core.sh: fail fast if npm pack doesn't produce a
  catalyst-core tarball, instead of silently falling through to
  npm install pulling the published package.
- buildAndroid/assets.js: scope the meta-data removal regex to a single
  tag ([^>]*? instead of [\s\S]*?) so it can't span tag boundaries and
  delete unrelated <meta-data> entries.
- buildIos/assets.js: drop the unnecessary async from
  validateNotificationAsset (no awaits inside) so sync errors surface
  to the caller's try/catch instead of becoming unhandled rejections.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…alerts

CI failures (catalyst-core, create-catalyst-app jobs both failing on
`npm ci` with ETARGET):
- All four AI packages (catalyst-ai, catalyst-cloud-ai, catalyst-web-ai-local,
  catalyst-native-ai-local) declared peerDependencies: { "catalyst-core":
  ">=0.1.0" }. catalyst-core is a prerelease (0.2.0-beta.1), and plain semver
  ranges never match prereleases outside their own major.minor.patch —
  confirmed via semver.satisfies('0.2.0-beta.1', '>=0.1.0') === false.
  Widened to ">=0.2.0-0", which matches 0.2.0 prereleases and everything
  >=0.2.0 stable.
- package-lock.json was also missing entries for all four packages (added
  well after the lockfile was last regenerated) — npm ci installs strictly
  from the lockfile and can't resolve what isn't pinned there, which was a
  second, independent cause of the same failure. Regenerated via
  `npm install --package-lock-only` and verified with the actual `npm ci` +
  scripts/test-catalyst-core.sh commands CI runs.
- test-catalyst-core.sh restored as executable (mode 100644 -> 100755).

CodeQL alerts (2 high, 4 medium, all on this PR's own code):
- route.js:453,476 — provider was interpolated directly into the first arg
  of console.error (format-string + log-injection risk per CodeQL's static
  analysis, even though provider is validated against a fixed PROVIDERS
  allowlist a few lines earlier). Switched to console.error("%s ...",
  provider, ...) so it's passed as a separate argument, not part of the
  format string.
- sync-packages.js:39,41 — rimraf() shelled out via execSync("rm -rf ...")
  / execSync("find ... -delete"). Replaced with fs.rmSync(dir, { recursive:
  true, force: true }), which does the same thing without invoking a shell
  at all, eliminating the "uncontrolled command line" class of finding.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
try {
await adapter.stream({ apiKey: cfg.apiKey, model: resolvedModel, messages, genConfig, conversationId, stateful, res })
} catch (err) {
console.error("[@catalyst/cloud-ai] %s stream error:", provider, err.message)
const result = await adapter.generate({ apiKey: cfg.apiKey, model: resolvedModel, messages, genConfig, conversationId, stateful })
res.json({ ...result, model: resolvedModel })
} catch (err) {
console.error("[@catalyst/cloud-ai] %s generate error:", provider, err.message)
mayankmahavar1mg and others added 5 commits July 10, 2026 08:34
…-ai-local)

Only @catalyst/cloud-ai is used at runtime (useAI.js requires it
directly). catalyst-ai is a one-line re-export nothing imports;
web-ai-local/native-ai-local are only referenced by dead
DefinePlugin flags in base.babel.js that nothing reads. Deleting
also removes catalyst-ai's preinstall check-peer.js, which was
breaking npm ci once its lockfile entry existed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…mcp stderr)

- Chess.js: reset aiThinking on generate() failure so the error-effect
  doesn't trigger a second fallback move for the same AI turn (critical)
- expressServer.js: only skip AI router mount on genuine MODULE_NOT_FOUND;
  surface/log other resolve or mount errors instead of swallowing them
- base.babel.js, route.js: guard AI_CONFIG JSON.parse so malformed env
  JSON doesn't abort webpack config eval or crash the route module
- androidSetup.js: fix misplaced eslint-disable-next-line that was
  targeting the nosemgrep comment instead of the exec() call it guards

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…yst-core

In an npm workspace install, sibling packages aren't guaranteed to be
linked yet when preinstall scripts run -- npm's own lifecycle ordering
varies across versions (verified locally: passes on npm 9, fails on
npm 10/Node 22, which matches what CI runs). check-peer.js was hard
process.exit(1)-ing on this false positive, breaking npm ci for the
catalyst-core and create-catalyst-app CI jobs.

peerDependencies + peerDependenciesMeta already declare the
requirement, so downgrade to a warning instead of aborting the
install.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
docs/agentic-framework-discussion.md, docs/github-discussion-draft.md,
docs/content/useai-rfc.md, and docs/ios-native-ai-deferred.md were
working/planning documents that got committed alongside the real docs
update but were never meant to ship as published documentation.

Co-Authored-By: Claude Sonnet 5 <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.

2 participants