@kaana.ai/contracts 8.5.0 → 8.7.0

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.
@@ -95,6 +95,10 @@ export declare const inferenceInputSchema: z.ZodDiscriminatedUnion<[z.ZodObject<
95
95
  }, z.core.$strict>, z.ZodObject<{
96
96
  type: z.ZodLiteral<"refusal">;
97
97
  text: z.ZodString;
98
+ }, z.core.$strict>, z.ZodObject<{
99
+ type: z.ZodLiteral<"reasoning">;
100
+ text: z.ZodOptional<z.ZodString>;
101
+ opaque: z.ZodOptional<z.ZodString>;
98
102
  }, z.core.$strict>], "type">>;
99
103
  name: z.ZodOptional<z.ZodString>;
100
104
  toolCallId: z.ZodOptional<z.ZodString>;
@@ -279,6 +283,10 @@ export declare const inferenceRequestSchema: z.ZodObject<{
279
283
  }, z.core.$strict>, z.ZodObject<{
280
284
  type: z.ZodLiteral<"refusal">;
281
285
  text: z.ZodString;
286
+ }, z.core.$strict>, z.ZodObject<{
287
+ type: z.ZodLiteral<"reasoning">;
288
+ text: z.ZodOptional<z.ZodString>;
289
+ opaque: z.ZodOptional<z.ZodString>;
282
290
  }, z.core.$strict>], "type">>;
283
291
  name: z.ZodOptional<z.ZodString>;
284
292
  toolCallId: z.ZodOptional<z.ZodString>;
@@ -307,11 +315,13 @@ export declare const inferenceRequestSchema: z.ZodObject<{
307
315
  stopSequences: z.ZodOptional<z.ZodArray<z.ZodString>>;
308
316
  }, z.core.$strict>;
309
317
  reasoning: z.ZodOptional<z.ZodObject<{
310
- effort: z.ZodEnum<{
318
+ effort: z.ZodOptional<z.ZodEnum<{
311
319
  low: "low";
312
320
  medium: "medium";
313
321
  high: "high";
314
- }>;
322
+ }>>;
323
+ maxTokens: z.ZodOptional<z.ZodNumber>;
324
+ exclude: z.ZodOptional<z.ZodBoolean>;
315
325
  }, z.core.$strict>>;
