Pinecall

ChatSession API

Full reference for ChatSession (vanilla) and usePinecallChat (React).

ChatSession (vanilla)#

Constructor#

import { ChatSession } from "@pinecall/chat-core";

const chat = new ChatSession({ agent: "florencia" });
OptionTypeRequiredDescription
agentstringAgent slug (e.g. "florencia", "dev-berna-florencia")
serverstringVoice server URL (default: https://voice.pinecall.io)

Methods#

MethodDescription
connect()Connect — fetches token, opens WebSocket
disconnect()Close the WebSocket connection
destroy()Disconnect + clear all subscribers. Do not reuse.
send(text)Send a text message to the agent
setContext(key, value)Inject / update / clear keyed context in the LLM prompt
getState()Read-only snapshot of current state
subscribe(cb)Subscribe to state changes (returns unsubscribe)

Events (EventTarget)#

EventdetailWhen
status{ status }Connection status changed
message{ message }New or updated message
error{ error }Error occurred
change{ state }Any state mutation (most general)
eventraw payloadEvery raw server event

State shape#

interface ChatSessionState {
  status: "idle" | "connecting" | "connected" | "error";
  error: string | null;
  messages: ChatMessage[];
  typing: boolean;          // true while bot is streaming a response
  streamingText: string;    // partial text of the current bot response
  sessionId: string | null;
}

interface ChatMessage {
  id: number;
  role: "user" | "bot";
  text: string;
  messageId?: string;       // server-assigned ID (bot messages)
  isStreaming?: boolean;    // true while bot is still streaming
}

Reactive subscribe pattern#

Works with any reactive system — MobX, signals, Vue refs, Svelte stores:

const unsubscribe = chat.subscribe(() => {
  const state = chat.getState();
  console.log("Messages:", state.messages.length);
  console.log("Typing:", state.typing);
});

// clean up
unsubscribe();

Injecting dynamic context#

Same pattern as voice-widget's setContext() — inject live UI state into the LLM's system prompt:

chat.setContext("cart", JSON.stringify({
  items: ["Corte de cabello", "Tinte"],
  total: 85.00,
}));

// clear a context key
chat.setContext("cart", null);

The agent's system prompt picks this up automatically:

## UI Context
### cart
{"items":["Corte de cabello","Tinte"],"total":85.00}

usePinecallChat (React)#

React-only hook exported from @pinecall/chat-core/react. Wraps ChatSession with useSyncExternalStore for efficient rendering. Session is created once on mount and destroyed on unmount.

Quick usage#

import { usePinecallChat } from "@pinecall/chat-core/react";

function Chat() {
  const { messages, send, connected, typing, streamingText } = usePinecallChat({
    agent: "florencia",
  });

  if (!connected) return <p>Connecting...</p>;

  return (
    <div>
      {messages.map((m) => (
        <p key={m.id}>
          <strong>{m.role}:</strong> {m.text}
          {m.isStreaming && "▊"}
        </p>
      ))}
      {typing && <p>Bot is typing: {streamingText}▊</p>}
      <input
        placeholder="Type a message..."
        onKeyDown={(e) => {
          if (e.key === "Enter") {
            send(e.currentTarget.value);
            e.currentTarget.value = "";
          }
        }}
      />
    </div>
  );
}

Hook options#

OptionTypeDefaultDescription
agentstringrequiredAgent ID
serverstring"https://voice.pinecall.io"Voice server URL
autoConnectbooleantrueConnect on mount automatically

Hook return#

FieldTypeDescription
messagesChatMessage[]All messages in the conversation
send(text: string) => voidSend a text message
connectedbooleantrue when connected to the server
typingbooleantrue while the bot is streaming
streamingTextstringPartial text of the current bot response
errorstring | nullCurrent error, if any
setContext(key, value) => voidInject dynamic context into the LLM prompt
connect() => voidManually connect (if autoConnect: false)
disconnect() => voidManually disconnect

Protocol#

What happens under the hood:

Browser                                  Voice Server
   │                                         │
   ├─ GET /chat/token?agent_id=X ──────────►│  (short-lived token)
   │◄─ { token: "cht_xxx" } ─────────────────┤
   │                                         │
   ├─ WS /chat/ws?token=cht_xxx ───────────►│  (open WebSocket)
   │◄─ { event: "chat.connected" } ──────────┤
   │                                         │
   ├─ { event: "message", text } ──────────►│  (user sends)
   │◄─ { event: "chat.token", token: "..." } │  (streaming bot tokens)
   │◄─ { event: "chat.token", token: "..." } │
   │◄─ { event: "chat.done", text: "..." } ──│  (stream complete)
   │                                         │
   ├─ { event: "set_context", key, value } ►│  (inject LLM context)
PackageDescription
@pinecall/sdkServer-side SDK — agent, call, tools, channels
@pinecall/voice-coreWebRTC voice session (framework-agnostic)
@pinecall/voice-widgetReact voice widget with animated orb