Design Patterns
Common patterns used in OpenPets development.
Plugin Patterns
Basic Plugin Structure
typescript
import { z, createPlugin, createLogger, loadEnv, type ToolDefinition } from "openpets-sdk"
export const MyPet = async () => {
const logger = createLogger("my-pet")
const env = loadEnv("my-pet")
// Check configuration
if (!env.API_KEY) {
logger.warn("Not configured")
return createPlugin([testConnectionTool])
}
// Create tools
const tools: ToolDefinition[] = [
// ... tool definitions
]
return createPlugin(tools)
}
export default MyPetMulti-Provider Pattern
Support multiple backends for the same functionality:
typescript
type Provider = "google" | "mapbox" | "osm"
const getProvider = (env: Record<string, string | undefined>): Provider => {
if (env.GOOGLE_MAPS_KEY) return "google"
if (env.MAPBOX_TOKEN) return "mapbox"
return "osm"
}
export const MapsPet = async () => {
const env = loadEnv("maps")
const provider = getProvider(env)
const geocode = async (address: string) => {
switch (provider) {
case "google": return googleGeocode(address)
case "mapbox": return mapboxGeocode(address)
default: return osmGeocode(address)
}
}
// Use geocode in tools...
}API Client Pattern
Centralize API interactions:
typescript
const createApiClient = (baseUrl: string, apiKey: string) => {
const request = async (endpoint: string, options: RequestInit = {}) => {
const response = await fetch(`${baseUrl}${endpoint}`, {
...options,
headers: {
"Authorization": `Bearer ${apiKey}`,
"Content-Type": "application/json",
...options.headers
}
})
if (!response.ok) {
throw new Error(`API error: ${response.status}`)
}
return response.json()
}
return {
get: (endpoint: string) => request(endpoint),
post: (endpoint: string, data: any) =>
request(endpoint, { method: "POST", body: JSON.stringify(data) }),
put: (endpoint: string, data: any) =>
request(endpoint, { method: "PUT", body: JSON.stringify(data) }),
delete: (endpoint: string) =>
request(endpoint, { method: "DELETE" })
}
}Tool Patterns
CRUD Tool Set
Standard operations for a resource:
typescript
const createCrudTools = (resource: string, api: ApiClient): ToolDefinition[] => [
{
name: `my-pet-list-${resource}s`,
description: `List all ${resource}s`,
schema: z.object({
limit: z.number().optional(),
offset: z.number().optional()
}),
execute: async (args) => {
const data = await api.get(`/${resource}s?limit=${args.limit || 50}`)
return JSON.stringify(data, null, 2)
}
},
{
name: `my-pet-get-${resource}`,
description: `Get a specific ${resource}`,
schema: z.object({ id: z.string() }),
execute: async (args) => {
const data = await api.get(`/${resource}s/${args.id}`)
return JSON.stringify(data, null, 2)
}
},
{
name: `my-pet-create-${resource}`,
description: `Create a new ${resource}`,
schema: z.object({ name: z.string(), description: z.string().optional() }),
execute: async (args) => {
const data = await api.post(`/${resource}s`, args)
return JSON.stringify(data, null, 2)
}
},
// ... update, delete
]Pagination Pattern
Handle paginated APIs:
typescript
{
name: "my-pet-list-items",
schema: z.object({
page: z.number().optional().describe("Page number (default: 1)"),
perPage: z.number().optional().describe("Items per page (default: 50)")
}),
execute: async (args) => {
const page = args.page || 1
const perPage = args.perPage || 50
const data = await api.get(`/items?page=${page}&per_page=${perPage}`)
return JSON.stringify({
items: data.items,
pagination: {
page,
perPage,
total: data.total,
totalPages: Math.ceil(data.total / perPage)
}
}, null, 2)
}
}Search Pattern
Flexible search with filters:
typescript
{
name: "my-pet-search",
schema: z.object({
query: z.string().describe("Search query"),
status: z.enum(["active", "archived", "all"]).optional(),
sortBy: z.enum(["created", "updated", "name"]).optional(),
sortOrder: z.enum(["asc", "desc"]).optional()
}),
execute: async (args) => {
const params = new URLSearchParams({ q: args.query })
if (args.status) params.append("status", args.status)
if (args.sortBy) params.append("sort", args.sortBy)
if (args.sortOrder) params.append("order", args.sortOrder)
const data = await api.get(`/search?${params}`)
return JSON.stringify(data, null, 2)
}
}Error Handling Patterns
Structured Error Response
typescript
const handleError = (error: unknown, context: Record<string, any> = {}) => {
const message = error instanceof Error ? error.message : "Unknown error"
return JSON.stringify({
success: false,
error: message,
...context
}, null, 2)
}
// Usage in execute:
execute: async (args) => {
try {
const data = await api.get(`/items/${args.id}`)
return JSON.stringify({ success: true, data }, null, 2)
} catch (error) {
return handleError(error, { itemId: args.id })
}
}Retry Pattern
typescript
const withRetry = async <T>(
fn: () => Promise<T>,
retries = 3,
delay = 1000
): Promise<T> => {
try {
return await fn()
} catch (error) {
if (retries > 0) {
await new Promise(r => setTimeout(r, delay))
return withRetry(fn, retries - 1, delay * 2)
}
throw error
}
}Caching Patterns
Response Caching
typescript
import { LocalCache } from "openpets-sdk"
const cache = new LocalCache<any>({ ttl: 60000 })
{
name: "my-pet-get-item",
execute: async (args) => {
const cacheKey = `item:${args.id}`
const cached = cache.get(cacheKey)
if (cached) return cached
const data = await api.get(`/items/${args.id}`)
const result = JSON.stringify(data, null, 2)
cache.set(cacheKey, result)
return result
}
}