@hiper2d/ai-agents 0.24.1 → 0.25.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.
package/dist/index.d.ts CHANGED
@@ -162,6 +162,17 @@ declare class ZodSchemaConverter {
162
162
  * Convert Zod schema to Mistral/DeepSeek JSON Schema format
163
163
  */
164
164
  static toMistralSchema(zodSchema: z.ZodSchema): any;
165
+ /**
166
+ * JSON Schema for Anthropic structured outputs (`output_config.format`). Anthropic takes
167
+ * standard JSON Schema with a few rules: every object carries `additionalProperties: false`,
168
+ * nullability is `anyOf: [inner, {type: "null"}]` (the OpenAPI `nullable: true` the Google
169
+ * path uses is an unknown keyword there, and a `["string", "null"]` type array is refused
170
+ * next to an enum), unions are `anyOf`, and value constraints (min/max, lengths) are not
171
+ * accepted — the converter never emits those. Optional keys stay out of `required`, which
172
+ * Anthropic allows.
173
+ */
174
+ static toAnthropicSchema(zodSchema: z.ZodSchema): any;
175
+ private static toStandardNullability;
165
176
  /**
166
177
  * Convert Zod schema to human-readable prompt description for Anthropic
167
178
  */
@@ -1424,8 +1435,14 @@ declare class ClaudeAgent extends AbstractAgent {
1424
1435
  private buildTokenUsage;
1425
1436
  private convertRole;
1426
1437
  /**
1427
- * New method using Zod with Anthropic's Claude API
1428
- * Since Anthropic doesn't support native JSON schemas, we generate prompt descriptions
1438
+ * Schema-validated ask on Anthropic structured outputs: the Zod schema goes out as JSON
1439
+ * Schema in `output_config.format`, so the API constrains the reply to that shape and the
1440
+ * model cannot answer in another one. Until 0.25.0 the schema was only described in prose
1441
+ * appended to the last message and the reply parsed as text; Sonnet 5.5 once answered a
1442
+ * vote with a tool-call shaped `<invoke name="json">` block instead (2026-10-18). The
1443
+ * field descriptions travel inside the schema, so nothing is appended to the prompt any
1444
+ * more. The reply is still run through the shared lenient parser + Zod on our side: same
1445
+ * error messages for the host's retry hints, and a guard for a null-formatted reply.
1429
1446
  */
1430
1447
  doAskWithZodSchema<T>(zodSchema: z.ZodSchema<T>, messages: AIMessage[]): Promise<[T, string, TokenUsage$1?, string?]>;
1431
1448
  /**
package/dist/index.js CHANGED
@@ -426,6 +426,41 @@ var ZodSchemaConverter = class {
426
426
  additionalProperties: false
427
427
  });
428
428
  }
429
+ /**
430
+ * JSON Schema for Anthropic structured outputs (`output_config.format`). Anthropic takes
431
+ * standard JSON Schema with a few rules: every object carries `additionalProperties: false`,
432
+ * nullability is `anyOf: [inner, {type: "null"}]` (the OpenAPI `nullable: true` the Google
433
+ * path uses is an unknown keyword there, and a `["string", "null"]` type array is refused
434
+ * next to an enum), unions are `anyOf`, and value constraints (min/max, lengths) are not
435
+ * accepted — the converter never emits those. Optional keys stay out of `required`, which
436
+ * Anthropic allows.
437
+ */
438
+ static toAnthropicSchema(zodSchema) {
439
+ const schema = this.zodToJsonSchema(zodSchema, { strict: true, includeDescription: true, additionalProperties: false });
440
+ return this.toStandardNullability(schema);
441
+ }
442
+ static toStandardNullability(schema) {
443
+ if (Array.isArray(schema)) {
444
+ return schema.map((item) => this.toStandardNullability(item));
445
+ }
446
+ if (typeof schema !== "object" || schema === null) {
447
+ return schema;
448
+ }
449
+ const { nullable, oneOf, ...rest } = schema;
450
+ const result = {};
451
+ for (const [key, value] of Object.entries(rest)) {
452
+ result[key] = key === "enum" || key === "required" ? value : this.toStandardNullability(value);
453
+ }
454
+ if (oneOf) {
455
+ result.anyOf = this.toStandardNullability(oneOf);
456
+ }
457
+ if (nullable === true) {
458
+ const { description, anyOf, ...inner } = result;
459
+ const variants = anyOf ? [...anyOf, { type: "null" }] : [inner, { type: "null" }];
460
+ return { anyOf: variants, ...description !== void 0 ? { description } : {} };
461
+ }
462
+ return result;
463
+ }
429
464
  /**
430
465
  * Convert Zod schema to human-readable prompt description for Anthropic
431
466
  */
@@ -3051,26 +3086,22 @@ var ClaudeAgent = class extends AbstractAgent {
3051
3086
  throw new Error(this.errorMessages.unsupportedRole(role));
3052
3087
  }
3053
3088
  /**
3054
- * New method using Zod with Anthropic's Claude API
3055
- * Since Anthropic doesn't support native JSON schemas, we generate prompt descriptions
3089
+ * Schema-validated ask on Anthropic structured outputs: the Zod schema goes out as JSON
3090
+ * Schema in `output_config.format`, so the API constrains the reply to that shape and the
3091
+ * model cannot answer in another one. Until 0.25.0 the schema was only described in prose
3092
+ * appended to the last message and the reply parsed as text; Sonnet 5.5 once answered a
3093
+ * vote with a tool-call shaped `<invoke name="json">` block instead (2026-10-18). The
3094
+ * field descriptions travel inside the schema, so nothing is appended to the prompt any
3095
+ * more. The reply is still run through the shared lenient parser + Zod on our side: same
3096
+ * error messages for the host's retry hints, and a guard for a null-formatted reply.
3056
3097
  */
3057
3098
  async doAskWithZodSchema(zodSchema, messages) {
3058
3099
  const aiMessages = this.prepareMessages(messages);
3059
3100
  this.logAsking(messages);
3060
3101
  this.logMessages(messages);
3061
3102
  try {
3062
- const schemaDescription = ZodSchemaConverter.toPromptDescription(zodSchema);
3063
- const lastMessage = aiMessages[aiMessages.length - 1];
3064
- const fullPrompt = `${lastMessage.content}
3065
-
3066
- ${schemaDescription}`;
3067
- const messagesWithSchema = [...aiMessages];
3068
- messagesWithSchema[messagesWithSchema.length - 1] = {
3069
- ...lastMessage,
3070
- content: fullPrompt
3071
- };
3072
3103
  const canUseThinking = this.enableThinking;
3073
- const anthropicMessages = canUseThinking ? this.convertToAnthropicMessagesWithThinking(messagesWithSchema) : this.convertToAnthropicMessages(messagesWithSchema);
3104
+ const anthropicMessages = canUseThinking ? this.convertToAnthropicMessagesWithThinking(aiMessages) : this.convertToAnthropicMessages(aiMessages);
3074
3105
  this.applyCacheBreakpoint(anthropicMessages);
3075
3106
  const params = {
3076
3107
  ...this.defaultParams,
@@ -3094,6 +3125,10 @@ ${schemaDescription}`;
3094
3125
  } else {
3095
3126
  params.temperature = this.temperature;
3096
3127
  }
3128
+ params.output_config = {
3129
+ ...params.output_config ?? {},
3130
+ format: { type: "json_schema", schema: ZodSchemaConverter.toAnthropicSchema(zodSchema) }
3131
+ };
3097
3132
  let response;
3098
3133
  try {
3099
3134
  response = await this.client.messages.create(params);