@preapexis/piexis 0.2.2 → 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (149) hide show
  1. package/AGENTS.md +21 -41
  2. package/README.md +73 -139
  3. package/extensions/agents.ts +205 -0
  4. package/extensions/ask-question.ts +54 -95
  5. package/extensions/recommended-tools.ts +62 -0
  6. package/extensions/ui.ts +204 -58
  7. package/lib/agent-config.ts +55 -0
  8. package/lib/agents.ts +208 -0
  9. package/lib/question-model.ts +98 -0
  10. package/lib/question-ui.ts +214 -0
  11. package/lib/recommended-tools.ts +119 -0
  12. package/lib/subagent-runner.ts +146 -0
  13. package/lib/ui-tips.json +41 -0
  14. package/package.json +5 -13
  15. package/prompts/init.md +10 -2
  16. package/prompts/summary.md +7 -6
  17. package/schemas/agent.schema.json +48 -0
  18. package/skills/{claude-api/LICENSE.txt → caveman/LICENSE} +2 -2
  19. package/skills/caveman/LICENSE-MIT +21 -0
  20. package/skills/caveman/NOTICE +10 -0
  21. package/skills/caveman/SKILL.md +89 -0
  22. package/skills/find-skills/SKILL.md +141 -0
  23. package/skills/ponytail/LICENSE +21 -0
  24. package/skills/ponytail/NOTICE +9 -0
  25. package/skills/ponytail/SKILL.md +52 -0
  26. package/skills-lock.json +6 -0
  27. package/tests/agents.test.mjs +605 -0
  28. package/tests/ask-question-pty.test.mjs +16 -0
  29. package/tests/ask-question.test.mjs +269 -0
  30. package/tests/fixtures/agents-provider.ts +55 -0
  31. package/tests/fixtures/ask-question-pty.py +91 -0
  32. package/tests/fixtures/ask-question-terminal.mjs +69 -0
  33. package/tests/fixtures/recommended-tools-terminal.mjs +103 -0
  34. package/tests/init-workflow.test.mjs +31 -0
  35. package/tests/recommended-tools-pty.test.mjs +16 -0
  36. package/tests/recommended-tools.test.mjs +423 -0
  37. package/tests/resources.test.mjs +66 -0
  38. package/tests/skills-routing.test.mjs +7 -4
  39. package/tests/ui-tip-reload.test.mjs +70 -0
  40. package/tests/ui.test.mjs +450 -0
  41. package/tsconfig.json +1 -0
  42. package/eval/README.md +0 -72
  43. package/eval/permission-cases.json +0 -28
  44. package/eval/permissions.mjs +0 -103
  45. package/extensions/background-tasks.ts +0 -433
  46. package/extensions/background-worker.ts +0 -50
  47. package/extensions/glob.ts +0 -32
  48. package/extensions/mode.ts +0 -397
  49. package/lib/bash-approvals.ts +0 -113
  50. package/lib/bash-policy.ts +0 -164
  51. package/lib/mode-policy.ts +0 -174
  52. package/lib/path-policy.ts +0 -182
  53. package/lib/permission-classifier.ts +0 -128
  54. package/lib/permission-config.ts +0 -91
  55. package/lib/permission-denials.ts +0 -32
  56. package/lib/permission-intent.ts +0 -128
  57. package/lib/permission-taxonomy.ts +0 -15
  58. package/lib/task-broker.ts +0 -65
  59. package/lib/task-checkout.ts +0 -39
  60. package/lib/task-git.ts +0 -44
  61. package/lib/task-patch.ts +0 -106
  62. package/lib/task-permissions.ts +0 -51
  63. package/lib/task-report.ts +0 -62
  64. package/lib/task-worktree-policy.ts +0 -56
  65. package/skills/claude-api/SKILL.md +0 -572
  66. package/skills/claude-api/csharp/claude-api/README.md +0 -362
  67. package/skills/claude-api/csharp/claude-api/batches.md +0 -14
  68. package/skills/claude-api/csharp/claude-api/files-api.md +0 -25
  69. package/skills/claude-api/csharp/claude-api/streaming.md +0 -28
  70. package/skills/claude-api/csharp/claude-api/tool-use.md +0 -164
  71. package/skills/claude-api/curl/examples.md +0 -256
  72. package/skills/claude-api/curl/managed-agents.md +0 -371
  73. package/skills/claude-api/go/claude-api/README.md +0 -188
  74. package/skills/claude-api/go/claude-api/files-api.md +0 -23
  75. package/skills/claude-api/go/claude-api/streaming.md +0 -43
  76. package/skills/claude-api/go/claude-api/tool-use.md +0 -220
  77. package/skills/claude-api/go/managed-agents/README.md +0 -564
  78. package/skills/claude-api/java/claude-api/README.md +0 -238
  79. package/skills/claude-api/java/claude-api/files-api.md +0 -27
  80. package/skills/claude-api/java/claude-api/streaming.md +0 -24
  81. package/skills/claude-api/java/claude-api/tool-use.md +0 -228
  82. package/skills/claude-api/java/managed-agents/README.md +0 -443
  83. package/skills/claude-api/php/claude-api/README.md +0 -174
  84. package/skills/claude-api/php/claude-api/batches.md +0 -15
  85. package/skills/claude-api/php/claude-api/files-api.md +0 -13
  86. package/skills/claude-api/php/claude-api/streaming.md +0 -27
  87. package/skills/claude-api/php/claude-api/tool-use.md +0 -253
  88. package/skills/claude-api/php/managed-agents/README.md +0 -443
  89. package/skills/claude-api/python/claude-api/README.md +0 -565
  90. package/skills/claude-api/python/claude-api/batches.md +0 -198
  91. package/skills/claude-api/python/claude-api/files-api.md +0 -170
  92. package/skills/claude-api/python/claude-api/sdk-upgrade.md +0 -286
  93. package/skills/claude-api/python/claude-api/streaming.md +0 -179
  94. package/skills/claude-api/python/claude-api/tool-use.md +0 -627
  95. package/skills/claude-api/python/managed-agents/README.md +0 -335
  96. package/skills/claude-api/ruby/claude-api/README.md +0 -134
  97. package/skills/claude-api/ruby/claude-api/streaming.md +0 -16
  98. package/skills/claude-api/ruby/claude-api/tool-use.md +0 -41
  99. package/skills/claude-api/ruby/managed-agents/README.md +0 -394
  100. package/skills/claude-api/shared/admin-api.md +0 -179
  101. package/skills/claude-api/shared/agent-design.md +0 -101
  102. package/skills/claude-api/shared/anthropic-cli.md +0 -269
  103. package/skills/claude-api/shared/claude-platform-on-aws.md +0 -59
  104. package/skills/claude-api/shared/cost-optimization.md +0 -233
  105. package/skills/claude-api/shared/error-codes.md +0 -262
  106. package/skills/claude-api/shared/live-sources.md +0 -166
  107. package/skills/claude-api/shared/managed-agents-api-reference.md +0 -455
  108. package/skills/claude-api/shared/managed-agents-client-patterns.md +0 -220
  109. package/skills/claude-api/shared/managed-agents-core.md +0 -359
  110. package/skills/claude-api/shared/managed-agents-environments.md +0 -226
  111. package/skills/claude-api/shared/managed-agents-events.md +0 -290
  112. package/skills/claude-api/shared/managed-agents-memory.md +0 -201
  113. package/skills/claude-api/shared/managed-agents-multiagent.md +0 -260
  114. package/skills/claude-api/shared/managed-agents-onboarding.md +0 -82
  115. package/skills/claude-api/shared/managed-agents-outcomes.md +0 -108
  116. package/skills/claude-api/shared/managed-agents-overview.md +0 -77
  117. package/skills/claude-api/shared/managed-agents-scheduled-deployments.md +0 -156
  118. package/skills/claude-api/shared/managed-agents-self-hosted-sandboxes.md +0 -282
  119. package/skills/claude-api/shared/managed-agents-tools.md +0 -504
  120. package/skills/claude-api/shared/managed-agents-webhooks.md +0 -143
  121. package/skills/claude-api/shared/model-migration.md +0 -1872
  122. package/skills/claude-api/shared/models.md +0 -142
  123. package/skills/claude-api/shared/platform-availability.md +0 -53
  124. package/skills/claude-api/shared/prompt-audit.md +0 -223
  125. package/skills/claude-api/shared/prompt-caching.md +0 -292
  126. package/skills/claude-api/shared/token-counting.md +0 -56
  127. package/skills/claude-api/shared/tool-use-concepts.md +0 -526
  128. package/skills/claude-api/typescript/claude-api/README.md +0 -405
  129. package/skills/claude-api/typescript/claude-api/batches.md +0 -106
  130. package/skills/claude-api/typescript/claude-api/files-api.md +0 -98
  131. package/skills/claude-api/typescript/claude-api/streaming.md +0 -178
  132. package/skills/claude-api/typescript/claude-api/tool-use.md +0 -586
  133. package/skills/claude-api/typescript/managed-agents/README.md +0 -359
  134. package/tests/auto-permissions.test.mjs +0 -315
  135. package/tests/background-tasks.test.mjs +0 -27
  136. package/tests/background-worker.test.mjs +0 -60
  137. package/tests/bash-permissions.test.mjs +0 -276
  138. package/tests/glob.test.mjs +0 -31
  139. package/tests/mode.test.mjs +0 -62
  140. package/tests/permission-evaluation.test.mjs +0 -70
  141. package/tests/permission-foundation.test.mjs +0 -194
  142. package/tests/permission-policy.test.mjs +0 -158
  143. package/tests/runtime/README.md +0 -51
  144. package/tests/runtime/harness.mjs +0 -190
  145. package/tests/runtime/permissions.test.mjs +0 -192
  146. package/tests/runtime/tui.test.mjs +0 -60
  147. package/tests/task-integration.test.mjs +0 -324
  148. package/tests/task-permissions.test.mjs +0 -178
  149. package/tests/task-worktree-policy.test.mjs +0 -53
