Skip to content

answerDeclined

JavaScript · package toolnexus · SPEC §10

function answerDeclined(id: string, reason?: "declined" | "cancelled" | "expired"): Answer
// reason defaults to "declined" => { id, ok: false, reason }

Wraps a human’s refusal into the Answer a suspended run resumes with, carrying a reason string that defaults to "declined" — the refusal counterpart to answerOutput. reason is advisory only: the loop rule that decides what happens next branches on Answer.ok, never on reason, so a host never has to enumerate every refusal shape correctly to keep the run behaving. id is required and must match the Request.id the suspension carried.

Use answerDeclined wherever a human refuses, dismisses, or lets a suspended request time out — a login the user closed without completing, an approval they said no to, an input prompt that expired unanswered. Pass a reason ("declined", "cancelled", or "expired") when you can distinguish the cases; omit it when you can’t, since "declined" is a safe, honest default.

1. The smallest useful call — the default reason

Section titled “1. The smallest useful call — the default reason”
import assert from "node:assert"
import { answerDeclined } from "toolnexus"
const answer = answerDeclined("req-1")
assert.deepEqual(answer, { id: "req-1", ok: false, reason: "declined" })
console.log("ok:", JSON.stringify(answer))

2. The realistic case — an explicit reason distinguishing expiry from refusal

Section titled “2. The realistic case — an explicit reason distinguishing expiry from refusal”
import assert from "node:assert"
import { answerDeclined } from "toolnexus"
// A human actively said no.
const declined = answerDeclined("req-1")
assert.equal(declined.reason, "declined")
// The request expired before anyone answered — a different reason, same shape, same `ok:false`.
const expired = answerDeclined("req-1", "expired")
assert.deepEqual(expired, { id: "req-1", ok: false, reason: "expired" })
// A caller that only checks `.ok` needs no reason-specific branch to behave correctly.
for (const a of [declined, expired]) {
assert.equal(a.ok, false)
}
console.log("ok:", declined.reason, expired.reason)

3. The full surface — id is required, and reason is purely advisory

Section titled “3. The full surface — id is required, and reason is purely advisory”
import assert from "node:assert"
import { answerDeclined } from "toolnexus"
// id is required — it must match the Request's id the suspension carried.
assert.throws(() => answerDeclined(""), /id is required/)
// Any reason string is accepted; the loop never branches on its value, only on `ok`.
const cancelled = answerDeclined("req-1", "cancelled")
assert.equal(cancelled.ok, false)
assert.equal(cancelled.reason, "cancelled")
console.log("ok:", cancelled.reason)
  • pending — Return a Pending from a tool to park the run until someone answers.
  • authRequired — The auth-shaped suspension: hand back a URL, resume once the user has granted access.
  • waitFor — The single hook where the host resolves a suspension — in-process prompt or durable queue, same contract.
  • pendingOf — Detect that a RunResult is parked rather than finished, and get the Request that parked it.