Joel Stevick

Appendix B · section 6 of 32

ask/setup.ts

From Talking to a Model.

import type Anthropic from "@anthropic-ai/sdk";

// The one place the model identifiers live. When Anthropic releases a newer
// model, these are the only lines in the whole project that change.
export const ANSWER_MODEL = "claude-opus-5-5";

// The small, fast model the pipeline's side jobs run on: rewording a
// question, ranking a shortlist. Each is a short reply nobody reads, and the
// price is a fortieth of the answering model's.
export const SMALL_MODEL = "claude-haiku-5-5";

// How long a side job may take. The SDK's own default is ten minutes with two
// retries, and a learner's question would wait that long on one hung call.
export const SIDE_JOB_TIMEOUT_MS = 5000;

// Every script from here on calls this before it does anything else.
export function requireApiKey(): void {
  if (!process.env.ANTHROPIC_API_KEY) {
    console.error(
      "ANTHROPIC_API_KEY is not set. Export it and run this again.",
    );
    process.exit(1);
  }
}

// The text of a reply, from whichever blocks carry text. A response can
// carry a thinking block ahead of its text, so `content[0]` is not the answer.
export function textOf(response: Anthropic.Message): string {
  return response.content
    .filter((block): block is Anthropic.TextBlock => block.type === "text")
    .map((block) => block.text)
    .join("");
}