ami-sdk
v0.0.6
Published
ami.dev sdk
Readme
Ami SDK
A TypeScript SDK for authenticating and running Ami agents programmatically.
Installation
pnpm add ami-sdkQuick Start
import {
AMI_BRIDGE_URL,
convertToAmiMessage,
createExchangeToken,
createMinimalEnvironment,
generateId,
getExchangeTokenAuthUrl,
getToken,
listProjects,
runAgentLoop,
} from "ami-sdk";
import type { AmiUIMessage } from "ami-sdk";
const run = async () => {
// 1. Authenticate
const exchangeToken = await createExchangeToken();
const authUrl = getExchangeTokenAuthUrl(exchangeToken);
console.log("Open this URL to authenticate:", authUrl);
const result = await getToken(exchangeToken, (status) => {
console.log("Auth status:", status);
});
if (!result.success || !result.token) {
throw new Error("Authentication failed");
}
const token = result.token;
// 2. Get a project
const projects = await listProjects({ token, limit: 1 });
const project = projects.projects[0];
const cwd = project?.gitRepo ?? "/";
// 3. Set up messages
const messages: AmiUIMessage[] = [
convertToAmiMessage({
id: generateId(),
role: "user",
content: "Hello, what can you help me with?",
}),
];
// 4. Run the agent
const status = await runAgentLoop({
messages,
context: {
environment: createMinimalEnvironment(cwd),
systemContext: [],
attachments: [],
},
url: `${AMI_BRIDGE_URL}/api/v1/agent-proxy`,
chatId: generateId(),
projectId: project?.id ?? "my-project",
token,
upsertMessage: (message) => {
const existingIndex = messages.findIndex((m) => m.id === message.id);
if (existingIndex >= 0) {
messages[existingIndex] = message;
} else {
messages.push(message);
}
console.log("Message updated:", message.id);
},
getMessages: () => messages,
});
console.log("Agent finished with status:", status);
};
run();Complete Browser Example
Here's a full example for browser environments with token persistence and message sanitization:
import {
AMI_BRIDGE_URL,
convertToAmiMessage,
createExchangeToken,
createMinimalEnvironment,
generateId,
getExchangeTokenAuthUrl,
getToken,
listProjects,
runAgentLoop,
} from "ami-sdk";
import type {
AmiUIMessage,
AmiUIMessagePart,
Project,
ToolUIPart,
} from "ami-sdk";
const AUTH_STORAGE_KEY = "ami-auth-token";
// Helper to check if a message part is a tool call
const isToolPart = (part: AmiUIMessagePart): part is ToolUIPart => {
return part.type.startsWith("tool-");
};
// Helper to check if a tool call is complete
const isToolCallComplete = (part: ToolUIPart): boolean => {
return part.state === "output-available" && part.output !== undefined;
};
// Check if a message has incomplete tool calls
const hasIncompleteToolCalls = (message: AmiUIMessage): boolean => {
if (message.role !== "assistant") return false;
const toolParts = message.parts.filter(isToolPart);
if (toolParts.length === 0) return false;
return toolParts.some((part) => !isToolCallComplete(part));
};
// Remove messages with incomplete tool calls from the end
const sanitizeMessages = (messages: AmiUIMessage[]): AmiUIMessage[] => {
const sanitized: AmiUIMessage[] = [];
for (const message of messages) {
if (hasIncompleteToolCalls(message)) {
break;
}
sanitized.push(message);
}
return sanitized;
};
// Token persistence helpers
const getStoredToken = (): string | null => {
try {
return localStorage.getItem(AUTH_STORAGE_KEY);
} catch {
return null;
}
};
const storeToken = (token: string) => {
try {
localStorage.setItem(AUTH_STORAGE_KEY, token);
} catch {}
};
// Authentication flow
const authenticate = async (): Promise<string> => {
const exchangeToken = await createExchangeToken();
const authUrl = getExchangeTokenAuthUrl(exchangeToken);
// Open auth URL in new tab
window.open(authUrl, "_blank");
// Wait for user to authenticate
const result = await getToken(exchangeToken, (status) => {
console.log("Auth status:", status);
});
if (!result.success || !result.token) {
throw new Error("Authentication failed");
}
storeToken(result.token);
return result.token;
};
// Main agent function
interface RunAgentOptions {
prompt: string;
token?: string;
projectId?: string;
}
export const runAgent = async ({
prompt,
token,
projectId,
}: RunAgentOptions) => {
// Get or create auth token
let authToken = token ?? getStoredToken();
if (!authToken) {
authToken = await authenticate();
}
// Resolve project
let project: Project | null = null;
let resolvedProjectId = projectId;
if (!resolvedProjectId) {
const projects = await listProjects({ token: authToken, limit: 1 });
project = projects.projects[0] ?? null;
resolvedProjectId = project?.id ?? "default-project";
}
const cwd = project?.gitRepo ?? "/";
const messages: AmiUIMessage[] = [
convertToAmiMessage({
id: generateId(),
role: "user",
content: prompt,
}),
];
const chatId = generateId();
const loggedTools = new Set<string>();
const upsertMessage = (message: AmiUIMessage) => {
const existingIndex = messages.findIndex((m) => m.id === message.id);
if (existingIndex >= 0) {
messages[existingIndex] = message;
} else {
messages.push(message);
}
// Log tool calls
for (const part of message.parts) {
if (isToolPart(part)) {
const toolKey = `${part.toolCallId}-${isToolCallComplete(part)}`;
if (!loggedTools.has(toolKey)) {
loggedTools.add(toolKey);
const toolName = part.type.replace("tool-", "");
const status = isToolCallComplete(part) ? "✓" : "⏳";
console.log(`[tool ${status}]`, toolName, part.input);
}
}
}
};
const status = await runAgentLoop({
messages: sanitizeMessages(messages),
context: {
environment: createMinimalEnvironment(cwd),
systemContext: [],
attachments: [],
},
url: `${AMI_BRIDGE_URL}/api/v1/agent-proxy`,
chatId,
projectId: resolvedProjectId,
token: authToken,
upsertMessage,
getMessages: () => sanitizeMessages(messages),
});
// Extract text response
const lastAssistantMessage = messages.findLast((m) => m.role === "assistant");
const textResponse = lastAssistantMessage?.parts
.filter((p) => p.type === "text")
.map((p) => p.text)
.join("");
return { status, messages, project, textResponse };
};
// Usage
runAgent({ prompt: "What files are in the current directory?" })
.then(({ status, textResponse }) => {
console.log("Status:", status);
console.log("Response:", textResponse);
})
.catch(console.error);React Hook Example
Here's how to use the SDK in a React application:
import {
AMI_BRIDGE_URL,
convertToAmiMessage,
createMinimalEnvironment,
generateId,
listProjects,
runAgentLoop,
} from "ami-sdk";
import { useEffect, useRef, useState } from "react";
import type { AmiUIMessage, Project } from "ami-sdk";
interface UseAgentOptions {
token: string;
onError?: (error: unknown) => void;
}
export const useAgent = ({ token, onError }: UseAgentOptions) => {
const [messages, setMessages] = useState<AmiUIMessage[]>([]);
const [isLoading, setIsLoading] = useState(false);
const [project, setProject] = useState<Project | null>(null);
const chatIdRef = useRef<string>(generateId());
const messagesRef = useRef<AmiUIMessage[]>(messages);
messagesRef.current = messages;
useEffect(() => {
listProjects({ token, limit: 1 })
.then((response) => {
const latestProject = response.projects[0];
if (latestProject) {
setProject(latestProject);
}
})
.catch(console.error);
}, [token]);
const upsertMessage = (message: AmiUIMessage) => {
setMessages((prev) => {
const existingIndex = prev.findIndex((m) => m.id === message.id);
if (existingIndex >= 0) {
const updated = [...prev];
updated[existingIndex] = message;
return updated;
}
return [...prev, message];
});
};
const sendMessage = async (content: string) => {
const userMessage = convertToAmiMessage({
id: generateId(),
role: "user",
content: content.trim(),
});
const newMessages = [...messagesRef.current, userMessage];
setMessages(newMessages);
messagesRef.current = newMessages;
setIsLoading(true);
try {
const cwd = project?.gitRepo ?? "/";
await runAgentLoop({
messages: newMessages,
context: {
environment: createMinimalEnvironment(cwd),
systemContext: [],
attachments: [],
},
url: `${AMI_BRIDGE_URL}/api/v1/agent-proxy`,
chatId: chatIdRef.current,
projectId: project?.id ?? "default-project",
token,
upsertMessage,
getMessages: () => messagesRef.current,
});
} catch (error) {
onError?.(error);
} finally {
setIsLoading(false);
}
};
return { messages, isLoading, project, sendMessage };
};API Reference
Authentication
createExchangeToken()
Creates a new exchange token for authentication.
const exchangeToken = await createExchangeToken();getExchangeTokenAuthUrl(exchangeToken)
Returns the authentication URL for a given exchange token.
const url = getExchangeTokenAuthUrl(exchangeToken);
// "https://app.ami.dev/daemon/auth?token=..."getToken(exchangeToken, onStatusChange, maxAttempts?)
Polls for token verification until success or timeout.
const result = await getToken(
exchangeToken,
(status) => console.log(status), // "polling" | "success" | "expired" | "timeout"
60, // max attempts (default: 60)
);
if (result.success) {
console.log(result.token); // JWT token for API requests
console.log(result.bridgeToken); // Optional bridge token
}verifyExchangeToken(exchangeToken)
Checks if an exchange token has been verified.
const result = await verifyExchangeToken(exchangeToken);
if (result.verified) {
console.log(result.token);
}Agent Loop
runAgentLoop(options)
Runs the agent loop, handling streaming responses and tool execution.
const status = await runAgentLoop({
messages,
context: {
environment: createMinimalEnvironment(cwd),
systemContext: [],
attachments: [],
},
url: `${AMI_BRIDGE_URL}/api/v1/agent-proxy`,
chatId: generateId(),
projectId: "my-project",
token: authToken,
upsertMessage: (message) => {
// Handle message updates
},
getMessages: () => messages,
});Options:
| Option | Type | Description |
| --------------- | -------------------------------------------------- | ------------------------------- |
| messages | AmiUIMessage[] | Initial message history |
| context | AgentContext | Agent context with environment |
| url | string | Agent proxy endpoint URL |
| chatId | string | Unique chat session ID |
| projectId | string | Project identifier |
| token | string | Authentication token |
| upsertMessage | (message: AmiUIMessage) => void \| Promise<void> | Updates or inserts a message |
| getMessages | () => AmiUIMessage[] | Returns current message history |
Returns: Promise<"completed" | "aborted" | "error">
Projects
listProjects(options)
Lists user's projects.
const response = await listProjects({
token: authToken,
limit: 10,
cursor: 0,
});
console.log(response.projects); // Project[]getProject(options)
Gets a specific project by ID.
const project = await getProject({
token: authToken,
projectId: "project-id",
});getProjectByGitRepo(options)
Finds a project by git repository URL.
const project = await getProjectByGitRepo({
token: authToken,
gitRepoUrl: "https://github.com/user/repo",
});Message Utilities
convertToAmiMessage(message)
Converts a simple message to the Ami message format.
const amiMessage = convertToAmiMessage({
id: generateId(),
role: "user",
content: "Hello!",
});generateId()
Generates a unique message ID.
const id = generateId(); // UUID stringcreateMinimalEnvironment(cwd)
Creates a minimal agent environment configuration.
const env = createMinimalEnvironment("/path/to/project");Returns: AgentEnvironment
interface AgentEnvironment {
cwd: string;
homeDir: string;
workingDirectory: string;
isGitRepo: boolean;
platform: string;
osVersion: string;
today: string;
isCodeServerAvailable: boolean;
rules: {
agents: string | null;
claude: string | null;
gemini: string | null;
cursor: string | null;
};
}Utilities
delay(ms)
Promise-based delay utility.
await delay(1000); // wait 1 secondTypes
AmiUIMessage
interface AmiUIMessage {
id: string;
role: "user" | "assistant" | "system";
parts: AmiUIMessagePart[];
createdAt?: Date;
metadata?: AmiMessageMetadata;
}AmiUIMessagePart
type AmiUIMessagePart =
| { type: "text"; text: string }
| { type: "reasoning"; text: string }
| {
type: "tool-invocation";
toolCallId: string;
toolName: string;
state: string;
input?: unknown;
output?: unknown;
};
// ... and moreToolUIPart
type ToolUIPart = Extract<AmiUIMessagePart, { type: `tool-${string}` }>;Project
interface Project {
id: string;
title: string;
gitRepo: string | null;
// ... additional fields
}AgentContext
interface AgentContext {
environment: AgentEnvironment;
systemContext: Array<unknown>;
attachments: Array<unknown>;
}Constants
import {
AMI_APP_BASE_URL, // "https://app.ami.dev" (production) or "http://localhost:3000" (dev)
AMI_APP_TRPC_ENDPOINT, // `${AMI_APP_BASE_URL}/api/v1/trpc`
AMI_BRIDGE_URL, // "https://bridge.ami.dev" (production) or "http://localhost:64685" (dev)
IS_IN_BROWSER, // true if running in browser
MAX_VERIFICATION_ATTEMPTS, // 60
VERIFICATION_INTERVAL_MS, // 1000
} from "ami-sdk";License
MIT
