Skip to main content
No dependencies, types included. It runs on Node 18+, Deno, Bun and Cloudflare Workers. Keep your API key on the server: the SDK isn’t meant for browsers.

Quickstart

run() starts the task and follows it to the end. When the agent asks something, it calls onQuestion and sends back what you return. When the agent wants to do something consequential, it calls onApproval: return true or 'approve' to allow it, false or 'deny' to refuse, or { decision: 'deny', note: 'why' }. If a question or approval comes up and you didn’t pass a handler, run() throws NeedsInputError (with error.task) instead of waiting forever, except for questions and approvals the customer can answer or decide on their screen (below).

Questions and approvals the customer answers

Unless the agent turns it off (customer_answers: false), the agent’s questions also appear on the customer’s screen, and they can answer them there. question.customer_can_answer says whether they can answer the open one. The first answer is used, from them or from you.
  • Without onQuestion, run() leaves those questions to the customer and keeps following the task (pass timeout to give up eventually). It throws NeedsInputError only for a question they can’t answer.
  • With onQuestion, return null to leave one to them. If they answer while your handler works, your answer is refused with a 409 and run() carries on.
  • gh.tasks.respond() after they answered throws ConflictError with extra.code === 'already_answered' and extra.answered_by === 'customer'.
  • The answer event has data.answered_by ('customer' or 'operator'), and the task.question_answered webhook carries the answer and who gave it.
Approvals work the same way. Unless the agent turns it off (customer_approvals: false), the customer can approve or deny the agent’s approval requests on their screen, and approval.customer_can_approve says whether they can decide the open one.
  • Without onApproval, run() leaves those approvals to the customer and keeps following. With onApproval, return null to leave one to them.
  • gh.tasks.respond() after they decided throws ConflictError with extra.code === 'already_answered' and extra.answered_by === 'customer'.
  • The approved and denied events have data.answered_by, and the task.approval_decided webhook carries the decision and who made it.

Client

Reads, deletes and task starts that carry a request_id are retried. Other writes are not.

Resources

listAll() walks every page for you:

Webhooks

verifyWebhook needs the raw body, compares in constant time and rejects deliveries older than 5 minutes ({ tolerance } in seconds). It throws WebhookVerificationError. See Webhooks.

Errors

Every error extends GuidingHandError, with status, type, message and extra: InvalidRequestError (400), AuthenticationError (401), PaymentRequiredError (402), PermissionDeniedError (403), NotFoundError (404), ConflictError (409, for example extra.active_task_id when a task is already running, or extra.code: 'already_answered' with extra.answered_by when someone answered a question or decided an approval first), RateLimitError (429), APIError (5xx), APIConnectionError, TimeoutError, NeedsInputError and SessionExpiredError.