316
326
  speech: z.ZodOptional<z.ZodObject<{
317
327
  voice: z.ZodString;
@@ -253,6 +253,7 @@ export declare const inferenceStreamEventSchema: z.ZodDiscriminatedUnion<[z.ZodO
253
253
  output_audio_transcript: "output_audio_transcript";
254
254
  }>;
255
255
  text: z.ZodString;
256
+ opaque: z.ZodOptional<z.ZodString>;
256
257
  }, z.core.$strip>, z.ZodObject<{
257
258
  schemaVersion: z.ZodLiteral<1>;
258
259
  type: z.ZodLiteral<"audio">;
@@ -11,24 +11,50 @@
11
11
  *
12
12
  * ## What is refused, and why
13
13
  *
14
- * - **`cache_control`, anywhere** (on a block, a tool, `system` or the
15
- * request). Kaana's usage units have no unit for a cache WRITE (see the
16
- * Anthropic adapter's usage meter): a cache write is billed at a premium
17
- * over input, so honouring the marker while folding the write into
18
- * `input_tokens` would report less than the request cost. The marker is
19
- * refused, by name and path, instead of being dropped.
20
- * - **`thinking` other than `{ type: 'disabled' }`.** The contract's reasoning
21
- * control is an effort (`low`, `medium`, `high`), not a token budget, and a
22
- * thinking block has no part in a message: reasoning is private and is never
23
- * returned. Reasoning effort is asked with `output_config.effort`, which
24
- * maps one to one. A `thinking` or `redacted_thinking` block in a message is
25
- * refused for the same reason.
14
+ * - **`thinking.display`** and every other `thinking` member this file does
15
+ * not name.
26
16
  * - **Server tools** (`web_search`, `bash`, `text_editor`, …), `mcp_servers`,
27
17
  * `container`, `service_tier`, `disable_parallel_tool_use`, `document`
28
18
  * blocks and any other member this file does not name.
29
19
  * - **An image `url` source**: nothing fetches an image URL (ADR 0007); a
30
20
  * `base64` source is carried inline under the image rules.
31
21
  *
22
+ * ## `cache_control`: accepted and IGNORED
23
+ *
24
+ * Claude Code puts a `cache_control` marker on every request, so refusing it
25
+ * made the endpoint unusable by its main client. The marker is accepted wherever
26
+ * the dialect puts it (the request, `system`, a tool, a content block) and is
27
+ * NOT honoured: Kaana forwards no cache hint, so no cache write happens and the
28
+ * usage it reports stays exactly what the request cost. The only effect is the
29
+ * one the caller asked for and does not get: no prompt-cache discount on a write
30
+ * (reads that a provider caches on its own are still metered as
31
+ * `cache_read_input_tokens`). A request whose marker was ignored is answered
32
+ * with `Kaana-Ignored-Parameters: cache_control`. Honouring it needs a priced
33
+ * cache-write unit (`cache_write_input_tokens`, contract 8.4.0) that nothing
34
+ * emits yet.
35
+ *
36
+ * The same holds for three more members Claude Code sends: `context_management`,
37
+ * `thinking.display` and `tools[].defer_loading`. Each is a hint or a beta that
38
+ * changes how a provider trims, shows or loads, never what a request means or
39
+ * costs; each present one is named in `Kaana-Ignored-Parameters`. Any other
40
+ * member this file does not name is still refused.
41
+ *
42
+ * ## Reasoning (contract set 8.6.0, ADR 0009 decision 2)
43
+ *
44
+ * `thinking: {type: 'enabled', budget_tokens}` is the contract's
45
+ * `reasoning.maxTokens` and `thinking: {type: 'adaptive'}` opts in to reasoning
46
+ * with the route's own sizing; `output_config.effort` is `reasoning.effort`, and
47
+ * a budget beside an effort is refused (the contract treats them as
48
+ * alternatives). A route that cannot take a budget refuses it by name.
49
+ *
50
+ * An answer carries `thinking` blocks before its text and tool calls, and an
51
+ * assistant message sent back carries them again, UNMODIFIED, ahead of its
52
+ * `tool_use` blocks. The block's `signature` is the contract's opaque
53
+ * continuation token (the sealed string a `reasoning` part carries), which is
54
+ * why a stock Anthropic SDK continues a tool turn without knowing anything
55
+ * else: it returns what it received. `redacted_thinking` is accepted on the way
56
+ * in (its `data` is the same token) and never produced.
57
+ *
32
58
  * `stop_reason: 'stop_sequence'` is never produced: the finish reason Kaana
33
59
  * measures does not say WHICH stop sequence ended the turn, and `stop_sequence`
34
60
  * would then have to name one. A turn that ended on a stop sequence reports
@@ -55,14 +81,17 @@ export declare const anthropicMessagesRequestSchema: z.ZodObject<{
55
81
  max_tokens: z.ZodNumber;
56
82
  messages: z.ZodArray<z.ZodObject<{
57
83
  role: z.ZodEnum<{
84
+ system: "system";
58
85
  user: "user";
59
86
  assistant: "assistant";
60
87
  }>;
61
88
  content: z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
62
89
  type: z.ZodLiteral<"text">;
63
90
  text: z.ZodString;
91
+ cache_control: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
64
92
  }, z.core.$strict>, z.ZodObject<{
65
93
  type: z.ZodLiteral<"image">;
94
+ cache_control: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
66
95
  source: z.ZodDiscriminatedUnion<[z.ZodObject<{
67
96
  type: z.ZodLiteral<"base64">;
68
97
  media_type: z.ZodString;
@@ -76,14 +105,17 @@ export declare const anthropicMessagesRequestSchema: z.ZodObject<{
76
105
  id: z.ZodString;
77
106
  name: z.ZodString;
78
107
  input: z.ZodRecord<z.ZodString, z.ZodUnknown>;
108
+ cache_control: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
79
109
  }, z.core.$strict>, z.ZodObject<{
80
110
  type: z.ZodLiteral<"tool_result">;
81
111
  tool_use_id: z.ZodString;
82
112
  content: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
83
113
  type: z.ZodLiteral<"text">;
84
114
  text: z.ZodString;
115
+ cache_control: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
85
116
  }, z.core.$strict>, z.ZodObject<{
86
117
  type: z.ZodLiteral<"image">;
118
+ cache_control: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
87
119
  source: z.ZodDiscriminatedUnion<[z.ZodObject<{
88
120
  type: z.ZodLiteral<"base64">;
89
121
  media_type: z.ZodString;
@@ -94,12 +126,22 @@ export declare const anthropicMessagesRequestSchema: z.ZodObject<{
94
126
  }, z.core.$strict>], "type">;
95
127
  }, z.core.$strict>]>>]>>;
96
128
  is_error: z.ZodOptional<z.ZodBoolean>;
129
+ cache_control: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
130
+ }, z.core.$strict>, z.ZodObject<{
131
+ type: z.ZodLiteral<"thinking">;
132
+ thinking: z.ZodString;
133
+ signature: z.ZodString;
134
+ }, z.core.$strict>, z.ZodObject<{
135
+ type: z.ZodLiteral<"redacted_thinking">;
136
+ data: z.ZodString;
97
137
  }, z.core.$strict>]>>]>;
98
138
  }, z.core.$strict>>;
