@kaana.ai/contracts 8.5.0 → 8.6.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">;
@@ -17,18 +17,30 @@
17
17
  * over input, so honouring the marker while folding the write into
18
18
  * `input_tokens` would report less than the request cost. The marker is
19
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.
20
+ * - **`thinking.display`** and every other `thinking` member this file does
21
+ * not name.
26
22
  * - **Server tools** (`web_search`, `bash`, `text_editor`, …), `mcp_servers`,
27
23
  * `container`, `service_tier`, `disable_parallel_tool_use`, `document`
28
24
  * blocks and any other member this file does not name.
29
25
  * - **An image `url` source**: nothing fetches an image URL (ADR 0007); a
30
26
  * `base64` source is carried inline under the image rules.
31
27
  *
28
+ * ## Reasoning (contract set 8.6.0, ADR 0009 decision 2)
29
+ *
30
+ * `thinking: {type: 'enabled', budget_tokens}` is the contract's
31
+ * `reasoning.maxTokens` and `thinking: {type: 'adaptive'}` opts in to reasoning
32
+ * with the route's own sizing; `output_config.effort` is `reasoning.effort`, and
33
+ * a budget beside an effort is refused (the contract treats them as
34
+ * alternatives). A route that cannot take a budget refuses it by name.
35
+ *
36
+ * An answer carries `thinking` blocks before its text and tool calls, and an
37
+ * assistant message sent back carries them again, UNMODIFIED, ahead of its
38
+ * `tool_use` blocks. The block's `signature` is the contract's opaque
39
+ * continuation token (the sealed string a `reasoning` part carries), which is
40
+ * why a stock Anthropic SDK continues a tool turn without knowing anything
41
+ * else: it returns what it received. `redacted_thinking` is accepted on the way
42
+ * in (its `data` is the same token) and never produced.
43
+ *
32
44
  * `stop_reason: 'stop_sequence'` is never produced: the finish reason Kaana
33
45
  * measures does not say WHICH stop sequence ended the turn, and `stop_sequence`
34
46
  * would then have to name one. A turn that ended on a stop sequence reports
@@ -94,6 +106,13 @@ export declare const anthropicMessagesRequestSchema: z.ZodObject<{
94
106
  }, z.core.$strict>], "type">;
95
107
  }, z.core.$strict>]>>]>>;
96
108
  is_error: z.ZodOptional<z.ZodBoolean>;
109
+ }, z.core.$strict>, z.ZodObject<{
110
+ type: z.ZodLiteral<"thinking">;
111
+ thinking: z.ZodString;
112
+ signature: z.ZodString;
113
+ }, z.core.$strict>, z.ZodObject<{
114
+ type: z.ZodLiteral<"redacted_thinking">;
115
+ data: z.ZodString;
97
116
  }, z.core.$strict>]>>]>;
98
117
  }, z.core.$strict>>;
99
118
  system: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodObject<{
@@ -122,9 +141,14 @@ export declare const anthropicMessagesRequestSchema: z.ZodObject<{
122
141
  type: z.ZodLiteral<"tool">;
123
142
  name: z.ZodString;
124
143
  }, z.core.$strict>], "type">>;
