Copilot
client.copilot drives the Copilot assistant's threads and streamed turns. It works only inside a Nodaro app, with a signed-in user's own session.
Available on Nodaro Cloud
client.copilot drives Copilot, Nodaro's assistant for workflows. A thread is a conversation opened on a workflow, and a turn is one message plus everything the assistant does to answer it. The methods call /v1/copilot/*. See Workflow Copilot for the feature.
Copilot works only inside a Nodaro app. Every route refuses a caller that does not carry a signed-in user's own session, with 403 in_app_only. The SDK throws it as ForbiddenError, whose code reads forbidden, so check its message. API tokens and OAuth tokens cannot drive anyone's Copilot. Use it from a browser app with supabaseAuth. It runs on Nodaro Cloud. A deployment with the feature switched off answers 503 feature_disabled when you open a thread or send a turn.
Methods
| Method | What it does |
|---|---|
create(input) | Open a thread on a workflow, or on a new one |
list(params) | Read the active thread of a workflow |
get(id, opts?) | Read a thread with its messages |
archive(id) | Archive a thread |
cancel(id) | Stop the running turn |
stream(threadId, opts) | Send a message and read the turn's frames as they arrive |
These methods return the API's { data } envelope as it arrives, except stream(), which yields frames.
client.copilot
create(input)
Opens a thread. Give an existing workflowId, or a prompt, and the server creates a workflow seeded by it. Opening a workflow that already has an active thread returns that thread instead of a second one.
create(input: { workflowId?: string; prompt?: string; name?: string }): Promise<{
data: { thread: CopilotThread; workflow: CopilotThreadWorkflow }
}>Prop
Type
const { data } = await client.copilot.create({ workflowId })
const threadId = data.thread.idA thread has id, workflowId, runMode (ask, which proposes runs and waits, or auto, which runs within autoRunLimitCredits), modelTier, allowPublishing, userTurnCount, lastMessageAt and createdAt. surface names the assistant the thread belongs to; when it is absent, the deployment did not say.
list(params)
Reads the active thread of a workflow, or null when there is none.
list(params: { workflowId: string }): Promise<{ data: { thread: CopilotThread | null } }>Prop
Type
const { data: { thread } } = await client.copilot.list({ workflowId })get(id, opts?)
Reads a thread with its messages. The thread also carries a derived status (running or idle) and activeTurnId.
get(id: string, opts?: { after?: number; limit?: number }): Promise<{
data: { thread: CopilotThread; messages: CopilotMessage[] }
}>Prop
Type
const { data: { messages } } = await client.copilot.get(threadId, { after: lastSeq })Each message has id, seq, turnId, role, createdAt and parts: text parts and tool-call parts.
archive(id)
Archives a thread. The messages stay readable; nothing is deleted. A thread with a running turn cannot be archived.
archive(id: string): Promise<{ data: { archived: true } }>Prop
Type
await client.copilot.archive(threadId)cancel(id)
Stops the running turn of a thread and answers which turn it asked to stop. That turn's stream ends with a done frame.
cancel(id: string): Promise<{ data: { cancelling: true; turnId: string } }>Prop
Type
await client.copilot.cancel(threadId)stream(threadId, opts)
Sends a message and yields the turn's frames as they arrive, as server-sent events. Render text, tool activity and proposals live instead of waiting for the full answer. It is the only method here that spends credits.
stream(threadId: string, opts: {
message: string
baseVersion?: number
tier?: "economy" | "standard" | "premium"
signal?: AbortSignal
}): AsyncGenerator<CopilotStreamFrame>Prop
Type
try {
for await (const frame of client.copilot.stream(threadId, { message: "Tidy the graph" })) {
if (frame.type === "token") appendText(frame.data.text)
if (frame.type === "run_proposed") await askTheUser(frame.data)
if (frame.type === "done") break
}
} catch (err) {
if ((err as Error).name !== "AbortError") throw err // a stopped turn is a normal ending
}CopilotStreamFrame is a union on type:
| Frame | What it carries |
|---|---|
metadata | The turn's ids, model, runMode, autoRunLimitCredits and settings |
token | A chunk of the answer's text |
tool_call | A tool the assistant uses, started, then finished or failed |
workflow_updated | The nodes added, changed and removed, and the new version |
workflow_created | A workflow the assistant created |
run_proposed | What the turn wants to run, for a person to confirm |
memory_saved | Something the assistant chose to remember |
usage | Tokens used and creditsCharged |
done | The end: completed, capped or cancelled |
error | The turn failed, with a code and a message |
A frame's data is passed through as it is, so fields this SDK does not model still reach you. A frame kind it does not model is skipped instead of throwing, so a newer server cannot break an older client. This version does not model action_proposed, the proposal card of threads on the studio surface, so those proposals do not reach the caller.
The turn's lifetime is yours. The client's timeoutMs does not apply to a turn, because a turn can run for minutes. Pass signal, or stop iterating, which ends the request. Aborting rejects the iteration with the runtime's own AbortError, not a NodaroError, whether it happens before the first frame or between two. An error status on the opening request still throws the usual typed error, before any frame.
Frequently asked questions
Related
Build a workflow with the Copilot
Authentication
Workflows and projects
Last updated on
Pipelines
Start a story-to-video pipeline from TypeScript, follow it stage by stage, approve or reject stages, chat with the director, and read the finished timeline.
Models and credits
List the AI models a Nodaro instance offers with client.models, read your credit balance, and look up the price of a model before you run it.