99
139
  system: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodObject<{
100
140
  type: z.ZodLiteral<"text">;
101
141
  text: z.ZodString;
142
+ cache_control: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
102
143
  }, z.core.$strict>>]>>;
144
+ cache_control: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
103
145
  temperature: z.ZodOptional<z.ZodNumber>;
104
146
  top_p: z.ZodOptional<z.ZodNumber>;
105
147
  top_k: z.ZodOptional<z.ZodNumber>;
@@ -111,6 +153,8 @@ export declare const anthropicMessagesRequestSchema: z.ZodObject<{
111
153
  description: z.ZodOptional<z.ZodString>;
112
154
  input_schema: z.ZodRecord<z.ZodString, z.ZodUnknown>;
113
155
  strict: z.ZodOptional<z.ZodBoolean>;
156
+ cache_control: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
157
+ defer_loading: z.ZodOptional<z.ZodBoolean>;
114
158
  }, z.core.$strict>>>;
115
159
  tool_choice: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
116
160
  type: z.ZodLiteral<"auto">;
@@ -122,9 +166,17 @@ export declare const anthropicMessagesRequestSchema: z.ZodObject<{
122
166
  type: z.ZodLiteral<"tool">;
123
167
  name: z.ZodString;
124
168
  }, z.core.$strict>], "type">>;