125
- thinking: z.ZodOptional<z.ZodObject<{
144
+ thinking: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
126
145
  type: z.ZodLiteral<"disabled">;
127
- }, z.core.$strict>>;
146
+ }, z.core.$strict>, z.ZodObject<{
147
+ type: z.ZodLiteral<"adaptive">;
148
+ }, z.core.$strict>, z.ZodObject<{
149
+ type: z.ZodLiteral<"enabled">;
150
+ budget_tokens: z.ZodNumber;
151
+ }, z.core.$strict>], "type">>;
128
152
  output_config: z.ZodOptional<z.ZodObject<{
129
153
  effort: z.ZodOptional<z.ZodEnum<{
130
154
  low: "low";
@@ -143,6 +167,10 @@ export declare const anthropicMessageSchema: z.ZodObject<{
143
167
  role: z.ZodLiteral<"assistant">;
144
168
  model: z.ZodString;
145
169
  content: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
170
+ type: z.ZodLiteral<"thinking">;
171
+ thinking: z.ZodString;
172
+ signature: z.ZodString;
173
+ }, z.core.$strict>, z.ZodObject<{
146
174
  type: z.ZodLiteral<"text">;
147
175
  text: z.ZodString;
148
176
  }, z.core.$strict>, z.ZodObject<{
@@ -198,6 +226,10 @@ export declare const anthropicStreamEventSchema: z.ZodDiscriminatedUnion<[z.ZodO
198
226
  role: z.ZodLiteral<"assistant">;
199
227
  model: z.ZodString;
200
228
  content: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
229
+ type: z.ZodLiteral<"thinking">;
230
+ thinking: z.ZodString;
231
+ signature: z.ZodString;
232
+ }, z.core.$strict>, z.ZodObject<{
201
233
  type: z.ZodLiteral<"text">;
202
234
  text: z.ZodString;
203
235
  }, z.core.$strict>, z.ZodObject<{
@@ -218,6 +250,10 @@ export declare const anthropicStreamEventSchema: z.ZodDiscriminatedUnion<[z.ZodO
218
250
  type: z.ZodLiteral<"content_block_start">;
219
251
  index: z.ZodNumber;
220
252
  content_block: z.ZodDiscriminatedUnion<[z.ZodObject<{
253
+ type: z.ZodLiteral<"thinking">;
254
+ thinking: z.ZodLiteral<"">;
255
+ signature: z.ZodLiteral<"">;
256
+ }, z.core.$strict>, z.ZodObject<{
221
257
  type: z.ZodLiteral<"text">;
222
258
  text: z.ZodLiteral<"">;
223
259
  }, z.core.$strict>, z.ZodObject<{
@@ -232,6 +268,12 @@ export declare const anthropicStreamEventSchema: z.ZodDiscriminatedUnion<[z.ZodO
232
268
  delta: z.ZodDiscriminatedUnion<[z.ZodObject<{
233
269
  type: z.ZodLiteral<"text_delta">;
234
270
  text: z.ZodString;
271
+ }, z.core.$strict>, z.ZodObject<{
272
+ type: z.ZodLiteral<"thinking_delta">;
273
+ thinking: z.ZodString;
274
+ }, z.core.$strict>, z.ZodObject<{
275
+ type: z.ZodLiteral<"signature_delta">;
276
+ signature: z.ZodString;
235
277
  }, z.core.$strict>, z.ZodObject<{
236
278
  type: z.ZodLiteral<"input_json_delta">;
237
279
  partial_json: z.ZodString;
@@ -17,18 +17,30 @@
17
17
  * over input, so honouring the marker while folding the write into
18
18
  * `input_tokens` would report less than the request cost. The marker is
19
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.
20
+ * - **`thinking.display`** and every other `thinking` member this file does
21
+ * not name.
26
22
  * - **Server tools** (`web_search`, `bash`, `text_editor`, …), `mcp_servers`,
27
23
  * `container`, `service_tier`, `disable_parallel_tool_use`, `document`
28
24
  * blocks and any other member this file does not name.
29
25
  * - **An image `url` source**: nothing fetches an image URL (ADR 0007); a
30
26
  * `base64` source is carried inline under the image rules.
31
27
  *
28
+ * ## Reasoning (contract set 8.6.0, ADR 0009 decision 2)
29
+ *
30
+ * `thinking: {type: 'enabled', budget_tokens}` is the contract's
31
+ * `reasoning.maxTokens` and `thinking: {type: 'adaptive'}` opts in to reasoning
32
+ * with the route's own sizing; `output_config.effort` is `reasoning.effort`, and
33
+ * a budget beside an effort is refused (the contract treats them as
34
+ * alternatives). A route that cannot take a budget refuses it by name.
35
+ *
36
+ * An answer carries `thinking` blocks before its text and tool calls, and an
37
+ * assistant message sent back carries them again, UNMODIFIED, ahead of its
38
+ * `tool_use` blocks. The block's `signature` is the contract's opaque
39
+ * continuation token (the sealed string a `reasoning` part carries), which is
40
+ * why a stock Anthropic SDK continues a tool turn without knowing anything
41
+ * else: it returns what it received. `redacted_thinking` is accepted on the way
42
+ * in (its `data` is the same token) and never produced.
43
+ *
32
44
  * `stop_reason: 'stop_sequence'` is never produced: the finish reason Kaana
33
45
  * measures does not say WHICH stop sequence ended the turn, and `stop_sequence`
34
46
  * would then have to name one. A turn that ended on a stop sequence reports
@@ -63,6 +75,21 @@ const imageBlockSchema = z
63
75
  ]),
64
76
  })
65
77
  .strict();
78
+ /**
79
+ * An assistant's reasoning. `thinking` is the readable text (empty when the
80
+ * model returned none); `signature` is the continuation token to return
81
+ * unmodified, empty only for a block that cannot be continued.
82
+ */
83
+ const thinkingBlockSchema = z
84
+ .object({
85
+ type: z.literal('thinking'),
86
+ thinking: z.string(),
87
+ signature: z.string(),
88
+ })
89
+ .strict();
90
+ const redactedThinkingBlockSchema = z
91
+ .object({ type: z.literal('redacted_thinking'), data: nonEmpty(1_000_000) })
92
+ .strict();
66
93
  const toolUseBlockSchema = z
67
94
  .object({
68
95
  type: z.literal('tool_use'),
@@ -91,7 +118,14 @@ const messageSchema = z
91
118
  content: z.union([
92
119
  z.string(),
93
120
  z
94
- .array(z.union([textBlockSchema, imageBlockSchema, toolUseBlockSchema, toolResultBlockSchema]))
121
+ .array(z.union([
122
+ textBlockSchema,
123
+ imageBlockSchema,
124
+ toolUseBlockSchema,
125
+ toolResultBlockSchema,
126
+ thinkingBlockSchema,
127
+ redactedThinkingBlockSchema,
128
+ ]))
95
129
  .min(1),
96
130
  ]),
97
131
  })
@@ -100,9 +134,10 @@ const messageSchema = z
100
134
  if (typeof message.content === 'string')
101
135
  return;
102
136
  for (const [index, block] of message.content.entries()) {
137
+ const reasoning = block.type === 'thinking' || block.type === 'redacted_thinking';
103
138
  const allowed = message.role === 'assistant'
104
- ? block.type === 'text' || block.type === 'tool_use'
105
- : block.type !== 'tool_use';
139
+ ? block.type === 'text' || block.type === 'tool_use' || reasoning
140
+ : block.type !== 'tool_use' && !reasoning;
106
141
  if (!allowed) {
107
142
  ctx.addIssue({
108
143
  code: 'custom',
@@ -142,10 +177,22 @@ export const anthropicMessagesRequestSchema = z
142
177
  stream: z.boolean().optional(),
143
178
  tools: z.array(toolSchema).max(128).optional(),
144
179
  tool_choice: toolChoiceSchema.optional(),
145
- /** Only "off" is accepted: see the file comment. */
180
+ /**
181
+ * `disabled` asks for nothing; `adaptive` opts in to reasoning (the
182
+ * contract's `reasoning: {}`); `enabled` sets a token budget
183
+ * (`reasoning.maxTokens`). See the file comment.
184
+ */
146
185
  thinking: z
147
- .object({ type: z.literal('disabled') })
148
- .strict()
186
+ .discriminatedUnion('type', [
187
+ z.object({ type: z.literal('disabled') }).strict(),
188
+ z.object({ type: z.literal('adaptive') }).strict(),
189
+ z
190
+ .object({
191
+ type: z.literal('enabled'),
192
+ budget_tokens: z.number().int().positive().safe(),
193
+ })
194
+ .strict(),
195
+ ])
149
196
  .optional(),
150
197
  /** `effort` is the contract's reasoning effort; no other member is carried. */
151
198
  output_config: z.object({ effort: reasoningEffortSchema.optional() }).strict().optional(),
@@ -175,6 +222,7 @@ const usageSchema = z
175
222
  .strict();
176
223
  const stopReasonSchema = z.enum(['end_turn', 'max_tokens', 'tool_use', 'refusal']);
177
224
  const outputBlockSchema = z.discriminatedUnion('type', [
225
+ thinkingBlockSchema,
178
226
  textBlockSchema,
179
227
  toolUseBlockSchema.extend({ input: z.record(z.string(), z.unknown()) }),
180
228
  ]);
@@ -243,6 +291,13 @@ export const anthropicStreamEventSchema = z.discriminatedUnion('type', [
243
291
  type: z.literal('content_block_start'),
244
292
  index: z.number().int().nonnegative(),
245
293
  content_block: z.discriminatedUnion('type', [
294
+ z
295
+ .object({
296
+ type: z.literal('thinking'),
297
+ thinking: z.literal(''),
298
+ signature: z.literal(''),
299
+ })
300
+ .strict(),
246
301
  z.object({ type: z.literal('text'), text: z.literal('') }).strict(),
247
302
  toolUseBlockSchema.extend({ input: z.object({}).strict() }),
248
303
  ]),
@@ -254,6 +309,8 @@ export const anthropicStreamEventSchema = z.discriminatedUnion('type', [
254
309
  index: z.number().int().nonnegative(),
255
310
  delta: z.discriminatedUnion('type', [
256
311
  z.object({ type: z.literal('text_delta'), text: z.string() }).strict(),
312
+ z.object({ type: z.literal('thinking_delta'), thinking: z.string() }).strict(),
313
+ z.object({ type: z.literal('signature_delta'), signature: z.string() }).strict(),
257
314
  z.object({ type: z.literal('input_json_delta'), partial_json: z.string() }).strict(),
258
315
  ]),
259
316
  })
@@ -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<{
@@ -33,34 +33,44 @@ export const inferenceContentSourceSchema = z.discriminatedUnion('kind', [
33
33
  /**
34
34
  * One part of a message's content. A message is always a list of parts.
35
35
  *
36
- * ## `refusal` is a member and `reasoning` is not, and the asymmetry is the point
36
+ * ## `refusal` and `reasoning` are members, and neither is ever answer text
37
37
  *
38
38
  * A model that DECLINES says why, and those words are meant for the customer:
39
39
  * they are the difference between "rephrase this" and "stop asking". Before this
40
40
  * member existed there was nowhere in an {@link InferenceMessage} to put them, so
41
- * a non-streaming fold kept `finishReason: 'refusal'` and dropped the sentence —
42
- * the customer learned they were refused and not why, while a streaming caller of
43
- * the same request got the whole explanation on the `refusal` delta channel.
41
+ * a non-streaming fold kept `finishReason: 'refusal'` and dropped the sentence.
42
+ * The part is a MEMBER rather than a field because a refusal need not have text:
43
+ * an Anthropic `stop_reason: "refusal"` maps to the finish reason and separates
44
+ * no words from the answer, while an OpenAI-compatible `refusal` does.
44
45
  *
45
- * Reasoning is the opposite case and stays absent. It is the model's private
46
- * working, and a `text` part is where somebody would put it — which renders
47
- * private reasoning to the customer AS the answer, the product bug the delta
48
- * channels exist to prevent. An opaque per-block reasoning blob crossing this
49
- * boundary needs a home nobody has chosen yet, and inventing one here would
50
- * choose it by accident.
46
+ * ## `reasoning` (contract set 8.6.0, Kaana ADR 0009 decision 2)
51
47
  *
52
- * Both public dialects can carry a refusal, which is what separates the two
53
- * cases at the boundary as well as in principle: `refusal` is OpenAI's OWN field
54
- * in both of its shapes (`delta.refusal` streaming, `message.refusal`
55
- * non-streaming), while reasoning has no OpenAI field at all — the
56
- * `reasoning_content`/`reasoning` spellings an OpenAI-compatible provider emits
57
- * are provider extensions. So carrying the refusal costs no dialect its
58
- * standard-client parseability, and carrying reasoning would.
48
+ * Reasoning was deliberately absent until the owner chose its home, because an
49
+ * opaque per-block reasoning blob had none. The cost of that absence was
50
+ * correctness: a thinking model that calls a tool cannot be continued unless the
51
+ * assistant's reasoning blocks, with their signatures, come back to the provider
52
+ * unmodified and in order, and a customer who only ever received reasoning TEXT
53
+ * on the `reasoning` delta channel had nothing to send back.
59
54
  *
60
- * The part is a MEMBER rather than a field because a refusal need not have text:
61
- * an Anthropic `stop_reason: "refusal"` maps to the finish reason and separates
62
- * no words from the answer, while an OpenAI-compatible `refusal` does. A required
63
- * field would force the first provider to invent a sentence.
55
+ * The part is `{type: 'reasoning', text?, opaque?}`:
56
+ *
57
+ * - `text` is what a person may read. It is a display value and is never
58
+ * forwarded to a provider on its own.
59
+ * - `opaque` is a continuation token the customer MUST return UNMODIFIED, in the
60
+ * position it arrived. Its content is Kaana's to define and the customer's to
61
+ * treat as a sealed string: it is provider-neutral on the surface and carries
62
+ * whatever the serving provider needs to continue. Sealing is NOT
63
+ * cryptographic: a forged or edited token is no more than a request the
64
+ * provider refuses, so nothing may rely on it for trust.
65
+ * - at least one of the two is present.
66
+ *
67
+ * A reasoning part is the assistant's alone, exactly as a refusal is. It is
68
+ * never a `text` part, so no renderer can present private reasoning as the
69
+ * answer, and the delta channels keep reasoning apart for the same reason.
70
+ *
71
+ * What a route does with a token it cannot read (it was issued by another
72
+ * provider family, after a failover or a model switch) is drop it, never fail:
73
+ * the continuation is lost, the conversation is not.
64
74
  */
65
75
  export const inferenceContentPartSchema = z.discriminatedUnion('type', [
66
76
  z.object({ type: z.literal('text'), text: z.string() }).strict(),
@@ -85,6 +95,20 @@ export const inferenceContentPartSchema = z.discriminatedUnion('type', [
85
95
  * renderer can present a refusal as the answer that was asked for.
86
96
  */
87
97
  z.object({ type: z.literal('refusal'), text: z.string() }).strict(),
98
+ /**
99
+ * The assistant's reasoning, readable and/or as an opaque continuation token
100
+ * to return unmodified. See the member note above.
101
+ */
102
+ z
103
+ .object({
104
+ type: z.literal('reasoning'),
105
+ text: z.string().optional(),
106
+ opaque: z.string().check(minUtf16Length(1)).optional(),
107
+ })
108
+ .strict()
109
+ .refine((part) => part.text !== undefined || part.opaque !== undefined, {
110
+ message: 'a reasoning part carries text, an opaque token, or both',
111
+ }),
88
112
  ]);
89
113
  /* -------------------------------------------------------------------------- */
90
114
  /* Image input (contract set 8.2.0, Kaana ADR 0007) */
@@ -257,15 +281,15 @@ export const inferenceMessageSchema = z
257
281
  });
258
282
  }
259
283
  // Same rule as `toolCalls`, for the same reason: only the assistant can
260
- // decline, so a refusal on any other role is a part every provider would
261
- // silently ignore — and one a renderer might not.
284
+ // decline or reason, so either part on any other role is one every provider
285
+ // would silently ignore — and one a renderer might not.
262
286
  if (message.role !== 'assistant') {
263
287
  for (const [index, part] of message.content.entries()) {
264
- if (part.type === 'refusal') {
288
+ if (part.type === 'refusal' || part.type === 'reasoning') {
265
289
  ctx.addIssue({
266
290
  code: 'custom',
267
291
  path: ['content', index, 'type'],
268
- message: 'only an assistant message carries a refusal',
292
+ message: `only an assistant message carries a ${part.type}`,
269
293
  });
270
294
  }
271
295
  }
@@ -327,12 +351,33 @@ export const responseFormatSchema = z.discriminatedUnion('type', [
327
351
  ]);
328
352
  /**
329
353
  * The caller's reasoning control. Strict, like every leaf: a provider-specific
330
- * knob (`budget_tokens`, `summary`) is refused rather than silently dropped.
354
+ * knob (`summary`) is refused rather than silently dropped.
355
+ *
356
+ * - `effort` is how much to reason, in the catalogue's words. Oxy forwards it
357
+ * only after checking the resolved model advertises it in `reasoningEfforts`.
358
+ * - `maxTokens` is a thinking BUDGET in tokens. It is the other way to size
359
+ * reasoning, so it excludes `effort`. A route that cannot take a budget
360
+ * refuses it with `invalid_request` naming `reasoning.maxTokens`.
361
+ * - `exclude: true` means the model may still reason, and be billed for it, but
362
+ * no reasoning is returned: no `reasoning` delta, no reasoning part, no opaque
363
+ * token. A caller that excludes reasoning cannot continue it.
331
364
  *
332
- * Oxy forwards it only after checking the resolved model advertises the effort
333
- * in its catalogue `reasoningEfforts`; Kaana translates it per provider.
365
+ * Carrying `reasoning` at all, even as `{}`, opts in to receiving reasoning
366
+ * continuation tokens (the `opaque` field of a `reasoning` delta); a request
367
+ * without it never receives one, which is what keeps older readers of the
368
+ * stream unaffected.
334
369
  */
335
- export const inferenceReasoningSchema = z.object({ effort: reasoningEffortSchema }).strict();
370
+ export const inferenceReasoningSchema = z
371
+ .object({
372
+ effort: reasoningEffortSchema.optional(),
373
+ maxTokens: z.number().int().min(1).max(2_147_483_647).optional(),
374
+ exclude: z.boolean().optional(),
375
+ })
376
+ .strict()
377
+ .refine((reasoning) => reasoning.effort === undefined || reasoning.maxTokens === undefined, {
378
+ message: 'reasoning takes an effort or a maxTokens budget, not both',
379
+ path: ['maxTokens'],
380
+ });
336
381
  /** Parameters for text-to-speech, preserved in the signed request. */
337
382
  export const inferenceSpeechParametersSchema = z
338
383
  .object({
@@ -92,6 +92,10 @@ export declare const responsesRequestSchema: z.ZodObject<{
92
92
  }, z.core.$strict>, z.ZodObject<{
93
93
  type: z.ZodLiteral<"refusal">;
94
94
  text: z.ZodString;
95
+ }, z.core.$strict>, z.ZodObject<{
96
+ type: z.ZodLiteral<"reasoning">;
97
+ text: z.ZodOptional<z.ZodString>;
98
+ opaque: z.ZodOptional<z.ZodString>;
95
99
  }, z.core.$strict>], "type">>;
96
100
  name: z.ZodOptional<z.ZodString>;
97
101
  toolCallId: z.ZodOptional<z.ZodString>;
@@ -136,11 +140,13 @@ export declare const responsesRequestSchema: z.ZodObject<{
136
140
  strict: z.ZodBoolean;
137
141
  }, z.core.$strict>], "type">>;
138
142
  reasoning: z.ZodOptional<z.ZodObject<{
139
- effort: z.ZodEnum<{
143
+ effort: z.ZodOptional<z.ZodEnum<{
140
144
  low: "low";
141
145
  medium: "medium";
142
146
  high: "high";
143
- }>;
147
+ }>>;
148
+ maxTokens: z.ZodOptional<z.ZodNumber>;
149
+ exclude: z.ZodOptional<z.ZodBoolean>;
144
150
  }, z.core.$strict>>;
145
151
  labels: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
146
152
  clientRequestId: z.ZodOptional<z.ZodString>;
@@ -240,6 +246,10 @@ export declare const responseSchema: z.ZodObject<{
240
246
  }, z.core.$strict>, z.ZodObject<{
241
247
  type: z.ZodLiteral<"refusal">;
242
248
  text: z.ZodString;
249
+ }, z.core.$strict>, z.ZodObject<{
250
+ type: z.ZodLiteral<"reasoning">;
251
+ text: z.ZodOptional<z.ZodString>;
252
+ opaque: z.ZodOptional<z.ZodString>;
243
253
  }, z.core.$strict>], "type">>;
244
254
  name: z.ZodOptional<z.ZodString>;
245
255
  toolCallId: z.ZodOptional<z.ZodString>;
@@ -25,6 +25,7 @@ export declare const inferenceStreamDeltaEventSchema: z.ZodObject<{
25
25
  output_audio_transcript: "output_audio_transcript";
26
26
  }>;
27
27
  text: z.ZodString;
28
+ opaque: z.ZodOptional<z.ZodString>;
28
29
  }, z.core.$strip>;
29
30
  /** The encodings an audio output may carry. */
30
31
  export declare const inferenceAudioMediaTypeSchema: z.ZodEnum<{
@@ -238,6 +239,7 @@ export declare const responseStreamEventSchema: z.ZodDiscriminatedUnion<[z.ZodOb
238
239
  output_audio_transcript: "output_audio_transcript";
239
240
  }>;
240
241
  text: z.ZodString;
242
+ opaque: z.ZodOptional<z.ZodString>;
241
243
  }, z.core.$strip>, z.ZodObject<{
242
244
  schemaVersion: z.ZodLiteral<1>;
243
245
  type: z.ZodLiteral<"audio">;
@@ -32,6 +32,14 @@ export const inferenceStreamDeltaEventSchema = z.object({
32
32
  */
33
33
  channel: z.enum(['output_text', 'reasoning', 'refusal', 'output_audio_transcript']),
34
34
  text: z.string(),
35
+ /**
36
+ * Contract set 8.6.0. On the `reasoning` channel only: the continuation token
37
+ * of one finished reasoning block, which the customer returns UNMODIFIED as the
38
+ * `opaque` of a `reasoning` content part, in the order the blocks arrived.
39
+ * Emitted once per block, after the block's text deltas (with empty `text`),
40
+ * and only to a request that carried `reasoning` and did not `exclude` it.
41
+ */
42
+ opaque: z.string().check(minUtf16Length(1)).optional(),
35
43
  });
36
44
  /** The encodings an audio output may carry. */
37
45
  export const inferenceAudioMediaTypeSchema = z.enum([
@@ -100,7 +100,7 @@
100
100
  * change to, say, the catalogue reject every in-flight inference request; the
101
101
  * per-shape `schemaVersion` is what a message is validated against.
102
102
  */
103
- export declare const INFERENCE_CONTRACT_VERSION = "8.5.0";
103
+ export declare const INFERENCE_CONTRACT_VERSION = "8.6.0";
104
104
  /**
105
105
  * Contract set 4.0.0 (Kaana ADR 0002) is the first published as
106
106
  * `@kaana.ai/contracts`. MAJOR because the set changed hands and split in two:
@@ -214,6 +214,15 @@ export declare const INFERENCE_CONTRACT_VERSION = "8.5.0";
214
214
  * `promptCacheKey`) on `POST /v1/chat/completions` and `POST /v1/responses`
215
215
  * (Kaana ADR 0008). The preference only narrows what the grant admits. Nothing
216
216
  * in 8.4.0 changes and the signed grant and envelope are untouched.
217
+ *
218
+ * Contract set 8.6.0 is additive: reasoning continuity (Kaana ADR 0009 decision
219
+ * 2). `reasoning` takes `maxTokens` and `exclude` beside `effort` (all optional;
220
+ * `effort` and `maxTokens` are exclusive), a message's content gains the
221
+ * assistant-only `reasoning` part (`text?`, `opaque?`), and a `reasoning` delta
222
+ * may carry `opaque`. Every 8.5.0 message parses the same; the delta's `opaque`
223
+ * is emitted only to a request that carried `reasoning`. A consumer on 8.5.0 or
224
+ * earlier (Oxy's legacy signed lane) refuses a message or request that carries
225
+ * what is new, so Oxy reads it before a customer sends it.
217
226
  */
218
227
  /** The public API's one origin and version. */
219
228
  export declare const KAANA_API_BASE_URL = "https://kaana.ai/v1";
@@ -100,7 +100,7 @@
100
100
  * change to, say, the catalogue reject every in-flight inference request; the
101
101
  * per-shape `schemaVersion` is what a message is validated against.
102
102
  */
103
- export const INFERENCE_CONTRACT_VERSION = '8.5.0';
103
+ export const INFERENCE_CONTRACT_VERSION = '8.6.0';
104
104
  /**
105
105
  * Contract set 4.0.0 (Kaana ADR 0002) is the first published as
106
106
  * `@kaana.ai/contracts`. MAJOR because the set changed hands and split in two:
@@ -214,6 +214,15 @@ export const INFERENCE_CONTRACT_VERSION = '8.5.0';
214
214
  * `promptCacheKey`) on `POST /v1/chat/completions` and `POST /v1/responses`
215
215
  * (Kaana ADR 0008). The preference only narrows what the grant admits. Nothing
216
216
  * in 8.4.0 changes and the signed grant and envelope are untouched.
217
+ *
218
+ * Contract set 8.6.0 is additive: reasoning continuity (Kaana ADR 0009 decision
219
+ * 2). `reasoning` takes `maxTokens` and `exclude` beside `effort` (all optional;
220
+ * `effort` and `maxTokens` are exclusive), a message's content gains the
221
+ * assistant-only `reasoning` part (`text?`, `opaque?`), and a `reasoning` delta
222
+ * may carry `opaque`. Every 8.5.0 message parses the same; the delta's `opaque`
223
+ * is emitted only to a request that carried `reasoning`. A consumer on 8.5.0 or
224
+ * earlier (Oxy's legacy signed lane) refuses a message or request that carries
225
+ * what is new, so Oxy reads it before a customer sends it.
217
226
  */
218
227
  /** The public API's one origin and version. */
219
228
  export const KAANA_API_BASE_URL = 'https://kaana.ai/v1';
package/openapi.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "openapi": "3.1.0",
3
3
  "info": {
4
4
  "title": "Kaana API",
5
- "version": "8.5.0",
5
+ "version": "8.6.0",
6
6
  "description": "Kaana inference API. Generated from @kaana.ai/contracts; the Zod source is the authority. Accounts, applications, oxy_sk_ keys, limits and billing are managed in Oxy Console: https://console.oxy.so"
7
7
  },
8
8
  "servers": [
@@ -1282,6 +1282,27 @@
1282
1282
  "type": "array",
1283
1283
  "items": {
1284
1284
  "oneOf": [
1285
+ {
1286
+ "type": "object",
1287
+ "properties": {
1288
+ "type": {
1289
+ "type": "string",
1290
+ "const": "thinking"
1291
+ },
1292
+ "thinking": {
1293
+ "type": "string"
1294
+ },
1295
+ "signature": {
1296
+ "type": "string"
1297
+ }
1298
+ },
1299
+ "required": [
1300
+ "type",
1301
+ "thinking",
1302
+ "signature"
1303
+ ],
1304
+ "additionalProperties": false
1305
+ },
1285
1306
  {
1286
1307
  "type": "object",
1287
1308
  "properties": {
@@ -1644,6 +1665,46 @@
1644
1665
  "tool_use_id"
1645
1666
  ],
1646
1667
  "additionalProperties": false
1668
+ },
1669
+ {
1670
+ "type": "object",
1671
+ "properties": {
1672
+ "type": {
1673
+ "type": "string",
1674
+ "const": "thinking"
1675
+ },
1676
+ "thinking": {
1677
+ "type": "string"
1678
+ },
1679
+ "signature": {
1680
+ "type": "string"
1681
+ }
1682
+ },
1683
+ "required": [
1684
+ "type",
1685
+ "thinking",
1686
+ "signature"
1687
+ ],
1688
+ "additionalProperties": false
1689
+ },
1690
+ {
1691
+ "type": "object",
1692
+ "properties": {
1693
+ "type": {
1694
+ "type": "string",
1695
+ "const": "redacted_thinking"
1696
+ },
1697
+ "data": {
1698
+ "type": "string",
1699
+ "minLength": 1,
1700
+ "maxLength": 1000000
1701
+ }
1702
+ },
1703
+ "required": [
1704
+ "type",
1705
+ "data"
1706
+ ],
1707
+ "additionalProperties": false
1647
1708
  }
1648
1709
  ]
1649
1710
  }
@@ -1812,17 +1873,53 @@
1812
1873
  ]
1813
1874
  },
1814
1875
  "thinking": {
1815
- "type": "object",
1816
- "properties": {
1817
- "type": {
1818
- "type": "string",
1819
- "const": "disabled"
1876
+ "oneOf": [
1877
+ {
1878
+ "type": "object",
1879
+ "properties": {
1880
+ "type": {
1881
+ "type": "string",
1882
+ "const": "disabled"
1883
+ }
1884
+ },
1885
+ "required": [
1886
+ "type"
1887
+ ],
1888
+ "additionalProperties": false
1889
+ },
1890
+ {
1891
+ "type": "object",
1892
+ "properties": {
1893
+ "type": {
1894
+ "type": "string",
1895
+ "const": "adaptive"
1896
+ }
1897
+ },
1898
+ "required": [
1899
+ "type"
1900
+ ],
1901
+ "additionalProperties": false
1902
+ },
1903
+ {
1904
+ "type": "object",
1905
+ "properties": {
1906
+ "type": {
1907
+ "type": "string",
1908
+ "const": "enabled"
1909
+ },
1910
+ "budget_tokens": {
1911
+ "type": "integer",
1912
+ "exclusiveMinimum": 0,
1913
+ "maximum": 9007199254740991
1914
+ }
1915
+ },
1916
+ "required": [
1917
+ "type",
1918
+ "budget_tokens"
1919
+ ],
1920
+ "additionalProperties": false
1820
1921
  }
1821
- },
1822
- "required": [
1823
- "type"
1824
- ],
1825
- "additionalProperties": false
1922
+ ]
1826
1923
  },
1827
1924
  "output_config": {
1828
1925
  "type": "object",
@@ -1894,6 +1991,27 @@
1894
1991
  "type": "array",
1895
1992
  "items": {
1896
1993
  "oneOf": [
1994
+ {
1995
+ "type": "object",
1996
+ "properties": {
1997
+ "type": {
1998
+ "type": "string",
1999
+ "const": "thinking"
2000
+ },
2001
+ "thinking": {
2002
+ "type": "string"
2003
+ },
2004
+ "signature": {
2005
+ "type": "string"
2006
+ }
2007
+ },
2008
+ "required": [
2009
+ "type",
2010
+ "thinking",
2011
+ "signature"
2012
+ ],
2013
+ "additionalProperties": false
2014
+ },
1897
2015
  {
1898
2016
  "type": "object",
1899
2017
  "properties": {
@@ -2014,6 +2132,29 @@
2014
2132
  },
2015
2133
  "content_block": {
2016
2134
  "oneOf": [
2135
+ {
2136
+ "type": "object",
2137
+ "properties": {
2138
+ "type": {
2139
+ "type": "string",
2140
+ "const": "thinking"
2141
+ },
2142
+ "thinking": {
2143
+ "type": "string",
2144
+ "const": ""
2145
+ },
2146
+ "signature": {
2147
+ "type": "string",
2148
+ "const": ""
2149
+ }
2150
+ },
2151
+ "required": [
2152
+ "type",
2153
+ "thinking",
2154
+ "signature"
2155
+ ],
2156
+ "additionalProperties": false
2157
+ },
2017
2158
  {
2018
2159
  "type": "object",
2019
2160
  "properties": {
@@ -2104,6 +2245,40 @@
2104
2245
  ],
2105
2246
  "additionalProperties": false
2106
2247
  },
2248
+ {
2249
+ "type": "object",
2250
+ "properties": {
2251
+ "type": {
2252
+ "type": "string",
2253
+ "const": "thinking_delta"
2254
+ },
2255
+ "thinking": {
2256
+ "type": "string"
2257
+ }
2258
+ },
2259
+ "required": [
2260
+ "type",
2261
+ "thinking"
2262
+ ],
2263
+ "additionalProperties": false
2264
+ },
2265
+ {
2266
+ "type": "object",
2267
+ "properties": {
2268
+ "type": {
2269
+ "type": "string",
2270
+ "const": "signature_delta"
2271
+ },
2272
+ "signature": {
2273
+ "type": "string"
2274
+ }
2275
+ },
2276
+ "required": [
2277
+ "type",
2278
+ "signature"
2279
+ ],
2280
+ "additionalProperties": false
2281
+ },
2107
2282
  {
2108
2283
  "type": "object",
2109
2284
  "properties": {
@@ -3876,6 +4051,26 @@
3876
4051
  "text"
3877
4052
  ],
3878
4053
  "additionalProperties": false
4054
+ },
4055
+ {
4056
+ "type": "object",
4057
+ "properties": {
4058
+ "type": {
4059
+ "type": "string",
4060
+ "const": "reasoning"
4061
+ },
4062
+ "text": {
4063
+ "type": "string"
4064
+ },
4065
+ "opaque": {
4066
+ "type": "string",
4067
+ "minLength": 1
4068
+ }
4069
+ },
4070
+ "required": [
4071
+ "type"
4072
+ ],
4073
+ "additionalProperties": false
3879
4074
  }
3880
4075
  ]
3881
4076
  },
@@ -4062,11 +4257,16 @@
4062
4257
  "properties": {
4063
4258
  "effort": {
4064
4259
  "$ref": "#/components/schemas/ReasoningEffort"
4260
+ },
4261
+ "maxTokens": {
4262
+ "type": "integer",
4263
+ "minimum": 1,
4264
+ "maximum": 2147483647
4265
+ },
4266
+ "exclude": {
4267
+ "type": "boolean"
4065
4268
  }
4066
4269
  },
4067
- "required": [
4068
- "effort"
4069
- ],
4070
4270
  "additionalProperties": false
4071
4271
  },
4072
4272
  "InferenceRegion": {
@@ -4193,6 +4393,10 @@
4193
4393
  },
4194
4394
  "text": {
4195
4395
  "type": "string"
4396
+ },
4397
+ "opaque": {
4398
+ "type": "string",
4399
+ "minLength": 1
4196
4400
  }
4197
4401
  },
4198
4402
  "required": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaana.ai/contracts",
3
- "version": "8.5.0",
3
+ "version": "8.6.0",
4
4
  "description": "Kaana inference contract: the Zod schemas of the public API at https://kaana.ai/v1 and, under /internal, of the Oxy-Kaana grant, settlement and signed lane.",
5
5
  "type": "module",
6
6
  "sideEffects": false,