Skip to content

feat(ai): AI assistant composables for nest-server AI module (1.7.0)#3

Merged
kaihaase merged 13 commits into
mainfrom
feature/ai-module
May 31, 2026
Merged

feat(ai): AI assistant composables for nest-server AI module (1.7.0)#3
kaihaase merged 13 commits into
mainfrom
feature/ai-module

Conversation

@kaihaase
Copy link
Copy Markdown
Member

@kaihaase kaihaase commented May 30, 2026

Summary

  • Adds 7 headless AI composables (useLtAi, useLtAiChat, useLtAiConnections, useLtAiUsage, useLtAiPrompts, useLtAiPlaceholders, useLtAiAdmin) plus helper lib with SSE parser, ~500 LoC of types, auto-imports and the ltExtensions.ai: { enabled, basePath } module option. Targets the @lenne.tech/nest-server AI module.
  • Resolves all 20 findings from the /lt-dev:review remediation catalog (2 Critical + 4 High + 9 Medium + 5 Low), including the LtAiPromptInput type collision, double applyFinal reactivity bug, AbortError handling, missing exports, and admin URL encodeURIComponent defense-in-depth.
  • Adds 25 new tests (52 total, up from 27): full SSE parser coverage (split-JSON, keep-alive, [DONE], EOF flush, malformed, all-comments, no-body, 1 MiB cap, AbortSignal), ltAiRequest/ltAiResponseError/buildLtAiUrl unit tests, and useLtAiChat reactivity/AbortError/once-applyFinal/maxMessages/clear() contract tests.

Key fixes from review

  • Critical: renamed colliding LtAiPromptInput (execution) → LtAiPromptRunInput; the CRUD input for LtAiPrompt keeps the conventional name. Pre-release-only breaking, with full rename map in CHANGELOG.
  • Critical: dropped duplicate applyFinal invocation in useLtAiChat.runTurn (was firing once from onFinal callback and again post-promise).
  • High: treat AbortError from stop() as a clean termination — assistant turn keeps streamed content, no error flag, no error.value set.
  • High: added missing exports (useLtAiPrompts, useLtAiPlaceholders + 6 types) to all three barrel files so direct ESM imports compile.
  • High: README + CLAUDE.md (shipped in the npm package) refreshed with correct composable names, correct ltExtensions config key, and full AI module documentation.
  • Medium: SSE parser buffer cap (1 MiB), optional AbortSignal, encodeURIComponent for ${id} segments, onScopeDispose(() => stop()), maxMessages cap option, package.json "sideEffects": false, HMR-safe globalThis.fetch wrapper guard, error-message length cap.

Test plan

  • pnpm run check green (audit, format, lint, vue-tsc, vitest 52/52, build) — confirmed locally before push
  • CI build pipeline green
  • No regressions in downstream nuxt-base-starter integration

🤖 Generated with Claude Code

kaihaase and others added 13 commits May 25, 2026 11:15
Headless client layer for the @lenne.tech/nest-server AI module — provider-
agnostic, auth-aware (Cookie/JWT via ltAuthFetch), SSR-safe.