125
- thinking: z.ZodOptional<z.ZodObject<{
169
+ thinking: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
126
170
  type: z.ZodLiteral<"disabled">;
127
- }, z.core.$strict>>;
171
+ }, z.core.$strict>, z.ZodObject<{
172
+ type: z.ZodLiteral<"adaptive">;
173
+ display: z.ZodOptional<z.ZodString>;
174
+ }, z.core.$strict>, z.ZodObject<{
175
+ type: z.ZodLiteral<"enabled">;
176
+ budget_tokens: z.ZodNumber;
177
+ display: z.ZodOptional<z.ZodString>;
178
+ }, z.core.$strict>], "type">>;
179
+ context_management: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
128
180
  output_config: z.ZodOptional<z.ZodObject<{
129
181
  effort: z.ZodOptional<z.ZodEnum<{
130
182
  low: "low";
@@ -143,12 +195,18 @@ export declare const anthropicMessageSchema: z.ZodObject<{
143
195
  role: z.ZodLiteral<"assistant">;
144
196
  model: z.ZodString;
145
197
  content: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
198
+ type: z.ZodLiteral<"thinking">;
199
+ thinking: z.ZodString;
200
+ signature: z.ZodString;
201
+ }, z.core.$strict>, z.ZodObject<{
146
202
  type: z.ZodLiteral<"text">;
147
203
  text: z.ZodString;
204
+ cache_control: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
148
205
  }, z.core.$strict>, z.ZodObject<{
149
206
  type: z.ZodLiteral<"tool_use">;
150
207
  id: z.ZodString;
151
208
  name: z.ZodString;
209
+ cache_control: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
152
210
  input: z.ZodRecord<z.ZodString, z.ZodUnknown>;
153
211
  }, z.core.$strict>], "type">>;
154
212
  stop_reason: z.ZodEnum<{
@@ -198,12 +256,18 @@ export declare const anthropicStreamEventSchema: z.ZodDiscriminatedUnion<[z.ZodO
198
256
  role: z.ZodLiteral<"assistant">;
199
257
  model: z.ZodString;
200
258
  content: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
259
+ type: z.ZodLiteral<"thinking">;
260
+ thinking: z.ZodString;
261
+ signature: z.ZodString;
262
+ }, z.core.$strict>, z.ZodObject<{
201
263
  type: z.ZodLiteral<"text">;
202
264
  text: z.ZodString;
265
+ cache_control: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
203
266
  }, z.core.$strict>, z.ZodObject<{
204
267
  type: z.ZodLiteral<"tool_use">;
205
268
  id: z.ZodString;
206
269
  name: z.ZodString;
270
+ cache_control: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
207
271
  input: z.ZodRecord<z.ZodString, z.ZodUnknown>;
208
272
  }, z.core.$strict>], "type">>;
209
273
  stop_reason: z.ZodLiteral<null>;
@@ -218,12 +282,17 @@ export declare const anthropicStreamEventSchema: z.ZodDiscriminatedUnion<[z.ZodO
218
282
  type: z.ZodLiteral<"content_block_start">;
219
283
  index: z.ZodNumber;
220
284
  content_block: z.ZodDiscriminatedUnion<[z.ZodObject<{
285
+ type: z.ZodLiteral<"thinking">;
286
+ thinking: z.ZodLiteral<"">;
287
+ signature: z.ZodLiteral<"">;
288
+ }, z.core.$strict>, z.ZodObject<{
221
289
  type: z.ZodLiteral<"text">;
222
290
  text: z.ZodLiteral<"">;
223
291
  }, z.core.$strict>, z.ZodObject<{
224
292
  type: z.ZodLiteral<"tool_use">;
225
293
  id: z.ZodString;
226
294
  name: z.ZodString;
295
+ cache_control: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
227
296
  input: z.ZodObject<{}, z.core.$strict>;
228
297
  }, z.core.$strict>], "type">;
229
298
  }, z.core.$strict>, z.ZodObject<{
@@ -232,6 +301,12 @@ export declare const anthropicStreamEventSchema: z.ZodDiscriminatedUnion<[z.ZodO
232
301
  delta: z.ZodDiscriminatedUnion<[z.ZodObject<{
233
302
  type: z.ZodLiteral<"text_delta">;
234
303
  text: z.ZodString;
304
+ }, z.core.$strict>, z.ZodObject<{
305
+ type: z.ZodLiteral<"thinking_delta">;
306
+ thinking: z.ZodString;
307
+ }, z.core.$strict>, z.ZodObject<{
308
+ type: z.ZodLiteral<"signature_delta">;
309
+ signature: z.ZodString;
235
310
  }, z.core.$strict>, z.ZodObject<{
236
311
  type: z.ZodLiteral<"input_json_delta">;
237
312
  partial_json: z.ZodString;
@@ -11,24 +11,50 @@
11
11
  *
12
12
  * ## What is refused, and why
13
13
  *
14
- * - **`cache_control`, anywhere** (on a block, a tool, `system` or the
15
- * request). Kaana's usage units have no unit for a cache WRITE (see the
16
- * Anthropic adapter's usage meter): a cache write is billed at a premium
17
- * over input, so honouring the marker while folding the write into
18
- * `input_tokens` would report less than the request cost. The marker is
19
- * refused, by name and path, instead of being dropped.
20
- * - **`thinking` other than `{ type: 'disabled' }`.** The contract's reasoning
21
- * control is an effort (`low`, `medium`, `high`), not a token budget, and a
22
- * thinking block has no part in a message: reasoning is private and is never
23
- * returned. Reasoning effort is asked with `output_config.effort`, which
24
- * maps one to one. A `thinking` or `redacted_thinking` block in a message is
25
- * refused for the same reason.
14
+ * - **`thinking.display`** and every other `thinking` member this file does
15
+ * not name.
26
16
  * - **Server tools** (`web_search`, `bash`, `text_editor`, …), `mcp_servers`,
27
17
  * `container`, `service_tier`, `disable_parallel_tool_use`, `document`
28
18
  * blocks and any other member this file does not name.
29
19
  * - **An image `url` source**: nothing fetches an image URL (ADR 0007); a
30
20
  * `base64` source is carried inline under the image rules.
31
21
  *
22
+ * ## `cache_control`: accepted and IGNORED
23
+ *
24
+ * Claude Code puts a `cache_control` marker on every request, so refusing it
25
+ * made the endpoint unusable by its main client. The marker is accepted wherever
26
+ * the dialect puts it (the request, `system`, a tool, a content block) and is
27
+ * NOT honoured: Kaana forwards no cache hint, so no cache write happens and the
28
+ * usage it reports stays exactly what the request cost. The only effect is the
29
+ * one the caller asked for and does not get: no prompt-cache discount on a write
30
+ * (reads that a provider caches on its own are still metered as
31
+ * `cache_read_input_tokens`). A request whose marker was ignored is answered
32
+ * with `Kaana-Ignored-Parameters: cache_control`. Honouring it needs a priced
33
+ * cache-write unit (`cache_write_input_tokens`, contract 8.4.0) that nothing
34
+ * emits yet.
35
+ *
36
+ * The same holds for three more members Claude Code sends: `context_management`,
37
+ * `thinking.display` and `tools[].defer_loading`. Each is a hint or a beta that
38
+ * changes how a provider trims, shows or loads, never what a request means or
39
+ * costs; each present one is named in `Kaana-Ignored-Parameters`. Any other
40
+ * member this file does not name is still refused.
41
+ *
42
+ * ## Reasoning (contract set 8.6.0, ADR 0009 decision 2)
43
+ *
44
+ * `thinking: {type: 'enabled', budget_tokens}` is the contract's
45
+ * `reasoning.maxTokens` and `thinking: {type: 'adaptive'}` opts in to reasoning
46
+ * with the route's own sizing; `output_config.effort` is `reasoning.effort`, and
47
+ * a budget beside an effort is refused (the contract treats them as
48
+ * alternatives). A route that cannot take a budget refuses it by name.
49
+ *
50
+ * An answer carries `thinking` blocks before its text and tool calls, and an
51
+ * assistant message sent back carries them again, UNMODIFIED, ahead of its
52
+ * `tool_use` blocks. The block's `signature` is the contract's opaque
53
+ * continuation token (the sealed string a `reasoning` part carries), which is
54
+ * why a stock Anthropic SDK continues a tool turn without knowing anything
55
+ * else: it returns what it received. `redacted_thinking` is accepted on the way
56
+ * in (its `data` is the same token) and never produced.
57
+ *
32
58
  * `stop_reason: 'stop_sequence'` is never produced: the finish reason Kaana
33
59
  * measures does not say WHICH stop sequence ended the turn, and `stop_sequence`
34
60
  * would then have to name one. A turn that ended on a stop sequence reports
@@ -46,10 +72,21 @@ import { maxUtf16Length, minUtf16Length } from '../stringBounds.js';
46
72
  import { reasoningEffortSchema } from './catalogue.js';
47
73
  import { modelTargetSchema } from './responses.js';
48
74
  const nonEmpty = (max) => z.string().check(minUtf16Length(1)).check(maxUtf16Length(max));
49
- const textBlockSchema = z.object({ type: z.literal('text'), text: z.string() }).strict();
75
+ /** Accepted and ignored, whatever members it carries (`type`, `ttl`, `scope`): see "`cache_control`" in the file comment. */
76
+ const cacheControlSchema = z.record(z.string(), z.unknown());
77
+ /** Accepted and ignored: how much reasoning text a client wants shown. */
78
+ const thinkingDisplaySchema = z.string();
79
+ const textBlockSchema = z
80
+ .object({
81
+ type: z.literal('text'),
82
+ text: z.string(),
83
+ cache_control: cacheControlSchema.optional(),
84
+ })
85
+ .strict();
50
86
  const imageBlockSchema = z
51
87
  .object({
52
88
  type: z.literal('image'),
89
+ cache_control: cacheControlSchema.optional(),
53
90
  source: z.discriminatedUnion('type', [
54
91
  z
55
92
  .object({
@@ -63,12 +100,28 @@ const imageBlockSchema = z
63
100
  ]),
64
101
  })
65
102
  .strict();
103
+ /**
104
+ * An assistant's reasoning. `thinking` is the readable text (empty when the
105
+ * model returned none); `signature` is the continuation token to return
106
+ * unmodified, empty only for a block that cannot be continued.
107
+ */
108
+ const thinkingBlockSchema = z
109
+ .object({
110
+ type: z.literal('thinking'),
111
+ thinking: z.string(),
112
+ signature: z.string(),
113
+ })
114
+ .strict();
115
+ const redactedThinkingBlockSchema = z
116
+ .object({ type: z.literal('redacted_thinking'), data: nonEmpty(1_000_000) })
117
+ .strict();
66
118
  const toolUseBlockSchema = z
67
119
  .object({
68
120
  type: z.literal('tool_use'),
69
121
  id: nonEmpty(128),
70
122
  name: nonEmpty(128),
71
123
  input: z.record(z.string(), z.unknown()),
124
+ cache_control: cacheControlSchema.optional(),
72
125
  })
73
126
  .strict();
74
127
  const toolResultBlockSchema = z
@@ -83,15 +136,24 @@ const toolResultBlockSchema = z
83
136
  * own content is what the model reads; the flag is not forwarded.
84
137
  */
85
138
  is_error: z.boolean().optional(),
139
+ cache_control: cacheControlSchema.optional(),
86
140
  })
87
141
  .strict();
88
142
  const messageSchema = z
89
143
  .object({
90
- role: z.enum(['user', 'assistant']),
144
+ /** `system` is a mid-conversation instruction Claude Code sends: text blocks only. */
145
+ role: z.enum(['user', 'assistant', 'system']),
91
146
  content: z.union([
92
147
  z.string(),
93
148
  z
94
- .array(z.union([textBlockSchema, imageBlockSchema, toolUseBlockSchema, toolResultBlockSchema]))
149
+ .array(z.union([
150
+ textBlockSchema,
151
+ imageBlockSchema,
152
+ toolUseBlockSchema,
153
+ toolResultBlockSchema,
154
+ thinkingBlockSchema,
155
+ redactedThinkingBlockSchema,
156
+ ]))
95
157
  .min(1),
96
158
  ]),
97
159
  })
@@ -100,9 +162,12 @@ const messageSchema = z
100
162
  if (typeof message.content === 'string')
101
163
  return;
102
164
  for (const [index, block] of message.content.entries()) {
165
+ const reasoning = block.type === 'thinking' || block.type === 'redacted_thinking';
103
166
  const allowed = message.role === 'assistant'
104
- ? block.type === 'text' || block.type === 'tool_use'
105
- : block.type !== 'tool_use';
167
+ ? block.type === 'text' || block.type === 'tool_use' || reasoning
168
+ : message.role === 'system'
169
+ ? block.type === 'text'
170
+ : block.type !== 'tool_use' && !reasoning;
106
171
  if (!allowed) {
107
172
  ctx.addIssue({
108
173
  code: 'custom',
@@ -119,6 +184,9 @@ const toolSchema = z
119
184
  description: z.string().check(maxUtf16Length(32768)).optional(),
120
185
  input_schema: z.record(z.string(), z.unknown()),
121
186
  strict: z.boolean().optional(),
187
+ cache_control: cacheControlSchema.optional(),
188
+ /** Accepted and ignored: Kaana loads every tool it is sent. */
189
+ defer_loading: z.boolean().optional(),
122
190
  })
123
191
  .strict();
124
192
  const toolChoiceSchema = z.discriminatedUnion('type', [
@@ -135,6 +203,7 @@ export const anthropicMessagesRequestSchema = z
135
203
  max_tokens: z.number().int().positive().safe(),
136
204
  messages: z.array(messageSchema).min(1),
137
205
  system: z.union([z.string(), z.array(textBlockSchema)]).optional(),
206
+ cache_control: cacheControlSchema.optional(),
138
207
  temperature: z.number().min(0).max(1).optional(),
139
208
  top_p: z.number().min(0).max(1).optional(),
140
209
  top_k: z.number().int().positive().safe().optional(),
@@ -142,11 +211,28 @@ export const anthropicMessagesRequestSchema = z
142
211
  stream: z.boolean().optional(),
143
212
  tools: z.array(toolSchema).max(128).optional(),
144
213
  tool_choice: toolChoiceSchema.optional(),
145
- /** Only "off" is accepted: see the file comment. */
214
+ /**
215
+ * `disabled` asks for nothing; `adaptive` opts in to reasoning (the
216
+ * contract's `reasoning: {}`); `enabled` sets a token budget
217
+ * (`reasoning.maxTokens`). See the file comment.
218
+ */
146
219
  thinking: z
147
- .object({ type: z.literal('disabled') })
148
- .strict()
220
+ .discriminatedUnion('type', [
221
+ z.object({ type: z.literal('disabled') }).strict(),
222
+ z
223
+ .object({ type: z.literal('adaptive'), display: thinkingDisplaySchema.optional() })
224
+ .strict(),
225
+ z
226
+ .object({
227
+ type: z.literal('enabled'),
228
+ budget_tokens: z.number().int().positive().safe(),
229
+ display: thinkingDisplaySchema.optional(),
230
+ })
231
+ .strict(),
232
+ ])
149
233
  .optional(),
234
+ /** Accepted and ignored (an Anthropic beta Claude Code sends). */
235
+ context_management: z.record(z.string(), z.unknown()).optional(),
150
236
  /** `effort` is the contract's reasoning effort; no other member is carried. */
151
237
  output_config: z.object({ effort: reasoningEffortSchema.optional() }).strict().optional(),
152
238
  /** Accepted and not forwarded or stored. */
@@ -175,6 +261,7 @@ const usageSchema = z
175
261
  .strict();
176
262
  const stopReasonSchema = z.enum(['end_turn', 'max_tokens', 'tool_use', 'refusal']);
177
263
  const outputBlockSchema = z.discriminatedUnion('type', [
264
+ thinkingBlockSchema,
178
265
  textBlockSchema,
179
266
  toolUseBlockSchema.extend({ input: z.record(z.string(), z.unknown()) }),
180
267
  ]);
@@ -243,6 +330,13 @@ export const anthropicStreamEventSchema = z.discriminatedUnion('type', [
243
330
  type: z.literal('content_block_start'),
244
331
  index: z.number().int().nonnegative(),
245
332
  content_block: z.discriminatedUnion('type', [
333
+ z
334
+ .object({
335
+ type: z.literal('thinking'),
336
+ thinking: z.literal(''),
337
+ signature: z.literal(''),
338
+ })
339
+ .strict(),
246
340
  z.object({ type: z.literal('text'), text: z.literal('') }).strict(),
247
341
  toolUseBlockSchema.extend({ input: z.object({}).strict() }),
248
342
  ]),
@@ -254,6 +348,8 @@ export const anthropicStreamEventSchema = z.discriminatedUnion('type', [
254
348
  index: z.number().int().nonnegative(),
255
349
  delta: z.discriminatedUnion('type', [
256
350
  z.object({ type: z.literal('text_delta'), text: z.string() }).strict(),
351
+ z.object({ type: z.literal('thinking_delta'), thinking: z.string() }).strict(),
352
+ z.object({ type: z.literal('signature_delta'), signature: z.string() }).strict(),
257
353
  z.object({ type: z.literal('input_json_delta'), partial_json: z.string() }).strict(),
258
354
  ]),
259
355
  })
@@ -70,6 +70,10 @@ export declare const chatCompletionMessageSchema: z.ZodObject<{
70
70
  }, z.core.$strict>, z.ZodObject<{
71
71
  type: z.ZodLiteral<"refusal">;
72
72
  text: z.ZodString;
73
+ }, z.core.$strict>, z.ZodObject<{
74
+ type: z.ZodLiteral<"reasoning">;
75
+ text: z.ZodOptional<z.ZodString>;
76
+ opaque: z.ZodOptional<z.ZodString>;
73
77
  }, z.core.$strict>], "type">>]>>>;
74
78
  name: z.ZodOptional<z.ZodString>;
75
79
  tool_call_id: z.ZodOptional<z.ZodString>;
@@ -142,6 +146,10 @@ export declare const chatCompletionsRequestSchema: z.ZodObject<{
142
146
  }, z.core.$strict>, z.ZodObject<{
143
147
  type: z.ZodLiteral<"refusal">;
144
148
  text: z.ZodString;
149
+ }, z.core.$strict>, z.ZodObject<{
150
+ type: z.ZodLiteral<"reasoning">;
151
+ text: z.ZodOptional<z.ZodString>;
152
+ opaque: z.ZodOptional<z.ZodString>;
145
153
  }, z.core.$strict>], "type">>]>>>;
146
154
  name: z.ZodOptional<z.ZodString>;
147
155
  tool_call_id: z.ZodOptional<z.ZodString>;
@@ -23,34 +23,44 @@ export declare const inferenceContentSourceSchema: z.ZodDiscriminatedUnion<[z.Zo
23
23
  /**
24
24
  * One part of a message's content. A message is always a list of parts.
25
25
  *
26
- * ## `refusal` is a member and `reasoning` is not, and the asymmetry is the point
26
+ * ## `refusal` and `reasoning` are members, and neither is ever answer text
27
27
  *
28
28
  * A model that DECLINES says why, and those words are meant for the customer:
29
29
  * they are the difference between "rephrase this" and "stop asking". Before this
30
30
  * member existed there was nowhere in an {@link InferenceMessage} to put them, so
31
- * a non-streaming fold kept `finishReason: 'refusal'` and dropped the sentence —
32
- * the customer learned they were refused and not why, while a streaming caller of
33
- * the same request got the whole explanation on the `refusal` delta channel.
31
+ * a non-streaming fold kept `finishReason: 'refusal'` and dropped the sentence.
32
+ * The part is a MEMBER rather than a field because a refusal need not have text:
33
+ * an Anthropic `stop_reason: "refusal"` maps to the finish reason and separates
34
+ * no words from the answer, while an OpenAI-compatible `refusal` does.
34
35
  *
35
- * Reasoning is the opposite case and stays absent. It is the model's private
36
- * working, and a `text` part is where somebody would put it — which renders
37
- * private reasoning to the customer AS the answer, the product bug the delta
38
- * channels exist to prevent. An opaque per-block reasoning blob crossing this
39
- * boundary needs a home nobody has chosen yet, and inventing one here would
40
- * choose it by accident.
36
+ * ## `reasoning` (contract set 8.6.0, Kaana ADR 0009 decision 2)
41
37
  *
42
- * Both public dialects can carry a refusal, which is what separates the two
43
- * cases at the boundary as well as in principle: `refusal` is OpenAI's OWN field
44
- * in both of its shapes (`delta.refusal` streaming, `message.refusal`
45
- * non-streaming), while reasoning has no OpenAI field at all — the
46
- * `reasoning_content`/`reasoning` spellings an OpenAI-compatible provider emits
47
- * are provider extensions. So carrying the refusal costs no dialect its
48
- * standard-client parseability, and carrying reasoning would.
38
+ * Reasoning was deliberately absent until the owner chose its home, because an
39
+ * opaque per-block reasoning blob had none. The cost of that absence was
40
+ * correctness: a thinking model that calls a tool cannot be continued unless the
41
+ * assistant's reasoning blocks, with their signatures, come back to the provider
42
+ * unmodified and in order, and a customer who only ever received reasoning TEXT
43
+ * on the `reasoning` delta channel had nothing to send back.
49
44
  *
50
- * The part is a MEMBER rather than a field because a refusal need not have text:
51
- * an Anthropic `stop_reason: "refusal"` maps to the finish reason and separates
52
- * no words from the answer, while an OpenAI-compatible `refusal` does. A required
53
- * field would force the first provider to invent a sentence.
45
+ * The part is `{type: 'reasoning', text?, opaque?}`:
46
+ *
47
+ * - `text` is what a person may read. It is a display value and is never
48
+ * forwarded to a provider on its own.
49
+ * - `opaque` is a continuation token the customer MUST return UNMODIFIED, in the
50
+ * position it arrived. Its content is Kaana's to define and the customer's to
51
+ * treat as a sealed string: it is provider-neutral on the surface and carries
52
+ * whatever the serving provider needs to continue. Sealing is NOT
53
+ * cryptographic: a forged or edited token is no more than a request the
54
+ * provider refuses, so nothing may rely on it for trust.
55
+ * - at least one of the two is present.
56
+ *
57
+ * A reasoning part is the assistant's alone, exactly as a refusal is. It is
58
+ * never a `text` part, so no renderer can present private reasoning as the
59
+ * answer, and the delta channels keep reasoning apart for the same reason.
60
+ *
61
+ * What a route does with a token it cannot read (it was issued by another
62
+ * provider family, after a failover or a model switch) is drop it, never fail:
63
+ * the continuation is lost, the conversation is not.
54
64
  */
55
65
  export declare const inferenceContentPartSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
56
66
  type: z.ZodLiteral<"text">;
@@ -94,6 +104,10 @@ export declare const inferenceContentPartSchema: z.ZodDiscriminatedUnion<[z.ZodO
94
104
  }, z.core.$strict>, z.ZodObject<{
95
105
  type: z.ZodLiteral<"refusal">;
96
106
  text: z.ZodString;
107
+ }, z.core.$strict>, z.ZodObject<{
108
+ type: z.ZodLiteral<"reasoning">;
109
+ text: z.ZodOptional<z.ZodString>;
110
+ opaque: z.ZodOptional<z.ZodString>;
97
111
  }, z.core.$strict>], "type">;
98
112
  /**
99
113
  * The image formats a request may carry. Every provider that serves image input
@@ -227,6 +241,10 @@ export declare const inferenceMessageSchema: z.ZodObject<{
227
241
  }, z.core.$strict>, z.ZodObject<{
228
242
  type: z.ZodLiteral<"refusal">;
229
243
  text: z.ZodString;
244
+ }, z.core.$strict>, z.ZodObject<{
245
+ type: z.ZodLiteral<"reasoning">;
246
+ text: z.ZodOptional<z.ZodString>;
247
+ opaque: z.ZodOptional<z.ZodString>;
230
248
  }, z.core.$strict>], "type">>;
231
249
  name: z.ZodOptional<z.ZodString>;
232
250
  toolCallId: z.ZodOptional<z.ZodString>;
@@ -280,17 +298,30 @@ export declare const responseFormatSchema: z.ZodDiscriminatedUnion<[z.ZodObject<
280
298
  }, z.core.$strict>], "type">;
281
299
  /**
282
300
  * The caller's reasoning control. Strict, like every leaf: a provider-specific
283
- * knob (`budget_tokens`, `summary`) is refused rather than silently dropped.
301
+ * knob (`summary`) is refused rather than silently dropped.
284
302
  *
285
- * Oxy forwards it only after checking the resolved model advertises the effort
286
- * in its catalogue `reasoningEfforts`; Kaana translates it per provider.
303
+ * - `effort` is how much to reason, in the catalogue's words. Oxy forwards it
304
+ * only after checking the resolved model advertises it in `reasoningEfforts`.
305
+ * - `maxTokens` is a thinking BUDGET in tokens. It is the other way to size
306
+ * reasoning, so it excludes `effort`. A route that cannot take a budget
307
+ * refuses it with `invalid_request` naming `reasoning.maxTokens`.
308
+ * - `exclude: true` means the model may still reason, and be billed for it, but
309
+ * no reasoning is returned: no `reasoning` delta, no reasoning part, no opaque
310
+ * token. A caller that excludes reasoning cannot continue it.
311
+ *
312
+ * Carrying `reasoning` at all, even as `{}`, opts in to receiving reasoning
313
+ * continuation tokens (the `opaque` field of a `reasoning` delta); a request
314
+ * without it never receives one, which is what keeps older readers of the
315
+ * stream unaffected.
287
316
  */
288
317
  export declare const inferenceReasoningSchema: z.ZodObject<{
289
- effort: z.ZodEnum<{
318
+ effort: z.ZodOptional<z.ZodEnum<{
290
319
  low: "low";
291
320
  medium: "medium";
292
321
  high: "high";
293
- }>;
322
+ }>>;
323
+ maxTokens: z.ZodOptional<z.ZodNumber>;
324
+ exclude: z.ZodOptional<z.ZodBoolean>;
294
325
  }, z.core.$strict>;
295
326
  /** Parameters for text-to-speech, preserved in the signed request. */
296
327
  export declare const inferenceSpeechParametersSchema: z.ZodObject<{