@presencelearning/ai
AI integration library for Presence Learning. Provides streaming chat against Chiron, background job management, session and feedback APIs, and UI components for AI-powered features using the Vercel AI SDK.
Current version: 2.9.0. Single entry point — everything is imported from @presencelearning/ai.
Install
Section titled “Install”npm install @presencelearning/ai @ai-sdk/angular ai markedPeer dependencies
Section titled “Peer dependencies”Declared in package.json:
@angular/core>=18.0.0 <22.0.0@angular/common>=18.0.0 <22.0.0@ai-sdk/angular^2.0.0ai^6.0.0rxjs^7.0.0marked>=15.0.0 <19.0.0
Additionally required at runtime, depending on what you use:
provideHttpClient()—AISessionService,AIAgentService,AIFeedbackService, andAIJobManagerall injectHttpClient.@angular/material—UserPromptDialogComponent(button, dialog, form-field, input, radio) andAIToolProgressComponent(icon).@angular/forms—UserPromptDialogComponent.
Configure the library once in your root providers. Because the auth callback is invoked per request (not during construction), resolve your token source in the factory and close over it:
import { ApplicationConfig, inject } from '@angular/core';import { provideHttpClient } from '@angular/common/http';import { PRESENCE_AI_CONFIG, PresenceAIConfig } from '@presencelearning/ai';
export const appConfig: ApplicationConfig = { providers: [ provideHttpClient(), { provide: PRESENCE_AI_CONFIG, useFactory: (): PresenceAIConfig => { const authStore = inject(AuthStore); return { chiron: { baseUrl: environment.apps.chiron.url }, workplace: { baseUrl: environment.apps.apiWorkplace.url }, auth: { getToken: () => authStore.getCurrentToken() }, }; }, }, ],};Do not write
getToken: () => inject(AuthStore).getCurrentToken().getToken()runs inside each service’sgetAuthHeaders()at request time, outside any injection context, soinject()throwsNG0203.
When the token does not come from DI, providePresenceAI() is a shorthand for the same token:
import { ApplicationConfig } from '@angular/core';import { provideHttpClient } from '@angular/common/http';import { providePresenceAI } from '@presencelearning/ai';
export const appConfig: ApplicationConfig = { providers: [ provideHttpClient(), providePresenceAI({ chiron: { baseUrl: environment.apps.chiron.url }, workplace: { baseUrl: environment.apps.apiWorkplace.url }, auth: { getToken: () => tokenStore.token }, }), ],};providePresenceAI() registers the config with useValue, so it cannot resolve DI dependencies itself — use the useFactory form above when it needs to.
Features
Section titled “Features”| Feature | Description |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Chat | PresenceChatFactory for streaming chat with tool call tracking. create(options) starts a new session; joinSession(options) attaches to an existing one and can seed prior turns via initialMessages to resume |
| Transport | ChironChatTransport — the SSE transport that talks to Chiron and surfaces ChironToolResults |
| Sessions | AISessionService — listConversations (browse, search/pagination), getMessages (full-fidelity transcript), rename, delete |
| Agents | AIAgentService.getSuggestions(agentId) — starter prompt suggestions from the serving plane |
| Feedback | AIFeedbackService — submit, getForSession, positive, negative, retract |
| Jobs | AIJobManager — start, getStatus, cancel, poll for background AI jobs |
| Errors | AIError plus AINetworkError, AIJobError, AIParseError, AIAuthError |
| Status messages | AI_STATUS_MESSAGES, getRandomAIStatusMessage(), createAIStatusMessageRotator() |
AISessionService methods are option-object based: getMessages and delete take SessionOptions, listConversations takes ListConversationsOptions (adds search/limit/offset), and rename takes RenameConversationOptions. None accept a version — the agent version is pinned server-side to the one a session started on.
Components
Section titled “Components”| Component | Selector | Description |
| ---------------------------- | ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| PresenceChatPanelComponent | pl-ai-chat-panel | Full chat drawer — message list, composer, and ask_user prompt handling. See inputs below |
| AIMarkdownComponent | pl-ai-markdown | Renders AI-generated markdown (bold, italic, code, lists, links, blockquotes, tables). marked parses it; Angular’s DomSanitizer then sanitizes the resulting HTML |
| AIToolStatusComponent | pl-ai-tool-status | Displays tool invocation status during streaming. Input: showCompleted |
| AIToolProgressComponent | pl-ai-tool-progress | Streaming tool progress with step descriptions and summaries. Input: idleMessage |
| SparkleLoaderComponent | pl-sparkle-loader | Animated loading indicator with therapy-themed status messages |
| UserPromptDialogComponent | pl-user-prompt-dialog | Material dialog for Chiron ask_user tool prompts (radio options + optional free text) |
PresenceChatPanelComponent inputs
Section titled “PresenceChatPanelComponent inputs”| Input | Type | Description |
| ---------------- | ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| agentId | string | Required. Chiron agent id to converse with |
| systemPrompt | string | Optional system prompt override |
| initialMessage | string | Message sent automatically once the panel mounts |
| placeholder | string | Placeholder/aria-label for the composer. Default 'Type a message…' |
| chat | PresenceChat | Host-owned chat instance. Read once in ngOnInit; the panel will not destroy() it. Omit to have the panel create and own one |
| localPrompt | { question, options } \| null | Host-owned prompt rendered with the same markup as an agent ask_user prompt. An active agent prompt takes precedence |
| localEntries | ReadonlyArray<{ id, kind, text }> | Host-owned 'status' / 'note' entries rendered after the message list. Pass a new array reference to update (OnPush) |
Output: localPromptAnswered: EventEmitter<string> — emits the chosen option when a localPrompt chip is clicked.
AIMarkdownComponent link handling: all links render with target="_blank" rel="noopener noreferrer". Absolute http(s):/mailto: URLs and same-origin app paths are allowed; protocol-relative, backslash, javascript:, and data: URLs are neutered to #.
File attachments
Section titled “File attachments”PresenceChat.send() accepts files. CHIRON_FILE_ATTACHMENT_LIMITS mirrors Chiron’s server-side limits so the UI validates against the same source of truth:
maxFiles: 5application/pdf: 15 MBtext/csv,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,image/jpeg,image/png,image/gif,image/webp: 5 MB each
Messages can also be sent hidden from the transcript — send(text, { hidden: true }) or send({ text, files, hidden: true }).
Utilities
Section titled “Utilities”| Export | Description |
| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------- |
| createAskUserHandler | Creates a reactive handler for the ask_user tool lifecycle — provides a pendingPrompt signal and a respond() method |
| getRandomAIStatusMessage | Picks one entry from AI_STATUS_MESSAGES |
| createAIStatusMessageRotator | Returns a rotator that cycles status messages while a request is in flight |
| isPlIcon-style guards | none — see @presencelearning/icons for icon helpers |
Key types
Section titled “Key types”| Type | Description |
| ----------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
| PresenceAIConfig | Shape of PRESENCE_AI_CONFIG — chiron.baseUrl, workplace.baseUrl, auth.getToken |
| PresenceChat | Chat instance with Angular signals for messages, status, streaming, tool calls, and deployedVersion |
| PresenceChatOptions / JoinSessionOptions | Arguments to PresenceChatFactory.create() / .joinSession() |
| PresenceChatSendInput | Argument to PresenceChat.send() (text, optional files, optional hidden) |
| ChatMessage | A single transcript entry, including files |
| ChatStatus | Chat lifecycle status |
| ToolCallPart / ToolCallStatus | Tool call parts surfaced during streaming |
| ChironChatTransportOptions | Options for ChironChatTransport |
| ChironToolResult | Tool result from the Chiron SSE stream |
| UserPrompt | Pending ask_user prompt data (question, options, allowCustom, toolCallId) |
| AskUserHandler | Return type of createAskUserHandler |
| UserPromptDialogData | Input data for UserPromptDialogComponent (adds optional title) |
| UserPromptDialogResult | Dialog return value (response string + isCustom flag) |
| ToolDescriptionMap | Maps tool names to user-friendly descriptions for AIToolProgressComponent |
| ToolSummarizer | Function that extracts a summary string from a ChironToolResult |
| SessionOptions / SessionMessage / SessionToolCall / ConversationSummary / ConversationPage / ListConversationsOptions / RenameConversationOptions | AISessionService argument and response shapes |
| FeedbackParams / FeedbackRating / SessionFeedbackEntry | AIFeedbackService argument and response shapes. Feedback is append-only; retract() records a 'none' rating |
| JobStatus / JobState / JobResult / JobTracker / StartJobOptions / PollOptions | AIJobManager types |
| AIStatusMessage | A single entry in AI_STATUS_MESSAGES |
Local demo
Section titled “Local demo”The package ships a Vite demo harness that exercises chat, attachments, and the panel against a proxied Chiron:
npm run demo --workspace=@presencelearning/aiSource: packages/ai