@animated-waffle/server
v0.3.0
Published
Server-side TypeScript SDK for the Animated Waffle /v1 API.
Downloads
449
Readme
@animated-waffle/server
Typed server-side access to the Animated Waffle /v1 API. This package uses
the runtime's native fetch, has no runtime dependencies, and must not be
bundled into a browser because it holds a server credential: either a workspace
key or a revocable Agent-authoring key. It requires Node.js 20.3 or newer.
pnpm add @animated-waffle/serverSetup
import { WaffleServer } from '@animated-waffle/server'
const waffle = new WaffleServer({
apiKey: process.env.ANIMATED_WAFFLE_API_KEY!,
})Production is the default API origin. Tests and non-production integrations
may set apiOrigin or inject fetch.
Browser session tokens
Keep the workspace key on the server and return only the short-lived token to the browser:
const grant = await waffle.createSessionToken({
agentId: '22222222-2222-4222-8222-222222222222',
endUserId: '55555555-5555-4555-8555-555555555555',
})endUserId is one stable UUID for the product's end user. It is required when
the published Agent uses memory, Calendar, or documents.
Developer Feedback grants
Developer Feedback is a creator-only development path. After the browser has
an actual persisted session.id, a creator-authenticated backend may request a
five-minute grant for that exact Agent Session:
const grant = await waffle.createDeveloperFeedbackGrant({
agentId,
sourceSessionId,
})
return Response.json({
endpoint: grant.endpoint,
token: grant.token,
expires_at: grant.expiresAt,
source_session_id: grant.sourceSessionId,
})The backend must authenticate the caller as the creator represented by its
user-bound workspace key before calling this method. Never put the awp_ key
in browser code. An ordinary Agent session token cannot request or refresh a
feedback grant.
Local Character Director
Use the separate @animated-waffle/cli package for interactive
local-Agent login and the waffle-agent executable. This package only owns the
typed server SDK.
The SDK and CLI accept the same camelCase authoring data. A complete SDK
workflow is:
const feedbackId = crypto.randomUUID()
let round = await waffle.addAgentAuthoringFeedback({
sourceSessionId,
feedback: {
id: feedbackId,
scope: 'message',
targetMessageId: assistantMessageId,
action: 'change',
dimension: 'instructions',
note: 'Sound warmer.',
},
})
round = await waffle.submitAgentAuthoringRound(round.id, round.lockVersion)
round = await waffle.saveAgentAuthoringCandidate(round.id, round.lockVersion, {
updatedPersona: round.baseRevision.persona,
updatedPrompt: 'Answer warmly in one concise sentence.',
summary: 'Added warmth while preserving brevity.',
changes: [{ area: 'instructions', summary: 'Added warmth.' }],
preserved: ['One concise sentence.'],
conflicts: [],
outsidePrompt: [],
preserveScenario: {
label: 'Preserve brevity',
userMessage: 'Summarize your day.',
expectedBehavior: 'Answer in one sentence.',
},
addressedFeedbackIds: [feedbackId],
unresolvedFeedback: [],
})
round = await waffle.replayAgentAuthoringCandidate(
round.id,
round.lockVersion,
round.proposal!.candidateHash,
)
round = await waffle.createAgentAuthoringDraft(
round.id,
round.lockVersion,
round.proposal!.candidateHash,
)
console.log(round.dashboardUrl)feedback.id is a caller-owned idempotency key: generate one UUID for each
feedback item. If the response is ambiguous, retry with the same ID and exact
payload; reusing the ID for different feedback returns 409. Candidate, replay,
and draft writes use the latest lockVersion; after an ambiguous result, reread
the Round and reconcile instead of retrying blindly. An ambiguous submit may
reuse its original Round and lock: once submitted, it returns the current later
Round state without another mutation. Run
the CLI help for every JSON shape.
The repository workflow for Codex and Claude-style Agents lives at
.agents/skills/animated-waffle-character-director/SKILL.md. The CLI can browse
authorized Sessions, manage feedback, save/replay a Candidate, and create an
immutable draft. It has no publish or delete command.
Messages and documents
Message and document writes require a caller-owned idempotency key:
const idempotencyKey = crypto.randomUUID()
const turn = await waffle.sendMessage({
agentId,
endUserId,
text: 'What is on my list?',
userName: 'Space',
timeZone: 'Asia/Shanghai',
idempotencyKey,
})If the caller loses the response and retries the same logical write, it must
reuse the same key. Generate a new key for a new write. The SDK deliberately
does not retry requests, so it never guesses whether two writes are the same.
The same rule applies to createDocument, updateDocument, and
deleteDocument.
Agent drafts and publication
createAgent and updateAgent save a draft. They never publish implicitly:
const draft = await waffle.updateAgent(agentId, {
instructions: 'Keep answers concise.',
})
const live = await waffle.publishAgent(draft.id)Errors and deadlines
Every SDK-originated failure is a WaffleServerError. HTTP failures preserve
the API's code, retryable, and request_id as requestId. Transport errors
have no status; use code === 'request_timeout' or code === 'network_error'.
import { WaffleServerError } from '@animated-waffle/server'
try {
await waffle.getAgent(agentId, { timeoutMs: 5_000 })
} catch (error) {
if (error instanceof WaffleServerError) {
console.error(error.code, error.status, error.requestId)
}
}Ordinary requests default to 20 seconds; sendMessage defaults to 120 seconds.
Every method accepts { signal, timeoutMs } as its final argument.
API surface
- Catalog:
listVoices,listAvatars. - Agents:
listAgents,getAgent,createAgent,updateAgent,publishAgent. - Shared documents:
ensureDocumentSet. - Sessions:
createSessionToken,createDeveloperFeedbackGrant,createRealtimeTranscriptionToken. - Local Character Director:
getAgentAuthoringStatus, Session/Round browse, feedback Round mutation, Candidate save/replay, and draft creation. - Conversation:
sendMessage,listMessages. - Documents:
listDocuments,listAllDocuments,getDocument,createDocument,updateDocument,deleteDocument. - Calendar:
createCalendarAuthorization,getCalendarConnection.
Public inputs and responses use camelCase; the SDK owns conversion to the
API's current wire format. There is intentionally no raw-request escape hatch.
