feat(ai): AI assistant composables for nest-server AI module (1.7.0)#3
Merged
Conversation
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>
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 free
to 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.
Summary
useLtAi,useLtAiChat,useLtAiConnections,useLtAiUsage,useLtAiPrompts,useLtAiPlaceholders,useLtAiAdmin) plus helper lib with SSE parser, ~500 LoC of types, auto-imports and theltExtensions.ai: { enabled, basePath }module option. Targets the@lenne.tech/nest-serverAI module./lt-dev:reviewremediation catalog (2 Critical + 4 High + 9 Medium + 5 Low), including theLtAiPromptInputtype collision, doubleapplyFinalreactivity bug,AbortErrorhandling, missing exports, and admin URLencodeURIComponentdefense-in-depth.[DONE], EOF flush, malformed, all-comments, no-body, 1 MiB cap, AbortSignal),ltAiRequest/ltAiResponseError/buildLtAiUrlunit tests, anduseLtAiChatreactivity/AbortError/once-applyFinal/maxMessages/clear()contract tests.Key fixes from review
LtAiPromptInput(execution) →LtAiPromptRunInput; the CRUD input forLtAiPromptkeeps the conventional name. Pre-release-only breaking, with full rename map in CHANGELOG.applyFinalinvocation inuseLtAiChat.runTurn(was firing once fromonFinalcallback and again post-promise).AbortErrorfromstop()as a clean termination — assistant turn keeps streamed content, no error flag, noerror.valueset.useLtAiPrompts,useLtAiPlaceholders+ 6 types) to all three barrel files so direct ESM imports compile.ltExtensionsconfig key, and full AI module documentation.AbortSignal,encodeURIComponentfor${id}segments,onScopeDispose(() => stop()),maxMessagescap option,package.json"sideEffects": false, HMR-safeglobalThis.fetchwrapper guard, error-message length cap.Test plan
pnpm run checkgreen (audit, format, lint, vue-tsc, vitest 52/52, build) — confirmed locally before pushnuxt-base-starterintegration🤖 Generated with Claude Code