Skip to content

Documentation / @agentick/angular

@agentick/angular

Angular integration for Agentick. Provides signal-first service wrappers around @agentick/client.

Installation

bash
pnpm add @agentick/angular

Quick Start — ChatSessionService

For chat UIs, use ChatSessionService with provideChatSession(). This wraps ChatSession from @agentick/client and exposes all state as Angular signals.

ts
import { Component, inject } from "@angular/core";
import { ChatSessionService, provideChatSession, provideAgentick } from "@agentick/angular";

@Component({
  selector: "app-chat",
  standalone: true,
  providers: [
    provideAgentick({ baseUrl: "/api/v2", token: myJwt }),
    provideChatSession({ renderMode: "streaming" }),
  ],
  template: `
    @for (msg of chat.messages(); track msg.id) {
      <div>{{ msg.role }}: {{ msg.content }}</div>
    }
    <input #input />
    <button (click)="chat.submit(input.value); input.value = ''">Send</button>
  `,
})
export class ChatComponent {
  chat = inject(ChatSessionService);
}

ChatSessionService Signals

ts
chat.messages(); // readonly ChatMessage[]
chat.chatMode(); // "idle" | "streaming" | "confirming_tool"
chat.isExecuting(); // boolean
chat.toolConfirmation(); // ToolConfirmationState | null
chat.lastSubmitted(); // string | null
chat.queued(); // readonly Message[]
chat.mode(); // "steer" | "queue"
chat.error(); // { message, name } | null
chat.attachments(); // readonly Attachment[]

// Computed
chat.isIdle(); // boolean
chat.isStreaming(); // boolean
chat.isConfirmingTool(); // boolean

ChatSessionService Actions

ts
chat.submit(text)                    // Send message
chat.steer(text)                     // Force send immediately
chat.queue(text)                     // Queue for next execution
chat.interrupt(text)                 // Abort current + send
chat.abort(reason?)                  // Abort current execution
chat.flush()                         // Flush next queued message
chat.respondToConfirmation(response) // Approve/deny tool
chat.clearMessages()                 // Clear all messages
chat.prependMessages(messages)       // Load older history (scroll-back)
chat.appendMessages(messages)        // Initial load or external sources
chat.setMode(mode)                   // Switch steer/queue mode

// Attachments
chat.addAttachment(input)            // Add file
chat.removeAttachment(id)            // Remove file
chat.clearAttachments()              // Clear all files

Paginated History

Load older messages as the user scrolls back:

ts
async loadOlderMessages(cursor: string) {
  const res = await fetch(`/api/messages?before=${cursor}&limit=20`);
  const { messages } = await res.json();
  this.chat.prependMessages(messages);
}

ChatSessionOptions

ts
provideChatSession({
  sessionId: "conv-123", // Connect to existing session
  initialMessages: [], // Pre-loaded messages from DB
  renderMode: "streaming", // "streaming" | "block" | "message"
  confirmationPolicy: undefined, // Auto-approve/deny policy
  autoSubscribe: true, // Subscribe on construction (default)
});

AgentickService (Low-Level)

For direct client access (sessions, channels, raw events), use AgentickService:

ts
import { Component, inject } from "@angular/core";
import { AgentickService, provideAgentick } from "@agentick/angular";

@Component({
  selector: "app-chat",
  providers: [provideAgentick({ baseUrl: "/api/agent" })],
  template: `
    <div>{{ agentick.text() }}</div>
    <button (click)="send('Hello')">Send</button>
  `,
})
export class ChatComponent {
  agentick = inject(AgentickService);

  constructor() {
    this.agentick.subscribe("conv-123");
  }

  send(message: string) {
    this.agentick.send(message);
  }
}

AgentickService API

ts
// Session management
service.session(sessionId)      // Cold accessor
service.subscribe(sessionId)    // Hot accessor
service.unsubscribe()           // Drop current subscription

// Messaging
service.send(input)             // Returns ClientExecutionHandle
service.abort(reason?)
service.close()
service.clearStreamingText()