- Types (runtime/types/ai.ts): prompt/response/stream/usage/connection/admin DTOs
- lib/ai.ts: buildLtAiUrl, ltAiRequest (JSON), parseLtAiSseStream (robust SSE),
  ltAiResponseError (surfaces the backend's translated message)
- useLtAi: one-shot prompt() + streaming promptStream() (POST /ai/stream)
- useLtAiChat: multi-turn state, token streaming, conversationId, budget,
  confirmation flow (requiresConfirmation → confirm()), stop()/clear()
- useLtAiConnections: available connections + user self-service select (selected/locked)
- useLtAiUsage: GET /ai/usage breakdown
- useLtAiAdmin: connections CRUD + detectCapabilities, preferences, budget-limits,
  interactions (all ADMIN-gated server-side)
- Module option `ai: { basePath:'/ai', enabled }`, auto-imports, runtimeConfig,
  barrel + package exports
- Test: parseLtAiSseStream (split JSON, keep-alives, [DONE], malformed, no-body)

Lint clean, build OK, 27 tests pass.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
useLtAiChat().messages was DeepReadonly, so its elements could not be passed to
child components (props typed as LtAiMessage). Type it as Readonly<Ref<LtAiMessage[]>>
(ref cannot be reassigned, elements stay bindable) and return the underlying ref.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
useLtAiChat.runTurn() created the assistant message as a plain object, pushed
it into the messages ref array, then mutated the original reference while
streaming. Vue wraps a plain object pushed into a reactive array in a *separate*
reactive proxy, so mutating the raw reference updated the data (lazy getters
still read it) but never triggered a re-render — the assistant bubble rendered
empty with only the pending cursor until a full component remount.

Wrap the message in reactive() so content/pending/actions mutations during
streaming go through the proxy and update the DOM live.

Found under real fullstack E2E conditions (nest-server AI module + nuxt-base
starter against a live OpenAI-compatible endpoint).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- auth-interceptor.client.ts: $fetch's type embeds Nuxt's generated route union;
  calling it with a plain string url made vue-tsc fail with "Excessive stack depth"
  (7 errors). Cast originalFetch to a loose callable — the wrapper is reassigned
  `as typeof globalThis.$fetch`, so the public type is preserved. test:types now green.
- Security overrides (fixed targets, scoped keys) to clear pnpm audit:
  kysely 0.28.17 (JSON-path traversal), devalue 5.8.1 (DoS), ws 8.21.0 (memory
  disclosure), brace-expansion 5.0.6 (ReDoS), @nuxt/nitro-server + @nuxt/schema +
  @nuxt/kit 4.4.6. Bumped nuxt devDep 4.4.4 → 4.4.6 (reflected XSS in navigateTo,
  __nuxt_island cache poisoning). `pnpm audit` now reports 0 vulnerabilities and 0
  unmet peers.

Full `pnpm run check` green (audit, format, lint, types, 27 tests, build).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ints

Extends useLtAiAdmin with CRUD for the new nest-server prompt-optimization
endpoints so consumers can build an admin UI:

- listPromptTemplates/create/update/delete (POST/GET/PUT/DELETE /ai/prompt-templates)
- listPromptHints/create/update/delete (POST/GET/PUT/DELETE /ai/prompt-hints)

Adds LtAiPromptTemplate(/Input) and LtAiPromptHint(/Input) types, re-exported
from the module + package index. test:types green.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…mers

useLtAiChat now also tracks the latest response's contextWindow as a reactive
ref (mirrors the new CoreAiResponse field). LtAiBudgetSummary gains
`maxTokens` + `scope` ('user'|'tenant'|'llm') matching the backend resolution
chain. These drive a usage progress bar + a closing-circle indicator in the
chat UI.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Headless companion to the admin-only useLtAiAdmin composable. Lets any
signed-in user manage their own prompt snippets ("Vorlagen") and use the
visible ones (own + tenant + global) in a chat input picker. Backed by the
new /ai/snippets REST endpoints in @lenne.tech/nest-server.

API surface:
  - LtAiPromptSnippet / LtAiPromptSnippetInput types
  - useLtAiSnippets() → { snippets, loading, error, load, create, update, remove }
  - Auto-imported when the AI module is enabled

`load()` returns every snippet the caller is allowed to see (own + tenant +
global), already filtered by the server. `create()` / `update()` / `remove()`
refresh the list on success so the picker always reflects the latest state.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…est-server

Matching frontend rename for the nest-server 41d5a75 refactor:

  * LtAiPromptTemplate → LtAiSlot (admin system-prompt building blocks)
  * LtAiPromptTemplateInput → LtAiSlotInput
  * LtAiPromptSnippet → LtAiPrompt (user re-usable prompts)
  * LtAiPromptSnippetInput → LtAiPromptInput
  * useLtAiSnippets() → useLtAiPrompts()
  * UseLtAiSnippetsReturn → UseLtAiPromptsReturn (`snippets` ref → `prompts`)

Admin composable methods renamed in lockstep:
  list/create/update/deletePromptTemplate(s) → list/create/update/deleteSlot(s)
The REST paths the composables hit follow the backend (`/ai/slots`,
`/ai/prompts`).

The existing LtAiPromptInput (= payload of the aiPrompt run; the user's
question to the LLM) is untouched. CRUD inputs of the new model are
LtAiPromptInput-shaped DTOs from the backend's CreateInput / UpdateInput
(re-used as one TypeScript shape via optional fields, matching the
nuxt-extensions pattern for the other AI types).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Frontend counterpart for the nest-server slot-tenant-scoping + placeholder
registry overhaul (commit 619b59f).

New types:
  * LtAiEffectiveSlot — slot row with isSystem / isOverride / systemKey
    flags for the admin UI (used by `listEffectiveSlots`).
  * LtAiPlaceholder — name + description + optional example, served by the
    backend's `/ai/placeholders` registry endpoint.
  * UseLtAiPlaceholdersReturn — composable return type.

Composable additions:
  * useLtAiAdmin().listEffectiveSlots() → effective view (defaults + overrides + customs).
  * useLtAiAdmin().resetSlot(id) → POST /ai/slots/:id/reset.
  * useLtAiPlaceholders() → load + read-only placeholders ref; auto-imported
    when the AI module is enabled.

Frontend stays naming-agnostic: the placeholder list is fetched at runtime,
so project-side additions to the backend registry show up in the editor
without a frontend change.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…AI module

Version bump to ship the AI-assistant composable surface accumulated on
feature/ai-module. CHANGELOG.md gets a new `[1.7.0]` entry listing every
new export (`useLtAi`, `useLtAiChat`, `useLtAiConnections`, `useLtAiUsage`,
`useLtAiAdmin` with `listEffectiveSlots` / `resetSlot`, `useLtAiPrompts`,
`useLtAiPlaceholders`) plus the cumulative `usedTokens` change on
`LtAiBudgetSummary`. Pairs with @lenne.tech/nest-server 11.26.0.

Pre-release-only breaking rename: `useLtAiSnippets` → `useLtAiPrompts`
(mirrors the backend rename `Snippet → Prompt`, `Template → Slot`).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Add `.claude/` to .gitignore and untrack the four previously committed
lt-dev-npm-package-maintainer memory files. These files are local
agent-cache artifacts produced by review/maintenance subagents and
should not be shared via the repository.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ract

ai-request.test.ts (13 tests):
- buildLtAiUrl + getLtAiBasePath default and configured base paths
- ltAiResponseError string / array / fallback message, 1 KiB cap
- ltAiRequest body serialisation, 204 handling, AbortSignal forwarding

use-lt-ai-chat.test.ts (6 tests):
- streaming reactive contract on assistant message
- applyFinal runs exactly once per turn (regression guard for the
  double-invocation bug fixed in this branch)
- AbortError is treated as a clean stop (no error flag, keeps content)
- real network errors still surface as assistant.error
- maxMessages cap trims the message history
- clear() aborts the in-flight stream

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@kaihaase kaihaase changed the title feat(ai): AI assistant composables (1.7.0) feat(ai): AI assistant composables for nest-server AI module (1.7.0) May 31, 2026
@kaihaase kaihaase merged commit 2c47d5c into main May 31, 2026
1 check passed
@kaihaase kaihaase deleted the feature/ai-module branch May 31, 2026 14:24
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.

1 participant