@tipsy-studio/sdk
v0.0.16
Published
React sdk for Tipsy Studio apps.
Readme
@tipsy-studio/sdk
SDK for Tipsy Studio iframe apps.
Install
npm install @tipsy-studio/sdk reactUsage
Plain JavaScript and TypeScript clients are available from the package root.
Non-stream text
import { createTipsyStudioClient } from "@tipsy-studio/sdk";
const client = createTipsyStudioClient();
await client.waitForReady();
const response = await client.respond<string>({
messages: [
{
role: "system",
content: "You are a helpful assistant.",
},
{
role: "user",
content: "Write a short greeting.",
},
],
stream: false,
response_format: { type: "text" },
});
console.log(response.data.result);Stream text
import { createTipsyStudioClient } from "@tipsy-studio/sdk";
const client = createTipsyStudioClient();
let text = "";
const result = await client.respond<string>(
{
messages: [
{
role: "system",
content: "You are a helpful assistant.",
},
{
role: "user",
content: "Write a short greeting.",
},
],
stream: true,
response_format: { type: "text" },
},
{
onEvent(event) {
if ("delta" in event) {
text += event.delta;
}
},
},
);
console.log(text);
console.log(result.result);Non-stream JSON Schema
import { createTipsyStudioClient } from "@tipsy-studio/sdk";
type UserProfile = {
name: string;
age: number;
email: string;
};
const client = createTipsyStudioClient();
const response = await client.respond<UserProfile>({
messages: [
{
role: "system",
content: "Return only JSON that matches the schema.",
},
{
role: "user",
content: "Extract a user from: Alice is 28 and [email protected]",
},
],
stream: false,
response_format: {
type: "json_schema",
name: "UserProfile",
schema: {
type: "object",
additionalProperties: false,
properties: {
name: { type: "string" },
age: { type: "integer" },
email: { type: "string" },
},
required: ["name", "age", "email"],
},
},
});
console.log(response.data.result.email);Stream JSON Schema
import { createTipsyStudioClient } from "@tipsy-studio/sdk";
type UserProfile = {
name: string;
age: number;
email: string;
};
const client = createTipsyStudioClient();
let snapshot: Partial<UserProfile> = {};
const result = await client.respond<UserProfile>(
{
messages: [
{
role: "system",
content: "Return only JSON that matches the schema.",
},
{
role: "user",
content: "Extract a user from: Alice is 28 and [email protected]",
},
],
stream: true,
response_format: {
type: "json_schema",
name: "UserProfile",
schema: {
type: "object",
additionalProperties: false,
properties: {
name: { type: "string" },
age: { type: "integer" },
email: { type: "string" },
},
required: ["name", "age", "email"],
},
},
},
{
onEvent(event) {
if ("snapshot" in event) {
snapshot = event.snapshot;
}
},
},
);
console.log(snapshot);
console.log(result.result);Character Chat (session-based)
Use characterRespond() for characters with a backend character_id. The server manages sessions and message history.
import { createTipsyStudioClient } from "@tipsy-studio/sdk";
const client = createTipsyStudioClient();
await client.waitForReady();
// Find existing session for a character
const sessions = await client.listSessions();
const session = sessions.data.sessions.find(
(s) => s.character_id === "YOUR_CHARACTER_ID",
);
// Send message
let text = "";
const result = await client.characterRespond(
{
character_id: "YOUR_CHARACTER_ID",
session_id: session?.session_id, // omit on first call to create a new session
content: "Hello!",
environment: [
"Scene: school infirmary",
"Time: after class",
"Player status: HP 5/20",
"Relationship: worried but guarded",
].join("\n"),
stream: true,
response_format: { type: "text" },
},
{
onEvent(event) {
if ("delta" in event) {
text += event.delta;
}
},
},
);
console.log(text);
// result.session_id — save for subsequent calls
// result.result — final textenvironment is optional per-turn game/runtime context. Keep it relevant to the current character and scene; do not send full saves or large logs. Message history returns stored environment by default for developer/debug access, but normal player-facing chat UIs should not render it unless that is a deliberate product design.
Operator Resolve
Use operatorResolve() when you want the built app to evaluate a project operator as game logic instead of free-form chat.
If the app is running inside the built-in Tipsy host iframe bridge, the host fills the current project_id automatically.
If you call the SDK route directly outside that host flow, pass the project's snowflake project_id yourself.
import { createTipsyStudioClient } from "@tipsy-studio/sdk";
type OperatorResult = {
operator_key: string;
should_trigger: boolean;
reason: string;
confidence?: number;
action: Record<string, unknown> | null;
};
const client = createTipsyStudioClient();
await client.waitForReady();
const result = await client.operatorResolve<OperatorResult>({
operator_key: "relationship_judge",
// Optional inside the built-in Tipsy host bridge; required for direct calls outside it.
project_id: "9001",
input: "The player comforted Mina and chose to stay with her in the storm.",
contexts: [
{
name: "game_state",
content: JSON.stringify({ phase: "scene_2", trust: 12 }),
},
{
name: "recent_dialogue",
content: "Mina said she did not want to be abandoned again.",
},
],
});
console.log(result.data.result.should_trigger);
console.log(result.data.result.action);Load message history
const history = await client.listMessages({
session_id: "SESSION_ID",
limit: 50,
});
// history.data.messages — newest first
// history.data.has_more / history.data.cursor — for paginationList sessions
const sessions = await client.listSessions();
// sessions.data.sessions — array of session objects:
// { session_id, project_id, character_id, status, message_count, title, last_message_at }
// Sessions with character_id === null have no character bindingReset chat
// Restart the current game timeline and clear current chat sessions
await client.resetChat({
project_id: "PROJECT_ID",
});
// response.data.reset === trueReact-specific exports remain available from the /react subpath.
import { TipsyStudioProvider, useTipsyStudio } from "@tipsy-studio/sdk/react";
function ChatButton() {
const studio = useTipsyStudio();
async function handleClick() {
let text = "";
const result = await studio.respond<string>(
{
messages: [
{
role: "system",
content: "You are a helpful assistant.",
},
{
role: "user",
content: "Write a short greeting.",
},
],
stream: true,
response_format: { type: "text" },
},
{
onEvent(event) {
if ("delta" in event) {
text += event.delta;
}
},
},
);
console.log(text);
console.log(result.result);
}
return <button onClick={handleClick}>Run chat</button>;
}
export function Demo() {
return (
<TipsyStudioProvider>
<ChatButton />
</TipsyStudioProvider>
);
}If the parent page uses a fixed allowlist, pass targetOrigin explicitly:
<TipsyStudioProvider targetOrigin="https://tipsy.chat">
<App />
</TipsyStudioProvider>