Chapter 1 · section 6 of 9
One place for the things that change
You are about to write a second program, and it needs the same things talk.ts needed, plus one more. Before there are two copies of anything, put them in a file of their own.
Listing: ask/setup.ts
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("");
} Two model IDs, because the pipeline runs two kinds of job. The answer a learner reads is written by the most capable model, once per question. The side jobs, which Chapters 6 and 7 build, are short replies that nobody reads: a reworded question, a list of five numbers. They run on the smallest current model, which costs a fortieth as much per token and answers in a fraction of the time.
SIDE_JOB_TIMEOUT_MS is the first design decision in the book, and it is worth knowing why it is here before you know what it is for. A timeout is the longest a program waits for a reply before giving up on it. The SDK’s own default is ten minutes, retried twice, which is right for an overnight job and wrong for a question somebody is waiting on: a side job that hangs would hold the learner’s question for half an hour before failing. Five seconds, no retries, and the pipeline goes on without the side job, which Chapter 10 shows is always possible.
requireApiKey prints the name of the missing variable and exits non-zero, instead of failing later with an AuthenticationError stack trace. The exit code is the small integer a process hands back to whatever started it; zero conventionally means success and anything else failure, which is how the shell, npm, and Chapter 11’s measurement decide whether a run worked.
talk.ts can stay as it is; it is a one-off, and leaving it standalone means your first program depends on nothing but the SDK. Every file from here on imports from setup.ts.