// Channels
service.channel(name)           // Session-scoped channel
service.channel$(name)          // Channel as Observable
service.eventsOfType(...types)  // Filtered event Observable

Signals

ts
service.connectionState(); // "disconnected" | "connecting" | "connected" | "error"
service.sessionId(); // string | undefined
service.streamingText(); // { text, isStreaming }
service.text(); // string (computed)
service.isStreaming(); // boolean (computed)
service.isConnected(); // boolean (computed)
service.isConnecting(); // boolean (computed)

RxJS Observables

All signals are also available as observables via toObservable():

ts
service.connectionState$;
service.isConnected$;
service.streamingText$;
service.text$;
service.isStreaming$;
service.events$;

Provider Pattern

Both services require provideAgentick() at the component level. Each provider creates an isolated instance with its own client and connection:

ts
// Two independent chat agents in the same app
@Component({
  providers: [provideAgentick({ baseUrl: "/api/support" })],
})
export class SupportChat { ... }

@Component({
  providers: [provideAgentick({ baseUrl: "/api/sales" })],
})
export class SalesChat { ... }

@agentick/angular - Modern Angular bindings for Agentick

Uses Angular signals for reactive state with RxJS interop for compatibility.

Examples

typescript
import { TENTICKLE_CONFIG } from '@agentick/angular';

bootstrapApplication(AppComponent, {
  providers: [
    { provide: TENTICKLE_CONFIG, useValue: { baseUrl: 'https://api.example.com' } },
  ],
});
typescript
import { Component, inject } from '@angular/core';
import { AgentickService } from '@agentick/angular';

@Component({
  selector: 'app-chat',
  standalone: true,
  template: `
    <div class="response">
      {{ agentick.text() }}
      @if (agentick.isStreaming()) {
        <span class="cursor">|</span>
      }
    </div>
    <input #input />
    <button (click)="send(input.value); input.value = ''">Send</button>
  `,
})
export class ChatComponent {
  agentick = inject(AgentickService);

  constructor() {
    this.agentick.subscribe("conv-123");
  }

  async send(message: string) {
    const handle = this.agentick.send(message);
    await handle.result;
  }
}
typescript
@Component({
  template: `
    <div>{{ text$ | async }}</div>
  `,
})
export class LegacyComponent {
  agentick = inject(AgentickService);
  text$ = this.agentick.text$;
}
typescript
import { provideAgentick, AgentickService } from '@agentick/angular';

// Each component gets its own service instance
@Component({
  selector: 'app-support-chat',
  standalone: true,
  providers: [provideAgentick({ baseUrl: '/api/support-agent' })],
  template: `<div>{{ agentick.text() }}</div>`,
})
export class SupportChatComponent {
  agentick = inject(AgentickService);
}

@Component({
  selector: 'app-sales-chat',
  standalone: true,
  providers: [provideAgentick({ baseUrl: '/api/sales-agent' })],
  template: `<div>{{ agentick.text() }}</div>`,
})
export class SalesChatComponent {
  agentick = inject(AgentickService);
}

Signals (Primary API)

SignalTypeDescription
connectionState()ConnectionStateConnection state
sessionId()string | undefinedActive session ID
streamingText()StreamingTextStateText + isStreaming
text()stringJust the text (computed)
isStreaming()booleanWhether streaming (computed)

RxJS Observables (Compatibility)

ObservableTypeDescription
connectionState$ConnectionStateConnection state
isConnected$booleanWhether connected
streamingText$StreamingTextStateText + isStreaming
text$stringJust the text
isStreaming$booleanWhether streaming
events$`StreamEventSessionStreamEvent`
result$ResultExecution results

Methods

MethodDescription
session(sessionId)Get cold accessor
subscribe(sessionId)Subscribe (hot)
unsubscribe()Unsubscribe active session
send(input)Send message
abort(reason?)Abort execution
close()Close active session
channel(name)Get channel accessor
channel$(name)Get channel as Observable
eventsOfType(...types)Filter events by type
clearStreamingText()Clear accumulated text

Classes

Interfaces

Type Aliases

Variables

Functions

Released under the ISC License.