Skip to content

@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.

Terminal window
npm install @presencelearning/ai @ai-sdk/angular ai marked

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.0
  • ai ^6.0.0
  • rxjs ^7.0.0
  • marked >=15.0.0 <19.0.0

Additionally required at runtime, depending on what you use:

  • provideHttpClient()AISessionService, AIAgentService, AIFeedbackService, and AIJobManager all inject HttpClient.
  • @angular/materialUserPromptDialogComponent (button, dialog, form-field, input, radio) and AIToolProgressComponent (icon).
  • @angular/formsUserPromptDialogComponent.

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:

app.config.ts
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’s getAuthHeaders() at request time, outside any injection context, so inject() throws NG0203.

When the token does not come from DI, providePresenceAI() is a shorthand for the same token:

app.config.ts
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.

| 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 | AISessionServicelistConversations (browse, search/pagination), getMessages (full-fidelity transcript), rename, delete | | Agents | AIAgentService.getSuggestions(agentId) — starter prompt suggestions from the serving plane | | Feedback | AIFeedbackServicesubmit, getForSession, positive, negative, retract | | Jobs | AIJobManagerstart, 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.

| 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) |

| 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 #.

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: 5
  • application/pdf: 15 MB
  • text/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 }).

| 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 |

| Type | Description | | ----------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- | | PresenceAIConfig | Shape of PRESENCE_AI_CONFIGchiron.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 |

The package ships a Vite demo harness that exercises chat, attachments, and the panel against a proxied Chiron:

Terminal window
npm run demo --workspace=@presencelearning/ai

Source: packages/ai