@core-ai/core-ai 0.23.0 → 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
@@ -143,10 +143,19 @@ type ToolResultMessage = {
143
143
  */
144
144
  metadata?: Record<string, unknown>;
145
145
  };
146
- type ToolDefinition = {
146
+ type ToolDefinition<TParameters extends z.ZodType = z.ZodType> = {
147
147
  name: string;
148
148
  description: string;
149
- parameters: z.ZodType;
149
+ parameters: TParameters;
150
+ /**
151
+ * `true` opts this tool into provider-enforced schema adherence. The
152
+ * schema must then satisfy the strict-capable schema contract (closed
153
+ * objects, every key required — use `.nullable()` instead of
154
+ * `.optional()` — and the shared keyword subset). Omitted or `false`
155
+ * means non-strict on every provider. Check
156
+ * `model.capabilities.tools.strictSchemas` before opting in.
157
+ */
158
+ strict?: boolean;
150
159
  };
151
160
  type ToolSet = Record<string, ToolDefinition>;
152
161
  type ToolChoice = 'auto' | 'none' | 'required' | {
@@ -169,6 +178,12 @@ type ChatInputModality = 'text' | 'image' | 'file' | 'audio' | 'video';
169
178
  * from `generate` / `stream`, not those dedicated APIs.
170
179
  */
171
180
  type ChatOutputModality = 'text' | 'image' | 'audio' | 'video';
181
+ type ToolSchemaStrictnessCapabilities = {
182
+ supported: false;
183
+ } | {
184
+ supported: true;
185
+ maxStrictTools?: number;
186
+ };
172
187
  type ModelCapabilities = {
173
188
  reasoning: {
174
189
  mode: 'unsupported' | 'optional' | 'always-on';
@@ -189,6 +204,9 @@ type ModelCapabilities = {
189
204
  */
190
205
  output: readonly ChatOutputModality[];
191
206
  };
207
+ tools: {
208
+ strictSchemas: ToolSchemaStrictnessCapabilities;
209
+ };
192
210
  };
193
211
  type ChatModel = {
194
212
  readonly provider: string;
@@ -433,6 +451,27 @@ type GeneratedImage = {
433
451
  revisedPrompt?: string;
434
452
  };
435
453
 
454
+ /**
455
+ * A single violation of the strict-capable schema contract, reported against
456
+ * the JSON Schema derived from the tool's Zod `parameters`.
457
+ */
458
+ type StrictToolSchemaViolation = {
459
+ toolName: string;
460
+ /** Dot path into the tool's JSON Schema, e.g. `properties.limit`. */
461
+ path: string;
462
+ /** What is wrong and how to fix it. */
463
+ message: string;
464
+ };
465
+ /**
466
+ * Checks the JSON Schema of a strict tool against the strict-capable schema
467
+ * contract: closed objects with every key required, the basic type set, and
468
+ * the keyword/format subset every strict-capable provider accepts.
469
+ *
470
+ * Pure and non-throwing — returns every violation found so callers can report
471
+ * them all at once.
472
+ */
473
+ declare function getStrictToolSchemaViolations(toolName: string, schema: Record<string, unknown>): StrictToolSchemaViolation[];
474
+
436
475
  declare class CoreAIError extends Error {
437
476
  readonly cause?: unknown;
438
477
  readonly provider?: string;
@@ -441,6 +480,29 @@ declare class CoreAIError extends Error {
441
480
  declare class ValidationError extends CoreAIError {
442
481
  constructor(message: string, cause?: unknown, provider?: string);
443
482
  }
483
+ type ToolSchemaStrictnessErrorReason = 'unsupported' | 'limit-exceeded' | 'invalid-schema';
484
+ type ToolSchemaStrictnessErrorOptions = {
485
+ providerId: string;
486
+ modelId: string;
487
+ toolNames: readonly string[];
488
+ } & ({
489
+ reason: 'unsupported';
490
+ } | {
491
+ reason: 'limit-exceeded';
492
+ maxStrictTools: number;
493
+ } | {
494
+ reason: 'invalid-schema';
495
+ violations: readonly StrictToolSchemaViolation[];
496
+ });
497
+ declare class ToolSchemaStrictnessError extends ValidationError {
498
+ readonly providerId: string;
499
+ readonly modelId: string;
500
+ readonly toolNames: readonly string[];
501
+ readonly reason: ToolSchemaStrictnessErrorReason;
502
+ readonly maxStrictTools?: number;
503
+ readonly violations?: readonly StrictToolSchemaViolation[];
504
+ constructor(options: ToolSchemaStrictnessErrorOptions);
505
+ }
444
506
  type UnsupportedInputModalityErrorOptions = {
445
507
  modelId: string;
446
508
  providerId: string;
@@ -503,6 +565,15 @@ declare class ContextLengthExceededError extends ProviderError {
503
565
  readonly actualTokens?: number;
504
566
  constructor(message: string, provider: string, options?: ContextLengthExceededErrorOptions);
505
567
  }
568
+ type ProviderQuotaExceededErrorOptions = ProviderErrorOptions;
569
+ /**
570
+ * The provider account cannot accept requests because its billing quota or
571
+ * credit balance is exhausted. This is not retryable until the account
572
+ * configuration changes.
573
+ */
574
+ declare class ProviderQuotaExceededError extends ProviderError {
575
+ constructor(message: string, provider: string, options?: ProviderQuotaExceededErrorOptions);
576
+ }
506
577
  type RateLimitErrorOptions = ProviderErrorOptions & {
507
578
  retryAfterSeconds?: number;
508
579
  };
@@ -565,16 +636,42 @@ declare function parseRetryAfterSeconds(headers: Headers | Record<string, string
565
636
  /** Reads `Retry-After` from common SDK error shapes that expose `headers`. */
566
637
  declare function getRetryAfterSecondsFromError(error: unknown): number | undefined;
567
638
 
568
- declare function defineTool(options: ToolDefinition): ToolDefinition;
639
+ declare function defineTool<TParameters extends ToolDefinition['parameters']>(options: ToolDefinition<TParameters>): ToolDefinition<TParameters>;
569
640
 
570
641
  /**
571
642
  * Convert a Zod schema to a JSON Schema object using Zod 4's native
572
643
  * `z.toJSONSchema()`.
573
644
  */
574
645
  declare function zodSchemaToJsonSchema(schema: z.ZodType): Record<string, unknown>;
646
+ /**
647
+ * Normalizes the JSON Schema of a strict tool for providers whose strict mode
648
+ * requires closed objects (OpenAI-style APIs). The transform is semantics
649
+ * preserving with respect to the tool's Zod schema:
650
+ *
651
+ * - drops `$schema` (metadata, not a constraint),
652
+ * - sets `additionalProperties: false` on object nodes where absent, which
653
+ * matches `z.object()` semantics (unknown keys are stripped at parse time),
654
+ * - drops Zod's implicit safe-integer bounds on integer nodes (see
655
+ * {@link isImplicitSafeIntegerBound}),
656
+ * - rewrites `oneOf` to `anyOf`. Zod emits `oneOf` only for
657
+ * `z.discriminatedUnion()`, whose branches are disjoint by construction, so
658
+ * the two keywords accept the same values there — and strict-capable
659
+ * providers accept `anyOf` only.
660
+ *
661
+ * It never widens or narrows what the user's Zod schema accepts; schemas that
662
+ * cannot be expressed in the strict subset are rejected by the contract
663
+ * validator instead of being rewritten.
664
+ */
665
+ declare function normalizeStrictJsonSchema(schema: Record<string, unknown>): Record<string, unknown>;
575
666
 
576
667
  declare function stripModelDateSuffix(modelId: string): string;
577
668
 
669
+ declare const UNSUPPORTED_TOOL_SCHEMA_STRICTNESS: Readonly<{
670
+ readonly supported: false;
671
+ }>;
672
+ declare const SUPPORTED_TOOL_SCHEMA_STRICTNESS: Readonly<{
673
+ readonly supported: true;
674
+ }>;
578
675
  declare function clampReasoningEffort(effort: ReasoningEffort, supportedEfforts: readonly ReasoningEffort[]): ReasoningEffort;
579
676
  /** Text in, text out — the default chat modality profile. */
580
677
  declare const TEXT_ONLY_MODALITIES: {
@@ -607,6 +704,23 @@ type ValidateInputModalitiesOptions = {
607
704
  */
608
705
  declare function validateInputModalities({ messages, capabilities, modelId, providerId, }: ValidateInputModalitiesOptions): void;
609
706
 
707
+ type ValidateToolSchemaStrictnessOptions = {
708
+ tools: ToolSet;
709
+ capabilities: ModelCapabilities;
710
+ providerId: string;
711
+ modelId: string;
712
+ };
713
+ /**
714
+ * Validates the strict tools of a request before it reaches the provider.
715
+ *
716
+ * Strictness is per-tool opt-in: only tools with `strict: true` are checked.
717
+ * Throws {@link ToolSchemaStrictnessError} when the model is known not to
718
+ * support strict schemas (`unsupported`), when more tools opt in than the
719
+ * model allows (`limit-exceeded`), or when a strict tool's schema falls
720
+ * outside the strict-capable schema contract (`invalid-schema`).
721
+ */
722
+ declare function validateToolSchemaStrictness({ tools, capabilities, providerId, modelId, }: ValidateToolSchemaStrictnessOptions): void;
723
+
610
724
  declare const UNKNOWN_MODEL: unique symbol;
611
725
  type ModelCapabilitiesRegistry<TCapabilities extends ModelCapabilities = ModelCapabilities> = Record<string, TCapabilities> & {
612
726
  [UNKNOWN_MODEL]?: TCapabilities;
@@ -683,4 +797,4 @@ type GenerateImageParams = ImageGenerateOptions & {
683
797
  };
684
798
  declare function generateImage(params: GenerateImageParams): Promise<ImageGenerateResult>;
685
799
 
686
- export { AbortedError, type AssistantContentPart, type AssistantMessage, type AssistantTextPart, type AudioPart, type BaseGenerateOptions, type ChatInputModality, type ChatInputTokenDetails, type ChatModel, type ChatModelMiddleware, type ChatOutputModality, type ChatOutputTokenDetails, type ChatStream, type ChatUsage, ContextLengthExceededError, type ContextLengthExceededErrorOptions, CoreAIError, type CreateChatStreamOptions, type EmbedOptions, type EmbedProviderOptions, type EmbedResult, type EmbeddingModel, type EmbeddingModelMiddleware, type EmbeddingUsage, type FilePart, type FinishReason, type GenerateObjectOptions, type GenerateObjectResult, type GenerateOptions, type GenerateProviderOptions, type GenerateResult, type GeneratedImage, type ImageGenerateOptions, type ImageGenerateResult, type ImageModel, type ImageModelMiddleware, type ImagePart, type ImageProviderOptions, MULTIMODAL_INPUT_MODALITIES, type Message, type ModelCapabilities, type ModelCapabilitiesRegistry, ModelOverloadedError, type ModelOverloadedErrorOptions, type ObjectStream, type ObjectStreamEvent, ProviderError, type ProviderErrorOptions, RateLimitError, type RateLimitErrorOptions, type ReasoningConfig, type ReasoningEffort, type ReasoningPart, RetryableProviderError, ServiceUnavailableError, type ServiceUnavailableErrorOptions, StreamAbortedError, type StreamEvent, type StreamObjectOptions, StructuredOutputError, StructuredOutputNoObjectGeneratedError, StructuredOutputParseError, StructuredOutputValidationError, type SystemMessage, TEXT_ONLY_MODALITIES, type TextPart, type ToolCall, type ToolCallPart, type ToolChoice, type ToolDefinition, type ToolResultMessage, type ToolSet, UNKNOWN_MODEL, UnsupportedInputModalityError, type UnsupportedInputModalityErrorOptions, type UserContentPart, type UserMessage, type ValidateInputModalitiesOptions, ValidationError, asObject, asRecord, assistantMessage, clampReasoningEffort, createChatStream, createObjectStream, defineTool, embed, generate, generateImage, generateObject, getErrorMessage, getHttpStatusCode, getProviderMetadata, getRegisteredModelCapabilities, getRetryAfterSecondsFromError, getString, isAbortErrorByName, isRateLimitStatus, isTransientUnavailableStatus, parseRetryAfterSeconds, resultToMessage, safeParseJsonObject, stream, streamObject, stripModelDateSuffix, supportsInputModality, supportsOutputModality, validateInputModalities, wrapChatModel, wrapEmbeddingModel, wrapImageModel, zodSchemaToJsonSchema };
800
+ export { AbortedError, type AssistantContentPart, type AssistantMessage, type AssistantTextPart, type AudioPart, type BaseGenerateOptions, type ChatInputModality, type ChatInputTokenDetails, type ChatModel, type ChatModelMiddleware, type ChatOutputModality, type ChatOutputTokenDetails, type ChatStream, type ChatUsage, ContextLengthExceededError, type ContextLengthExceededErrorOptions, CoreAIError, type CreateChatStreamOptions, type EmbedOptions, type EmbedProviderOptions, type EmbedResult, type EmbeddingModel, type EmbeddingModelMiddleware, type EmbeddingUsage, type FilePart, type FinishReason, type GenerateObjectOptions, type GenerateObjectResult, type GenerateOptions, type GenerateProviderOptions, type GenerateResult, type GeneratedImage, type ImageGenerateOptions, type ImageGenerateResult, type ImageModel, type ImageModelMiddleware, type ImagePart, type ImageProviderOptions, MULTIMODAL_INPUT_MODALITIES, type Message, type ModelCapabilities, type ModelCapabilitiesRegistry, ModelOverloadedError, type ModelOverloadedErrorOptions, type ObjectStream, type ObjectStreamEvent, ProviderError, type ProviderErrorOptions, ProviderQuotaExceededError, type ProviderQuotaExceededErrorOptions, RateLimitError, type RateLimitErrorOptions, type ReasoningConfig, type ReasoningEffort, type ReasoningPart, RetryableProviderError, SUPPORTED_TOOL_SCHEMA_STRICTNESS, ServiceUnavailableError, type ServiceUnavailableErrorOptions, StreamAbortedError, type StreamEvent, type StreamObjectOptions, type StrictToolSchemaViolation, StructuredOutputError, StructuredOutputNoObjectGeneratedError, StructuredOutputParseError, StructuredOutputValidationError, type SystemMessage, TEXT_ONLY_MODALITIES, type TextPart, type ToolCall, type ToolCallPart, type ToolChoice, type ToolDefinition, type ToolResultMessage, type ToolSchemaStrictnessCapabilities, ToolSchemaStrictnessError, type ToolSchemaStrictnessErrorOptions, type ToolSchemaStrictnessErrorReason, type ToolSet, UNKNOWN_MODEL, UNSUPPORTED_TOOL_SCHEMA_STRICTNESS, UnsupportedInputModalityError, type UnsupportedInputModalityErrorOptions, type UserContentPart, type UserMessage, type ValidateInputModalitiesOptions, type ValidateToolSchemaStrictnessOptions, ValidationError, asObject, asRecord, assistantMessage, clampReasoningEffort, createChatStream, createObjectStream, defineTool, embed, generate, generateImage, generateObject, getErrorMessage, getHttpStatusCode, getProviderMetadata, getRegisteredModelCapabilities, getRetryAfterSecondsFromError, getStrictToolSchemaViolations, getString, isAbortErrorByName, isRateLimitStatus, isTransientUnavailableStatus, normalizeStrictJsonSchema, parseRetryAfterSeconds, resultToMessage, safeParseJsonObject, stream, streamObject, stripModelDateSuffix, supportsInputModality, supportsOutputModality, validateInputModalities, validateToolSchemaStrictness, wrapChatModel, wrapEmbeddingModel, wrapImageModel, zodSchemaToJsonSchema };
package/dist/index.js CHANGED
@@ -15,6 +15,49 @@ var ValidationError = class extends CoreAIError {
15
15
  this.name = "ValidationError";
16
16
  }
17
17
  };
18
+ var ToolSchemaStrictnessError = class extends ValidationError {
19
+ providerId;
20
+ modelId;
21
+ toolNames;
22
+ reason;
23
+ maxStrictTools;
24
+ violations;
25
+ constructor(options) {
26
+ super(
27
+ getToolSchemaStrictnessErrorMessage(options),
28
+ void 0,
29
+ options.providerId
30
+ );
31
+ this.name = "ToolSchemaStrictnessError";
32
+ this.providerId = options.providerId;
33
+ this.modelId = options.modelId;
34
+ this.toolNames = options.toolNames;
35
+ this.reason = options.reason;
36
+ if (options.reason === "limit-exceeded") {
37
+ this.maxStrictTools = options.maxStrictTools;
38
+ }
39
+ if (options.reason === "invalid-schema") {
40
+ this.violations = options.violations;
41
+ }
42
+ }
43
+ };
44
+ function getToolSchemaStrictnessErrorMessage(options) {
45
+ const tools = options.toolNames.length === 0 ? "(none)" : options.toolNames.map((name) => `"${name}"`).join(", ");
46
+ switch (options.reason) {
47
+ case "limit-exceeded": {
48
+ const toolWord = options.maxStrictTools === 1 ? "tool" : "tools";
49
+ return `${options.providerId} model "${options.modelId}" supports at most ${options.maxStrictTools} strict ${toolWord}, but received ${options.toolNames.length}: ${tools}`;
50
+ }
51
+ case "invalid-schema": {
52
+ const violations = options.violations.map(
53
+ (violation) => `tool "${violation.toolName}"${violation.path === "" ? "" : ` at ${violation.path}`}: ${violation.message}`
54
+ ).join("; ");
55
+ return `${options.providerId} model "${options.modelId}" received strict tools whose schemas are outside the strict-capable schema contract: ${violations}`;
56
+ }
57
+ case "unsupported":
58
+ return `${options.providerId} model "${options.modelId}" does not support per-tool strict schemas. Requested by: ${tools}`;
59
+ }
60
+ }
18
61
  var UnsupportedInputModalityError = class extends ValidationError {
19
62
  requestedModalities;
20
63
  supportedModalities;
@@ -82,6 +125,12 @@ var ContextLengthExceededError = class extends ProviderError {
82
125
  this.actualTokens = options.actualTokens;
83
126
  }
84
127
  };
128
+ var ProviderQuotaExceededError = class extends ProviderError {
129
+ constructor(message, provider, options = {}) {
130
+ super(message, provider, options);
131
+ this.name = "ProviderQuotaExceededError";
132
+ }
133
+ };
85
134
  var RateLimitError = class extends RetryableProviderError {
86
135
  retryAfterSeconds;
87
136
  constructor(message, provider, options = {}) {
@@ -250,6 +299,348 @@ function zodSchemaToJsonSchema(schema) {
250
299
  io: "input"
251
300
  });
252
301
  }
302
+ var MAX_SAFE_INTEGER_BOUND = 9007199254740991;
303
+ function isPlainObject(value) {
304
+ return typeof value === "object" && value !== null && !Array.isArray(value);
305
+ }
306
+ function isObjectSchemaNode(node) {
307
+ return node.type === "object" || isPlainObject(node.properties);
308
+ }
309
+ function isImplicitSafeIntegerBound(node, key) {
310
+ if (node.type !== "integer") {
311
+ return false;
312
+ }
313
+ return key === "minimum" ? node.minimum === -MAX_SAFE_INTEGER_BOUND : node.maximum === MAX_SAFE_INTEGER_BOUND;
314
+ }
315
+ var NESTED_MAP_KEYWORDS = ["properties"];
316
+ var NESTED_LIST_KEYWORDS = ["anyOf", "oneOf", "allOf"];
317
+ var NESTED_SINGLE_KEYWORDS = ["items"];
318
+ var DEFINITION_KEYWORDS = ["$defs", "definitions"];
319
+ function getChildSchemaNodeEntries(node) {
320
+ const entries = getNamedChildEntries(node, NESTED_MAP_KEYWORDS);
321
+ for (const keyword of NESTED_SINGLE_KEYWORDS) {
322
+ if (node[keyword] !== void 0) {
323
+ entries.push({ segment: keyword, child: node[keyword] });
324
+ }
325
+ }
326
+ for (const keyword of NESTED_LIST_KEYWORDS) {
327
+ const value = node[keyword];
328
+ if (Array.isArray(value)) {
329
+ value.forEach((child, index) => {
330
+ entries.push({ segment: `${keyword}.${index}`, child });
331
+ });
332
+ }
333
+ }
334
+ return entries;
335
+ }
336
+ function getSchemaDefinitionEntries(node) {
337
+ return getNamedChildEntries(node, DEFINITION_KEYWORDS);
338
+ }
339
+ function getNamedChildEntries(node, keywords) {
340
+ const entries = [];
341
+ for (const keyword of keywords) {
342
+ const value = node[keyword];
343
+ if (isPlainObject(value)) {
344
+ for (const [key, child] of Object.entries(value)) {
345
+ entries.push({ segment: `${keyword}.${key}`, child });
346
+ }
347
+ }
348
+ }
349
+ return entries;
350
+ }
351
+ function mapChildSchemaNodes(node, map) {
352
+ const result = { ...node };
353
+ for (const keyword of [...NESTED_MAP_KEYWORDS, ...DEFINITION_KEYWORDS]) {
354
+ const value = result[keyword];
355
+ if (isPlainObject(value)) {
356
+ result[keyword] = Object.fromEntries(
357
+ Object.entries(value).map(([key, child]) => [key, map(child)])
358
+ );
359
+ }
360
+ }
361
+ for (const keyword of NESTED_SINGLE_KEYWORDS) {
362
+ if (result[keyword] !== void 0) {
363
+ result[keyword] = map(result[keyword]);
364
+ }
365
+ }
366
+ for (const keyword of NESTED_LIST_KEYWORDS) {
367
+ const value = result[keyword];
368
+ if (Array.isArray(value)) {
369
+ result[keyword] = value.map((child) => map(child));
370
+ }
371
+ }
372
+ return result;
373
+ }
374
+ function normalizeStrictJsonSchema(schema) {
375
+ return normalizeObjectNode(schema);
376
+ }
377
+ function normalizeNode(node) {
378
+ return isPlainObject(node) ? normalizeObjectNode(node) : node;
379
+ }
380
+ function normalizeObjectNode(node) {
381
+ const stripped = {};
382
+ for (const [key, value] of Object.entries(node)) {
383
+ if (key === "$schema") {
384
+ continue;
385
+ }
386
+ if ((key === "minimum" || key === "maximum") && isImplicitSafeIntegerBound(node, key)) {
387
+ continue;
388
+ }
389
+ stripped[key] = value;
390
+ }
391
+ const result = mapChildSchemaNodes(stripped, normalizeNode);
392
+ if (Array.isArray(result.oneOf) && result.anyOf === void 0) {
393
+ result.anyOf = result.oneOf;
394
+ delete result.oneOf;
395
+ }
396
+ if (isObjectSchemaNode(result) && result.additionalProperties === void 0) {
397
+ result.additionalProperties = false;
398
+ }
399
+ return result;
400
+ }
401
+
402
+ // src/strict-tool-schema-contract.ts
403
+ var MAX_SCHEMA_DEPTH = 64;
404
+ var ALLOWED_KEYWORDS = /* @__PURE__ */ new Set([
405
+ "$schema",
406
+ "$ref",
407
+ "$defs",
408
+ "definitions",
409
+ "type",
410
+ "properties",
411
+ "required",
412
+ "additionalProperties",
413
+ "items",
414
+ "enum",
415
+ "const",
416
+ "anyOf",
417
+ "oneOf",
418
+ "format",
419
+ "pattern",
420
+ "title",
421
+ "description",
422
+ "default",
423
+ "examples",
424
+ "minimum",
425
+ "maximum"
426
+ ]);
427
+ var ALLOWED_TYPES = /* @__PURE__ */ new Set([
428
+ "object",
429
+ "array",
430
+ "string",
431
+ "number",
432
+ "integer",
433
+ "boolean",
434
+ "null"
435
+ ]);
436
+ var ALLOWED_STRING_FORMATS = /* @__PURE__ */ new Set([
437
+ "date-time",
438
+ "time",
439
+ "date",
440
+ "duration",
441
+ "email",
442
+ "hostname",
443
+ "ipv4",
444
+ "ipv6",
445
+ "uuid"
446
+ ]);
447
+ var BOUND_HINTS = {
448
+ minimum: "remove numeric bounds (.min()/.gt()/.gte()) \u2014 numeric range constraints are outside the strict-capable subset; validate ranges after parsing instead",
449
+ maximum: "remove numeric bounds (.max()/.lt()/.lte()) \u2014 numeric range constraints are outside the strict-capable subset; validate ranges after parsing instead"
450
+ };
451
+ var KEYWORD_HINTS = {
452
+ ...BOUND_HINTS,
453
+ minLength: "remove .min()/.length() \u2014 string length constraints are outside the strict-capable subset; validate lengths after parsing instead",
454
+ maxLength: "remove .max()/.length() \u2014 string length constraints are outside the strict-capable subset; validate lengths after parsing instead",
455
+ exclusiveMinimum: "remove .gt() \u2014 numeric range constraints are outside the strict-capable subset; validate ranges after parsing instead",
456
+ exclusiveMaximum: "remove .lt() \u2014 numeric range constraints are outside the strict-capable subset; validate ranges after parsing instead",
457
+ multipleOf: "remove .multipleOf()/.step() \u2014 numeric constraints are outside the strict-capable subset; validate after parsing instead",
458
+ minItems: "remove .min()/.nonempty() on arrays \u2014 array length constraints are outside the strict-capable subset; validate after parsing instead",
459
+ maxItems: "remove .max() on arrays \u2014 array length constraints are outside the strict-capable subset; validate after parsing instead",
460
+ uniqueItems: "array uniqueness constraints are outside the strict-capable subset; validate after parsing instead",
461
+ prefixItems: "tuples (z.tuple) are not strict-capable; use a uniform z.array() or an object instead",
462
+ allOf: "intersections (z.intersection()/.and(), serialized as allOf) are not strict-capable; flatten the members into a single z.object() instead",
463
+ not: "negated schemas (not) are not strict-capable",
464
+ patternProperties: "pattern properties are not strict-capable; declare explicit keys with z.object()",
465
+ propertyNames: "property-name constraints are not strict-capable; declare explicit keys with z.object()"
466
+ };
467
+ function getStrictToolSchemaViolations(toolName, schema) {
468
+ const violations = [];
469
+ const refEdges = /* @__PURE__ */ new Map();
470
+ if (schema.type !== "object") {
471
+ violations.push({
472
+ toolName,
473
+ path: "type",
474
+ message: "tool parameters must be an object at the root; wrap the schema in z.object()"
475
+ });
476
+ }
477
+ walkNode(schema, "", "root", 0, toolName, violations, refEdges);
478
+ for (const { segment, child } of getSchemaDefinitionEntries(schema)) {
479
+ walkNode(child, segment, segment, 0, toolName, violations, refEdges);
480
+ }
481
+ for (const region of findCyclicRegions(refEdges)) {
482
+ violations.push({
483
+ toolName,
484
+ path: region === "root" ? "" : region,
485
+ message: "recursive schemas (z.lazy or self-referencing types) are not strict-capable"
486
+ });
487
+ }
488
+ return violations;
489
+ }
490
+ function walkNode(node, path, region, depth, toolName, violations, refEdges) {
491
+ if (!isPlainObject(node)) {
492
+ return;
493
+ }
494
+ if (depth > MAX_SCHEMA_DEPTH) {
495
+ violations.push({
496
+ toolName,
497
+ path,
498
+ message: `schema nesting exceeds the supported depth of ${MAX_SCHEMA_DEPTH}`
499
+ });
500
+ return;
501
+ }
502
+ for (const key of Object.keys(node)) {
503
+ if (ALLOWED_KEYWORDS.has(key)) {
504
+ continue;
505
+ }
506
+ const hint = KEYWORD_HINTS[key];
507
+ violations.push({
508
+ toolName,
509
+ path: joinPath(path, key),
510
+ message: hint ?? `keyword "${key}" is outside the strict-capable subset; remove it and validate after parsing instead`
511
+ });
512
+ }
513
+ validateBounds(node, path, toolName, violations);
514
+ validateType(node, path, toolName, violations);
515
+ validateFormat(node, path, toolName, violations);
516
+ validateObjectShape(node, path, toolName, violations);
517
+ validateRef(node, path, region, toolName, violations, refEdges);
518
+ for (const { segment, child } of getChildSchemaNodeEntries(node)) {
519
+ walkNode(
520
+ child,
521
+ joinPath(path, segment),
522
+ region,
523
+ depth + 1,
524
+ toolName,
525
+ violations,
526
+ refEdges
527
+ );
528
+ }
529
+ }
530
+ function validateBounds(node, path, toolName, violations) {
531
+ for (const key of ["minimum", "maximum"]) {
532
+ if (key in node && !isImplicitSafeIntegerBound(node, key)) {
533
+ violations.push({
534
+ toolName,
535
+ path: joinPath(path, key),
536
+ message: BOUND_HINTS[key]
537
+ });
538
+ }
539
+ }
540
+ }
541
+ function validateType(node, path, toolName, violations) {
542
+ if (!("type" in node)) {
543
+ return;
544
+ }
545
+ const types = Array.isArray(node.type) ? node.type : [node.type];
546
+ for (const type of types) {
547
+ if (typeof type !== "string" || !ALLOWED_TYPES.has(type)) {
548
+ violations.push({
549
+ toolName,
550
+ path: joinPath(path, "type"),
551
+ message: `type "${String(type)}" is outside the strict-capable subset (object, array, string, number, integer, boolean, null)`
552
+ });
553
+ }
554
+ }
555
+ }
556
+ function validateFormat(node, path, toolName, violations) {
557
+ if (typeof node.format !== "string") {
558
+ return;
559
+ }
560
+ if (!ALLOWED_STRING_FORMATS.has(node.format)) {
561
+ violations.push({
562
+ toolName,
563
+ path: joinPath(path, "format"),
564
+ message: `format "${node.format}" is outside the strict-capable subset (${[...ALLOWED_STRING_FORMATS].join(", ")}); remove the format refinement (for URLs, use z.string() instead of z.url()) and validate after parsing instead`
565
+ });
566
+ }
567
+ }
568
+ function validateObjectShape(node, path, toolName, violations) {
569
+ if (!isObjectSchemaNode(node)) {
570
+ return;
571
+ }
572
+ const properties = isPlainObject(node.properties) ? node.properties : {};
573
+ const required = new Set(
574
+ Array.isArray(node.required) ? node.required.filter((key) => typeof key === "string") : []
575
+ );
576
+ for (const key of Object.keys(properties)) {
577
+ if (!required.has(key)) {
578
+ violations.push({
579
+ toolName,
580
+ path: joinPath(path, `properties.${key}`),
581
+ message: `"${key}" is optional; strict schemas require every key \u2014 use .nullable() instead of .optional() (note that .default() also makes a key optional)`
582
+ });
583
+ }
584
+ }
585
+ if ("additionalProperties" in node && node.additionalProperties !== false) {
586
+ violations.push({
587
+ toolName,
588
+ path: joinPath(path, "additionalProperties"),
589
+ message: "open objects are not strict-capable; use z.object() with explicit keys instead of z.record(), .catchall(), .passthrough(), or z.looseObject()"
590
+ });
591
+ }
592
+ }
593
+ function validateRef(node, path, region, toolName, violations, refEdges) {
594
+ if (typeof node.$ref !== "string") {
595
+ return;
596
+ }
597
+ if (node.$ref === "#") {
598
+ addRefEdge(refEdges, region, "root");
599
+ return;
600
+ }
601
+ const match = /^#\/(\$defs|definitions)\/([^/]+)$/.exec(node.$ref);
602
+ if (!match) {
603
+ violations.push({
604
+ toolName,
605
+ path: joinPath(path, "$ref"),
606
+ message: `only local references into $defs are strict-capable; found "${node.$ref}"`
607
+ });
608
+ return;
609
+ }
610
+ addRefEdge(refEdges, region, `${match[1]}.${match[2]}`);
611
+ }
612
+ function addRefEdge(refEdges, from, to) {
613
+ const edges = refEdges.get(from) ?? /* @__PURE__ */ new Set();
614
+ edges.add(to);
615
+ refEdges.set(from, edges);
616
+ }
617
+ function findCyclicRegions(refEdges) {
618
+ const cyclic = /* @__PURE__ */ new Set();
619
+ const visiting = /* @__PURE__ */ new Set();
620
+ const done = /* @__PURE__ */ new Set();
621
+ function visit(region) {
622
+ if (done.has(region)) {
623
+ return;
624
+ }
625
+ if (visiting.has(region)) {
626
+ cyclic.add(region);
627
+ return;
628
+ }
629
+ visiting.add(region);
630
+ for (const target of refEdges.get(region) ?? []) {
631
+ visit(target);
632
+ }
633
+ visiting.delete(region);
634
+ done.add(region);
635
+ }
636
+ for (const region of refEdges.keys()) {
637
+ visit(region);
638
+ }
639
+ return [...cyclic];
640
+ }
641
+ function joinPath(path, segment) {
642
+ return path === "" ? segment : `${path}.${segment}`;
643
+ }
253
644
 
254
645
  // src/model-id.ts
255
646
  var MODEL_DATE_SUFFIX_PATTERN = /[-@](?:\d{8}|\d{4}-\d{2}-\d{2})$/;
@@ -265,6 +656,12 @@ var EFFORT_RANK = {
265
656
  high: 3,
266
657
  max: 4
267
658
  };
659
+ var UNSUPPORTED_TOOL_SCHEMA_STRICTNESS = Object.freeze({
660
+ supported: false
661
+ });
662
+ var SUPPORTED_TOOL_SCHEMA_STRICTNESS = Object.freeze({
663
+ supported: true
664
+ });
268
665
  function clampReasoningEffort(effort, supportedEfforts) {
269
666
  if (supportedEfforts.includes(effort)) {
270
667
  return effort;
@@ -343,6 +740,63 @@ function collectRequestedInputModalities(messages) {
343
740
  return [...requested];
344
741
  }
345
742
 
743
+ // src/validate-tool-schema-strictness.ts
744
+ var contractViolationCache = /* @__PURE__ */ new WeakMap();
745
+ function getContractViolations(tool) {
746
+ let cached = contractViolationCache.get(tool.parameters);
747
+ if (cached === void 0) {
748
+ cached = getStrictToolSchemaViolations(
749
+ tool.name,
750
+ zodSchemaToJsonSchema(tool.parameters)
751
+ ).map(({ path, message }) => ({ path, message }));
752
+ contractViolationCache.set(tool.parameters, cached);
753
+ }
754
+ return cached.map((violation) => ({ toolName: tool.name, ...violation }));
755
+ }
756
+ function validateToolSchemaStrictness({
757
+ tools,
758
+ capabilities,
759
+ providerId,
760
+ modelId
761
+ }) {
762
+ const strictTools = Object.values(tools).filter(
763
+ (tool) => tool.strict === true
764
+ );
765
+ if (strictTools.length === 0) {
766
+ return;
767
+ }
768
+ const strictToolNames = strictTools.map((tool) => tool.name);
769
+ const strictCapabilities = capabilities.tools.strictSchemas;
770
+ if (!strictCapabilities.supported) {
771
+ throw new ToolSchemaStrictnessError({
772
+ providerId,
773
+ modelId,
774
+ toolNames: strictToolNames,
775
+ reason: "unsupported"
776
+ });
777
+ }
778
+ const maxStrictTools = strictCapabilities.maxStrictTools;
779
+ if (maxStrictTools !== void 0 && strictTools.length > maxStrictTools) {
780
+ throw new ToolSchemaStrictnessError({
781
+ providerId,
782
+ modelId,
783
+ toolNames: strictToolNames,
784
+ reason: "limit-exceeded",
785
+ maxStrictTools
786
+ });
787
+ }
788
+ const violations = strictTools.flatMap(getContractViolations);
789
+ if (violations.length > 0) {
790
+ throw new ToolSchemaStrictnessError({
791
+ providerId,
792
+ modelId,
793
+ toolNames: [...new Set(violations.map((v) => v.toolName))],
794
+ reason: "invalid-schema",
795
+ violations
796
+ });
797
+ }
798
+ }
799
+
346
800
  // src/model-capabilities-registry.ts
347
801
  var UNKNOWN_MODEL = /* @__PURE__ */ Symbol("unknown-model");
348
802
  function getRegisteredModelCapabilities(registry, modelId) {
@@ -991,8 +1445,10 @@ export {
991
1445
  MULTIMODAL_INPUT_MODALITIES,
992
1446
  ModelOverloadedError,
993
1447
  ProviderError,
1448
+ ProviderQuotaExceededError,
994
1449
  RateLimitError,
995
1450
  RetryableProviderError,
1451
+ SUPPORTED_TOOL_SCHEMA_STRICTNESS,
996
1452
  ServiceUnavailableError,
997
1453
  StreamAbortedError,
998
1454
  StructuredOutputError,
@@ -1000,7 +1456,9 @@ export {
1000
1456
  StructuredOutputParseError,
1001
1457
  StructuredOutputValidationError,
1002
1458
  TEXT_ONLY_MODALITIES,
1459
+ ToolSchemaStrictnessError,
1003
1460
  UNKNOWN_MODEL,
1461
+ UNSUPPORTED_TOOL_SCHEMA_STRICTNESS,
1004
1462
  UnsupportedInputModalityError,
1005
1463
  ValidationError,
1006
1464
  asObject,
@@ -1019,10 +1477,12 @@ export {
1019
1477
  getProviderMetadata,
1020
1478
  getRegisteredModelCapabilities,
1021
1479
  getRetryAfterSecondsFromError,
1480
+ getStrictToolSchemaViolations,
1022
1481
  getString,
1023
1482
  isAbortErrorByName,
1024
1483
  isRateLimitStatus,
1025
1484
  isTransientUnavailableStatus,
1485
+ normalizeStrictJsonSchema,
1026
1486
  parseRetryAfterSeconds,
1027
1487
  resultToMessage,
1028
1488
  safeParseJsonObject,
@@ -1032,6 +1492,7 @@ export {
1032
1492
  supportsInputModality,
1033
1493
  supportsOutputModality,
1034
1494
  validateInputModalities,
1495
+ validateToolSchemaStrictness,
1035
1496
  wrapChatModel,
1036
1497
  wrapEmbeddingModel,
1037
1498
  wrapImageModel,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@core-ai/core-ai",
3
- "version": "0.23.0",
3
+ "version": "0.25.0",
4
4
  "description": "Type-safe LLM abstraction layer over native provider SDKs",
5
5
  "license": "MIT",
6
6
  "author": "Omnifact (https://omnifact.ai)",