Use polling instead of SSE for environments that can't maintain long-lived HTTP connections.
When to Use Polling
Some environments cannot hold open Server-Sent Events (SSE) connections:
- Convex actions — HTTP requests must complete within a timeout
- Cloudflare Workers — no long-lived inbound connections
- Restricted edge runtimes — firewalls or proxies that drop idle connections
- Serverless functions — Lambda, Cloud Functions with short timeouts
In these cases, set stream: false to switch from SSE to lightweight polling. The SDK automatically polls a status endpoint and only fetches full data when the status changes.
Client-Level Default
Set polling as the default transport for all operations:
1import { inference } from '@inferencesh/sdk';23const client = inference({4 apiKey: 'inf_your_key',5 stream: false, // Use polling instead of SSE6 pollIntervalMs: 2000, // Poll every 2 seconds (default)7});All client.run(), agent.sendMessage(), and AgentChatProvider calls will use polling unless overridden.
Task Polling
Override the transport per-call with stream: false on run():
1const result = await client.run(2 { app: 'infsh/flux', input: { prompt: 'A sunset' } },3 {4 stream: false,5 pollIntervalMs: 3000, // Optional: override interval6 onUpdate: (task) => {7 console.log('Status:', task.status);8 },9 }10);1112console.log('Output:', result.output);How it works:
- Task is created via
POST /apps/run(same as streaming) - SDK polls
GET /tasks/{id}/statuseverypollIntervalMs - When the status changes, it fetches the full task via
GET /tasks/{id} onUpdatefires with the full task data- Promise resolves on completion, rejects on failure or cancellation (the error message comes from the task's
errorfield when present)
When status polling cannot recover (for example repeated network errors), set maxReconnects on run() options (default 5) to control retry behavior.
Agent Polling
Imperative SDK
1const client = inference({ apiKey: 'inf_your_key' });23const agent = client.agent('my-team/support-agent@latest');45const response = await agent.sendMessage('Hello!', {6 stream: false,7 pollIntervalMs: 2000,8 onMessage: (message) => {9 console.log('Message:', message.content);10 },11 onChat: (chat) => {12 console.log('Chat status:', chat.status);13 },14 onToolCall: (invocation) => {15 console.log('Tool call:', invocation.name);16 },17});How it works:
- Message is sent via
POST /agents/run(same as streaming) - SDK polls
GET /chats/{id}/statuseverypollIntervalMs - When the status changes, it fetches chat metadata via
GET /chats/{id}and dispatchesonChat - New and updated messages arrive from
chat_messagesstream events (streaming mode) or require a separateGET /chats/{id}/messagescall —GET /chats/{id}does not include message history - Client tool invocations (
in_progress, orawaiting_inputfor backwards compatibility) are dispatched toonToolCall - Promise resolves when the chat becomes
idle
Transient status poll failures (for example a single network error on GET /chats/{id}/status) log a [Agent] Poll error: warning and polling continues — sendMessage does not reject until the chat reaches a terminal state or the initial send fails. Use onError on AgentChatProvider to surface persistent transport failures to your UI.
React Provider
1import { AgentChatProvider, useAgentChat } from '@inferencesh/sdk/agent';23function App() {4 return (5 <AgentChatProvider6 client={client}7 agentConfig={{ agent: 'my-team/support-agent@latest' }}8 stream={false}9 pollIntervalMs={2000}10 >11 <ChatUI />12 </AgentChatProvider>13 );14}How it works (React provider):
- First message:
POST /chats(create chat with agent ref), thenPOST /chats/{id}/messages - Follow-ups:
POST /chats/{id}/messagesonly - SDK polls
GET /chats/{id}/statuseverypollIntervalMs - When the status changes, the SDK fetches chat metadata (
GET /chats/{id}) and message history (GET /chats/{id}/messages) and merges them into chat state —GET /chats/{id}alone does not include messages - New and updated messages are available through
useAgentChat()(chat.chat_messages) sendMessageresolves when the POST completes; assistant replies arrive via polling or SSE
The provider uses the same status-polling mechanism as the imperative SDK after the message is sent. All hooks (useAgentChat, useAgentActions) work identically regardless of transport.
Loading older messages
Long chats load only the 50 newest messages on connect. v0.6.41+: use useAgentActions().loadOlderMessages() to fetch older pages — the provider tracks the cursor from the initial load, prepends deduplicated messages to useAgentChat().messages, and returns true when more pages exist. Check hasOlderMessages before showing a scroll-up affordance. Works the same with SSE and polling.
1const { messages } = useAgentChat();2const { loadOlderMessages, hasOlderMessages } = useAgentActions();34// Call when the user scrolls near the top of the message list5if (hasOlderMessages) {6 await loadOlderMessages();7}Optional lifecycle callbacks (available on AgentChatProvider with both SSE and polling):
| Prop | When it fires |
|---|---|
onChatCreated | A new chat ID is assigned after the first message |
onStatusChange | Connection status changes (connecting, streaming, idle) |
onTurnEnd | The agent's turn ends — chat status transitions from busy to idle, completed, or another non-busy state. Receives the full ChatDTO. Use to refetch host data after agent tool actions without polling or window-focus workarounds. |
onError | Stream or poll transport errors, failed sends, or tool submission failures |
1<AgentChatProvider2 client={client}3 agentConfig={{ agent: 'my-team/support-agent@latest' }}4 stream={false}5 onError={(error) => console.error('Agent error:', error)}6 onStatusChange={(status) => console.log('Status:', status)}7 onTurnEnd={(chat) => refetchDashboard(chat.id)}8>9 <ChatUI />10</AgentChatProvider>onError on the provider is separate from the client-level onError in inference({ onError }), which handles REST and initial SSE handshake failures.
Configuration Reference
| Option | Type | Default | Scope |
|---|---|---|---|
stream | boolean | true | Client, run(), sendMessage(), Provider |
pollIntervalMs | number | 2000 | Client, run(), sendMessage(), Provider |
maxReconnects | number | 5 | run() when stream: false |
Precedence: Per-call options override provider props, which override client defaults.
Comparison: Streaming vs Polling
| SSE (default) | Polling | |
|---|---|---|
| Latency | Real-time | Up to pollIntervalMs delay |
| Partial updates | Yes (onPartialUpdate) | No (full fetch only) |
| Connection | Long-lived HTTP | Short-lived requests |
| Edge compatible | No | Yes |
| Bandwidth | Lower (deltas only) | Higher (status checks + full fetches) |
Use SSE when you can. Use polling when you must.