@@ -1,565 +0,0 @@
1
- # Claude API - Python
2
-
3
- ## Installation
4
-
5
- ```bash
6
- pip install anthropic
7
- ```
8
-
9
- ## Client Initialization
10
-
11
- ```python
12
- import anthropic
13
-
14
- # Default - resolves credentials from the environment:
15
- # ANTHROPIC_API_KEY, or ANTHROPIC_AUTH_TOKEN, or an `ant auth login` profile.
16
- # Prefer this for local dev; don't hardcode a key.
17
- client = anthropic.Anthropic()
18
-
19
- # Explicit API key (only when you must inject a specific key)
20
- client = anthropic.Anthropic(api_key="your-api-key")
21
-
22
- # Async client
23
- async_client = anthropic.AsyncAnthropic()
24
- ```
25
-
26
- ---
27
-
28
- ## Client Configuration
29
-
30
- ### Per-request overrides
31
-
32
- Use `with_options()` to override client settings for a single call without mutating the client:
33
-
34
- ```python
35
- client.with_options(timeout=5.0, max_retries=5).messages.create(
36
- model="claude-opus-5",
37
- max_tokens=1024,
38
- messages=[{"role": "user", "content": "Hello"}],
39
- )
40
- ```
41
-
42
- ### Timeouts
43
-
44
- Default request timeout is 10 minutes. Pass a float (seconds) or an `anthropic.Timeout` for granular control. On timeout the SDK raises `anthropic.APITimeoutError` (and retries per `max_retries`).
45
-
46
- ```python
47
- client = anthropic.Anthropic(timeout=20.0)
48
- client = anthropic.Anthropic(
49
- timeout=anthropic.Timeout(60.0, read=5.0, write=10.0, connect=2.0),
50
- )
51
- ```
52
-
53
- `anthropic` 1.x is built on [`httpx2`](https://pypi.org/project/httpx2/), not `httpx`. `anthropic.Timeout` is `httpx2.Timeout`; if you import the HTTP library yourself, write `import httpx2 as httpx` - an object from the `httpx` package (`httpx.Timeout`, `httpx.Client`, transports, limits) is rejected or fails at request time. Existing `httpx`-era code is covered by the [v1 migration guide](https://github.com/anthropics/anthropic-sdk-python/blob/main/MIGRATION.md) and `/claude-api upgrade python`.
54
-
55
- ### Retries
56
-
57
- The SDK auto-retries connection errors, 408, 409, 429, and >=500 with exponential backoff (default 2 retries). Set `max_retries` on the client or via `with_options()`; `max_retries=0` disables.
58
-
59
- ### Async performance (aiohttp backend)
60
-
61
- For high-concurrency async workloads, install `anthropic[aiohttp]` and pass `DefaultAioHttpClient` instead of the default httpx2 backend:
62
-
63
- ```python
64
- from anthropic import AsyncAnthropic, DefaultAioHttpClient
65
-
66
- async with AsyncAnthropic(http_client=DefaultAioHttpClient()) as client:
67
- ...
68
- ```
69
-
70
- ### Custom HTTP client (proxy, base URL)
71
-
72
- Use `DefaultHttpxClient` / `DefaultAsyncHttpxClient` - not a raw `httpx2.Client` (and never a client from the `httpx` package) - so the SDK's default timeouts and connection limits are preserved:
73
-
74
- ```python
75
- from anthropic import Anthropic, DefaultHttpxClient
76
-
77
- client = Anthropic(
78
- base_url="http://my.test.server.example.com:8083", # or ANTHROPIC_BASE_URL env var
79
- http_client=DefaultHttpxClient(proxy="http://my.test.proxy.example.com"),
80
- )
81
- ```
82
-
83
- ### Logging
84
-
85
- Set `ANTHROPIC_LOG=debug` (or `info`) to enable SDK logging via the standard `logging` module.
86
-
87
- ---
88
-
89
- ## Basic Message Request
90
-
91
- ```python
92
- response = client.messages.create(
93
- model="claude-opus-5",
94
- max_tokens=16000,
95
- messages=[
96
- {"role": "user", "content": "What is the capital of France?"}
97
- ]
98
- )
99
- # response.content is a list of content block objects (TextBlock, ThinkingBlock,
100
- # ToolUseBlock, ...). Check .type before accessing .text.
101
- for block in response.content:
102
- if block.type == "text":
103
- print(block.text)
104
- ```
105
-
106
- ---
107
-
108
- ## System Prompts
109
-
110
- ```python
111
- response = client.messages.create(
112
- model="claude-opus-5",
113
- max_tokens=16000,
114
- system="You are a helpful coding assistant. Always provide examples in Python.",
115
- messages=[{"role": "user", "content": "How do I read a JSON file?"}]
116
- )
117
- ```
118
-
119
- ### Mid-conversation system messages (model-gated)
120
-
121
- For operator instructions that arrive mid-conversation (mode switches, injected state), append `{"role": "system", ...}` to `messages` instead of editing top-level `system` - this preserves the cached prefix and carries operator authority. Must follow a user message (or an `assistant` message ending in server-tool use), and must be either the last entry in `messages` or be followed by an `assistant` turn; cannot be `messages[0]`. Unsupported models return a 400 (`role 'system' is not supported on this model`). See `shared/prompt-caching.md` for when to use this vs. top-level `system`.
122
-
123
- ```python
124
- response = client.messages.create(
125
- model=MODEL_ID, # must support mid-conversation system messages
126
- max_tokens=16000,
127
- system=[{"type": "text", "text": STABLE_SYSTEM, "cache_control": {"type": "ephemeral"}}],
128
- messages=history + [
129
- {"role": "user", "content": user_message},
130
- {"role": "system", "content": "Terse mode enabled - keep responses under 40 words."},
131
- ],
132
- ) # No beta header needed - use regular client.messages.create
133
- ```
134
-
135
- ---
136
-
137
- ## Vision (Images)
138
-
139
- ### Base64
140
-
141
- ```python
142
- import base64
143
-
144
- with open("image.png", "rb") as f:
145
- image_data = base64.standard_b64encode(f.read()).decode("utf-8")
146
-
147
- response = client.messages.create(
148
- model="claude-opus-5",
149
- max_tokens=16000,
150
- messages=[{
151
- "role": "user",
152
- "content": [
153
- {
154
- "type": "image",
155
- "source": {
156
- "type": "base64",
157
- "media_type": "image/png",
158
- "data": image_data
159
- }
160
- },
161
- {"type": "text", "text": "What's in this image?"}
162
- ]
163
- }]
164
- )
165
- ```
166
-
167
- ### URL
168
-
169
- ```python
170
- response = client.messages.create(
171
- model="claude-opus-5",
172
- max_tokens=16000,
173
- messages=[{
174
- "role": "user",
175
- "content": [
176
- {
177
- "type": "image",
178
- "source": {
179
- "type": "url",
180
- "url": "https://example.com/image.png"
181
- }
182
- },
183
- {"type": "text", "text": "Describe this image"}
184
- ]
185
- }]
186
- )
187
- ```
188
-
189
- ---
190
-
191
- ## Prompt Caching
192
-
193
- Cache large context to reduce costs (up to 90% savings). **Caching is a prefix match** - any byte change anywhere in the prefix invalidates everything after it. For placement patterns, architectural guidance (frozen system prompt, deterministic tool order, where to put volatile content), and the silent-invalidator audit checklist, read `shared/prompt-caching.md`.
194
-
195
- ### Automatic Caching (Recommended)
196
-
197
- Use top-level `cache_control` to automatically cache the last cacheable block in the request - no need to annotate individual content blocks:
198
-
199
- ```python
200
- response = client.messages.create(
201
- model="claude-opus-5",
202
- max_tokens=16000,
203
- cache_control={"type": "ephemeral"}, # auto-caches the last cacheable block
204
- system="You are an expert on this large document...",
205
- messages=[{"role": "user", "content": "Summarize the key points"}]
206
- )
207
- ```
208
-
209
- ### Manual Cache Control
210
-
211
- For fine-grained control, add `cache_control` to specific content blocks:
212
-
213
- ```python
214
- response = client.messages.create(
215
- model="claude-opus-5",
216
- max_tokens=16000,
217
- system=[{
218
- "type": "text",
219
- "text": "You are an expert on this large document...",
220
- "cache_control": {"type": "ephemeral"} # default TTL is 5 minutes
221
- }],
222
- messages=[{"role": "user", "content": "Summarize the key points"}]
223
- )
224
-
225
- # With explicit TTL (time-to-live)
226
- response = client.messages.create(
227
- model="claude-opus-5",
228
- max_tokens=16000,
229
- system=[{
230
- "type": "text",
231
- "text": "You are an expert on this large document...",
232
- "cache_control": {"type": "ephemeral", "ttl": "1h"} # 1 hour TTL
233
- }],
234
- messages=[{"role": "user", "content": "Summarize the key points"}]
235
- )
236
- ```
237
-
238
- ### Verifying Cache Hits
239
-
240
- ```python
241
- print(response.usage.cache_creation_input_tokens) # tokens written to cache (~1.25x cost)
242
- print(response.usage.cache_read_input_tokens) # tokens served from cache (~0.1x cost)
243
- print(response.usage.input_tokens) # uncached tokens (full cost)
244
- ```
245
-
246
- If `cache_read_input_tokens` is zero across repeated identical-prefix requests, a silent invalidator is at work - `datetime.now()` or a UUID in the system prompt, unsorted `json.dumps()`, or a varying tool set. See `shared/prompt-caching.md` for the full audit table.
247
-
248
- ---
249
-
250
- ## Extended Thinking
251
-
252
- > **Fable 5, Claude Opus 5, Opus 4.8, Opus 4.7, Opus 4.6, and Sonnet 4.6:** Use adaptive thinking. `budget_tokens` is removed on Fable 5, Claude Opus 5, Opus 4.8, and 4.7 (400 if sent); deprecated on Opus 4.6 and Sonnet 4.6.
253
- > **Claude Opus 5:** thinking is on by default - omitting `thinking` runs adaptive (`{"type": "adaptive"}` is equivalent), unlike Opus 4.8/4.7 where omitting it meant no thinking. `{"type": "disabled"}` is accepted only at effort `high` or lower; pairing it with `xhigh`/`max` returns a 400.
254
- > **Older models:** Use `thinking: {type: "enabled", budget_tokens: N}` (must be < `max_tokens`, min 1024).
255
-
256
- ```python
257
- # Fable 5 / Claude Opus 5 / Opus 4.8 / 4.7 / 4.6: adaptive thinking (recommended)
258
- response = client.messages.create(
259
- model="claude-opus-5",
260
- max_tokens=16000,
261
- thinking={"type": "adaptive", "display": "summarized"}, # display opt-in: default is omitted (empty thinking text) on Fable 5 / Mythos 5 / Claude Opus 5 / Opus 4.8 / 4.7
262
- output_config={"effort": "high"}, # low | medium | high | xhigh | max
263
- messages=[{"role": "user", "content": "Solve this step by step..."}]
264
- )
265
-
266
- # Access thinking and response
267
- for block in response.content:
268
- if block.type == "thinking":
269
- print(f"Thinking: {block.thinking}")
270
- elif block.type == "text":
271
- print(f"Response: {block.text}")
272
- ```
273
-
274
- ---
275
-
276
- ## Error Handling
277
-
278
- ```python
279
- import anthropic
280
-
281
- try:
282
- response = client.messages.create(...)
283
- except anthropic.BadRequestError as e:
284
- print(f"Bad request: {e.message}")
285
- except anthropic.AuthenticationError:
286
- print("Invalid API key")
287
- except anthropic.PermissionDeniedError:
288
- print("API key lacks required permissions")
289
- except anthropic.NotFoundError:
290
- print("Invalid model or endpoint")
291
- except anthropic.RateLimitError as e:
292
- retry_after = int(e.response.headers.get("retry-after", "60"))
293
- print(f"Rate limited. Retry after {retry_after}s.")
294
- except anthropic.APIStatusError as e:
295
- if e.status_code >= 500:
296
- print(f"Server error ({e.status_code}). Retry later.")
297
- else:
298
- print(f"API error: {e.message}")
299
- except anthropic.APIConnectionError:
300
- print("Network error. Check internet connection.")
301
- ```
302
-
303
- ---
304
-
305
- ## Response Helpers
306
-
307
- Every response object exposes `_request_id` (populated from the `request-id` header) - log it when reporting failures to Anthropic. Despite the underscore prefix, this property is public.
308
-
309
- ```python
310
- message = client.messages.create(...)
311
- print(message._request_id) # req_018EeWyXxfu5pfWkrYcMdjWG
312
- print(message.to_json()) # serialize the Pydantic model
313
- print(message.to_dict()) # plain dict
314
- ```
315
-
316
- To access raw headers or other response metadata, use `.with_raw_response`:
317
-
318
- ```python
319
- raw = client.messages.with_raw_response.create(
320
- model="claude-opus-5",
321
- max_tokens=1024,
322
- messages=[{"role": "user", "content": "Hello"}],
323
- )
324
- print(raw.headers.get("request-id"))
325
- message = raw.parse() # the Message object messages.create() would have returned
326
- ```
327
-
328
- ---
329
-
330
- ## Multi-Turn Conversations
331
-
332
- The API is stateless - send the full conversation history each time.
333
-
334
- ```python
335
- class ConversationManager:
336
- """Manage multi-turn conversations with the Claude API."""
337
-
338
- def __init__(self, client: anthropic.Anthropic, model: str, system: str = None):
339
- self.client = client
340
- self.model = model
341
- self.system = system
342
- self.messages = []
343
-
344
- def send(self, user_message: str, **kwargs) -> str:
345
- """Send a message and get a response."""
346
- self.messages.append({"role": "user", "content": user_message})
347
-
348
- response = self.client.messages.create(
349
- model=self.model,
350
- max_tokens=kwargs.get("max_tokens", 16000),
351
- system=self.system,
352
- messages=self.messages,
353
- **kwargs
354
- )
355
-
356
- assistant_message = next(
357
- (b.text for b in response.content if b.type == "text"), ""
358
- )
359
- self.messages.append({"role": "assistant", "content": assistant_message})
360
-
361
- return assistant_message
362
-
363
- # Usage
364
- conversation = ConversationManager(
365
- client=anthropic.Anthropic(),
366
- model="claude-opus-5",
367
- system="You are a helpful assistant."
368
- )
369
-
370
- response1 = conversation.send("My name is Alice.")
371
- response2 = conversation.send("What's my name?") # Claude remembers "Alice"
372
- ```
373
-
374
- **Rules:**
375
-
376
- - Consecutive same-role messages are allowed - the API combines them into a single turn
377
- - First message must be `user`
378
- - `role: "system"` messages are allowed mid-conversation on supporting models (no beta header needed) - see § Mid-conversation system messages above
379
-
380
- ---
381
-
382
- ### Compaction (long conversations)
383
-
384
- > **Beta, Fable 5, Claude Opus 5, Opus 4.8, Opus 4.7, Opus 4.6, and Sonnet 4.6.** When conversations approach the 200K context window, compaction automatically summarizes earlier context server-side. The API returns a `compaction` block; you must pass it back on subsequent requests - append `response.content`, not just the text.
385
-
386
- ```python
387
- import anthropic
388
-
389
- client = anthropic.Anthropic()
390
- messages = []
391
-
392
- def chat(user_message: str) -> str:
393
- messages.append({"role": "user", "content": user_message})
394
-
395
- response = client.beta.messages.create(
396
- betas=["compact-2026-01-12"],
397
- model="claude-opus-5",
398
- max_tokens=16000,
399
- messages=messages,
400
- context_management={
401
- "edits": [{"type": "compact_20260112"}]
402
- }
403
- )
404
-
405
- # Append full content - compaction blocks must be preserved
406
- messages.append({"role": "assistant", "content": response.content})
407
-
408
- return next(block.text for block in response.content if block.type == "text")
409
-
410
- # Compaction triggers automatically when context grows large
411
- print(chat("Help me build a Python web scraper"))
412
- print(chat("Add support for JavaScript-rendered pages"))
413
- print(chat("Now add rate limiting and error handling"))
414
- ```
415
-
416
- ---
417
-
418
- ## Stop Reasons
419
-
420
- The `stop_reason` field in the response indicates why the model stopped generating:
421
-
422
- | Value | Meaning |
423
- |-------|---------|
424
- | `end_turn` | Claude finished its response naturally |
425
- | `max_tokens` | Hit the `max_tokens` limit - increase it or use streaming |
426
- | `stop_sequence` | Hit a custom stop sequence |
427
- | `tool_use` | Claude wants to call a tool - execute it and continue |
428
- | `pause_turn` | Model paused and can be resumed (agentic flows) |
429
- | `refusal` | Claude refused for safety reasons - check `stop_details` |
430
-
431
- ### Structured Stop Details
432
-
433
- When `stop_reason` is `"refusal"`, the response includes a `stop_details` object with structured information about the refusal:
434
-
435
- ```python
436
- if response.stop_reason == "refusal" and response.stop_details:
437
- print(f"Category: {response.stop_details.category}") # e.g. "cyber", "bio", "reasoning_extraction", "frontier_llm", or None - see docs for the full set
438
- print(f"Explanation: {response.stop_details.explanation}")
439
- ```
440
-
441
- ### Refusal Fallbacks (Claude Fable 5.1) - opt in by default
442
-
443
- Fallbacks are **opt-in**: without them a refused request simply stops. Include the server-side `fallbacks` parameter in `claude-fable-5-1` code by default - on a policy decline the API re-runs the same request on the fallback model inside the same call. A decline before any output isn't billed (a mid-stream decline bills the streamed partial); the rescue bills at the fallback model's own rates, with cache repricing applied automatically.
444
-
445
- ```python
446
- response = client.beta.messages.create(
447
- model="claude-fable-5-1",
448
- max_tokens=16000,
449
- betas=["server-side-fallback-2026-06-01"],
450
- fallbacks=[{"model": "claude-opus-4-8"}],
451
- messages=[{"role": "user", "content": "..."}],
452
- )
453
-
454
- # Switch points: one fallback block per model that ran and declined this turn
455
- for block in response.content:
456
- if block.type == "fallback":
457
- print(f"{block.from_.model} declined; {block.to.model} continued")
458
-
459
- # Served-by signal - covers sticky turns, which carry no fallback block.
460
- # Pair with stop_reason: the fallback model can itself refuse.
461
- fallback_ran = any(
462
- entry.type == "fallback_message" for entry in response.usage.iterations or []
463
- )
464
- if fallback_ran and response.stop_reason != "refusal":
465
- print(f"Served by {response.model}")
466
- ```
467
-
468
- A `stop_reason: "refusal"` on the final response means the whole chain refused. The header must be exactly `server-side-fallback-2026-06-01` **for this array form**; the newer `fallbacks: "default"` scalar form uses `server-side-fallback-2026-07-01` instead (see `shared/model-migration.md` -> Migrating to Claude Opus 5 -> New API features), and pairing either header with the other form returns a 400. The parameter is rejected on the Batches API and unavailable on Amazon Bedrock, Vertex AI, and Microsoft Foundry - register the client-side `BetaRefusalFallbackMiddleware` on the client there instead. Full semantics (sticky routing, billing, streaming, echoing fallback turns back): `shared/model-migration.md` -> Migrating to Claude Fable 5.1 -> `refusal` stop reason.
469
-
470
- ---
471
-
472
- ## Cost Optimization Strategies
473
-
474
- ### 1. Use Prompt Caching for Repeated Context
475
-
476
- ```python
477
- # Automatic caching (simplest - caches the last cacheable block)
478
- response = client.messages.create(
479
- model="claude-opus-5",
480
- max_tokens=16000,
481
- cache_control={"type": "ephemeral"},
482
- system=large_document_text, # e.g., 50KB of context
483
- messages=[{"role": "user", "content": "Summarize the key points"}]
484
- )
485
-
486
- # First request: full cost
487
- # Subsequent requests: ~90% cheaper for cached portion
488
- ```
489
-
490
- ### 2. Choose the Right Model
491
-
492
- ```python
493
- # Default to Opus for most tasks
494
- response = client.messages.create(
495
- model="claude-opus-5", # $5.00/$25.00 per 1M tokens
496
- max_tokens=16000,
497
- messages=[{"role": "user", "content": "Explain quantum computing"}]
498
- )
499
-
500
- # Use Sonnet for high-volume production workloads
501
- standard_response = client.messages.create(
502
- model="claude-sonnet-5", # $2.00/$10.00 per 1M tokens
503
- max_tokens=16000,
504
- messages=[{"role": "user", "content": "Summarize this document"}]
505
- )
506
-
507
- # Use Haiku only for simple, speed-critical tasks
508
- simple_response = client.messages.create(
509
- model="claude-haiku-4-5", # $1.00/$5.00 per 1M tokens
510
- max_tokens=256,
511
- messages=[{"role": "user", "content": "Classify this as positive or negative"}]
512
- )
513
- ```
514
-
515
- ### 3. Use Token Counting Before Requests
516
-
517
- ```python
518
- count_response = client.messages.count_tokens(
519
- model="claude-opus-5",
520
- messages=messages,
521
- system=system
522
- )
523
-
524
- estimated_input_cost = count_response.input_tokens * 0.000005 # $5/1M tokens
525
- print(f"Estimated input cost: ${estimated_input_cost:.4f}")
526
- ```
527
-
528
- ---
529
-
530
- ## Retry with Exponential Backoff
531
-
532
- > **Note:** The Anthropic SDK automatically retries rate limit (429) and server errors (5xx) with exponential backoff. You can configure this with `max_retries` (default: 2). Only implement custom retry logic if you need behavior beyond what the SDK provides.
533
-
534
- ```python
535
- import time
536
- import random
537
- import anthropic
538
-
539
- def call_with_retry(
540
- client: anthropic.Anthropic,
541
- max_retries: int = 5,
542
- base_delay: float = 1.0,
543
- max_delay: float = 60.0,
544
- **kwargs
545
- ):
546
- """Call the API with exponential backoff retry."""
547
- last_exception = None
548
-
549
- for attempt in range(max_retries):
550
- try:
551
- return client.messages.create(**kwargs)
552
- except anthropic.RateLimitError as e:
553
- last_exception = e
554
- except anthropic.APIStatusError as e:
555
- if e.status_code >= 500:
556
- last_exception = e
557
- else:
558
- raise # Client errors (4xx except 429) should not be retried
559
-
560
- delay = min(base_delay * (2 ** attempt) + random.uniform(0, 1), max_delay)
561
- print(f"Retry {attempt + 1}/{max_retries} after {delay:.1f}s")
562
- time.sleep(delay)
563
-
564
- raise last_exception
565
- ```