@polpo-ai/sdk works in React Native out of the box — it uses fetch and EventSource which are available in the React Native runtime.
Install
npm install @polpo-ai/sdk
npm install react-native-sse
Setup
// lib/polpo.ts
import { PolpoClient } from "@polpo-ai/sdk";
export const client = new PolpoClient({
baseUrl: "https://api.polpo.sh",
apiKey: "pk_live_...",
});
Chat screen
import { useState, useCallback } from "react";
import { View, TextInput, FlatList, Text, TouchableOpacity } from "react-native";
import { client } from "../lib/polpo";
interface Message {
role: "user" | "assistant";
content: string;
}
export function ChatScreen() {
const [messages, setMessages] = useState<Message[]>([]);
const [input, setInput] = useState("");
const [streaming, setStreaming] = useState(false);
const send = useCallback(async () => {
if (!input.trim() || streaming) return;
const userMsg: Message = { role: "user", content: input };
const updated = [...messages, userMsg];
setMessages(updated);
setInput("");
setStreaming(true);
try {
const stream = client.chatCompletionsStream({
agent: "assistant",
messages: updated,
});
let content = "";
setMessages(prev => [...prev, { role: "assistant", content: "" }]);
for await (const chunk of stream) {
const delta = chunk.choices[0]?.delta?.content ?? "";
content += delta;
setMessages(prev => [
...prev.slice(0, -1),
{ role: "assistant", content },
]);
}
} catch (err) {
setMessages(prev => [
...prev,
{ role: "assistant", content: "Error: " + (err as Error).message },
]);
} finally {
setStreaming(false);
}
}, [messages, input, streaming]);
return (
<View style={{ flex: 1, padding: 16 }}>
<FlatList
data={messages}
keyExtractor={(_, i) => String(i)}
renderItem={({ item }) => (
<View style={{ marginBottom: 12 }}>
<Text style={{ fontWeight: "bold" }}>
{item.role === "user" ? "You" : "Agent"}
</Text>
<Text>{item.content}</Text>
</View>
)}
/>
<View style={{ flexDirection: "row", gap: 8 }}>
<TextInput
value={input}
onChangeText={setInput}
placeholder="Message..."
style={{ flex: 1, borderWidth: 1, borderColor: "#ccc", padding: 8, borderRadius: 8 }}
onSubmitEditing={send}
/>
<TouchableOpacity onPress={send} disabled={streaming}>
<Text>Send</Text>
</TouchableOpacity>
</View>
</View>
);
}
Agent list
import { useEffect, useState } from "react";
import { View, Text, FlatList, Image } from "react-native";
import { client } from "../lib/polpo";
export function AgentsScreen() {
const [agents, setAgents] = useState([]);
useEffect(() => {
client.getAgents().then(setAgents);
}, []);
return (
<FlatList
data={agents}
keyExtractor={(a) => a.name}
renderItem={({ item }) => (
<View style={{ flexDirection: "row", padding: 16, gap: 12 }}>
{item.identity?.avatar && (
<Image
source={{ uri: item.identity.avatar }}
style={{ width: 40, height: 40, borderRadius: 20 }}
/>
)}
<View>
<Text style={{ fontWeight: "bold" }}>
{item.identity?.displayName ?? item.name}
</Text>
<Text style={{ color: "#666" }}>{item.role}</Text>
</View>
</View>
)}
/>
);
}
SSE events
For real-time updates, usereact-native-sse:
import EventSource from "react-native-sse";
const url = client.getEventsUrl(["task:", "agent:"]);
const es = new EventSource(url, {
headers: { "x-api-key": "pk_live_..." },
});
es.addEventListener("task:transition", (event) => {
const data = JSON.parse(event.data);
console.log("Task moved to:", data.to);
});
// Cleanup
es.close();
Notes
- The
@polpo-ai/reacthooks package usesuseSyncExternalStorewhich works on React Native 0.69+ - For Expo, all APIs work without extra native modules
- File uploads (agent avatars, storage) use standard
fetchwithFormData - Chat streaming uses the Fetch API’s
ReadableStream— supported in React Native 0.71+ (Hermes)
For older React Native versions without
ReadableStream support, use the non-streaming client.chatCompletions() method instead.